bash> ls -l /dev
total 0
crw------- 1 root root 5, 1 Jul 16 10:02 console
...
lrwxrwxrwx 1 root root 3 Oct 6 10:02 cdrom -> hdc
...
brw-rw---- 1 root disk 3, 0 Oct 6 2007 hda
brw-rw---- 1 root disk 3, 1 Oct 6 2007 hda1
...
crw------- 1 root tty 4, 1 Oct 6 10:20 tty1
crw------- 1 root tty 4, 2 Oct 6 10:02 tty2
第一个字符:c:字符设备、l:symlink、b:块设备
第五个字符5,1:主设备号,次设备号;主设备号能够寻找到驱动,而次设备号则能够定位设备。
字符设备和块设备的空间不一样,因此,可以使用同一主设备号
从编码角度看需要:(1)、初始化并注册设备。(2)、应用通过/dev下节点的文件系统调用:open()、read()、ioctl()、llseek()、write()
(3)中断处理程序、底半部、时钟处理、内核辅助线程和其他支撑架构。
从数据流角度看,字符设备需要:(1)设备对应结构体;(2)struct cdev;(3)、struct file_operation;(4)struct file。
Device Example:System CMOS
BIOS使用CMOS来存储诸如startup option,boot order,and the system date,您可以通过BIOS启动菜单修改。例子CMOS驱动可以像普通文件一样访问2个PC CMOS块,应用程序可以操作/dev/cmos/0和/dev/cmos/1,并使用I/O系统调用来访问两个bank里面的数据。因为BIOS给CMOS区域以位级粒度,驱动也能够以位级访问。所以read()可以读取指定的位数,并移动相应位数指针。
Table 5.1. Register Layout on the CMOS
Register Name | Description |
---|
CMOS_BANK0_INDEX_PORT | Specify the desired CMOS bank 0 offset in this register. |
CMOS_BANK0_DATA_PORT | Read/write data from/to the address specified in CMOS_BANK0_INDEX_PORT. |
CMOS_BANK1_INDEX_PORT | Specify the desired CMOS bank 1 offset in this register. |
CMOS_BANK1_DATA_PORT | Read/write data from/to the address specified in CMOS_BANK1_INDEX_PORT. |
Driver Initialization
init()的责任:(1)请求分配设备主设备号;(2)为每个设备结构分配内存;(3)链接字符设备驱动cdev的入口点(open()、read()等);(4)联系主设备号和驱动的cdev;(5)在/dev和/sys下创建节点;(6)初始化硬件。以下是示例程序:
Listing 5.1. CMOS Driver Initialization
Code View: #include <linux/fs.h>
/* Per-device (per-bank) structure */
struct cmos_dev {
unsigned short current_pointer; /* Current pointer within the
bank */
unsigned int size; /* Size of the bank */
int bank_number; /* CMOS bank number */
struct cdev cdev; /* The cdev structure */
char name[10]; /* Name of I/O region */
/* ... */ /* Mutexes, spinlocks, wait
queues, .. */
} *cmos_devp;
/* File operations structure. Defined in linux/fs.h */
static struct file_operations cmos_fops = {
.owner = THIS_MODULE, /* Owner */
.open = cmos_open, /* Open method */
.release = cmos_release, /* Release method */
.read = cmos_read, /* Read method */
.write = cmos_write, /* Write method */
.llseek = cmos_llseek, /* Seek method */
.ioctl = cmos_ioctl, /* Ioctl method */
};
static dev_t cmos_dev_number; /* Allotted 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 "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,};
/*
* Driver Initialization
*/
int __init
cmos_init(void)
{
int i;
/* Request dynamic allocation of a device major number */
if (alloc_chrdev_region(&cmos_dev_number, 0,
NUM_CMOS_BANKS, DEVICE_NAME) < 0) {
printk(KERN_DEBUG "Can't register device\n"); return -1;
}
/* Populate sysfs entries */
cmos_class = class_create(THIS_MODULE, DEVICE_NAME);
for (i=0; i<NUM_CMOS_BANKS; i++) {
/* Allocate memory for the per-device structure */
cmos_devp = kmalloc(sizeof(struct cmos_dev), GFP_KERNEL);
if (!cmos_devp) {
printk("Bad Kmalloc\n"); return 1;
}
/* Request I/O region */
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;
}
/* Fill in the bank number to correlate this device
with the corresponding CMOS bank */
cmos_devp->bank_number = i;
/* Connect the file operations with the cdev */
cdev_init(&cmos_devp->cdev, &cmos_fops);
cmos_devp->cdev.owner = THIS_MODULE;
/* Connect the major/minor number to the cdev */
if (cdev_add(&cmos_devp->cdev, (dev_number + i), 1)) {
printk("Bad cdev\n");
return 1;
}
/* Send uevents to udev, so it'll create /dev nodes */
class_device_create(cmos_class, NULL, (dev_number + i),
NULL, "cmos%d", i);
}
printk("CMOS Driver Initialized.\n");
return 0;
}
/* Driver Exit */
void __exit
cmos_cleanup(void)
{
int i;
/* Remove the cdev */
cdev_del(&cmos_devp->cdev);
/* Release the major number */
unregister_chrdev_region(MAJOR(dev_number), NUM_CMOS_BANKS);
/* Release I/O region */
for (i=0; i<NUM_CMOS_BANKS; i++) {
class_device_destroy(cmos_class, MKDEV(MAJOR(dev_number), i));
release_region(addrports[i], 2);
}
/* Destroy cmos_class */
class_destroy(cmos_class);
return();
}
module_init(cmos_init);
module_exit(cmos_cleanup); |
cmos_init()使用alloc_chrdev_region()来动态请求未使用的主设备号,dev_number包含了得到的主设备号,第二个和第三个参数用于指定起始次设备号和支持的次设备数目,最后一个参数是设备名,可用于在/proc/devices中指定CMOS:
bash> cat /proc/devices | grep cmos
253 cmos
在2.6以前,无法动态分配主设备号,只有通过register_chrdev()来注册指定的主设备号。
cdev_init()把文件操作cmos_fops和cdev联系起来,cdev_add()将alloc_chrdev_region()分配的主/次设备号给cdev。
class_create()为该设备产生一个sysfs入口,class_device_create()则导致产生两个uevent:cmos0和cmos1,udevd会侦听uevent并在查询/etc/udev/rules.d里的规则库后产生/dev/cmos/0和/dev/cmos/1对应的设备节点。要在/etc/udev/rules.d/里面加入:
KERNEL="cmos[0-1]*", NAME="cmos/%n"
设备驱动需要操作一个I/O地址范围,因此使用request_region()来请求该地址,该技术会使其他请求该I/O地址空间的请求者失败,直到拥有者释放空间release_region().request_region()通常由总线申请,如PCI、ISA。request_region()的最后一个参数是/proc/ioports使用的标记,因此:
bash> grep cmos /proc/ioports
0070-0071 : cmos0
0072-0073 : cmos1
Open and Release
当应用程序打开节点时,就会激发驱动程序的open()。您可以通过bash> cat /dev/cmos/0来激发cmos_open()的执行,当cat关闭/dev/cmos/0的文件描述符时,会导致cmos_release()的执行。
Listing 5.2. Open and Release
Code View: /*
* Open CMOS bank
*/
int
cmos_open(struct inode *inode, struct file *file)
{
struct cmos_dev *cmos_devp;
/* Get the per-device structure that contains this cdev */
cmos_devp = container_of(inode->i_cdev, struct cmos_dev, cdev);
/* Easy access to cmos_devp from rest of the entry points */
file->private_data = cmos_devp;
/* Initialize some fields */
cmos_devp->size = CMOS_BANK_SIZE;
cmos_devp->current_pointer = 0;
return 0;
}
/*
* Release CMOS bank
*/
int
cmos_release(struct inode *inode, struct file *file)
{
struct cmos_dev *cmos_devp = file->private_data;
/* Reset file pointer */
cmos_devp->current_pointer = 0;
return 0;
} |
Exchanging Data
read()和write()是用户空间和设备交换的基本函数,其他的还有fsync(),aio_read(),aio_write()和mmap()。
COMS是一个简单的memory设备,因此CMOS数据访问程序不必是sleep-wait 设备IO完成,但其他的一些字符设备都要支持blocking和nonblocking操作。除非设备文件以nonblocking(O_NONBLOCK)方式打开,否则read()和write()允许calling进程睡眠直到相应操作完成;(2)CMOS驱动以完全同步方式而不是依赖于中断。
内核空间和用户空间的交互使用函数copy_to_user()和copy_from_user(),由于这两个函数会陷入睡眠,所以调用时不能持有spinlock锁。读取或写入不同大小的数据使用一些架构无关的函数in[b|w|l|sb|sl]()和out[b|w|l|sb|sl]().
Listing 5.3. Read and Write
Code View: /*
* Read from a CMOS Bank at bit-level granularity
*/
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;
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; /*EOF*/
}
/* Adjust count if it edges past the end of the CMOS bank */
if (cmos_devp->current_pointer + count > cmos_devp->size) {
count = cmos_devp->size - cmos_devp->current_pointer;
}
/* Get the specified number of bits from the CMOS */
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)) {
data[i] |= (port_data_in (start_byte + 1,
cmos_devp->bank_number) << (8 - start_bit));
xferred += start_bit;
}
start_byte++;
i++;
}
if (xferred > count) {
/* Zero out (xferred-count) bits from the MSB
of the last data byte */
zero_out = xferred - count;
mask = 1 << (8 - zero_out);
for (l=0; l < zero_out; l++) {
data[i-1] &= ~mask; mask <<= 1;
}
xferred = count;
}
if (!xferred) return -EIO;
/* Copy the read bits to the user buffer */
if (copy_to_user(buf, (void *)data, ((xferred/8)+1)) != 0) {
return -EIO;
}
/* Increment the file pointer by the number of xferred bits */
cmos_devp->current_pointer += xferred;
return xferred; /* Number of bits read */
}
/*
* Write to a CMOS bank at bit-level granularity. 'count' holds the
* number of bits to be written.
*/
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; /* EOF */
}
/* Adjust count if it edges past the end of the CMOS bank */
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;
/* Get the bits from the user buffer */
if (copy_from_user(kbuf,buf,(count/8)+1)) {
kfree(kbuf);
return -EFAULT;
}
/* Write the specified number of bits to the CMOS bank */
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 = 1 << 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;
/* Push the offset pointer forward */
cmos_devp->current_pointer += xferred;
return xferred; /* Return the number of written bits */
}
/*
* Read data from specified CMOS bank
*/
unsigned char
port_data_in(unsigned char offset, int bank)
{
unsigned char data;
if (unlikely(bank >= NUM_CMOS_BANKS)) {
printk("Unknown CMOS Bank\n");
return 0;
} else {
outb(offset, addrports[bank]); /* Read a byte */
data = inb(dataports[bank]);
}
return data;
}
/*
* Write data to specified CMOS bank
*/
void
port_data_out(unsigned char offset, unsigned char data,
int bank)
{
if (unlikely(bank >= NUM_CMOS_BANKS)) {
printk("Unknown CMOS Bank\n");
return;
} else {
outb(offset, addrports[bank]); /* Output a byte */
outb(data, dataports[bank]);
}
return;
} |
当write()成功返回时,说明应用程序已经将数据成功传给了驱动,但并不保证数据写到了设备里面,如果需要这个保证,那么需要调用fsync()。当应用程序的数据来源自不同的分散的buffers时,需要使用readv和writev(),从2.6.19以后这两个函数被folded into Linux AIO层里面了,the vector driver相关的原型包括:
ssize_t aio_read(struct kiocb *iocb, const struct iovec *vector,
unsigned long count, loff_t offset);
ssize_t aio_write(struct kiocb *iocb, const struct iovec *vector,
unsigned long count, loff_t offset);
第一个参数描述了AIO操作,第二个参数则是iovecs的数组,记录了buffers的地址和长度,可查看iovecs的定义和/drivers/net/tun.c下对vectors drivers的实现。另外一个数据访问方法是mmap(),它将设备内存和用户虚拟内存联系起来。应用可以调用同名系统调用mmap(),并在返回的内存区域上直接操作设备驻留内存。可查看/drivers/char/mem.c查看实例。
Seek
内核使用内部指针来跟踪文件内访问位置,应用程序调用lseek(),进而调用相应的驱动llseek()。这里的CMOS驱动是按bit移动的。先看一下lseek()使用的几个参数:(1)SEEK_SET,从文件头计算;(2)SEEK_CUR,从当前位置计算;(3)SEEK_END,从文件尾部计算。
Listing 5.4. Seek
Code View: /*
* Seek to a bit offset within a CMOS bank
*/
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: /* SEEK_SET */
if (offset >= cmos_devp->size) {
return -EINVAL;
}
cmos_devp->current_pointer = offset; /* Bit Offset */
break;
case 1: /* SEEK_CURR */
if ((cmos_devp->current_pointer + offset) >=
cmos_devp->size) {
return -EINVAL;
}
cmos_devp->current_pointer = offset; /* Bit Offset */
break;
case 2: /* SEEK_END - Not supported */
return -EINVAL;
default:
return -EINVAL;
}
return(cmos_devp->current_pointer);
} |
Control
ioctl用于接收用户空间命令并请求特殊操作,CMOS memory由cyclic redundancy check(CRC)算法保护。为检测数据是否崩溃,CMOS驱动支持两个ioctl命令:(1)Adjust checksum,用于当CMOS内容改变后重新计算CRC。计算出的checksum放在实现定义好的CMOS bank1的某处。(2)Verify checksum,用于检查CMOS的内容是否healthy,通过比较当前内容的CRC和预存的CRC来判断。应用程序想进行校验操作时,就将这些命令通过ioctl()系统调用发给驱动。
Listing 5.5. I/O Control
#define CMOS_ADJUST_CHECKSUM 1
#define CMOS_VERIFY_CHECKSUM 2
#define CMOS_BANK1_CRC_OFFSET 0x1E
/*
* Ioctls to adjust and verify CRC16s.
*/
static int
cmos_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
unsigned short crc = 0;
unsigned char buf;
switch (cmd) {
case CMOS_ADJUST_CHECKSUM:
/* Calculate the CRC of bank0 using a seed of 0 */
crc = adjust_cmos_crc(0, 0);
/* Seed bank1 with CRC of bank0 */
crc = adjust_cmos_crc(1, crc);
/* Store calculated CRC */
port_data_out(CMOS_BANK1_CRC_OFFSET,
(unsigned char)(crc & 0xFF), 1);
port_data_out((CMOS_BANK1_CRC_OFFSET + 1),
(unsigned char) (crc >> 8), 1);
break;
case CMOS_VERIFY_CHECKSUM:
/* Calculate the CRC of bank0 using a seed of 0 */
crc = adjust_cmos_crc(0, 0);
/* Seed bank1 with CRC of bank0 */
crc = adjust_cmos_crc(1, crc);
/* Compare the calculated CRC with the stored CRC */
buf = port_data_in(CMOS_BANK1_CRC_OFFSET, 1);
if (buf != (unsigned char) (crc & 0xFF)) return -EINVAL;
buf = port_data_in((CMOS_BANK1_CRC_OFFSET+1), 1);
if (buf != (unsigned char)(crc >> 8)) return -EINVAL;
break;
default:
return -EIO;
}
return 0;
} |