//------------------------------------------------------------------- // // smram.c // // This Linux kernel module is a character-mode device-driver // for the System Management Memory in one of our classroom's // workstations during Fall 2008. It is specific to the Host // Bridge (Intel 3000 Memory Controller Hub) and ROM-BIOS for // these Intel Core-2 Quad based machines (and is unlikely to // operate on other platforms without various modifications). // It was created solely for an in-class demonstration of the // 'System Management Interrupt' feature discussed in CS 630, // which also will rely on our ATI ES100 Graphics Controller. // // compile using: $ ./mmake smram.x // install using: $ /sbin/insmod smram.ko // // NOTE: Written and tested for Linux x86_64 kernel 2.6.26.6. // // programmer: ALLAN CRUSE // written on: 07 DEC 2008 //------------------------------------------------------------------- #include // for init_module() #include // for create_proc_read_entry() #include // for pci_get_class() #include // for utsname() #include // for copy_to_user() #define VENDOR_ID 0x8086 // Intel Corporation #define CLASS_MCH 0x060000 // Memory Controller Hub char modname[] = "smram"; int my_major = 87; struct pci_dev *devp; struct new_utsname *uts; unsigned int lapic_base = 0xFEE00000; unsigned int lapic_size = 0x00001000; unsigned int *apic; unsigned int smram_base = 0x000A0000; unsigned int smram_size = 0x00020000; void *smramp; unsigned int mch_tolud; unsigned int mch_smram; unsigned int mch_esmramc; MODULE_LICENSE("GPL"); ssize_t my_write( struct file *file, const char *buf, size_t len, loff_t *pos ), my_read( struct file *file, char *buf, size_t len, loff_t *pos ); loff_t my_seek( struct file *file, loff_t where, int whence ); int my_ioctl( struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg ); struct file_operations my_fops = { owner: THIS_MODULE, write: my_write, read: my_read, llseek: my_seek, ioctl: my_ioctl, }; int my_info( char *buf, char **s, off_t off, int count, int *eof, void *d ) { int i, len = 0; len += sprintf( buf+len, "\n\n " ); len += sprintf( buf+len, "Intel Memory Controller Hub " ); len += sprintf( buf+len, "PCI Configuration Space " ); len += sprintf( buf+len, "on \'%s\' \n", uts->nodename ); for (i = 0; i < 0x100; i+=4) { u32 datum; if ( (i % 32) == 0 ) len += sprintf( buf+len, "\n %03X: ", i ); pci_read_config_dword( devp, i, &datum ); len += sprintf( buf+len, "%08X ", datum ); } len += sprintf( buf+len, "\n\n" ); len += sprintf( buf+len, " Top of Low Usable DRAM: " ); len += sprintf( buf+len, "0x%08X \n", mch_tolud << 24 ); len += sprintf( buf+len, " System Management RAM Control: " ); len += sprintf( buf+len, " D_OPEN=%X ", (mch_smram >> 6)&1 ); len += sprintf( buf+len, " D_CLOSE=%X ", (mch_smram >> 5)&1 ); len += sprintf( buf+len, " D_LOCK=%X ", (mch_smram >> 4)&1 ); len += sprintf( buf+len, " G_SMRAME=%X ", (mch_smram >> 3)&1 ); len += sprintf( buf+len, "\n" ); len += sprintf( buf+len, " Extended System Management RAM Control: " ); len += sprintf( buf+len, " H_SMRAME=%X ", (mch_esmramc >> 7)&1 ); len += sprintf( buf+len, " TSEG_SZ=%X ", (mch_esmramc >> 7)&3 ); len += sprintf( buf+len, " T_EN=%X ", (mch_esmramc >> 0)&1 ); len += sprintf( buf+len, "\n\n" ); return len; } static int __init smram_init( void ) { u32 datum; printk( "<1>\nInstalling \'%s\' module ", modname ); printk( "(major=%d) \n", my_major ); devp = pci_get_class( CLASS_MCH, devp ); if ( !devp ) return -ENODEV; if ( devp->vendor != VENDOR_ID ) return -ENODEV; printk( " Memory Controller Hub: " ); printk( " vendor_id=%04X ", devp->vendor ); printk( " device_id=%04X ", devp->device ); printk( "\n" ); pci_read_config_dword( devp, 0x9C, &datum ); mch_tolud = (datum >> 0) & 0xFF; mch_smram = (datum >> 8) & 0xFF; mch_esmramc = (datum >> 16) & 0xFF; if ( mch_esmramc & (1<<0) ) // T_EN (TSEG Enabled) { switch ( (mch_esmramc >> 1)&3 ) { case 0: smram_size = (1 << 20); break; // 1MB-TSEG case 1: smram_size = (2 << 20); break; // 2MB-TSEG case 2: smram_size = (8 << 20); break; // 8MB-TSEG } smram_base = (mch_tolud << 24) - smram_size; } smramp = ioremap( smram_base, smram_size ); if ( !smramp ) return -ENOSPC; apic = (unsigned int*)ioremap_nocache( lapic_base, lapic_size ); uts = utsname(); create_proc_read_entry( modname, 0, NULL, my_info, NULL ); return register_chrdev( my_major, modname, &my_fops ); } static void __exit smram_exit( void ) { unregister_chrdev( my_major, modname ); remove_proc_entry( modname, NULL ); iounmap( apic ); iounmap( smramp ); printk( "<1>Removing \'%s\' module\n", modname ); } module_init( smram_init ); module_exit( smram_exit ); ssize_t my_write( struct file *file, const char *buf, size_t len, loff_t *pos ) { char *to = smramp + *pos; int more; if ( *pos >= smram_size ) return 0; if ( *pos + len > smram_size ) len = smram_size - *pos; // check D_LOCK bit if ( mch_smram & (1<<4) ) return -EPERM; // Set D_OPN bit in SMRAM Control Register pci_write_config_byte( devp, 0x9D, 0x4A ); // copy len bytes to SMRAM from user-buffer more = copy_from_user( to, buf, len ); // Clear D_OPN bit in SMRAM Control Register pci_write_config_byte( devp, 0x9D, 0x0A ); if ( more ) return -EFAULT; *pos += len; return len; } ssize_t my_read( struct file *file, char *buf, size_t len, loff_t *pos ) { char *from = smramp + *pos; int more; if ( *pos >= smram_size ) return 0; if ( *pos + len > smram_size ) len = smram_base - *pos; // check D_LOCK bit if ( mch_smram & (1<<4) ) return -EPERM; // Set D_OPN bit in SMRAM Control Register pci_write_config_byte( devp, 0x9D, 0x4A ); // copy len bytes from SMRAM to user-buffer more = copy_to_user( buf, from, len ); // Clear D_OPN bit in SMRAM Control Register pci_write_config_byte( devp, 0x9D, 0x0A ); if ( more ) return -EFAULT; *pos += len; return len; } loff_t my_seek( struct file *file, loff_t offset, int whence ) { loff_t newpos = -1; switch( whence ) { case 0: newpos = offset; break; // SEEK_SET case 1: newpos = file->f_pos + offset; break; // SEEL_CUR case 2: newpos = smram_size + offset; break; // SEEK_END } if (( newpos < 0 )||( newpos > smram_size )) return -EINVAL; file->f_pos = newpos; return newpos; } int my_ioctl( struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg ) { unsigned int *ipi_reg = (unsigned int*)((long)apic + 0x300); unsigned int *ipi_dst = (unsigned int*)((long)apic + 0x310); unsigned int i, val; //val = ioread32( ipi_reg ); //printk( "%p: %08X \n", ipi_reg, val ); // await idle delivery-status for (i = 0; i < 8; i++) { val = ioread32( ipi_reg ); if ( (val & (1<<12)) == 0 )break; } // Here the 'cmd' field selects the destination-shorthand // and the 'arg' field selects the destination-processor val = ((unsigned char)arg) << 24; iowrite32( val, ipi_dst ); // issue a System Management Interrupt val = 0x00004200 | ((cmd & 3)<<18); iowrite32( val, ipi_reg ); // await delivery for (i = 0; i < 8; i++) { unsigned int val = ioread32( ipi_reg ); //printk( "%p: %08X \n", ipi_reg, val ); if ( (val & (1<<12)) == 0 ) break; } return 0; // SUCCESS }