Re: pseudo device driver with mmap

rubini@pop.systemy.it
Thu, 1 May 1997 00:23:41 +0200 (MET DST)


> Now the problem is, what code do I need in buf_mmap to make a piece of
> allocated kernel memory available to userland?

The problem is you need to play with the vm-operations in order to
have it work right. It's not trivial, but I've done once just
to try it out.

You need to implement an open method, a close method and a no-page method
to have the things work right. Sure you also need to play with
the module usage count, because an mmapped region may survice device
close, and you don't want to remove a mapped module, do you?

The following code refers to 2.0 kernels, and works with intel, sparc,
alpha.

To keep track of the usage count you need something like this:

static struct vm_operations_struct simple_vm_ops = {
simple_vma_open,
simple_vma_close, /* fill with NULLs */
};

void simple_vma_open(struct vm_area_struct * area)
{ MOD_INC_USE_COUNT; }

void simple_vma_close(struct vm_area_struct * area)
{ MOD_DEC_USE_COUNT; }

int simple_mmap(struct inode *inode, struct file *filp,
struct vm_area_struct *vma)
{
/* int remap_page_range(virt_add, phys_add, size, protection); */
if (remap_page_range(vma->vm_start, vma->vm_offset,
vma->vm_end-vma->vm_start, vma->vm_page_prot))
return -EAGAIN;
if (vma->vm_ops)
return -EINVAL; /* Hmm... shouldn't happen */
vma->vm_ops = &simple_vm_ops;
MOD_INC_USE_COUNT; /* open(vma) wasn't called this time */
vma->vm_inode = inode;
inode->i_count++;
return 0;
}

A nopage implementation is needed if you want mremap to work, and
makes your own job (mapping a kmalloc or vmalloc buffer) easier.

This is the simple case of nopage for I/O memory (like the previous one,
it uses remap_page_range() to obtain mapped buffers:

static struct vm_operations_struct simple_vm_ops = {
NULL, NULL, NULL, NULL, NULL, NULL, simple_nopage,
};

void simple_nopage(struct vm_area_struct *vma, unsigned long address,
int write)
{
/* int remap_page_range(virt_add, phys_add, size, protection); */
remap_page_range(address & PAGE_MASK,
address - vma->vm_start + vma->vm_offset,
PAGE_SIZE, vma->vm_page_prot);
}

int simple_mmap(struct inode *inode, struct file *filp,
struct vm_area_struct *vma)
{
vma->vm_ops = &simple_vm_ops;
vma->vm_inode = inode;
inode->i_count++;
return 0;
}

If your buffer is made up of single pages -- get_free_pages(0), the
following will work:

unsigned long scullp_vma_nopage(struct vm_area_struct *vma,
unsigned long address, int write)
{
unsigned long offset = address - vma->vm_start + vma->vm_offset;
void *pageptr = NULL; /* default to "missing" */

/*
* Now retrieve the page.
* Don't want to allocate: I'm too lazy. If the device has holes,
* the process receives a SIGBUS when accessing the hole.
*/

/* retrieve pageptr, the address allocated via get_free_page() */

if (!pageptr) return 0; /* hole or end-of-file: SIGBUS */

/* got it, now increment the count */
atomic_inc(&mem_map[MAP_NR(pageptr)].count);
return (unsigned long)pageptr;
}

A vmalloc buffer needs some tweaking, as the pageptr you got
is not a physical one, so the following is needded:

page = VMALLOC_VMADDR(pageptr);
PDEBUG("page %lx\n",page); /* skip-this-line */

pgd = pgd_offset(init_mm_ptr, page);
pmd = pmd_offset(pgd, page);
pte = pte_offset(pmd, page);
page = pte_page(*pte); /* this is the physical address */

/* now increment the count and return */
atomic_inc(&mem_map[MAP_NR(page)].count);
return page;
}

--
    __ o         Tu vuo` fa` l'americano: sient'a me` chi t'o fa fa`.
   _`\<,                                               (Renato Carosone)
__( )/( )__  alessandro.rubini@linux.it  +39-382-529554