Linux设备驱动实例:并行端口LED板(包含/dev以及/sysfs下的方法)

本文引用自《精通Linux设备驱动程序开发》Sreekrishnan Venkateswaran 宋宝华[等]译 北京:人民邮电出版社,2010.06
        为了学习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 *);
        ssize_t (*store)(const char *, size_t cont);
};

#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,代码可重用,设计流程严谨。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值