Table of Contents

Device Driver MMAP interface

The mmap interface is used when a user space task wants to directly access kernel or device memory.

Typical uses are framebuffer drivers and I/O devices with their own on board memories.

In uClinux the normal access restrictions on kernel memory space may not occur. You should take care to make the interface and any devices that use it as compatible as possible with “normal” Linux.

The mapping call will create a pointer in user space that can be addressed a a usual memory structure. This is trivial in uClinux with no mmu but to make a driver call that will work in both cases is not so straightforward.

MMAP in user Space

The user space interface is shown:

  /*  the function prototype */                                         
 
   void  *  mmap(void  *start,  size_t length, int prot , int           
         flags, int fd, off_t offset);                                  
 
 
 
  /* A typical use ... make data addressable in virtual address space */
 
  #include <unistd.h>                                                   
  #include <sys/mman.h>                                                 
     char * p;                                                          
     int i;                                                             
     int fd;                                                            
     int len=SCMD_SIZE;                                                 
 
     fd = open("/dev/scmd",O_RDWR);                                     
 
     p = (char *)mmap(0, len, PROT_READ, MAP_PRIVATE, fd, 0);           
     printf("buffer address 0x%x \n",p);                                
     printf("buffer data <%s> \n",p);

MMAP kernel code example 1

This code maps a memory area using remap_page_range to build a new set of page tables for the memory area. The memory area must start at a page boundary.

NOTE this is due to be replaced in the latest kernels by remap_pfn_range to allow the use of greater than 4G of memory space.

  #include <linux/mm.h>                                               
  /* needed for virt_to_phys() */                                     
  #include <asm/io.h> // virt_to_phys()                               
  #include <linux/wrapper.h> // mem_map_[un]reserve()                 
 
  static char * buffer; // we use this as a test                      
 
  static int scmd_mmap(struct file * filp, struct vm_area_struct *vma)
  {                                                                   
     scmd_dev_t * dev;                                                
     unsigned long page,pos,start,size,offset;                        
 
     dev = ( scmd_dev_t * )filp->private_data;                        
 
     start=(unsigned long)vma->vm_start;                              
     size=(unsigned long)vma->vm_end-vma->vm_start;                   
 
 
  #ifndef CONFIG_MMU                                                  
          /* this may not be correct yet */                           
          vma->vm_start = dev->data + vma->vm_offset;                 
          return (0);                                                 
  #else /* /CONFIG_MMU */                                             
 
     offset = vma->vm_pgoff << PAGE_SHIFT;                            
     if (offset >= __pa(high_memory) || (filp->f_flags & O_SYNC ))    
        vma->vm_flags |= VM_IO;                                       
     vma->vm_flags |= VM_RESERVED;                                    
 
     pos  = (unsigned long)buffer;                                    
     page = virt_to_phys((void *)pos);                                
 
     if ( remap_page_range(start, page, size, vma->vm_page_prot | PAGE_SHARED)) {                                                     
        printk(" scmd_mmap: error in remap page_range \n");           
        return -EAGAIN;                                               
     }                                                                
  #endif  /* end NO_MM */                                             
     return 0;                                                        
  }                                                                           

The following code is also added to set up the buffer

  /* in scmd_init */                               
 
     scmd_driver_fops.mmap = scmd_mmap;            
 
     buffer = kmalloc(4096,GFP_KERNEL);            
     printk(" scmd_mmap buffer = %p\n",buffer);    
     if ( buffer ) {                               
        struct page *  page = virt_to_page(buffer);
        mem_map_reserve(page);                     
        strcpy(buffer,"scmd_mmap test data");      
     }                                             
 
  /* in scmd_exit */                               
     if ( buffer ) {                               
        struct page * page = virt_to_page(buffer); 
        mem_map_unreserve(page);                   
        kfree(buffer);                             
     }                                             

MMAP kernel code example 2

In Blackfin uClinux we can reserve some memory for user application or driver (the kernel doesn't manage this memory buffer). To reserve memory just set bootargs in uboot ” max_mem=xxM$# mem=yyM ”. The “yy - xx” is the reserved memory, “$#” means enable cache.

file: user/blkfin-test/mmap_test/simple.c

scm failed with exit code 1:
file does not exist in git

And use “mknod simple c 254 0” to create the ”/dev/simple” device for applications to open. This assumes that 254 is the next free major number. Check your /proc/devices to be sure.

MMAP User code

A typical user code example is shown

The pointer returned by the mmap function can be used as normal read / write memory

                // bfin-uclinux-gcc -Wl,-elf2flt -o test_simple test_simple.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
 
#define SIMPLE_DEVICE "/dev/simple"
 
int main()
{
	int i, fd;
	char *p;
	fd = open(SIMPLE_DEVICE, O_RDWR);
	if (fd < 0) {
		perror("error in device");
		exit(1);
	}
	p = mmap(NULL, 1024, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
	if (p == MAP_FAILED) {
		perror("failed to mmap device");
		exit(1);
	}
	printf("mmap device ok p = %p\n",p);
	for (i = 0; i < 1024; i++)
		p[i] = 0x55;
 
	for (i = 0; i < 1024; i++) {
		if (p[i] != 0x55) {
			printf(" memory write/read error! \n");
			return -1;
		}
	}
	printf(" memory write/read succeed\n");
	close(fd);
	return 0;
}

mmap system call

Related source files:

 
arch/blackfin/kernel/sys_bfin.c
mm/nommu.c: do_mmap_pgoff()

There are several situation when mmap system call is invoked. Here we discuss them case by case.

Allocate memory in user space

The uClibc implements malloc using mmap(). This is the only (?) way for a usr space process to ask for memory from kernel.

 
toolchain/uClibc/libc/stdlib/malloc/malloc.c
mmap ((void *)0, block_size, PROT_READ | PROT_WRITE,
                    MAP_SHARED | MAP_ANONYMOUS | MAP_UNINITIALIZE, 0, 0)

The “MAP_UNINITIALIZE” flag tells kernel not to set the memory block to zero.

Load exectuable

fs/binfmt_elf_fdpic.c:load_elf_fdpic_binary()

Allocate stack:
current->mm->start_brk = do_mmap(NULL, 0, stack_size,
                                  PROT_READ | PROT_WRITE | PROT_EXEC,
                                  MAP_PRIVATE | MAP_ANONYMOUS | MAP_GROWSDOWN
                                  | MAP_SPLIT_PAGES, 0);
flags = MAP_PRIVATE | MAP_DENYWRITE | MAP_SPLIT_PAGES;
if (params->flags & ELF_FDPIC_FLAG_EXECUTABLE)
                        flags |= MAP_EXECUTABLE;
maddr = do_mmap(file, maddr, phdr->p_memsz + disp, prot, flags,
                 phdr->p_offset - disp);