设为首页 收藏本站
查看: 316|回复: 0

[经验分享] Linux字符设备驱动编写基本流程

[复制链接]
累计签到:1 天
连续签到:1 天
发表于 2014-4-15 16:13:04 | 显示全部楼层 |阅读模式
---简介

Linux下的MISC简单字符设备驱动虽然使用简单,但却不灵活。
只能建立主设备号为10的设备文件。
字符设备比较容易理解,同时也能够满足大多数简单的硬件设备
字符设备通过文件系统中的名字来读取。这些名字就是文件系统
中的特殊文件或者称为设备文件、文件系统的简单结点,一般位
于/dev/目录下使用ls进行查看会显示以C开头证明这是字符设备文
件crw--w----  1 root tty       4,   0  4月 14 11:05 tty0。第一个数字是主设
备号,第二个数字是次设备号。

---分配和释放设备编号
1)在建立字符设备驱动时首先要获取设备号,为此目的的必要的
函数是register_chrdev_region,在linux/fs.h中声明:
int register_chrdev_region(dev_t first, unsigned int count, char *name);
first是你想要分配的起始设备编号,first的次编号通常是0,count
是你请求的连续设备编号的总数。count如果太大会溢出到下一个
主设备号中。name是设备的名字,他会出现在/proc/devices 和sysfs中。
操作成功返回0,如果失败会返回一个负的错误码。
2)如果明确知道设备号可用那么上一个方法可行,否则我们可以使用
内核动态分配的设备号int alloc_chrdev_region(dev_t *dev, unsigned int firstminor,
unsigned  int count, char *name);
dev是个只输出的参数,firstminor请求的第一个要用的次编号,count和name
的作用如上1)对于新驱动,最好的方法是进行动态分配
3)释放设备号,void unregister_chrdev_region(dev_t first unsigned int count);

---文件操作

file_operations结构体,内部连接了多个设备具体操作函数。该变量内部的
函数指针指向驱动程序中的具体操作,没有对应动作的指针设置为NULL。
1)fops的第一个成员是struct module *owner  通常都是设置成THIS_MODULE。
linux/module.h中定义的宏。用来在他的操作还在被使用时阻止模块被卸载。
2)loff_t (*llseek) (struct file *, loff_t, int);该方法用以改变文件中的当前读/写位置
返回新位置。
3)ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);该函数用以从设备文件
中读取数据,读取成功返回读取的字节数。
4)ssize_t (*write) (struct file *, const char __user *,size_t , loff_t *);该函数用以向设备
写入数据,如果成功返回写入的字节数。
5)int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);ioctl系统调用提供
发出设备特定命令的方法。
6)int (*open) (struct inode *, struct file *);设备文件进行的第一个操作,打开设备文件。
7)int (*release) (struct inode *, struct file *);释放文件结构函数指针。
一般初始化该结构体如下:
struct file_operations fops = {
.owner = THIS_MODULE, .llseek = xxx_llseek,  .read = xxx_read,  .write = xxx_write,
.ioctl = xxx_ioctl, .open = xxx_open,  .release = xxx_release  };
PS:以上的文件操作函数指针并不是全部,只是介绍了几个常用的操作。

---文件结构

struct file定义在linux/fs.h中,是设备驱动中第二个最重要的数据结构,此处的file和
用户空间程序中的FILE指针没有关系。前者位于内核空间,后者位于用户控件。
文件结构代表一个打开的文件。(他不特定给设备驱动;系统中每个打开的文件
有一个关联的struct file在内核空间)。它由内核在open时创建,并可以传递给文件件
操作函数,文件关闭之后,内核释放数据结构。

1)mode_t  f_mode。确定文件读写模式
2)loff_t  f_ops。当前读写位置
3)unsigned int f_flags 。文件标志,O_RDONLY、O_NONBLOCK,
4)struct file_operations *f_op。关联文件相关操作
5)void *private_data。open系统调用设置该指针NULL,指向分配的数据。
6)struct dentry *f_dentry。关联到文件的目录入口dentry结构。

---inode结构

inode结构由内核在内部用来表示文件。它和代表打开文件描述符的文件结构是不
同的。inode结构包含大量关于文件的信息。作为通用规则,这个结构只有两个成
员对驱动代码有作用。
dev_t  i_rdev。对于代表设备文件的节点,这个成员包含实际的设备编号。
struct cdev *i_cdev。内核内部结构,代表字符设备。

---字符设备注册

在内核调用你的设备操作前,你编写分配并注册一个或几个struct cdev.
struct cdev *my_cdev = cdev_alloc(); my_cdev->ops = &my_fops;
或者定义成static均可。
对定义的cdev变量进行初始化,可以使用专门的函数,或者使用如上的方法。
cdev_init( my_cdev, &my_fops);   其实上边的两行代码就是做了这个函数的工作。
最后告诉内核该cdev。
cdev_add(struct  cdev *dev, dev_t num, unsigned int count);

/*上述总结,到此关于设备文件相关的结构数据以及如何注册销毁等操作相关的
   函数基本上都已经介绍完毕。主要的还是要设计具体操作的函数来实现具体的
   逻辑操作*/

   以下代码整理、摘录自《Android深度探索HAL与驱动开发-李宁》LED驱动篇
