static void free_fdmem(void *ptr) { is_vmalloc_addr(ptr) ? vfree(ptr) : kfree(ptr); }
void memunmap(void *addr) { if (is_vmalloc_addr(addr)) iounmap((void __iomem *) addr); }
static int traverse(struct seq_file *m, loff_t offset) { loff_t pos = 0, index; int error = 0; void *p; m->version = 0; index = 0; m->count = m->from = 0; if (!offset) { m->index = index; return 0; } if (!m->buf) { m->buf = kmalloc(m->size = PAGE_SIZE, GFP_KERNEL); if (!m->buf) return -ENOMEM; } p = m->op->start(m, &index); while (p) { error = PTR_ERR(p); if (IS_ERR(p)) break; error = m->op->show(m, p); if (error < 0) break; if (unlikely(error)) { error = 0; m->count = 0; } if (seq_overflow(m)) goto Eoverflow; if (pos + m->count > offset) { m->from = offset - pos; m->count -= m->from; m->index = index; break; } pos += m->count; m->count = 0; if (pos == offset) { index++; m->index = index; break; } p = m->op->next(m, p, &index); } m->op->stop(m, p); m->index = index; return error; Eoverflow: m->op->stop(m, p); #ifdef CONFIG_LOW_ORDER_SEQ_MALLOC is_vmalloc_addr(m->buf) ? vfree(m->buf) : kfree(m->buf); m->count = 0; m->size <<= 1; if (m->size <= (2* PAGE_SIZE)) m->buf = kmalloc(m->size, GFP_KERNEL); else m->buf = vmalloc(m->size); #else kfree(m->buf); m->count = 0; m->buf = kmalloc(m->size <<= 1, GFP_KERNEL); #endif return !m->buf ? -ENOMEM : -EAGAIN; }
/** * seq_read - ->read() method for sequential files. * @file: the file to read from * @buf: the buffer to read to * @size: the maximum number of bytes to read * @ppos: the current position in the file * * Ready-made ->f_op->read() */ ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) { struct seq_file *m = file->private_data; size_t copied = 0; loff_t pos; size_t n; void *p; int err = 0; mutex_lock(&m->lock); /* * seq_file->op->..m_start/m_stop/m_next may do special actions * or optimisations based on the file->f_version, so we want to * pass the file->f_version to those methods. * * seq_file->version is just copy of f_version, and seq_file * methods can treat it simply as file version. * It is copied in first and copied out after all operations. * It is convenient to have it as part of structure to avoid the * need of passing another argument to all the seq_file methods. */ m->version = file->f_version; /* Don't assume *ppos is where we left it */ if (unlikely(*ppos != m->read_pos)) { while ((err = traverse(m, *ppos)) == -EAGAIN) ; if (err) { /* With prejudice... */ m->read_pos = 0; m->version = 0; m->index = 0; m->count = 0; goto Done; } else { m->read_pos = *ppos; } } /* grab buffer if we didn't have one */ if (!m->buf) { m->buf = kmalloc(m->size = PAGE_SIZE, GFP_KERNEL); if (!m->buf) goto Enomem; } /* if not empty - flush it first */ if (m->count) { n = min(m->count, size); err = copy_to_user(buf, m->buf + m->from, n); if (err) goto Efault; m->count -= n; m->from += n; size -= n; buf += n; copied += n; if (!m->count) m->index++; if (!size) goto Done; } /* we need at least one record in buffer */ pos = m->index; p = m->op->start(m, &pos); while (1) { err = PTR_ERR(p); if (!p || IS_ERR(p)) break; err = m->op->show(m, p); if (err < 0) break; if (unlikely(err)) m->count = 0; if (unlikely(!m->count)) { p = m->op->next(m, p, &pos); m->index = pos; continue; } if (m->count < m->size) goto Fill; m->op->stop(m, p); #ifdef CONFIG_LOW_ORDER_SEQ_MALLOC is_vmalloc_addr(m->buf) ? vfree(m->buf) : kfree(m->buf); m->count = 0; m->size <<= 1; if (m->size <= (2* PAGE_SIZE)) m->buf = kmalloc(m->size, GFP_KERNEL); else m->buf = vmalloc(m->size); #else kfree(m->buf); m->count = 0; m->buf = kmalloc(m->size <<= 1, GFP_KERNEL); #endif if (!m->buf) goto Enomem; m->version = 0; pos = m->index; p = m->op->start(m, &pos); } m->op->stop(m, p); m->count = 0; goto Done; Fill: /* they want more? let's try to get some more */ while (m->count < size) { size_t offs = m->count; loff_t next = pos; p = m->op->next(m, p, &next); if (!p || IS_ERR(p)) { err = PTR_ERR(p); break; } err = m->op->show(m, p); if (seq_overflow(m) || err) { m->count = offs; if (likely(err <= 0)) break; } pos = next; } m->op->stop(m, p); n = min(m->count, size); err = copy_to_user(buf, m->buf, n); if (err) goto Efault; copied += n; m->count -= n; if (m->count) m->from = n; else pos++; m->index = pos; Done: if (!copied) copied = err; else { *ppos += copied; m->read_pos += copied; } file->f_version = m->version; mutex_unlock(&m->lock); return copied; Enomem: err = -ENOMEM; goto Done; Efault: err = -EFAULT; goto Done; }
static int create_pagelist(char __user *buf, size_t count, unsigned short type, struct task_struct *task, PAGELIST_T ** ppagelist) { PAGELIST_T *pagelist; struct page **pages; struct page *page; unsigned long *addrs; unsigned int num_pages, offset, i; char *addr, *base_addr, *next_addr; int run, addridx, actual_pages; unsigned long *need_release; offset = (unsigned int)buf & (PAGE_SIZE - 1); num_pages = (count + offset + PAGE_SIZE - 1) / PAGE_SIZE; *ppagelist = NULL; /* Allocate enough storage to hold the page pointers and the page ** list */ pagelist = kmalloc(sizeof(PAGELIST_T) + (num_pages * sizeof(unsigned long)) + sizeof(unsigned long) + (num_pages * sizeof(pages[0])), GFP_KERNEL); vchiq_log_trace(vchiq_arm_log_level, "create_pagelist - %x", (unsigned int)pagelist); if (!pagelist) return -ENOMEM; addrs = pagelist->addrs; need_release = (unsigned long *)(addrs + num_pages); pages = (struct page **)(addrs + num_pages + 1); if (is_vmalloc_addr(buf)) { for (actual_pages = 0; actual_pages < num_pages; actual_pages++) { pages[actual_pages] = vmalloc_to_page(buf + (actual_pages * PAGE_SIZE)); } *need_release = 0; /* do not try and release vmalloc pages */ } else { down_read(&task->mm->mmap_sem); actual_pages = get_user_pages(task, task->mm, (unsigned long)buf & ~(PAGE_SIZE - 1), num_pages, (type == PAGELIST_READ) /*Write */ , 0 /*Force */ , pages, NULL /*vmas */); up_read(&task->mm->mmap_sem); if (actual_pages != num_pages) { vchiq_log_info(vchiq_arm_log_level, "create_pagelist - only %d/%d pages locked", actual_pages, num_pages); /* This is probably due to the process being killed */ while (actual_pages > 0) { actual_pages--; page_cache_release(pages[actual_pages]); } kfree(pagelist); if (actual_pages == 0) actual_pages = -ENOMEM; return actual_pages; } *need_release = 1; /* release user pages */ } pagelist->length = count; pagelist->type = type; pagelist->offset = offset; /* Group the pages into runs of contiguous pages */ base_addr = VCHIQ_ARM_ADDRESS(page_address(pages[0])); next_addr = base_addr + PAGE_SIZE; addridx = 0; run = 0; for (i = 1; i < num_pages; i++) { addr = VCHIQ_ARM_ADDRESS(page_address(pages[i])); if ((addr == next_addr) && (run < (PAGE_SIZE - 1))) { next_addr += PAGE_SIZE; run++; } else { addrs[addridx] = (unsigned long)base_addr + run; addridx++; base_addr = addr; next_addr = addr + PAGE_SIZE; run = 0; } } addrs[addridx] = (unsigned long)base_addr + run; addridx++; /* Partial cache lines (fragments) require special measures */ if ((type == PAGELIST_READ) && ((pagelist->offset & (CACHE_LINE_SIZE - 1)) || ((pagelist->offset + pagelist->length) & (CACHE_LINE_SIZE - 1)))) { FRAGMENTS_T *fragments; if (down_interruptible(&g_free_fragments_sema) != 0) { kfree(pagelist); return -EINTR; } WARN_ON(g_free_fragments == NULL); down(&g_free_fragments_mutex); fragments = (FRAGMENTS_T *) g_free_fragments; WARN_ON(fragments == NULL); g_free_fragments = *(FRAGMENTS_T **) g_free_fragments; up(&g_free_fragments_mutex); pagelist->type = PAGELIST_READ_WITH_FRAGMENTS + (fragments - g_fragments_base); } for (page = virt_to_page(pagelist); page <= virt_to_page(addrs + num_pages - 1); page++) { flush_dcache_page(page); } *ppagelist = pagelist; return 0; }
void foo(void) { is_vmalloc_addr(NULL); }