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
第一個(gè)字符:c:字符設(shè)備、l:symlink、b:塊設(shè)備
第五個(gè)字符5,1:主設(shè)備號,次設(shè)備號;主設(shè)備號能夠?qū)ふ业津?qū)動,而次設(shè)備號則能夠定位設(shè)備。
字符設(shè)備和塊設(shè)備的空間不一樣,因此,可以使用同一主設(shè)備號
從編碼角度看需要:(1)、初始化并注冊設(shè)備。(2)、應(yīng)用通過/dev下節(jié)點(diǎn)的文件系統(tǒng)調(diào)用:open()、read()、ioctl()、llseek()、write()
(3)中斷處理程序、底半部、時(shí)鐘處理、內(nèi)核輔助線程和其他支撐架構(gòu)。
從數(shù)據(jù)流角度看,字符設(shè)備需要:(1)設(shè)備對應(yīng)結(jié)構(gòu)體;(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驅(qū)動可以像普通文件一樣訪問2個(gè)PC CMOS塊,應(yīng)用程序可以操作/dev/cmos/0和/dev/cmos/1,并使用I/O系統(tǒng)調(diào)用來訪問兩個(gè)bank里面的數(shù)據(jù)。因?yàn)锽IOS給CMOS區(qū)域以位級粒度,驅(qū)動也能夠以位級訪問。所以read()可以讀取指定的位數(shù),并移動相應(yīng)位數(shù)指針。
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()的責(zé)任:(1)請求分配設(shè)備主設(shè)備號;(2)為每個(gè)設(shè)備結(jié)構(gòu)分配內(nèi)存;(3)鏈接字符設(shè)備驅(qū)動cdev的入口點(diǎn)(open()、read()等);(4)聯(lián)系主設(shè)備號和驅(qū)動的cdev;(5)在/dev和/sys下創(chuàng)建節(jié)點(diǎn);(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()來動態(tài)請求未使用的主設(shè)備號,dev_number包含了得到的主設(shè)備號,第二個(gè)和第三個(gè)參數(shù)用于指定起始次設(shè)備號和支持的次設(shè)備數(shù)目,最后一個(gè)參數(shù)是設(shè)備名,可用于在/proc/devices中指定CMOS:
bash> cat /proc/devices | grep cmos
253 cmos
在2.6以前,無法動態(tài)分配主設(shè)備號,只有通過register_chrdev()來注冊指定的主設(shè)備號。
cdev_init()把文件操作cmos_fops和cdev聯(lián)系起來,cdev_add()將alloc_chrdev_region()分配的主/次設(shè)備號給cdev。
class_create()為該設(shè)備產(chǎn)生一個(gè)sysfs入口,class_device_create()則導(dǎo)致產(chǎn)生兩個(gè)uevent:cmos0和cmos1,udevd會偵聽uevent并在查詢/etc/udev/rules.d里的規(guī)則庫后產(chǎn)生/dev/cmos/0和/dev/cmos/1對應(yīng)的設(shè)備節(jié)點(diǎn)。要在/etc/udev/rules.d/里面加入:
KERNEL="cmos[0-1]*", NAME="cmos/%n"
設(shè)備驅(qū)動需要操作一個(gè)I/O地址范圍,因此使用request_region()來請求該地址,該技術(shù)會使其他請求該I/O地址空間的請求者失敗,直到擁有者釋放空間release_region().request_region()通常由總線申請,如PCI、ISA。request_region()的最后一個(gè)參數(shù)是/proc/ioports使用的標(biāo)記,因此:
bash> grep cmos /proc/ioports
0070-0071 : cmos0
0072-0073 : cmos1
Open and Release
當(dāng)應(yīng)用程序打開節(jié)點(diǎn)時(shí),就會激發(fā)驅(qū)動程序的open()。您可以通過bash> cat /dev/cmos/0來激發(fā)cmos_open()的執(zhí)行,當(dāng)cat關(guān)閉/dev/cmos/0的文件描述符時(shí),會導(dǎo)致cmos_release()的執(zhí)行。
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()是用戶空間和設(shè)備交換的基本函數(shù),其他的還有fsync(),aio_read(),aio_write()和mmap()。
COMS是一個(gè)簡單的memory設(shè)備,因此CMOS數(shù)據(jù)訪問程序不必是sleep-wait 設(shè)備IO完成,但其他的一些字符設(shè)備都要支持blocking和nonblocking操作。除非設(shè)備文件以nonblocking(O_NONBLOCK)方式打開,否則read()和write()允許calling進(jìn)程睡眠直到相應(yīng)操作完成;(2)CMOS驅(qū)動以完全同步方式而不是依賴于中斷。
內(nèi)核空間和用戶空間的交互使用函數(shù)copy_to_user()和copy_from_user(),由于這兩個(gè)函數(shù)會陷入睡眠,所以調(diào)用時(shí)不能持有spinlock鎖。讀取或?qū)懭氩煌笮〉臄?shù)據(jù)使用一些架構(gòu)無關(guān)的函數(shù)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;
} |
當(dāng)write()成功返回時(shí),說明應(yīng)用程序已經(jīng)將數(shù)據(jù)成功傳給了驅(qū)動,但并不保證數(shù)據(jù)寫到了設(shè)備里面,如果需要這個(gè)保證,那么需要調(diào)用fsync()。當(dāng)應(yīng)用程序的數(shù)據(jù)來源自不同的分散的buffers時(shí),需要使用readv和writev(),從2.6.19以后這兩個(gè)函數(shù)被folded into Linux AIO層里面了,the vector driver相關(guān)的原型包括:
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);
第一個(gè)參數(shù)描述了AIO操作,第二個(gè)參數(shù)則是iovecs的數(shù)組,記錄了buffers的地址和長度,可查看iovecs的定義和/drivers/net/tun.c下對vectors drivers的實(shí)現(xiàn)。另外一個(gè)數(shù)據(jù)訪問方法是mmap(),它將設(shè)備內(nèi)存和用戶虛擬內(nèi)存聯(lián)系起來。應(yīng)用可以調(diào)用同名系統(tǒng)調(diào)用mmap(),并在返回的內(nèi)存區(qū)域上直接操作設(shè)備駐留內(nèi)存??刹榭?drivers/char/mem.c查看實(shí)例。
Seek
內(nèi)核使用內(nèi)部指針來跟蹤文件內(nèi)訪問位置,應(yīng)用程序調(diào)用lseek(),進(jìn)而調(diào)用相應(yīng)的驅(qū)動llseek()。這里的CMOS驅(qū)動是按bit移動的。先看一下lseek()使用的幾個(gè)參數(shù):(1)SEEK_SET,從文件頭計(jì)算;(2)SEEK_CUR,從當(dāng)前位置計(jì)算;(3)SEEK_END,從文件尾部計(jì)算。
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)算法保護(hù)。為檢測數(shù)據(jù)是否崩潰,CMOS驅(qū)動支持兩個(gè)ioctl命令:(1)Adjust checksum,用于當(dāng)CMOS內(nèi)容改變后重新計(jì)算CRC。計(jì)算出的checksum放在實(shí)現(xiàn)定義好的CMOS bank1的某處。(2)Verify checksum,用于檢查CMOS的內(nèi)容是否healthy,通過比較當(dāng)前內(nèi)容的CRC和預(yù)存的CRC來判斷。應(yīng)用程序想進(jìn)行校驗(yàn)操作時(shí),就將這些命令通過ioctl()系統(tǒng)調(diào)用發(fā)給驅(qū)動。
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;
} |