电路板驱动程序"/>
LED电路板驱动程序
为了学习parport提供的服务,让我们编写一个简单的驱动程序。考虑一个有8个发光二极管(LED),提供和标准25针并行端口接口的电路板。因为PC上的8位并行端口数据寄存器直接映射到并行端口的2~9针,所以这些针脚和电路板上的LED连通。向并行端口数据寄存器写数据可以控制这些针脚的电平,进而控制LED的开关。如下代码为一个字符设备驱动程序,它通过系统并行端口和此电路板通信。代码内的注释解释了其中所使用的parport服务例程。 并行端口LED电路板驱动程序(led.c) #include<linux/fs.h> #include<linux/cdev.h> #include<linux/parport.h> #include<asm/uaccess.h> #include<linux/platform_device.h>#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;
/* 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 paralled 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 */ /* 新的设备模型将驱动程序和设备区分开来。led_init()通过parport_register_driver()调用项parport注册LED驱动程序。当内核在led_attach()中查找到LED板时,它调用parport_register_device()注册设备 */ int __init led_init(void) { /* Request dynamic allocatino of a device major number */ if (alloc_chrdev_region(&dev_number, 0, 1, DEVICE_NAME) < 0) { printk(KERN_DEUG "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 operation 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); /* 该函数创建设备节点/dev/led,可以用此设备节点控制每个LED的状态 编译并将驱动程序模块加入到内核中: bash> make -C /path/to/kerneltree/ M=$PWD modules PS:也许你不一定能成功,因为Linux Kernel 2.6要求编译模块之前,必须先在内核源代码目录下执行make,换言之,必须先配置过内核,执行过make,然后才能make自己的内核。(仔细想想你没有配置过内核,内核怎么知道该部分是编译成模块还是编译进内核的呢) bash> insmod ./led.ko LED Driver Initialized PS:早期的版本使用class_create()和class_device_create()这两个函数创建设备节点,到了 2.6.29内核以后,使用的函数则变成了class_create()和device_create(),并且要在声明中加入#include <linux/device.h>,不过使用这两个函数的前提是用户空间已经移植了udev。 内核中定义了struct class结构体,一个struct class结构体类型变量对应一个类(有待商榷), 内核同时提供了class_create(…)函数,可以用它来创建一个类,这个类存放于/sysfs下面,一旦创建好了这个类,再调用 device_create(…)函数来在/dev目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的udev会自动响应 device_create(…)函数,去/sysfs下寻找对应的类从而创建设备节点。 */
/* 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(dev_number, 1); class_device_destroy(led_class, dev_number); class_destroy(led_class); return ; }
module_init(led_init); module_exit(led_cleanup);
MODULE_LICENSE("GPL");
为了有选择地驱动一些并行端口针脚,点亮相应的LED,将相应的值赋给/dev/led: bash> echo 1 > /dev/led 因为1的ASCII值是31(00110001),第1,5和6个LED将会发亮。 前述命令触发led_write()调用。此驱动程序方法首先通过copy_from_user()将用户内存数据(在本例中为31)复制到内核缓冲区。然后占用并行端口,写入数据,释放端口,所有这些都使用parport接口。 相比于/dev,sysfs是更好的控制设备状态的地方。因此将LED控制委托给sysfs效果更佳。下面代码为此种驱动程序的实现代码,其中哦功能sysfs操作代码也可以作为模版用到其它的设备控制中去
使用sysfs控制并行端口LED电路板: #include<linux/fs.h> #include<linux/cdev.h> #include<linux/parport.h> #include<asm/uaccess.h> #include<linux/pci.h> 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 *);
#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<<number)); \ } else { \ parport_write_data(pdev->port, buf & ~(1<<number)); \ } \ 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); /* glow_show_led()使用了内核源代码中经常使用的技术,以便简洁地定义几个类似的函数。定义的read()和write()方法(在sysfs中用术语show()和store()表示)同8个/sys文件相关,电路板上每个LED对应一个文件。因此glow_show_led(0)将glow_led_0()和show_led_0()与第一个LED对应一个文件。这些函数分别负责点亮/熄灭第一个LED,并读取其状态。##在宏定义中用于把字符串连接在一起,因此当编译器处理语句glow_show_led(0)时,glow_led_##number就变成glow_led_0()。 */ #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); }
/* 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 8a, 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_atrrs[] = { &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_atach, };
/* Driver Initialization */ int __init led_init(void) { struct class_device *c_d; if calloc_chrdev_region(&dev_number, 01, DEVICE_NAME <0) { printk(KERN_DEBUG "can't register device \n"); return -1; }
/* 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/classpardevice/led */ kobj.parent = &c_d->kobj;
/* the sysfs file corresponding to kboj is /sys/class/pardevice/led/control/ */ strlcpy(kobj.name, "control", KOBJ_NAME_LED);
/* Description of the kobject. Specifies the list of attribute files in /sys/class/pardevice/led/control/ */ kobj.ktype = &ktype_led; /* ktype描述了kobject。ktype_led结构描述了“控制”kobject,它包含指向属性数组的指针led_attrs[]。led_attrs[]数组包含每个LED的设备属性的地址。每个LED的属性通过下列语句连接在一起: static struct led_attr led##number = __ATTR(led##number, 0644, show_led_##number, glow_led_##number); 其结果是为每个LED产生一个控制文件/sys/class/pardevice/led/control/ledX,其中X是LED序号。为了改变ledX的状态,将1(或者0)回送给相应的控制文件。如为了点亮第一个LED,可做如下操作: bash> echo 1 > /sys/class/pardevice/led/control/led0 在模块退出期间,驱动程序使用kobject_unregister(),class_device_destroy()和class_destroy()移除kobject和class。 */ /* Register the kobject */ kobject_register(&kobj); /* 基于sysfs版本的驱动程序使用了kobject用于代表“控制”抽象,它模拟了一个软件按钮控制LED。sysfs下的每个目录名代表一个kobject,因此代码清单中的kobject_register()创建了/sys/class/pardevice/led/control/目录 */ 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, dev_number);
/* Destroy /sys/class/pardevice */ class_destroy(led_class);
return; }
module_init(led_init); module_exit(led_cleanup);
MODULE_LICENSE("GPL");
编写字符驱动程序不再像2.4内核中那样简单了。在上下文中,为了开发简单的LED驱动彻骨女婿,我们使用了6个数据抽象:cdev,sysfs,kobject,class,class device 和parport。当然,这些数据抽象也有优点,如编译模块无bug,代码可重用,设计流程严谨。
更多推荐
LED电路板驱动程序
发布评论