全部博文(71)
分类: LINUX
2009-11-09 10:03:26
对话并行端口
并口是PC兼容机上可以经常看到的25针接口。并口(不管是单向的, 双向的,支持DMA等待)能力取决于底层芯片组.
drivers/parport/目录包含了IEEE 1284并扣口通讯的实现代码(parport). 一些连接并口的设备如打印机和扫描仪都使用并口服务. 并口代码有一个独立于CPU架构的模块"parport.ko"和一个架构相关的模块(PC架构上是"parport_pc.ko"), 它们为接口是并口的设备驱动提供编程接口.
我们先来看下并口打印机的驱动drivers/char/lp.c. 以下是打印一个文件所需的概括步骤:
1. 打印机驱动创建字符设备结点/dev/lp0一直到/dev/lpN, 每个连接打印机都有一个结点.
2. 通用UNIX打印系统(CUPS)是Linux上提供打印功能的框架. CUPS配置文件/etc/printers.conf映射打印机和它们的字符设备结点(/dev/lpX).
3. CUPS解析该文件并导流数据到对应的设备结点. 于是如果你有个打印机接到系统的第一个并口, 执行命令lpr myfile时就通过导流的/dev/lp0进入打印机驱动的写函数lp_write()(定义在drivers/char/lp.c).
4. lp_write()使用并口服务发送数据到打印机。
有一个字符驱动ppdev(drivers/char/ppdev.c)导出了可以让用户程序直接与并口通讯的/dev/parportX设备结点.
设备例子:并口LED板
关于如何利用parport提供的服务,我们先来写一个简单的驱动. 试想一个包含8个LED灯的板子连向标准25针并口. 由于PC上的8比特并口数据寄存器直接映射到并口连接器的2到9针, 这些针也就连到了板上的8个LED灯, 现在写并口数据寄存器控制这些针脚电压使得LED灯可亮可灭. 下面代码实现了该字符驱动:
#include
#include
#include
#include
#include
#define DEVICE_NAME "led"
static dev_t dev_number; /* Allotted device number */
static struct class *led_class; /* Class to which this device
belongs */
struct cdev led_cdev; /* Associated cdev */
struct pardevice *pdev; /* Parallel port device */
/* LED open */
int
led_open(struct inode *inode, struct file *file)
{
return 0;
}
/* Write to the LED */
ssize_t
led_write(struct file *file, const char *buf,
size_t count, loff_t *ppos)
{
char kbuf;
if (copy_from_user(&kbuf, buf, 1)) return -EFAULT;
/* Claim the port */
parport_claim_or_block(pdev);
/* Write to the device */
parport_write_data(pdev->port, kbuf);
/* Release the port */
parport_release(pdev);
return count;
}
/* Release the device */
int
led_release(struct inode *inode, struct file *file)
{
return 0;
}
/* File Operations */
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
.release = led_release,
};
static int
led_preempt(void *handle)
{
return 1;
}
/* Parport attach method */
static void
led_attach(struct parport *port)
{
/* Register the parallel LED device with parport */
pdev = parport_register_device(port, DEVICE_NAME,
led_preempt, NULL,
NULL, 0, NULL);
if (pdev == NULL) printk("Bad register\n");
}
/* Parport detach method */
static void
led_detach(struct parport *port)
{
/* Do nothing */
}
/* Parport driver operations */
static struct parport_driver led_driver = {
.name = "led",
.attach = led_attach,
.detach = led_detach,
};
/* Driver Initialization */
int __init
led_init(void)
{
/* Request dynamic allocation of a device major number */
if (alloc_chrdev_region(&dev_number, 0, 1, DEVICE_NAME)
< 0) {
printk(KERN_DEBUG "Can't register device\n");
return -1;
}
/* Create the led class */
led_class = class_create(THIS_MODULE, DEVICE_NAME);
if (IS_ERR(led_class)) printk("Bad class create\n");
/* Connect the file operations with the cdev */
cdev_init(&led_cdev, &led_fops);
led_cdev.owner = THIS_MODULE;
/* Connect the major/minor number to the cdev */
if (cdev_add(&led_cdev, dev_number, 1)) {
printk("Bad cdev add\n");
return 1;
}
class_device_create(led_class, NULL, dev_number,
NULL, DEVICE_NAME);
/* Register this driver with parport */
if (parport_register_driver(&led_driver)) {
printk(KERN_ERR "Bad Parport Register\n");
return -EIO;
}
printk("LED Driver Initialized.\n");
return 0;
}
/* Driver Exit */
void __exit
led_cleanup(void)
{
unregister_chrdev_region(MAJOR(dev_number), 1);
class_device_destroy(led_class, MKDEV(MAJOR(dev_number), 0));
class_destroy(led_class);
return;
}
module_init(led_init);
module_exit(led_cleanup);
MODULE_LICENSE("GPL");
led_init()和前面代码中的cmos_init()很类似,但有两点不同:
1. 新的设备模型可以区分驱动和设备. led_init(0)通过parport注册LED驱动,使用的是调用parport_register_driver()函数. 当内核在led_attach()中找到LED板后就调用parport_register_device()注册设备.
2. led_init()创建设备结点/dev/led, 你可以用它来控制单个LED灯的状态.
编译并添加设备模块到内核中:
bash> make –C /path/to/kerneltree/ M=$PWD modules
bash> insmod ./led.ko
LED Driver Initialized
要选择性的驱动并口针来控制对应的LED灯, 只需echo适当数据给/dev/led即可:
bash> echo 1 > /dev/led
由于1的ASCII值是31(00110001), 因此第一,第五,第六个LED灯会被点亮.
前面的命令触发了led_write(),这个驱动函数首先通过copy_from_user()来拷贝用户内存数据(31)到内核缓冲区,接着申请得到并口,写数据最后释放使用parport接口的并口.
sysfs是比/dev更好控制设备状态的地方, 所以委托sysfs文件来控制LED是个好主意. 下面代码包含了该想法的驱动实现. 这些对sysfs操作代码也可以作为模板实现驱动中对其他设备的控制.
使用sysfs控制并口LED板:
#include
#include
#include
#include
#include
static dev_t dev_number; /* Allotted Device Number */
static struct class *led_class; /* Class Device Model */
struct cdev led_cdev; /* Character dev struct */
struct pardevice *pdev; /* Parallel Port device */
struct kobject kobj; /* Sysfs directory object */
/* Sysfs attribute of the leds */
struct led_attr {
struct attribute attr;
ssize_t (*show)(char *);
ssize_t (*store)(const char *, size_t count);
};
#define glow_show_led(number) \
static ssize_t \
glow_led_##number(const char *buffer, size_t count) \
{ \
unsigned char buf; \
int value; \
\
sscanf(buffer, "%d", &value); \
\
parport_claim_or_block(pdev); \
buf = parport_read_data(pdev->port); \
if (value) { \
parport_write_data(pdev->port, buf | (1<
} else { \
parport_write_data(pdev->port, buf & ~(1<
} \
parport_release(pdev); \
return count; \
} \
\
static ssize_t \
show_led_##number(char *buffer) \
{ \
unsigned char buf; \
\
parport_claim_or_block(pdev); \
\
buf = parport_read_data(pdev->port); \
parport_release(pdev); \
\
if (buf & (1 << number)) { \
return sprintf(buffer, "ON\n"); \
} else { \
return sprintf(buffer, "OFF\n"); \
} \
} \
\
static struct led_attr led##number = \
__ATTR(led##number, 0644, show_led_##number, glow_led_##number);
glow_show_led(0); glow_show_led(1); glow_show_led(2);
glow_show_led(3); glow_show_led(4); glow_show_led(5);
glow_show_led(6); glow_show_led(7);
#define DEVICE_NAME "led"
static int
led_preempt(void *handle)
{
return 1;
}
/* Parport attach method */
static void
led_attach(struct parport *port)
{
pdev = parport_register_device(port, DEVICE_NAME,
led_preempt, NULL, NULL, 0,
NULL);
if (pdev == NULL) printk("Bad register\n");
}
/* Parent sysfs show() method. Calls the show() method
corresponding to the individual sysfs file */
static ssize_t
l_show(struct kobject *kobj, struct attribute *a, char *buf)
{
int ret;
struct led_attr *lattr = container_of(a, struct led_attr,attr);
ret = lattr->show ? lattr->show(buf) : -EIO;
return ret;
}
/* Sysfs store() method. Calls the store() method
corresponding to the individual sysfs file */
static ssize_t
l_store(struct kobject *kobj, struct attribute *a,
const char *buf, size_t count)
{
int ret;
struct led_attr *lattr = container_of(a, struct led_attr, attr);
ret = lattr->store ? lattr->store(buf, count) : -EIO;
return ret;
}
/* Sysfs operations structure */
static struct sysfs_ops sysfs_ops = {
.show = l_show,
.store = l_store,
};
/* Attributes of the /sys/class/pardevice/led/control/ kobject.
Each file in this directory corresponds to one LED. Control
each LED by writing or reading the associated sysfs file */
static struct attribute *led_attrs[] = {
&led0.attr,
&led1.attr,
&led2.attr,
&led3.attr,
&led4.attr,
&led5.attr,
&led6.attr,
&led7.attr,
NULL
};
/* This describes the kobject. The kobject has 8 files, one
corresponding to each LED. This representation is called the
ktype of the kobject */
static struct kobj_type ktype_led = {
.sysfs_ops = &sysfs_ops,
.default_attrs = led_attrs,
};
/* Parport methods. We don't have a detach method */
static struct parport_driver led_driver = {
.name = "led",
.attach = led_attach,
};
/* Driver Initialization */
int __init
led_init(void)
{
struct class_device *c_d;
/* Create the pardevice class - /sys/class/pardevice */
led_class = class_create(THIS_MODULE, "pardevice");
if (IS_ERR(led_class)) printk("Bad class create\n");
/* Create the led class device - /sys/class/pardevice/led/ */
c_d = class_device_create(led_class, NULL, dev_number,
NULL, DEVICE_NAME);
/* Register this driver with parport */
if (parport_register_driver(&led_driver)) {
printk(KERN_ERR "Bad Parport Register\n");
return -EIO;
}
/* Instantiate a kobject to control each LED
on the board */
/* Parent is /sys/class/pardevice/led/ */
kobj.parent = &c_d->kobj;
/* The sysfs file corresponding to kobj is
/sys/class/pardevice/led/control/ */
strlcpy(kobj.name, "control", KOBJ_NAME_LEN);
/* Description of the kobject. Specifies the list of attribute
files in /sys/class/pardevice/led/control/ */
kobj.ktype = &ktype_led;
/* Register the kobject */
kobject_register(&kobj);
printk("LED Driver Initialized.\n");
return 0;
}
/* Driver Exit */
void
led_cleanup(void)
{
/* Unregister kobject corresponding to
/sys/class/pardevice/led/control */
kobject_unregister(&kobj);
/* Destroy class device corresponding to
/sys/class/pardevice/led/ */
class_device_destroy(led_class, MKDEV(MAJOR(dev_number), 0));
/* Destroy /sys/class/pardevice */
class_destroy(led_class);
return;
}
module_init(led_init);
module_exit(led_cleanup);
MODULE_LICENSE("GPL");
宏定义glow_show_led()使用了内核代码中一种流行技巧来紧凑定义一些类似函数,为板上每个LED定义产生附加到8个/sys文件的read()和write()函数(在sysfs术语里面称为show()和store()). 因此glow_show_led(0)将glow_led_0()和show_led_0()附加到对应于第一个LED的/sys文件. 这些函数分别负责第一个LED的发光/熄灭和读状态. ##是粘一个值给字符串,当编译器处理语句glow_show_led(0)时,glow_led_##number就转换为glow_led_0().
sysfs版本的驱动使用kobject来表示模拟软件把手控制LED的“控制”抽象. 每个kobject通过sysfs里面一个文件夹名字代表,所以kobject_register()会创建/sys/class/pardevice/led/control/文件夹.
ktype描述了一个kobject. "控制"kobject通过ktype_led结构体描述, 这个结构体包含指向属性数组led_attrs[]的指针, 这个数组包含了每个LED设备属性的地址. 每个LED的属性通过下面语句捆绑在一起:
static struct led_attr led##number =
__ATTR(led##number, 0644, show_led_##number, glow_led_##number);
这会为每个LED产生控制文件/sys/class/pardevice/control/ledX, 其中X是LED编号. 要改变ledX的状态,echo一个1(或0)到对应的控制文件即可.
在模块退出阶段,驱动使用kobject_unregister(), class_device_destroy()和class_destroy()反注册kobject和class.
写一个字符驱动不再像2.4内核里面那样简单了, 为开发上述简单的LED驱动,我们使用了6个抽象: cdev, kobject,class,class device和parport. 但抽象也带给我们很多优点如无bug构建块, 代码复用和优雅设计.