Let's implement a char driver to access the system CMOS.
首先仅仅是创建设备模块,最简单的,类似于前面hello world模块一样的东东,从最简单的框架慢慢搭
/************************************************************ code writer : EOF code date : 2014.08.15 code file : cmos_demo.c e-mail: [email protected] code purpose: This code is a demo for how to build a CMOS-driver step by step. If there is something wrong with my code, please touch me by e-mail. Thank you. ************************************************************/ #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/cdev.h> #include <linux/slab.h> /* kmalloc() */ #include <linux/kernel.h> /* printk */ #include <linux/device.h> #include <linux/ioport.h> #include <linux/kdev_t.h> int cmos_major = 0;//defualt int cmos_minor = 0; module_param(cmos_major,int,S_IRUGO); module_param(cmos_minor,int,S_IRUGO); //static dev_t cmos_dev_number; /* Allocated device number */ struct class *cmos_class; /* Tie with the device model */ #define NUM_CMOS_BANKS 2 #define CMOS_BANK_SIZE (0xFF*8) #define DEVICE_NAME "Jason_cmos" #define CMOS_BANK0_INDEX_PORT 0x70 #define CMOS_BANK0_DATA_PORT 0x71 #define CMOS_BANK1_INDEX_PORT 0x72 #define CMOS_BANK1_DATA_PORT 0x73 unsigned char addrports[NUM_CMOS_BANKS] = {CMOS_BANK0_INDEX_PORT,CMOS_BANK1_INDEX_PORT}; unsigned char dataports[NUM_CMOS_BANKS] = {CMOS_BANK0_DATA_PORT,CMOS_BANK1_DATA_PORT}; MODULE_AUTHOR("Jason Leaster"); MODULE_LICENSE("Dual BSD/GPL"); struct cmos_dev { unsigned short current_pointer; /*Current point within the bank*/ unsigned int size; /*size of the bank*/ int bank_number; /*Size of bank*/ struct cdev cdev; char name[10]; /*Name of I/O regeion*/ /* ... */ }*cmos_devp; static struct file_operations cmos_fops = { .owner = THIS_MODULE, }; int cmos_init(void) { int i; dev_t dev = 0; if(alloc_chrdev_region(&dev,cmos_minor,NUM_CMOS_BANKS,DEVICE_NAME) < 0) { printk(KERN_DEBUG "Can't regiester device\n"); return -1; } cmos_major = MAJOR(dev); /*********I don't know what this is************/ cmos_class = class_create(THIS_MODULE,DEVICE_NAME); release_region(0x70,0x8); for(i = 0;i < NUM_CMOS_BANKS;i++) { cmos_devp = kmalloc(sizeof(struct cmos_dev),GFP_KERNEL); if(!cmos_devp) { printk("Bad Kmalloc\n"); return 1; } sprintf(cmos_devp->name,"cmos%d",i); if(!(request_region(addrports[i],2,cmos_devp->name))) { printk("cmos: I/O port 0x%x is not free.\n",addrports[i]); return -EIO; } cmos_devp->bank_number = i; cdev_init(&cmos_devp->cdev,&cmos_fops); cmos_devp->cdev.owner = THIS_MODULE; if(cdev_add(&cmos_devp->cdev,(dev + i),1)) { printk("Bad cdev\n"); return 1; } device_create(cmos_class,NULL,(dev + i),NULL,"cmos%d",i); } printk("CMOS Driver Initialized.\n"); return 0; } void cmos_cleanup(void) { int i; dev_t devno = MKDEV(cmos_major,cmos_minor); cdev_del(&cmos_devp->cdev); unregister_chrdev_region(devno,NUM_CMOS_BANKS); for(i = 0;i < NUM_CMOS_BANKS;i++) { device_destroy(cmos_class,MKDEV(MAJOR(devno),i)); release_region(addrports[i],2); } class_destroy(cmos_class); return ; } module_init(cmos_init); module_exit(cmos_cleanup);
update : 2014.08.15 17:55
给出完整的cmos driver,只是这个版本还有bug...
上面的demo也是有下面列出的bug的
#bug1:装在之后是不能卸载的,除非重启电脑,而且感觉0x70-0x73口一直被占用,
#bug2:如果init的时候,不释放0x70-0x77口,会装载失败,即使释放用的4个口都不行,必须一次释放1byte,就是八个口
希望高手路过能够交流,好缓解一下渣渣我焦虑的心情啊~
update: 2014.08.15 20:28
我删除了之前贴出来有问题的demo,但保留了发现的bug部分,并给出目前bug最少(我还没发现bug)的版本,希望路过高手批评指正交流
void cmos_cleanup(void) { int i; dev_t devno = MKDEV(cmos_major,cmos_minor); cdev_del(&cmos_devp->cdev); unregister_chrdev_region(devno,NUM_CMOS_BANKS);</span> for(i = 0;i < NUM_CMOS_BANKS;i++) { device_destroy(cmos_class,MKDEV(MAJOR(devno),i)); release_region(addrports[i],2); } class_destroy(cmos_class);</span> return ; }
我是看着ELDD的demo照着写的,这里其是有问题的!unregister_chrdev_region不能发生在class_destory之前的,否则会出现#bug1
#bug3,之前的代码设备名和文件名是不一至的,这倒置/dev目录下面找不到设备!
把文件名和设备名保持一致即可消除#bug3
关于CMOS-PC芯片储存数据地址相关信息:
在CMOS内存中,0-0DH为实时钟的有关信息,0E-&127;3FH包含计算机的硬件配置信息,如常规内存的大小、扩展内存的大小、&127;软盘的类型、固定盘的类型及其物理参数、显示器的类型等,这些参数与计算机能否正常工作具有密切的关系,另外还有计算机的开机口令和其它辅助设置信息。表1列出了&127;CMOS内存各字节的用途。
地 址 功能 说明
0,1 秒,秒报警
2,3 分,分报警
4,5 时,时报警
6 星期几
7,8,9 日,月,年
A 状态寄存器A
B 状态寄存器B
C 状态寄存器C
D 状态寄存器D 0=电池失效,80=电池有效
E 诊断状态
F 关机状态 由上电诊断定义
10 软驱 高4位为A驱,低4位为B驱,0=无, 1=360KB, 2=1.2KB, 4=1.44KB, 6=720KB
11 保留
12 固定盘 高4位为C驱,低4位为D驱,0=无,F=用户定义盘, 其它为系统定义盘
13 保留
14 设备状态 标志驱动器数、显示器类型、有无数学处理器等
15-16 内存 以KB计的常规内存数,100H=256KB,200H=512KB, 280H=640KB
17-18 扩展内存 以KB计的扩展内存数,200H=512KB,400H=1024KB等
2014.08.16 01:35 release version:
/************************************************************ code writer : EOF code date : 2014.08.16 code file : cmos.c e-mail: [email protected] code purpose: This code is a demo for how to build a CMOS-driver step by step. If you find something wrong with my code, please touch me by e-mail. Thank you all the time. ************************************************************/ #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/cdev.h> #include <linux/slab.h> /* kmalloc() */ #include <linux/kernel.h> /* printk */ #include <linux/device.h> #include <linux/ioport.h> #include <linux/kdev_t.h> #include <linux/types.h> #include <linux/ioport.h> #include <asm/uaccess.h> /* ** We allocate the device number dynamically */ int cmos_major = 0; int cmos_minor = 0; module_param(cmos_major,int,S_IRUGO); module_param(cmos_minor,int,S_IRUGO); struct class *cmos_class; /* Tie with the device model */ #define NUM_CMOS_BANKS 2 #define CMOS_BANK_SIZE (0xFF*8) #define DEVICE_NAME "cmos" #define CMOS_BANK0_INDEX_PORT 0x70 #define CMOS_BANK0_DATA_PORT 0x71 #define CMOS_BANK1_INDEX_PORT 0x72 //At this moment , I still don't know what was stored in port 0x72 and 0x73 #define CMOS_BANK1_DATA_PORT 0x73 unsigned char addrports[NUM_CMOS_BANKS] = {CMOS_BANK0_INDEX_PORT,CMOS_BANK1_INDEX_PORT}; unsigned char dataports[NUM_CMOS_BANKS] = {CMOS_BANK0_DATA_PORT,CMOS_BANK1_DATA_PORT}; MODULE_AUTHOR("Jason Leaster"); MODULE_LICENSE("Dual BSD/GPL"); unsigned char port_data_in(unsigned char offset,int bank) { unsigned char data; if(unlikely(bank >= NUM_CMOS_BANKS)) { printk("Unknow CMOS Bank\n"); return 0; } else { outb(offset,addrports[bank]); data = inb(dataports[bank]); } return data; } void port_data_out(unsigned char offset,unsigned char data,int bank) { if(unlikely(bank >= NUM_CMOS_BANKS)) { printk("Unknow CMOS BANK\n"); return ; } else { outb(offset,addrports[bank]); outb(data,dataports[bank]); } return ; } struct cmos_dev { unsigned short current_pointer; /*Current point within the bank*/ unsigned int size; /*size of the bank*/ int bank_number; /*Size of bank*/ struct cdev cdev; char name[10]; /*Name of I/O regeion*/ /* ... */ }*cmos_devp; int cmos_open(struct inode *inode,struct file* file) { struct cmos_dev* cmos_devp; cmos_devp = container_of(inode->i_cdev,struct cmos_dev,cdev); file->private_data = cmos_devp; cmos_devp->size = CMOS_BANK_SIZE; /* Initialize pointer -- current_pointer */ cmos_devp->current_pointer = 0; return 0; } int cmos_release(struct inode* inode,struct file* file) { struct cmos_dev *cmos_devp = file->private_data; cmos_devp->current_pointer = 0; return 0; } ssize_t cmos_read(struct file *file,char* buf,size_t count,loff_t *ppos) { struct cmos_dev *cmos_devp = file->private_data; char data[CMOS_BANK_SIZE]; unsigned char mask; int xferred = 0,i =0,l,zero_out; /* ** if we run 'read' first time, 'start_byte' must be 0 */ int start_byte = cmos_devp->current_pointer/8; int start_bit = cmos_devp->current_pointer%8; if(cmos_devp->current_pointer >= cmos_devp->size) { return 0; } /* ** If 'count' is bigger than the max size of the space left, ** you can't read data beyond 'cmos_devp->size', so assign the ** count by value of the space left. */ if(cmos_devp->current_pointer + count > cmos_devp->size) { count = cmos_devp->size - cmos_devp->current_pointer; } while( xferred < count) { data[i] = port_data_in(start_byte,cmos_devp->bank_number) >> start_bit; xferred += (8-start_bit); if((start_bit) && (count + start_bit > 8)) { printk("unalign\n"); data[i] |= (port_data_in(start_byte+1,cmos_devp->bank_number) << (8-start_bit)); xferred += start_bit; } printk("%d %X \n",i,data[i]); start_byte++; i++; } if(xferred > count) { zero_out = xferred - count; mask = 1 << (8-zero_out); for(l = 0; l < zero_out; l++) { data[i-l] &= ~mask; mask <<= 1; } xferred = count; } if(!xferred) { return -EIO; } if(copy_to_user(buf,(char*)data,((xferred/8)+1)) != 0) { return -EIO; } cmos_devp->current_pointer += xferred; return xferred; } ssize_t cmos_write(struct file * file,const char *buf,size_t count,loff_t *ppos) { struct cmos_dev* cmos_devp = file->private_data; int xferred = 0,i = 0,l ,end_l,start_l; char *kbuf,tmp_kbuf; unsigned char tmp_data = 0,mask; int start_byte = cmos_devp->current_pointer/8; int start_bit = cmos_devp->current_pointer%8; if(cmos_devp->current_pointer >= cmos_devp->size) { return 0; } if(cmos_devp->current_pointer + count > cmos_devp->size) { count = cmos_devp->size - cmos_devp->current_pointer; } kbuf = kmalloc((count/8)+1,GFP_KERNEL); if(kbuf == NULL) { return -ENOMEM; } if(copy_from_user(kbuf,buf,(count/8)+1)) { kfree(kbuf); return -EFAULT; } while(xferred < count) { tmp_data = port_data_in(start_byte,cmos_devp->bank_number); mask = 1<<start_bit; end_l = 8; if((count - xferred) < (8-start_bit)) { end_l = (count - xferred) + start_bit; } for(l = start_bit; l < end_l;l++) { tmp_data &= ~mask; mask <<= 1; } tmp_kbuf = kbuf[i]; mask = 1 << end_l; for(l = end_l; l < 8; l++) { tmp_kbuf &= ~mask; mask <<=1; } port_data_out(start_byte,tmp_data | (tmp_kbuf << start_bit),cmos_devp->bank_number); xferred += (end_l - start_bit); if((xferred < count) && (start_bit) && (count + start_bit > 8)) { tmp_data = port_data_in(start_byte+1,cmos_devp->bank_number); start_l = ((start_bit + count) % 8); mask = l << start_l; for(l = 0;l < start_l;l++) { mask >>= 1; tmp_data &= ~mask; } port_data_out((start_byte+1),tmp_data | (kbuf[i] >> (8 - start_bit)), cmos_devp->bank_number); xferred += start_l; } start_byte++; i++; } if(!xferred) { return -EIO; } cmos_devp->current_pointer += xferred; return xferred; } static loff_t cmos_llseek(struct file* file,loff_t offset,int orig) { struct cmos_dev * cmos_devp = file->private_data; switch(orig) { case 0: if(offset >= cmos_devp->size) { return -EINVAL; } cmos_devp->current_pointer = offset; break; case 1: if((cmos_devp->current_pointer + offset) >= cmos_devp->size) { return -EINVAL; } cmos_devp->current_pointer = offset; break; case 2: return -EINVAL; default: return -EINVAL; } return (cmos_devp->current_pointer); } static struct file_operations cmos_fops = { .owner = THIS_MODULE, .open = cmos_open, .release= cmos_release, .read = cmos_read, .write = cmos_write, .llseek = cmos_llseek, /* wait for implementation */ // .ioctl = cmos_ioctl, }; int cmos_init(void) { int i; dev_t devno = 0; if(alloc_chrdev_region(&devno,cmos_minor,NUM_CMOS_BANKS,DEVICE_NAME) < 0) { printk(KERN_DEBUG "Can't regiester device\n"); return -1; } cmos_major = MAJOR(devno); /*********I don't know what this is************/ cmos_class = class_create(THIS_MODULE,DEVICE_NAME); release_region(0x70,0x8); for(i = 0;i < NUM_CMOS_BANKS;i++) { cmos_devp = kmalloc(sizeof(struct cmos_dev),GFP_KERNEL); if(!cmos_devp) { printk("Bad Kmalloc\n"); return 1; } sprintf(cmos_devp->name,"cmos%d",i); /* ** request for two port one for address and the other for data */ if(!(request_region(addrports[i],2,cmos_devp->name))) { printk("cmos: I/O port 0x%x is not free.\n",addrports[i]); return -EIO; } cmos_devp->bank_number = i; /* ** initialization for character device by function cdev_init */ cdev_init(&cmos_devp->cdev,&cmos_fops); cmos_devp->cdev.owner = THIS_MODULE; if(cdev_add(&cmos_devp->cdev,(devno + i),1)) { printk("Bad cdev\n"); return 1; } device_create(cmos_class,NULL,(devno + i),NULL,"cmos%d",i); } printk("CMOS Driver Initialized.\n"); return 0; } void cmos_cleanup(void) { int i; dev_t devno = MKDEV(cmos_major,cmos_minor); cdev_del(&cmos_devp->cdev); for(i = 0;i < NUM_CMOS_BANKS;i++) { device_destroy(cmos_class,MKDEV(MAJOR(devno),i)); release_region(addrports[i],2); } class_destroy(cmos_class); unregister_chrdev_region(devno,NUM_CMOS_BANKS); return ; } module_init(cmos_init); module_exit(cmos_cleanup);
测试程序:(update 2014.08.16 17:14,谢谢@浪陨 帮忙找出了测试demo里面的bug)
/********************************************************************* code writer : EOF code date : 2014.08.16 code file : cmos_test_user_space.c e-mail : [email protected] code purpose: This is a demo for user how to use CMOS device driver. Unfortunately, there are some bugs I can't fix it up. #BUG1--2014.08.16: mid-night You know that data store in CMOS register as BCD-code.If you want to represent it as deciminal, we could transform two-bits BCD-code into deciminal number by this way: Deciminal number = ((BCD-code&0xF0)>>4)*10+(BCD-code)&0x0F; But there is something strange, it does work in 'second' which in my code is cmos[0]. If you have someidea to fix it up, please touch me. Thank you. #BUG2--2014.08.16: mid-night The day value is smaller than the real time by one day. Eg: Today is 08.16, but it print out 'day:15'.God know it... #BUG1 fixed in 2014.08.16 afternoon by @lang_yun. I have to say thanks to him. Hey guys, never forget the () around 'cmos[0]&0xF0' ... Otherwise, you will be puzzle.. *********************************************************************/ #include <stdio.h> #include <fcntl.h> #define BUFSIZE 100 #define CMOS_TO_BE_READ 80 int main() { int fd = 0; int counter = 0; unsigned char cmos[CMOS_TO_BE_READ]; unsigned char buf[BUFSIZE]; if((fd = open("/dev/cmos0",O_RDONLY)) < 0) { printf("fopen failed!\n"); return 0; } if(read(fd,cmos,CMOS_TO_BE_READ) != CMOS_TO_BE_READ) { printf("read error\n"); return 0; } counter += sprintf(buf+counter,"Time now: "); counter += sprintf(buf+counter,"Year: 20%d ",((cmos[9]&0xF0)>>4)*10 + (cmos[9]&0x0F)); counter += sprintf(buf+counter,"month: %d ",((cmos[8]&0xF0)>>4)*10 + (cmos[8]&0x0F)); counter += sprintf(buf+counter,"day: %d ",((cmos[7]&0xF0)>>4)*10 + (cmos[7]&0x0F)); counter += sprintf(buf+counter,"hour: %d ",((cmos[4]&0xF0)>>4)*10 + (cmos[4]&0x0F)); counter += sprintf(buf+counter,"minute: %d ",((cmos[2]&0xF0)>>4)*10 + (cmos[2]&0x0F)); counter += sprintf(buf+counter,"second: %d \n",((cmos[0]&0xF0)>>4)*10 + (cmos[0]&0x0F)); printf("%s",buf); close(fd); return 0; }
运行结果:
平凡着的,在路上的