Linux混杂设备驱动

来源:互联网 发布:淘宝客服开头语 编辑:程序博客网 时间:2024/05/18 01:39
一、混杂设备的概念
Misc(或miscellaneous)驱动是一些拥有着共同特性的简单字符设备驱动。内核抽象出这些特性而形成一些API(在文件drivers/char/misc.c中实现),以简化这些设备驱动程序的初始化。所有的misc设备被分配同一个主设备号MISC_MAJOR(10),但是每一个可以选择一个单独的次设备号。如果一个字符设备驱动要驱动多个设备,那么它就不应该用misc设备来实现。
通常情况下,一个字符设备都不得不在初始化的过程中进行下面的步骤:
通过alloc_chrdev_region()分配主/次设备号。
使用cdev_init()和cdev_add()来以一个字符设备注册自己。
而一个misc驱动,则可以只用一个调用misc_register()来完成这所有的步骤。
所有的miscdevice设备形成一个链表,对设备访问时,内核根据次设备号查找对应的miscdevice设备,然后调用其file_operations中注册的文件操作方法进行操作。
在Linux内核中,使用struct miscdevice来表示miscdevice。这个结构体的定义为:
struct miscdevice  {
    int minor;
    const char *name;
    const struct file_operations *fops;
    struct list_head list;
    struct device *parent;
    struct device *this_device;
    const char *nodename;
    mode_t mode;
};
minor是这个混杂设备的次设备号,若由系统自动配置,则可以设置为MISC_DYNANIC_MINOR,name是设备名。
每一个misc驱动会自动出现在/sys/class/misc下,而不需要驱动程序作者明确的去做。
 
二、驱动程序及解释
实现对S3C2440GPIO的控制,是一个ARM-LINUX上最简单的驱动。本驱动通过S3C2440GPB5~8控制4LED,属MISC(混杂)驱动,其实MISC也是一种特殊的字符驱动,只不过是把主设备号为10的字符驱动归类为MISC类驱动。
MISC类驱动结构如下:
static struct miscdevice misc = {
                .minor = MISC_DYNAMIC_MINOR,
                .name = DEVICE_NAME,
                .fops = &dev_fops,
};

Minor为指定次设备号,等于MISC_DYNAMIC_MINOR表示为动态获取次设备号。       Name为设备名。
       Fops file_operations结构,设备的操作函数指针集合。
   file_operations结构:
static struct file_operations dev_fops = {
                .owner    =    THIS_MODULE,
                .ioctl    =    sbc2440_leds_ioctl,
};


  该设备省略了OPENRELESE操作函数,即默认为打开状态。我们只需要对IO口操作就可以实现LED灯的亮和灭,所以只需要一个IOCTL操作函数就可以满足要求了。
  
GPIO口的设置:
S3C2440设置GPIO口主要是设置3个寄存器。分别为GPxCON, GPxDAT, GPxUPxA…J)。GPxCON用于设置IO口的功能,每两位对应一个IO口,输入=00,输出=01;特殊功能=10GPxDATIO口的数据寄存器,每一位对应一个IO口。GPxUP为是否使用上拉电阻,0为使用,注意的是并不是每组IO口都有内部集成上拉电阻,如果没有集成,则没该寄存器。
设置这三个寄存器相对应的函数为:s3c2410_gpio_cfgpins3c2410_gpio_setpin这两个函数为ARM提供的,添加相应的头文件,直接调用就可以。不必深究其代码,了解功能方可。
加载函数:
static int __init dev_init(void)
{
    int ret;

    int i;
    
    for (i = 0; i < 4; i++) {
        s3c2410_gpio_cfgpin(led_table, led_cfg_table);
        s3c2410_gpio_setpin(led_table, 0);
    }

    ret = misc_register(&misc);

    printk (DEVICE_NAME"\tinitialized\n");

    return ret;
}

  在加载函数里,主要完成的就是初始化IO口和注册一个MISC类设备,MISC类在misc_register这个调用里,会帮我们根据struct miscdevice里面的成员帮我们注册一个字符设备,所以我们这个MISC的驱动加载函数比较短。
  卸载函数:
    static void __exit dev_exit(void)
{
        misc_deregister(&misc);
     printk (DEVICE_NAME"\tuninstalled\n");
}
  
 在卸载函数里,只需要调用misc_deregister卸载掉MISC设备就OK了。
  IOCTL函数:
    static int sbc2440_leds_ioctl(
    struct inode *inode, 
    struct file *file, 
    unsigned int cmd, 
    unsigned long arg)
{
    switch(cmd) {
    case 0:
    case 1:
        if (arg > 4) {
            return -EINVAL;
        }
        s3c2410_gpio_setpin(led_table[arg], !cmd);
        return 0;
    default:
        return -EINVAL;
    }
}

  在这个MISC设备里,主要用到的是控制函数。通过之前介绍的操作IO寄存器的函数,我们就可以很轻松地对IO口进行控制。
  完整代码:
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/string.h>
#include <linux/list.h>
#include <linux/pci.h>
#include <linux/gpio.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include <asm/unistd.h>


