//------------------------------------------------------------------- // vram.c // // This character-mode Linux device-driver allows applications // to both read and write directly to graphics display memory. // It invokes the kernel's PCI bus-interface routines, to find // the physical address of the display adapter's video memory; // it calls the kernel's 'ioremap()' and 'iounmap()' functions // to setup and destroy temporary mappings of VRAM page-frames // into the x86 processor's virtual address-space; it uses the // kernel's 'memcpy_toio()' and 'memcpy_fromio()' to write and // read these mapped pages of video memory; it also implements // a 'mmap()' routine, thus allowing applications to treat the // graphics display memory as an ordinary array in user-space. // // In this driver the device major number is hard-coded as 98, // 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 98 0 // root# chmod a+rw /dev/vram // // NOTE: Written and tested using Linux kernel version 2.6.12. // // programmer: ALLAN CRUSE // written on: 17 AUG 2005 -- for ATI Inc. graphics controllers // revised on: 08 DEC 2008 -- for x86_64 Linux version 2.6.26.6 //------------------------------------------------------------------- #include // for init_module() #include // for pci_get_class() #include // for create_proc_read_entry() #include // for put_user() #define CLASS_SVGA 0x030000 // for SVGA-compatible controller #define VENDOR_ID 0x1002 // for ATI Technologies, Inc. char modname[] = "vram"; // used for displaying driver-name int my_major = 98; // driver's assigned major-number struct pci_dev *devp; // pointer to pci device structure unsigned long fb_base; // physical address of frame-buffer unsigned long fb_size; // size of the frame-buffer (bytes) int my_info( char *buf, char **start, off_t off, int count, int *eof, void *data ) { int i, len = 0; len += sprintf( buf+len, "\n PCI Configuration Space -- " ); len += sprintf( buf+len, "SVGA Graphics Controller \n" ); for (i = 0; i < 0x100; i += 4 ) { u32 datum; pci_read_config_dword( devp, i, &datum ); if ( (i % 32) == 0 ) len += sprintf( buf+len, "\n 0x%02X: ", i ); len += sprintf( buf+len, "%08X ", datum ); } len += sprintf( buf+len, "\n" ); len += sprintf( buf+len, "\n" ); return len; } 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; } 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; } 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; } int my_mmap( struct file *file, struct vm_area_struct *vma ) { unsigned long region_origin = vma->vm_pgoff * PAGE_SIZE; unsigned long region_length = vma->vm_end - vma->vm_start; unsigned long physical_addr = fb_base + region_origin; unsigned long user_virtaddr = vma->vm_start; // sanity check: mapped region cannot expend 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 that sets up the page-table entries if ( remap_pfn_range( vma, user_virtaddr, physical_addr >> PAGE_SHIFT, region_length, vma->vm_page_prot ) ) return -EAGAIN; return 0; // SUCCESS } struct file_operations my_fops = { owner: THIS_MODULE, llseek: my_llseek, write: my_write, read: my_read, mmap: my_mmap, }; static int __init my_init( void ) { printk( "<1>\nInstalling \'%s\' module ", modname ); printk( "(major=%d) \n", my_major ); if ( !devp ) devp = pci_get_class( CLASS_SVGA, NULL ); if ( !devp ) return -ENODEV; printk( " SVGA Controller: vendor=%04X device=%04X \n", devp->vendor, devp->device ); // 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( "%08lX-%08lX ", fb_base, fb_base+fb_size ); printk( "(%lu MB) \n", fb_size >> 20 ); create_proc_read_entry( modname, 0, NULL, my_info, NULL ); return register_chrdev( my_major, modname, &my_fops ); } static void __exit my_exit(void ) { remove_proc_entry( modname, NULL ); unregister_chrdev( my_major, modname ); printk( "<1>Removing \'%s\' module\n", modname ); } module_init( my_init ); module_exit( my_exit ); MODULE_LICENSE("GPL");