一步步写 CMOS 驱动模块

一步步写 CMOS 驱动模块


 Let's implement a char driver to access the system CMOS.


一步步写 CMOS 驱动模块 <ELDD 学习笔记>_第1张图片


首先仅仅是创建设备模块,最简单的,类似于前面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);

这里还每个给出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:	[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;
}




运行结果:

















平凡着的,在路上的

一步步写 CMOS 驱动模块 <ELDD 学习笔记>_第2张图片



你可能感兴趣的:(一步步写 CMOS 驱动模块 )