#define DEVICE_NAME "zhjled"

static unsigned long led_table [] = {
    S3C2410_GPB(5),
    S3C2410_GPB(6),
    S3C2410_GPB(7),
    S3C2410_GPB(8),
};

static unsigned int led_cfg_table [] = {
    S3C2410_GPIO_OUTPUT,
    S3C2410_GPIO_OUTPUT,
    S3C2410_GPIO_OUTPUT,
    S3C2410_GPIO_OUTPUT,
};

static int sbc2440_leds_ioctl(
    struct inode *inode, 
    struct file *file, 
    unsigned int cmd, 
    unsigned long arg)
{
    switch(cmd) {
    case 0:
    case 1:
        if (arg > 4) {
            return -EINVAL;
        }
        s3c2410_gpio_setpin(led_table[arg], !cmd);
        return 0;
    default:
        return -EINVAL;
    }
}

static struct file_operations dev_fops = {
    .owner    =    THIS_MODULE,
    .ioctl    =    sbc2440_leds_ioctl,
};

static struct miscdevice misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = DEVICE_NAME,
    .fops = &dev_fops,
};

static int __init dev_init(void)
{
    int ret;

    int i;
    
    for (i = 0; i < 4; i++) {
        s3c2410_gpio_cfgpin(led_table, led_cfg_table);
        s3c2410_gpio_setpin(led_table, 0);
    }

    ret = misc_register(&misc);

    printk (DEVICE_NAME"\tinstalled\n");

    return ret;
}

static void __exit dev_exit(void)
{
    misc_deregister(&misc);
  printk (DEVICE_NAME"\tuninstalled\n");
}

module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zhj");
 
三、驱动模块的制作与加载
  当我们编写好驱动程序后,保存成*.c文件,那这个文件如何编译,如何添加到内核中,又如何测试该驱动是否正常运行呢?
        1.准备
  首先要有一台主机,装好发行版linux操作系统(fedora 9),安装好交叉编译器(arm-linux-gcc),将mini2440源光盘中的linux内核源码解压到/opt/FriendlyARM/mini2440/目录下.
进入/opt/FriendlyARM/mini2440/linux-2.6.32.2目录,执行cp config_mini2440_t35 .config     
注意:t35 后面有个空格,然后有个“.”开头的config 这个是配置好的内核配置文件,也可以自己建立.
将led驱动保存成leds_name.c(zhjled.c),并且要存入目录opt/FriendlyARM/mini2440/linux-2.6.32.2/drivers/char下。
准备好mini2440开发板,预装linux操作系统,内核要同PC上的版本相同(linux-2.6.32.2).
2.配置
2.1改写Kconfig文件:cd 到opt/FriendlyARM/mini2440/linux-2.6.32.2/drivers/char目录,打开Kconfig文件,在文件中添加几行语句并保存,如下:
config LEDS_NAME                                 #(LEDS_ZHJ)
       tristate "LED Support for Mini2440 GPIO LEDs_ZHJ (name)"           