#include <linux/fs.h>  
#include <linux/cdev.h>  
#include <asm/uaccess.h>  
#include <linux/pci.h>  
#include <mach/map.h>  
#include <mach/regs-gpio.h>  
#include <mach/gpio-bank-m.h>  

#deifne DEVICE_NAME "s3c6410_leds"  
#define DEVICE_COUNT 1  
#define S3C6410_LEDS_MAJOR 0  
#define S3C6410_LEDS_MINOR 234  
#define PARAM_SIZE 3  

static int major = S3C6410_LEDS_MAJOR;  
static int minor = S3C6410_LEDS_MINOR;  
static dev_t dev_number;  
static int leds_state = 1;  
static charchar *params[] = {"string1","string2","string3"};  
static iint param_size = PARAM_SIZE;  
static struct classclass *leds_class = NULL;  

static int s3c6410_leds_ioctl (struct file *file, unsigned int cmd, unsigned long arg)  
{  
  switch (cmd)  
  {  
    unsigned tmp;  
    case 0:  
    case 1:  
    if (arg > 4)  
        return -EINVAL;  
    tmp = ioread32 (S3C64XX_GPMDAT);  
    if (cmd == 1)  
        tmp &= (~(1 << arg));  
    else  
        tmp |= (1 << arg);  
    iowrite32 (tmp, S3C64XX_GPMDAT);  
    return 0;  
    default : return -EINVAL;  
  }  
}   

static ssize_t s3c6410_leds_write (struct file *file, const char __user *buf, size_t count, loff_t *ppos)  
{  
  unsigned tmp = count;  
  unsigned long i = 0;  
  memset(mem, 0, 4);  

  if (count > 4)  
    tmp = 4;  
  if (copy_from_user (mem, buf, tmp) )  
    return -EFAULT;  
  else{  
    for( i=0; i<4; i++)  
    {  
      tmp = ioread32(S3C64XX_GPMDAT);  
      if (mem[i] == '1')  
        tmp &= (~(1 << i));  
      else  
        tmp |= (1 << i);  
      iowrite32(tmp, S3C64XX_GPMDAT);  
    }  
    return count;  
  }  
}  

static struct file_operations dev_fops =   
{.owner = THIS_MODULE, .unlocked_ioctl = s3c6410_leds_ioctl, .write = s3c6410_leds_write};  

static struct cdev leds_cdev;  

static int leds_create_device(void)  
{  
  int ret = 0;  
  int err = 0;  

  cdev_init (&leds_cdev,  &dev_fops);  
  leds_cdev.owner = THIS_MODULE;  

  if (major > 0)  
  {  
    dev_number = MKDEV(major,minor);  
    err = register_chrdev_region(dev_number, DEVICE_COUNT, DEVICE_NAME);  
    if (err < 0)  
    {  
      printk(KERN_WANRING "register_chrdev_region error\n");  
      return err  
    }  
  }  
  else{  
    err = alloc_chrdev_region(&leds_cdev.dev, 10, DEVICE_COUNT, DEVICE_NAME);  
    if(err < 0)  
    {  
        printk (KERN_WARNING "alloc_chrdev_region error\n");  
        return err;  
    }  
    major = MAJOR(leds_cdev.dev);  
    major = MINOR(leds_cdev.dev);  
    dev_number = leds_cdev.dev;  
  }  
  ret = cdev_add(&leds_cdev,dev_number, DEVICE_COUNT);  
  leds_class = class_create (THIS_MODULE, DEVICE_NAME);  
  device_create (leds_class, NULL, dev_number, NULL, DEVICE_NAME);  
  return ret;  
}  

static void leds_init_gpm(int leds_default){  

  int tmp = 0;  
  tmp = ioread32(S3C64XX_GPMCON);  
  tmp &= (~0xffff);  
  tmp |= 0x1111;  
  iowrite32(tmp,S3C64XX_GPMCON);  

  tmp = ioread32(S3C64XX_GPMPUD);  
  tmp &= (~0XFF);  
  tmp |= 0xaa;  
  iowrite32(tmp,S3C64XX_GPMPUD);  

  tmp = ioread32(S3C64XX_GPMDAT);  
  tmp &= (~0xf);  
  tmp |= leds_default;  
  iowrite32(tmp, S3C64XX_GPMDAT);  

}  

static leds_init( void)  
{  
  int ret;  

  ret = leds_create_device();  
  leds_init_gpm (~leds_state);  
  printk(DEVICE_NAME"\tinitialized\n");  

  return ret;  
}  

static void leds_destroy_device(void)  
{  
    device_destroy(leds_class, dev_number);  
    if(leds_class)  
        class_destroy(leds_class);  
    unregister_chrdev_region(dev_number, DEVICE_NAME);  
}  

static void leds_exit(void)  
{  
  leds_destroy_device();  
  printk(DEVICE_NAME"\texit\n");  
}  

module_init(leds_init);  
module_exit(leds_exit);  
module_param(leds_state, int, S_IRUGO|S_IWUSR);  
module_param_array(params, charp, ?m_size, S_IRUGO|S_IWUSR);  
MODULE_LICENSE("GPL");  
MODULE_AUTHOR("lining");  


运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-17488-1-1.html 上篇帖子: linux 配置 ftp服务 下篇帖子: linux内核升级图文攻略 Linux
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表