一步步写 CMOS 驱动模块
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: jasonleaster@gmail.com
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);
这里还每个给出open,writer,read,release的实现,仅仅是实现注册模块的部分。不过,感觉不错~
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: jasonleaster@gmail.com
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 : jasonleaster@gmail.com
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;
}
运行结果:
平凡着的,在路上的