//------------------------------------------------------------------- // gateway.c (A revision of our 'vram.c' device-driver) // // This character-mode Linux device-driver allows applications // to both read and write directly to graphics display memory, // or to map the graphics display memory into an application's // address-space. In addition, two 'ioctl()' commands allow a // user-program to read or write a graphics processor register // which controls the current address of the GPU frame-buffer. // (Hence this driver is hardware-specfic to an AMD Radeon M56 // graphics processor or compatible architectures.) In earlier // eras the register possessing this capability was called the // CRTC_START_ADDRESS, but it is named PRIMARY SURFACE ADDRESS // in AMD's more recent "M56 Register Reference Guide" (2007). // // 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.33. // // Reference: "M56 Register Reference Guide," Advanced Micro // Devices, Inc. (2007). // // programmer: ALLAN CRUSE // written on: 14 MAR 2005 // revised on: 18 MAR 2011 -- for Gateway NV53A Laptop's GPU //------------------------------------------------------------------- #include // for init_module() #include // for pci_find_class() #include // for register_chrdev() #include // for create_proc_read_entry() #include // for copy_to/from_user() #define VENDOR_ID 0x1002 // Advanced Technologies, Inc #define DEVICE_ID 0x9712 // ATI Radeon Mobility HD 4200 #define GET_CRTC_START_ADDRESS 0 // identifies 'ioctl' cmd #define SET_CRTC_START_ADDRESS 1 // identifies 'ioctl' cmd char modname[] = "gateway"; // used for displaying module-name char devname[] = "vram"; // used for displaying driver-name char pciname[] = "pci"; // used for displaying PCI congig char gpuname[] = "gpu"; int my_major = 98; // driver's assigned major-number unsigned long fb_base; // physical address of frame-buffer unsigned long fb_size; // size of the frame-buffer (bytes) struct pci_dev *devp = NULL; // pointer to device information unsigned long gpu_base; // physical address of GPU registers unsigned long gpu_size; // size of GPU register-bank (bytes) void *gpu; // virtual address for GPU registers 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; // tell the kernel to exclude this region from core dumps vma->vm_flags |= VM_IO; // 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 } int my_ioctl( struct inode *inode, struct file *file, unsigned int cmd, unsigned long addr ) { void *src, *dst; int len = sizeof( unsigned int ); switch ( cmd ) { case GET_CRTC_START_ADDRESS: src = (u32*)(gpu + 0x6110); dst = (void *)addr; // point to user's buffer if ( copy_to_user( dst, src, len ) ) return -EFAULT; return 0; // SUCCESS case SET_CRTC_START_ADDRESS: dst = (u32*)(gpu + 0x6110); src = (void *)addr; // point to user's buffer if ( copy_from_user( dst, src, len ) ) return -EFAULT; return 0; // SUCCESS } return -EINVAL; } struct file_operations my_fops = { owner: THIS_MODULE, llseek: my_llseek, write: my_write, read: my_read, mmap: my_mmap, ioctl: my_ioctl, }; int my_pci_info( char *buf, char **s, off_t off, int count, int *eof, void *data) { u32 datum; int i, len = 0; len += sprintf( buf+len, "\n\n PCI Configuration Space " ); len += sprintf( buf+len, "for ATI Mobility Radeon HD 4200 " ); len += sprintf( buf+len, "\n" ); for (i = 0; i < 0x100; i += 4) { if ( (i % 32) == 0 ) len += sprintf( buf+len, "\n %02X: ", i ); pci_read_config_dword( devp, i, &datum ); len += sprintf( buf+len, "%08X ", datum ); } len += sprintf( buf+len, "\n\n" ); return len; } int my_gpu_info( char *buf, char **s, off_t off, int count, int *eof, void *data) { u32 *gpup; int len = 0; len += sprintf( buf+len, "\n\n GPU engine registers " ); len += sprintf( buf+len, "0x%08lX-", gpu_base ); len += sprintf( buf+len, "0x%08lX ", gpu_base+gpu_size ); len += sprintf( buf+len, "\n\n" ); gpup = (u32*)(gpu + 0x6100); // Display 1 Graphics Control len += sprintf( buf+len, " D1GRPH_PRIMARY_SURFACE_ADDRESS = " ); len += sprintf( buf+len, "0x%08X ", gpup[ 0x10>>2 ] ); len += sprintf( buf+len, " D1GRPH_PITCH = " ); len += sprintf( buf+len, "0x%08X \n", gpup[ 0x20>>2 ] ); gpup = (u32*)(gpu + 0x6900); // Display 2 Graphics Control len += sprintf( buf+len, " D2GRPH_PRIMARY_SURFACE_ADDRESS = " ); len += sprintf( buf+len, "0x%08X ", gpup[ 0x10>>2 ] ); len += sprintf( buf+len, " D2GRPH_PITCH = " ); len += sprintf( buf+len, "0x%08X \n", gpup[ 0x20>>2 ] ); len += sprintf( buf+len, "\n\n" ); return len; } int init_module( void ) { printk( "<1>\nInstalling \'%s\' module ", modname ); printk( "(major=%d) \n", my_major ); // identify the video display device devp = pci_get_device( VENDOR_ID, DEVICE_ID, devp ); if ( !devp ) return -ENODEV; // 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 ); // determine location and length of the register-bank gpu_base = pci_resource_start( devp, 2 ); gpu_size = pci_resource_len( devp, 2 ); gpu = ioremap_nocache( gpu_base, gpu_size ); if ( !gpu ) return -ENOSPC; // setup the pseudo-files and the device-node create_proc_read_entry( pciname, 0, NULL, my_pci_info, NULL ); create_proc_read_entry( gpuname, 0, NULL, my_gpu_info, NULL ); return register_chrdev( my_major, devname, &my_fops ); } void cleanup_module( void ) { unregister_chrdev( my_major, devname ); remove_proc_entry( gpuname, NULL ); remove_proc_entry( pciname, NULL ); iounmap( gpu ); printk( "<1>Removing \'%s\' module\n", modname ); } MODULE_LICENSE("GPL");