Chinaunix首页 | 论坛 | 博客
  • 博客访问: 9941
  • 博文数量: 6
  • 博客积分: 106
  • 博客等级: 民兵
  • 技术积分: 65
  • 用 户 组: 普通用户
  • 注册时间: 2012-03-09 23:21
文章分类

全部博文(6)

文章存档

2012年(6)

我的朋友

分类:

2012-03-26 20:05:54

原文地址:LED字符设备驱动程序@2440 作者:angrad

Step:
1.设备驱动程序通常需要一个入口函数(通俗理解就是加载此驱动程序模块就会执行的函数,通常做一些初始化操作),用module_init(firstdrv_init)修饰。

2.同样的需要一个出口函数(在模块卸载时调用,通常做与初始化相反的卸载工作。用module_exit(firstdrv_exit)修饰。

3.定义初始化函数.
static int firstdrv_init(void)
{
...
}
所做的事情通常为:
a.为一个字符驱动获取一个或多个设备编号。
int register_chrdev_region(dev_t from, unsigned count, const char *name)
int alloc_chrdev_region(dev_t * dev, unsigned baseminor, unsigned count, const char * name)
两者的区别是,前者为静态分配后者为动态分配。
b.初始化字符设备结构体,通常要定义相应的文件操作结构体(稍后介绍),以使它们关联,使用之前必须定义cdev结构体。
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
c.添加字符设备到系统
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
d.为驱动注册一个类,需要先定义这个类结构体,在2.6.32内核里的定义应区别与更早更早的版本
class_register(class);
static struct class xxx_class = {
.name = "xxx",
...
};
e.创建类下的设备
struct device *device_create(struct class *cls, struct device *parent,dev_t devt, void *drvdata,const char *fmt, ...)

上面五个为基本步骤,在卸载函数里应该做相反的动作,稍后介绍。
因为本程序旨在写一个LED字符驱动程序,所以在初始化函数里应该将
将LED所在IO地址空间映射到内核的虚拟地址空间上去,便于访问,调用
void *ioremap(unsigned long phys_addr, unsigned long size)

4.定义出口函数.
static void firstdrv_exit(void)
{
...
}
通常做与注册相反的操作:
a.去除类下的设备
void device_destroy(struct class *class, dev_t devt)
b.去除类
void class_destroy(struct class *cls)
c.字符设备删除
void cdev_del(struct cdev *p)
d.释放原先申请的设备号
void unregister_chrdev_region(dev_t from, unsigned count)
一般的操作结束之后,本驱动还要去除io映射
void iounmap(*io_addr)

5.定义与字符设备相关联的操作函数结构体
static struct file_operations firstdrv_ops = {
.owner = THIS_MODULE,
.open   = xxx_open,
.write   = xxx_write,
        .read   = xxx_read,
        ...
};

6.实现各xxx函数,其中包括设备文件被打开时会调用的xxx_open函数,被关闭时会调用的xxx_release函数,被读/写数据时的xxx_read/xxx_write函数等。
本驱动程序在打开时,需要配置寄存器为输出模式(实现xxx_open函数),参照数据手册:
因为LED被配置为输入模式,只需要接受用户空间传到内核空间的数据,所以还需要实现xxx_write函数。根据不同的值设置寄存器即可点亮或者关闭LED。其中会用到函数
static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)
在像用户空间传数据时还会用到
static inline long copy_to_user(void __user *to, const void *from, unsigned long n)。

7.包含必要的头文件,遵循GPL协议
MODULE_LICENSE("GPL");

具体实现代码如下:

点击(此处)折叠或打开

  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/device.h>

  11. #define FIRST_DEV MKDEV(250, 0)

  12. volatile unsigned long *gpbcon = NULL;
  13. volatile unsigned long *gpbdat = NULL;

  14. static struct cdev firstdrv_cdev;
  15. static struct class_device *firstdrv_class_dev;

  16. static int firstdrv_open(struct inode *inode, struct file *file)
  17. {
  18.     printk(KERN_NOTICE "Device opened!\n");
  19.     /* 配置为输出 */
  20.     *gpbcon &= ~((0x3<<(5*2)) |(0x3<<(6*2)) |(0x3<<(7*2)) |(0x3<<(8*2)));
  21.     *gpbcon |= ((0x1<<(5*2)) | (0x1<<(6*2)) | (0x1<<(7*2)) | (0x1<<(8*2)));

  22.     return 0;
  23. }

  24. static ssize_t firstdrv_write(struct file *file, const char __user *userbuf,
  25.          size_t bytes, loff_t *off)
  26. {
  27.     int val;
  28.     copy_from_user(&val, userbuf, bytes);
  29.     if (1 == val) {
  30.         *gpbdat &= ~((0x1<<5) | (0x1<<6) | (0x1<<7) | (0x1<<8));
  31.     } else {
  32.         *gpbdat |= ((0x1<<5) | (0x1<<6) | (0x1<<7) | (0x1<<8));
  33.     }
  34.         
  35.     return 0;
  36. }

  37. static struct class firstdrv_class = {
  38.     .name        = "firstdrv_class",
  39. };

  40. static struct file_operations firstdrv_ops = {
  41.     .owner = THIS_MODULE,
  42.     .open = firstdrv_open,
  43.     .write = firstdrv_write,
  44. };
  45. static int firstdrv_init(void)
  46. {
  47.     int ret;

  48.     ret = register_chrdev_region(FIRST_DEV, 1, "firstdrv");

  49.     if (ret) {
  50.         printk(KERN_ERR "Unable to register firstdrv\n");
  51.         goto err_reg;
  52.     }
  53.     cdev_init(&firstdrv_cdev, &firstdrv_ops);
  54.     ret = cdev_add(&firstdrv_cdev, FIRST_DEV, 1);
  55.     if (ret) {
  56.         printk(KERN_ERR "Unable to add cdev\n");
  57.         goto err_add_cdev;
  58.     }
  59.     class_register(&firstdrv_class);
  60.     
  61.     /* /dev/first */
  62.     device_create(&firstdrv_class, NULL, FIRST_DEV, NULL, "first");
  63.     gpbcon = (volatile unsigned long *)ioremap(0x56000010, 16);
  64.     gpbdat = gpbcon + 1;
  65.     
  66.     return 0;
  67.     
  68. err_add_cdev:
  69.     cdev_del(&firstdrv_cdev);
  70. err_reg:
  71.     unregister_chrdev_region(FIRST_DEV, 1);
  72.     return 0;
  73. }

  74. static void firstdrv_exit(void)
  75. {
  76.     device_destroy(&firstdrv_class, FIRST_DEV);
  77.     class_destroy(&firstdrv_class);
  78.     cdev_del(&firstdrv_cdev);
  79.     unregister_chrdev_region(FIRST_DEV, 1);
  80.     iounmap(gpbcon);
  81. }
  82. module_init(firstdrv_init);
  83. module_exit(firstdrv_exit);

  84. MODULE_LICENSE("GPL");
  85. MODULE_AUTHOR("Angrad Young");

文章说明:
此博文只为总结分享学习经验,许多内容不能也并未详细描述,如每个函数的参数、返回值,何为内核空间、用户空间,怎么操作寄存器等等,这些知识点都在相关资料中能得到更专业、详细的解答。
阅读(290) | 评论(0) | 转发(0) |
0

上一篇:ARM与单片机的区别

下一篇:没有了

给主人留下些什么吧!~~