字符设备驱动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!\n");
 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\n");
193                 *gpfdat &= ~((1<<4) | (1<<5) | (1<<6));
194             }
195             else//灭灯
196             {
197                 printk("led_write led off\n");
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!\n", 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\r\n", 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!\n");
312         return PTR_ERR(leds_class);
313     }
314     printk("create leds class ok!\n");
315     //在/dev目录下创建设备节点
316     leds_class_devs[0] = device_create(leds_class, NULL, MKDEV(LED_MAJOR, 0), NULL, "leds");
317     printk("create device leds ok!\n");
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!\n", minor);
323         if (unlikely(IS_ERR(leds_class_devs[minor])))
324         {
325             printk("unlikely is error!\n");
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\n");
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:\n");
13     printf("%s <dev> <on|off>\n",file);
14     printf("eg. \n");
15     printf("%s /dev/leds on\n", file);
16     printf("%s /dev/leds off\n", file);
17     printf("%s /dev/led1 on\n", file);
18     printf("%s /dev/led1 off\n", 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\n", 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.9\drivers\base\class.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设备。

 

 

 

 

 

posted on 2020-02-12 02:32  mofei004  阅读(175)  评论(0编辑  收藏  举报

导航