//------------------------------------------------------------------- // vram.c // // This character-mode Linux device-driver supports mapping of // the video display memory into the user's address space, and // it supports the standard read/write/lseek system-calls. It // invokes the kernel's PCI bus interface routines to identify // the graphics adapter, and to determine the physical address // and size of its on-board device memory (i.e., the so-called // graphics 'frame buffer'). It uses the kernel's 'ioremap()' // and 'iounmap()' functions to map pages of display memory to // virtual addresses in kernel-space, on an "as needed" basis. // It uses the kernel's 'remap_page_range()' function to map a // segment of video memory to virtual-addresses in user-space. // // In this driver the device major number is hard-coded as 99, // and it is left to the user (or the system administrator) to // create the corresponding device-node, using the 'mknod' and // 'chmod' shell commands: // // root# mknod /dev/vram c 99 0 // root# chmod a+rw /dev/vram // // Certain graphics devices may require execution of a special // instruction-sequence in order to "enable" linear addressing // by the cpu of the memory that is mapped by this driver, but // to keep this driver as generic as possible, the application // is responsible for executing such instructions (if needed). // // NOTE: Written and tested using Linux kernel version 2.4.21. // // programmer: ALLAN CRUSE // written on: 26 AUG 2003 //------------------------------------------------------------------- #define __KERNEL__ #define MODULE #include // for init_module() #include // for pci_find_class() #define VGA_CLASS 0x030000 // for VGA Display Adapters static char modname[] = "vram"; static struct pci_dev *devp = NULL; static int my_major = 99; // driver's assigned major-number static unsigned long fb_base; // physical address of frame-buffer static unsigned long fb_size; // size of the frame-buffer (bytes) static ssize_t my_write( struct file *file, const char *buf, size_t count, loff_t *pos ), my_read( struct file *file, char *buf, size_t count, loff_t *pos ); static loff_t my_llseek( struct file *file, loff_t pos, int whence ); static int my_mmap( struct file *file, struct vm_area_struct *vma ); static struct file_operations my_fops = { owner: THIS_MODULE, llseek: my_llseek, write: my_write, read: my_read, mmap: my_mmap, }; int init_module( void ) { printk( "<1>\nInstalling \'%s\' module ", modname ); printk( "(major=%d) \n", my_major ); // identify the video display device devp = pci_find_class( VGA_CLASS, devp ); if ( !devp ) return -ENODEV; printk( "<1> %s \n", devp->name ); // determine location and length of the frame buffer fb_base = pci_resource_start( devp, 0 ); fb_size = pci_resource_len( devp, 0 ); printk( "<1> address-range of frame-buffer: " ); printk( "%08X-%08X ", fb_base, fb_base+fb_size ); printk( "(%d MB) \n", fb_size >> 20 ); return register_chrdev( my_major, modname, &my_fops ); } void cleanup_module( void ) { unregister_chrdev( my_major, modname ); printk( "<1>Removing \'%s\' module\n", modname ); } MODULE_LICENSE("GPL"); static loff_t my_llseek( struct file *file, loff_t pos, int whence ) { loff_t newpos = -1; switch ( whence ) { case 0: newpos = pos; break; // SEEK_SET case 1: newpos = file->f_pos + pos; break; // SEEK_CUR case 2: newpos = fb_size + pos; break; // SEEK_END } if (( newpos < 0 )||( newpos > fb_size )) return -EINVAL; file->f_pos = newpos; return newpos; } static ssize_t my_write( struct file *file, const char *buf, size_t count, loff_t *pos ) { unsigned long phys_address = fb_base + *pos; unsigned long page_number = phys_address / PAGE_SIZE; unsigned long page_indent = phys_address % PAGE_SIZE; unsigned long where; void *vaddr; // sanity check: we cannot write anything past end-of-vram if ( *pos >= fb_size ) return 0; // we can only write up to the end of the current page if ( page_indent + count > PAGE_SIZE ) count = PAGE_SIZE - page_indent; // ok, map the current page of physical vram to virtual address where = page_number * PAGE_SIZE; // page-addrerss vaddr = ioremap( where, PAGE_SIZE ); // setup mapping // copy 'count' bytes from the caller's buffer to video memory memcpy_toio( vaddr + page_indent, buf, count ); iounmap( vaddr ); // unmap memory // tell the kernel how many bytes were actually transferred *pos += count; return count; } static ssize_t my_read( struct file *file, char *buf, size_t count, loff_t *pos ) { unsigned long phys_address = fb_base + *pos; unsigned long page_number = phys_address / PAGE_SIZE; unsigned long page_indent = phys_address % PAGE_SIZE; unsigned long where; void *vaddr; // sanity check: we cannot read anything past end-of-vram if ( *pos >= fb_size ) return 0; // we can only read up to the end of the current page if ( page_indent + count > PAGE_SIZE ) count = PAGE_SIZE - page_indent; // ok, map the current page of physical vram to virtual address where = page_number * PAGE_SIZE; // page-addrerss vaddr = ioremap( where, PAGE_SIZE ); // setup mapping // copy 'count' bytes from video memory to the caller's buffer memcpy_fromio( buf, vaddr + page_indent, count ); iounmap( vaddr ); // unmap memory // tell the kernel how many bytes were actually transferred *pos += count; return count; } static int my_mmap( struct file *file, struct vm_area_struct *vma ) { unsigned long region_length = vma->vm_end - vma->vm_start; unsigned long region_origin = vma->vm_pgoff * PAGE_SIZE; unsigned long phys_address = fb_base + region_origin; unsigned long virt_address = vma->vm_start; // sanity check: mapped region cannot extend past end of vram if ( region_origin + region_length > fb_size ) return -EINVAL; // let the kernel know not to try swapping out this region vma->vm_flags |= VM_RESERVED; // invoke kernel function to setup the page-table entries if ( remap_page_range( virt_address, phys_address, region_length, vma->vm_page_prot ) ) return -EAGAIN; return 0; // SUCCESS }