zoukankan      html  css  js  c++  java
  • 字符设备驱动leds

    内核版本:4.12.9

    编译器:arm-linux-gcc-4.4.3

    本驱动基于jz2440 v2开发板,实现3个led设备的驱动程序。

    代码如下:

      1 #include <linux/module.h>
      2 #include <linux/kernel.h>
      3 #include <linux/fs.h>
      4 #include <linux/init.h>
      5 #include <linux/delay.h>
      6 #include <asm/uaccess.h>
      7 #include <asm/irq.h>
      8 #include <asm/io.h>
      9 #include <linux/cdev.h>
     10 #include <linux/uaccess.h>
     11 
     12 #define DEVICE_NAME        "leds"
     13 #define LED_MAJOR        231
     14 
     15 #define LED_COUNT    (4)
     16 int major;
     17 int minor;
     18 static struct cdev led_cdev;
     19 
     20 static struct class *leds_class;
     21 static struct device *leds_class_devs[4];
     22 
     23 
     24 /* bit0<=>D10, 0:亮, 1:灭 
     25  *  bit1<=>D11, 0:亮, 1:灭 
     26  *  bit2<=>D12, 0:亮, 1:灭 
     27  */ 
     28 static char leds_status = 0x0;
     29 static DEFINE_SEMAPHORE(leds_lock);//定义赋值
     30 
     31 volatile unsigned long *gpfcon = NULL;
     32 volatile unsigned long *gpfdat = NULL;
     33 
     34 
     35 #define S3C2410_GPF4 4
     36 #define S3C2410_GPF5 5
     37 #define S3C2410_GPF6 6
     38 
     39 #define S3C2410_GPF4_OUTP 1
     40 #define S3C2410_GPF5_OUTP 1
     41 #define S3C2410_GPF6_OUTP 1
     42 
     43 
     44 static void s3c2410_gpio_cfgpin(int pin, int type)
     45 {        
     46     *gpfcon &= ~(0x3<<(pin*2));
     47     *gpfcon |= (1<<(pin*2));
     48 
     49 }
     50 
     51 static void s3c2410_gpio_setpin(int pin, int data)
     52 {
     53     if(data == 0)
     54         *gpfdat &= ~(1<<pin);
     55     else
     56         *gpfdat |= (1<<pin);
     57 }
     58 
     59 /* 应用程序对设备文件/dev/leds执行open(...)时,
     60  * 就会调用s3c24xx_leds_open函数
     61  */
     62 static int s3c24xx_leds_open(struct inode* inode, struct file* file)
     63 {
     64     int minor = MINOR(inode->i_rdev);
     65 
     66     switch(minor)
     67     {
     68         case 0:/*/dev/leds*/
     69         {
     70             /* 配置GPF4,5,6为输出*/
     71             *gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2)));
     72             *gpfcon |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x01<<(6*2)));
     73             
     74             // 都输出0
     75             *gpfdat &= ~(1<<4);
     76             *gpfdat &= ~(1<<5);
     77             *gpfdat &= ~(1<<6);    
     78 
     79             down(&leds_lock);
     80             leds_status = 0x0;
     81             up(&leds_lock);
     82             printk("/dev/leds open!
    ");
     83 
     84         }break;
     85 
     86         case 1: /* /dev/led1 */
     87         {
     88             s3c2410_gpio_cfgpin(S3C2410_GPF4, S3C2410_GPF4_OUTP);
     89             s3c2410_gpio_setpin(S3C2410_GPF4, 0);
     90             
     91             down(&leds_lock);
     92             leds_status &= ~(1<<0);
     93             up(&leds_lock);
     94             
     95             break;
     96         }
     97 
     98         case 2: /* /dev/led2 */
     99         {
    100             s3c2410_gpio_cfgpin(S3C2410_GPF5, S3C2410_GPF5_OUTP);
    101             s3c2410_gpio_setpin(S3C2410_GPF5, 0);
    102             leds_status &= ~(1<<1);
    103             break;
    104         }
    105 
    106         case 3: /* /dev/led3 */
    107         {
    108             s3c2410_gpio_cfgpin(S3C2410_GPF6, S3C2410_GPF6_OUTP);
    109             s3c2410_gpio_setpin(S3C2410_GPF6, 0);
    110 
    111             down(&leds_lock);
    112             leds_status &= ~(1<<2);
    113             up(&leds_lock);
    114             
    115             break;
    116         }
    117         default:
    118             break;
    119 
    120     }
    121 
    122     return 0;
    123 }
    124 
    125 static int s3c24xx_leds_read(struct file *filp, char __user *buff, 
    126                                          size_t count, loff_t *offp)
    127 {
    128     int minor = MINOR(filp->f_inode->i_rdev);
    129     char val;
    130     long ret;
    131 
    132     switch (minor)
    133     {
    134         case 0: /* /dev/leds */
    135         {
    136             
    137             ret = copy_to_user(buff, (const void *)&leds_status, 1);                    
    138             break;
    139         }
    140 
    141         case 1: /* /dev/led1 */
    142         {
    143             down(&leds_lock);
    144             val = leds_status & 0x1;
    145             up(&leds_lock);
    146             ret = copy_to_user(buff, (const void *)&val, 1);
    147             break;
    148         }
    149 
    150         case 2: /* /dev/led2 */
    151         {
    152             down(&leds_lock);
    153             val = (leds_status>>1) & 0x1;
    154             up(&leds_lock);
    155             ret = copy_to_user(buff, (const void *)&val, 1);
    156             break;
    157         }
    158 
    159         case 3: /* /dev/led3 */
    160         {
    161             down(&leds_lock);
    162             val = (leds_status>>2) & 0x1;
    163             up(&leds_lock);
    164             ret = copy_to_user(buff, (const void *)&val, 1);
    165             break;
    166         }
    167         
    168     }
    169 
    170     return 1;
    171 }
    172 
    173 static ssize_t s3c24xx_leds_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
    174 {
    175     int minor = MINOR(file->f_inode->i_rdev);
    176     char val;
    177 
    178     long ret = 0;
    179     ret = copy_from_user(&val, buf, 1);
    180     if(ret > 0)
    181     {
    182         printk("s3c24xx_leds_write():copy_from_user fail! ret:%ld", ret);
    183         return -EFAULT;
    184     }
    185 
    186     switch (minor)
    187     {
    188         case 0: /* /dev/leds */
    189         {            
    190             if(val == 0)//点灯
    191             {
    192                 printk("led_write led on
    ");
    193                 *gpfdat &= ~((1<<4) | (1<<5) | (1<<6));
    194             }
    195             else//灭灯
    196             {
    197                 printk("led_write led off
    ");
    198                 *gpfdat |= ((1<<4) | (1<<5) | (1<<6));
    199             }
    200 
    201             
    202             down(&leds_lock);
    203             leds_status = val;
    204             up(&leds_lock);
    205             printk("/dev/leds write val:%d!
    ", val);
    206             break;
    207         }
    208 
    209         case 1: /* /dev/led1 */
    210         {
    211             s3c2410_gpio_setpin(S3C2410_GPF4, val);
    212 
    213             if (val == 0)
    214             {
    215                 down(&leds_lock);
    216                 leds_status &= ~(1<<0);
    217                 up(&leds_lock);
    218             }
    219             else
    220             {
    221                 down(&leds_lock);
    222                 leds_status |= (1<<0);                
    223                 up(&leds_lock);
    224             }
    225             break;
    226         }
    227 
    228         case 2: /* /dev/led2 */
    229         {
    230             s3c2410_gpio_setpin(S3C2410_GPF5, val);
    231             if (val == 0)
    232             {
    233                 down(&leds_lock);
    234                 leds_status &= ~(1<<1);
    235                 up(&leds_lock);
    236             }
    237             else
    238             {
    239                 down(&leds_lock);
    240                 leds_status |= (1<<1);                
    241                 up(&leds_lock);
    242             }
    243             break;
    244         }
    245 
    246         case 3: /* /dev/led3 */
    247         {
    248             s3c2410_gpio_setpin(S3C2410_GPF6, val);
    249             if (val == 0)
    250             {
    251                 down(&leds_lock);
    252                 leds_status &= ~(1<<2);
    253                 up(&leds_lock);
    254             }
    255             else
    256             {
    257                 down(&leds_lock);
    258                 leds_status |= (1<<2);                
    259                 up(&leds_lock);
    260             }
    261             break;
    262         }
    263         
    264     }
    265 
    266     return 1;
    267 }
    268 
    269 
    270 /* 这个结构是字符设备驱动程序的核心
    271  * 当应用程序操作设备文件时所调用的open、read、write等函数,
    272  * 最终会调用这个结构中指定的对应函数
    273  */
    274 static struct file_operations s3c24xx_leds_fops = {
    275     .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    276     .open   =   s3c24xx_leds_open,     
    277     .read    =    s3c24xx_leds_read,       
    278     .write    =    s3c24xx_leds_write,       
    279 };
    280 
    281 /*
    282  * 执行insmod命令时就会调用这个函数 
    283  */
    284 static int __init s3c24xx_leds_init(void)
    285 {
    286     int minor = 0;
    287     int result = 0;
    288     dev_t devid;
    289     
    290     major = LED_MAJOR;
    291 
    292     devid = MKDEV(major, 0);//从主设备号major,次设备号0,得到dev_t类型
    293     if(major)
    294     {
    295         result = register_chrdev_region(devid, LED_COUNT, "leds");//注册字符设备
    296         minor = MINOR(devid);
    297     }
    298     else
    299     {
    300         result = alloc_chrdev_region(&devid, 0, LED_COUNT, "leds");//注册字符设备
    301         major = MAJOR(devid);    //从dev_t类型得到主设备
    302         minor = MINOR(devid);
    303     }
    304     printk("led major=%d, minor=%d
    ", major, minor);
    305 
    306     cdev_init(&led_cdev, &s3c24xx_leds_fops);
    307     cdev_add(&led_cdev, devid, LED_COUNT);
    308     leds_class = class_create(THIS_MODULE, "myleds");//在/sys/class/下创建类目录
    309     if (IS_ERR(leds_class))
    310     {
    311         printk("class_create is error!
    ");
    312         return PTR_ERR(leds_class);
    313     }
    314     printk("create leds class ok!
    ");
    315     //在/dev目录下创建设备节点
    316     leds_class_devs[0] = device_create(leds_class, NULL, MKDEV(LED_MAJOR, 0), NULL, "leds");
    317     printk("create device leds ok!
    ");
    318     
    319     for (minor = 1; minor < 4; minor++)
    320     {
    321         leds_class_devs[minor] = device_create(leds_class, NULL, MKDEV(LED_MAJOR, minor), NULL, "led%d", minor);
    322         printk("create led%d ok!
    ", minor);
    323         if (unlikely(IS_ERR(leds_class_devs[minor])))
    324         {
    325             printk("unlikely is error!
    ");
    326             return PTR_ERR(leds_class_devs[minor]);
    327         }
    328     }
    329 
    330     gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);    //映射到虚拟地址
    331     gpfdat = gpfcon + 1;
    332     
    333     printk(DEVICE_NAME " initialized
    ");
    334     return 0;
    335 }
    336 
    337 /*
    338  * 执行rmmod命令时就会调用这个函数 
    339  */
    340 static void __exit s3c24xx_leds_exit(void)
    341 {
    342     int minor;
    343     /* 卸载驱动程序 */
    344 
    345     //1.销毁设备
    346     for (minor = 0; minor < 4; minor++)
    347     {
    348         device_destroy(leds_class, MKDEV(LED_MAJOR, minor));
    349     }
    350     //2.销毁类
    351     class_destroy(leds_class);
    352     //3.销毁cdev
    353     cdev_del(&led_cdev);
    354 
    355 
    356     unregister_chrdev_region(MKDEV(LED_MAJOR, 0), LED_COUNT);
    357     iounmap(gpfcon);
    358     
    359 }
    360 
    361 /* 这两行指定驱动程序的初始化函数和卸载函数 */
    362 module_init(s3c24xx_leds_init);
    363 module_exit(s3c24xx_leds_exit);
    364 
    365 /* 描述驱动程序的一些信息,不是必须的 */
    366 MODULE_AUTHOR("jz2440");
    367 MODULE_VERSION("0.1.0");
    368 MODULE_DESCRIPTION("S3C2410/S3C2440 LED Driver");
    369 MODULE_LICENSE("GPL");
    View Code

     测试代码:

     1 #include <sys/types.h>
     2 #include <sys/stat.h>
     3 #include <fcntl.h>
     4 #include <stdio.h>
     5 
     6 /*
     7   *  ledtest <dev> <on|off>
     8   */
     9 
    10 void print_usage(char *file)
    11 {
    12     printf("Usage:
    ");
    13     printf("%s <dev> <on|off>
    ",file);
    14     printf("eg. 
    ");
    15     printf("%s /dev/leds on
    ", file);
    16     printf("%s /dev/leds off
    ", file);
    17     printf("%s /dev/led1 on
    ", file);
    18     printf("%s /dev/led1 off
    ", file);
    19 }
    20 
    21 int main(int argc, char **argv)
    22 {
    23     int fd;
    24     char* filename;
    25     char val;
    26 
    27     if (argc != 3)
    28     {
    29         print_usage(argv[0]);
    30         return 0;
    31     }
    32 
    33     filename = argv[1];
    34 
    35     fd = open(filename, O_RDWR);
    36     if (fd < 0)
    37     {
    38         printf("error, can't open %s
    ", filename);
    39         return 0;
    40     }
    41 
    42     if (!strcmp("on", argv[2]))
    43     {
    44         // 亮灯
    45         val = 0;
    46         write(fd, &val, 1);
    47     }
    48     else if (!strcmp("off", argv[2]))
    49     {
    50         // 灭灯
    51         val = 1;
    52         write(fd, &val, 1);
    53     }
    54     else
    55     {
    56         print_usage(argv[0]);
    57         return 0;
    58     }
    59     
    60     
    61     return 0;
    62 }
    View Code

    本驱动程序可实现对3个LED灯整体控制和单个控制。仔细分析该代码有利于理解linux对于字符驱动实现方式及架构的理解。

    本文主要分析驱动代码中的两个关键函数s3c24xx_leds_init()和s3c24xx_leds_exit()。

    1、注册字符设备

    内核提供了三个函数来注册一组字符设备编号,这三个函数分别是 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()。

    (1)register_chrdev 比较老的内核注册的形式   早期的驱动

    (2)register_chrdev_region/alloc_chrdev_region + cdev  新的驱动形式,需要配合下面两个函数使用:

        cdev_init(&led_cdev, &s3c24xx_leds_fops);
        cdev_add(&led_cdev, devid, LED_COUNT);

    区别: register_chrdev函数是老版本里面的设备号注册函数,可以实现静态和动态注册两种方法,主要是通过给定的主设备号是否为0来进行区别,为0的时候为动态注册。register_chrdev_region以及alloc_chrdev_region就是将上述函数的静态和动态注册设备号进行了拆分的强化。

    1.1 register_chrdev_region

    静态注册方法,即已知需要注册的设备号,通过调用该函数进行注册。

    函数原型:

    int register_chrdev_region(dev_t from, unsigned count, const char *name);

    from:设备编号。
    count:连续编号范围,是这组设备号的大小(也是次设备号的个数)。
    name:编号相关联的设备名称. (/proc/devices); 本组设备的驱动名称。

    对应卸载函数:

    void unregister_chrdev_region(dev_t from, unsigned count)

     

    1.2 alloc_chrdev_region

    动态注册方法。

    函数原型

    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);

      dev:获得一个分配到的设备,可以用MAJOR宏和MINOR宏,将主设备号和次设备号提取出来。
      baseminor:次设备号的基准,从第几个次设备号开始分配。
      count:次设备号的个数。
      name:驱动的名字。

      说明:register_chrdev_region是在事先知道要使用的主、次设备号时使用的;要先查看cat /proc/devices去查看没有使用的。更简便、更智能的方法是让内核给我们自动分配一个主设备号,使用alloc_chrdev_region就可以自动分配了。自动分配的设备号,我们必须去知道他的主次设备号,否则后面没法去创建他对应的设备文件。

      对应卸载函数:

    void unregister_chrdev_region(dev_t from, unsigned count)

      1.3 cdev_init初始化

      执行注册后,需要实现cdev对象的初始化。以下是cdev结构的定义

    struct cdev {
       struct kobject kobj;          // 每个 cdev 都是一个 kobject
       struct module *owner;       // 指向实现驱动的模块
       const struct file_operations *ops;   // 操纵这个字符设备文件的方法
       struct list_head list;       // 与 cdev 对应的字符设备文件的 inode->i_devices 的链表头
       dev_t dev;                   // 起始设备编号
       unsigned int count;       // 设备范围号大小
    };

      本驱动代码使用的是静态内存定义的方式,使用以下两个函数。

      1)、cdev_init 初始化函数

      从以下代码中可见,该函数一方面对cdev的内部对象进行了初始化,另一方面,把fops与cdev进行了绑定。

    /**
     * cdev_init() - initialize a cdev structure
     * @cdev: the structure to initialize
     * @fops: the file_operations for this device
     *
     * Initializes @cdev, remembering @fops, making it ready to add to the
     * system with cdev_add().
     */
    void cdev_init(struct cdev *cdev, const struct file_operations *fops)
    {
        memset(cdev, 0, sizeof *cdev);
        INIT_LIST_HEAD(&cdev->list);
        kobject_init(&cdev->kobj, &ktype_cdev_default);
        cdev->ops = fops;
    }

      2)、cdev_add函数

      该函数将p加入到当前系统中,并使其立即生效。如果失败,将返回一个负的错误码。

    /**
     * cdev_add() - add a char device to the system
     * @p: the cdev structure for the device
     * @dev: the first device number for which this device is responsible
     * @count: the number of consecutive minor numbers corresponding to this
     *         device
     *
     * cdev_add() adds the device represented by @p to the system, making it
     * live immediately.  A negative error code is returned on failure.
     */
    int cdev_add(struct cdev *p, dev_t dev, unsigned count)
    {
        int error;
    
        p->dev = dev;
        p->count = count;
    
        error = kobj_map(cdev_map, dev, count, NULL,
                 exact_match, exact_lock, p);
        if (error)
            return error;
    
        kobject_get(p->kobj.parent);
    
        return 0;
    }

      需要说明的是,无论是动态注册还是静态注册,都是需要通过设备号获取设备在设备表中的位置(一个dev_t类型变量),该变量在后续会用的到。

      获取该设备号的方法如下:

    MKDEV(MAJOR, MINOR);

      MKDEV是一个宏,返回一个dev_t类型数据;

      MAJOR:主设备号

      MINOR:次设备号

      2.创建设备节点

      2.1 手动创建

      在开发板执行insmod命令后,在执行"mknod /dev/led c 252 0",这样就创建出了主设备号252,次设备号0的/dev/led这个设备。

      2.2自动创建

      通过调用class_creat和device_create分别创建类和设备文件。

      调用class_creat这个宏会在/sys/class目录下创建类目录,如下图所示,创建了myleds目录,并创建了4个连接。

    /* This is a #define to keep the compiler from merging different
     * instances of the __key variable */
    #define class_create(owner, name)        
    ({                        
        static struct lock_class_key __key;    
        __class_create(owner, name, &__key);    
    })

      owner:一般使用 THIS_MODULE

      name:本驱动使用“myleds”

      __class_create函数实现可参见 linux-4.12.9driversaseclass.c

      调用device_create函数将在/dev目录下创建对应的设备节点。

      效果如下:

       有以下注释可知,该函数在sysfs中创建了一个device。注意,调用该函数前,必须先创建一个类。

    /**
     * device_create - creates a device and registers it with sysfs
     * @class: pointer to the struct class that this device should be registered to
     * @parent: pointer to the parent struct device of this new device, if any
     * @devt: the dev_t for the char device to be added
     * @drvdata: the data to be added to the device for callbacks
     * @fmt: string for the device's name
     *
     * This function can be used by char device classes.  A struct device
     * will be created in sysfs, registered to the specified class.
     *
     * A "dev" file will be created, showing the dev_t for the device, if
     * the dev_t is not 0,0.
     * If a pointer to a parent struct device is passed in, the newly created
     * struct device will be a child of that device in sysfs.
     * The pointer to the struct device will be returned from the call.
     * Any further sysfs files that might be required can be created using this
     * pointer.
     *
     * Returns &struct device pointer on success, or ERR_PTR() on error.
     *
     * Note: the struct class passed to this function must have previously
     * been created with a call to class_create().
     */
    struct device *device_create(struct class *class, struct device *parent,
                     dev_t devt, void *drvdata, const char *fmt, ...)
    {
        va_list vargs;
        struct device *dev;
    
        va_start(vargs, fmt);
        dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
        va_end(vargs);
        return dev;
    }

      对应的卸载函数:

    /**
     * device_destroy - removes a device that was created with device_create()
     * @class: pointer to the struct class that this device was registered with
     * @devt: the dev_t of the device that was previously registered
     *
     * This call unregisters and cleans up a device that was created with a
     * call to device_create().
     */
    void device_destroy(struct class *class, dev_t devt);
    /**
     * class_destroy - destroys a struct class structure
     * @cls: pointer to the struct class that is to be destroyed
     *
     * Note, the pointer to be destroyed must have been created with a call
     * to class_create().
     */
    void class_destroy(struct class *cls)

      3. ioremap

      几乎每一种外设都是通过读写设备上的寄存器来进行的,通常包括控制寄存器、状态寄存器和数据寄存器三大类,外设的寄存器通常被连续地编址。根据CPU体系结构的不同,CPU对IO端口的编址方式有两种:

    a -- I/O 映射方式(I/O-mapped)
        典型地,如X86处理器为外设专门实现了一个单独的地址空间,称为"I/O地址空间"或者"I/O端口空间",CPU通过专门的I/O指令(如X86的IN和OUT指令)来访问这一空间中的地址单元。

    b -- 内存映射方式(Memory-mapped)
        RISC指令系统的CPU(如ARM、PowerPC等)通常只实现一个物理地址空间,外设I/O端口成为内存的一部分。此时,CPU可以象访问一个内存单元那样访问外设I/O端口,而不需要设立专门的外设I/O指令。

      一般来说,在系统运行时,外设的I/O内存资源的物理地址是已知的,由硬件的设计决定。但是CPU通常并没有为这些已知的外设I/O内存资源的物理地址预定义虚拟地址范围,驱动程序并不能直接通过物理地址访问I/O内存资源,而必须将它们映射到核心虚地址空间内(通过页表),然后才能根据映射所得到的核心虚地址范围,通过访内指令访问这些I/O内存资源。

      Linux在io.h头文件中声明了函数ioremap,用来将I/O内存资源的物理地址映射到核心虚地址空间(3GB-4GB)中(这里是内核空间),原型如下:

    static inline void __iomem *ioremap(phys_addr_t offset, size_t size)

      phys_addr:要映射的起始的IO地址
      size:要映射的空间的大小

       对应卸载函数:

    void iounmap(void * addr);

      内核代码中关于这个函数挺复杂,看了下最终应该会调用到__iounmap这个函数,mmu这部分后面再看了。这里应该知道在驱动卸载时调用该函数。

     

      总结:以上介绍了字符驱动安装和退出相关的函数,以及他们在linux系统的对应的行为。需要留意的一点是,无论init还是exit时对应的函数调用,都需要遵循一定的顺序。并且init和exit的顺序刚好是相反的。

      字符驱动初始化:

      1、注册字符驱动,cdev设备初始化。

      2、创建对应的class。

      3、创建设备device。

      4、调用ioremap进行内存空间到物理空间地址的映射。

    无论设备个数,类作为对设备的抽象,只有一个。但是表现在内核中/sys/class/classname的类对象实际上是由device_create函数创建的。在本例中就创建了4个led设备。

     

     

     

  • 相关阅读:
    mock 数据模拟
    利用css绘制三角形,半圆等形状
    页面底部固定
    Form Data格式传参
    element 页面显示效果及需要注意的点
    vue 组件加载顺序
    vue-router 导航钩子
    vue 总结
    前端开发的碎碎念
    值匹配的方式
  • 原文地址:https://www.cnblogs.com/mofei004/p/12297595.html
Copyright © 2011-2022 走看看