#((wtt)) 上一行主要是为了在配置内核编译模式时提供选择目标驱动所用的显示文字
       depends on MACH_MINI2440      #基于mini2440
       default y if MACH_MINI2440       #默认编译入内核 稍后会更改成编译为module形式
       help                                                #帮助信息
         This option enables support for LEDs connected to GPIO lines
         on Mini2440 boards. 
2.2配置.config文件
cd 到opt/FriendlyARM/mini2440/linux-2.6.32.2/目录,执行make menuconfig
cd 到opt/FriendlyARM/mini2440/linux-2.6.32.2/目录,执行make menuconfig,出现如下界面: 
找到
Device Drivers  ---> (回车)
Character devices  --->(回车)
用方向键选中 “LED Support for Mini2440 GPIO LEDs_ZHJ (NEW)”,按空格键使该行最前面的符号变为<M>(表示为编译成Module形式,默认<*>为编译入内核)。(该行即为3.1中所添加第二行),配置最后结果见图: 
配置完成后,一直按Exit,直到出现 
按YES(回车保存)。
2.3更改Makefile文件
cd 到opt/FriendlyARM/mini2440/linux-2.6.32.2/drivers/char目录,打开Makefile文件,在文件中添加如下语句并保存:
obj-$(CONFIG_LEDS_ZHJ)            += zhjled.o
#其中CONFIG_LEDS_ZHJ应该和3.1中的config LEDS_ZHJ相同。
2.4编译驱动
之前做了很多工作,到了编译环节了,编译就很容易了。
cd 到opt/FriendlyARM/mini2440/linux-2.6.32.2/目录下,执行make modules
编译好的module在驱动程序同一目录下(zhjled.ko)
2.5装载模块
编译好的驱动需要装载到目标板才能发挥作用。
将opt/FriendlyARM/mini2440/linux-2.6.32.2/drivers/char目录下的zhjled.ko文件拷到mini2440开发板的为/lib/modules/2.6.32.2-FriendlyARM目录下,必须这样才可以!
然后在目标板上执行modprobe leds_wtt 
我在驱动程序的init函数中添加了输出语句,所以我装载好之后目标板输出显示:
zhjled installed
装载完成,同时在/dev/modules文件夹下 多了一个设备文件zhjled
 
 
四、测试程序
#include <stdio.h>
     #include <stdlib.h>
  #include <unistd.h>
       #include <sys/ioctl.h>
int main(int argc, char **argv)
{
      int on;
      int led_no;
      int fd;
      if (argc != 3 || sscanf(argv[1], "%d", &led_no) != 1 || sscanf(argv[2],"%d", &on) != 1 ||
      on < 0 || on > 1 || led_no < 0 || led_no > 3) 
{
      fprintf(stderr, "Usage: leds led_no 0|1\n");
      exit(1);
      }
//打开led设备
      fd = open("/dev/zhjled", 0);
      if (fd < 0) 
{
      fd = open("/dev/zhjled", 0);
     }
     if (fd < 0) 
{
       perror("open device leds");
       exit(1);
       }
          /*通过系统调用 ioctl 和输入的参数控制 led*/
       ioctl(fd, on, led_no);
//关闭设备
       close(fd);
       return 0;
}
将上述程序保存成led_test.c,用arm-linux-gcc编译,如下:
arm-linux-gcc –o run led_test.c
编译得到的目标程序run,拷到mini2440开发板下,执行该程序,如下:
./run 0 1
可以看到开发板上的第一盏亮。同理./run 1 1    ./run 2 1  ./run 3 1  灯全亮了(灭把第三个参数设0)
注:mini2440预装的linux+qtopia上电后,led自动以0~16的二进制形式在4个led上变化,需要运行qtopia上的led_test,关掉该演示功能才能正常测试led驱动。
 
 
五、卸载模块
在目标板上执行
rmmod zhjled
我在驱动程序的exit函数中添加了输出语句,所以我卸载好之后目标板输出显示:
zhjled uninstalled
卸载完成。
 
六、此文章来自多个博客的总结。感谢前辈们的开源精神
0 0