首页 > 其他分享 >kernel——字符设备驱动

kernel——字符设备驱动

时间:2022-11-16 23:44:45浏览次数:37  
标签:__ kernel struct rtc 字符 int dev 驱动 class

字符设备驱动的框架


设备节点:inode,类型为字符设备,记录设备号
设备号:内核确定驱动的唯一编号
cdev:字符驱动对象

框架代码

驱动

#include <linux/module.h>
#include <linux/file.h>
#include <linux/rtc.h>

static ssize_t rtc_read (struct file *fp, char __user *buf, size_t sz, loff_t *pos)
{
        printk("%s\n", __func__);
        return 0;
}

static ssize_t rtc_write (struct file *fp, const char __user *buf, size_t sz, loff_t *pos)
{
        printk("%s\n", __func__);
        return 0;
}

static int rtc_open (struct inode *pinode, struct file *fp)
{
        printk("%s\n", __func__);
        return 0;
}

static int rtc_release (struct inode *pinode, struct file *fp)
{
        printk("%s\n", __func__);
        return 0;
}

static const struct file_operations rtc_fops = {
        .owner = THIS_MODULE,
        .read = rtc_read,
        .write = rtc_write,
        .open = rtc_open,
        .release = rtc_release,
};

static int __init rtc_init(void)
{
        if (register_chrdev(222, "rtc-demo", &rtc_fops) < 0) {
                printk("failed to register_chrdev\n");
                return -1;
        }

        return 0;
}

static void __exit rtc_exit(void)
{
}

module_init(rtc_init);
module_exit(rtc_exit);

MODULE_LICENSE("GPL");

测试应用

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

struct rtc_time {
        unsigned int year;
        unsigned int mon;
        unsigned int day;
        unsigned int hour;
        unsigned int min;
        unsigned int sec;
};

int main()
{
        int fd;
        struct rtc_time tm;

        if ((fd = open("/dev/rtc-demo", O_RDWR)) < 0) {
                perror("open");
                return -1;
        }

        if (read(fd, &tm, sizeof(tm)) < sizeof(tm)) {
                perror("read");
                return -1;
        }

        printf("%d:%d:%d\n", tm.hour, tm.min, tm.sec);

        close(fd);

        return 0;
}

运行前构造inode

mknod /dev/rtc-demo c 222 0

register_chrdev 分析


注册设备驱动需要的操作

  • 申请设备号
  • 构建 cdev 结构
  • 将 cdev 加入 kobj 树

open分析

  • 根据pathname找到 inode
  • 根据inode的i_flag知道是设备文件,并使用 inode->i_rdev找到驱动
  • 将cdev->ops复制给 file->f_op,返回file
    open后,file->f_op就关联了驱动代码

read分析


可见由于open后,file->f_op 为 cdev->fops 所以,基于文件的操作都通过file->f_op就能调用驱动。

另一种写法

#include <linux/module.h>
#include <linux/file.h>
#include <linux/rtc.h>

static ssize_t rtc_read (struct file *fp, char __user *buf, size_t sz, loff_t *pos)
{
        printk("%s\n", __func__);
        return 0;
}

static ssize_t rtc_write (struct file *fp, const char __user *buf, size_t sz, loff_t *pos)
{
        printk("%s\n", __func__);
        return 0;
}

static int rtc_open (struct inode *pinode, struct file *fp)
{
        printk("%s\n", __func__);
        return 0;
}

static int rtc_release (struct inode *pinode, struct file *fp)
{
        printk("%s\n", __func__);
        return 0;
}

static const struct file_operations rtc_fops = {
        .owner = THIS_MODULE,
        .read = rtc_read,
        .write = rtc_write,
        .open = rtc_open,
        .release = rtc_release,
};

static dev_t dev;
static struct cdev *rtc_cdev;

static int __init rtc_init(void)
{
        // 构建cdev
        rtc_cdev = cdev_alloc();
        cdev_init(rtc_cdev, &rtc_fops);

        // 申请设备号
        dev = MKDEV(222, 0);
        register_chrdev_region(dev, 1, "rtc-demo");

        // 将cdev加入kobj树
        cdev_add(rtc_cdev, dev, 1);

        return 0;
}

static void __exit rtc_exit(void)
{
        // 将cdev从kobj树移除
        cdev_del(rtc_cdev);

        // 释放设备号
        unregister_chrdev_region(dev, 1);
}

module_init(rtc_init);
module_exit(rtc_exit);

MODULE_LICENSE("GPL");

动态申请设备号

主设备号高12位,次设备号低20位
设备号的转换

#define MAJOR(dev)  ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)  ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))
        // 申请设备号
#if 0
        dev = MKDEV(222, 0);
        register_chrdev_region(dev, 1, "rtc-demo");
#else
        alloc_chrdev_region(&dev, 0, 1, "rtc-demo");
        printk("major : %d, minor : %d\n", MAJOR(dev), MINOR(dev));
#endif

自动创建设备节点

由于动态申请设备号所以需要自动创建设备节点

udev/mdev : 一个用户空间程序,通过扫描sysfs获得内核编号,用于创建设备节点。此外该程序还依赖 tmpfs。

为了防udev获得驱动的信息,驱动必须将信息导出到sysfs,方法是 class_create , device_create

  /**
   * class_create - create a struct class structure
   * @owner: pointer to the module that is to "own" this struct class
   * @name: pointer to a string for the name of this class.
   *
   * This is used to create a struct class pointer that can then be used
   * in calls to device_create().
   *
   * Returns &struct class pointer on success, or ERR_PTR() on error.
   *
   * Note, the pointer created here is to be destroyed when finished by
   * making a call to class_destroy().
   */
  #define class_create(owner, name)       \
  ({                      \
      static struct lock_class_key __key; \
      __class_create(owner, name, &__key);    \
  })


  /**
   * 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_groups_vargs(class, parent, devt, drvdata, NULL,
                        fmt, vargs);
      va_end(vargs);
      return dev;
  }


static const struct file_operations rtc_fops = {
        .owner = THIS_MODULE,
        .read = rtc_read,
        .write = rtc_write,
        .open = rtc_open,
        .release = rtc_release,
};

static dev_t dev;
static struct cdev *rtc_cdev;

static struct class *rtc_test_class;
static struct device *rtc_device;

static int __init rtc_init(void)
{
        // 构建cdev
        rtc_cdev = cdev_alloc();
        cdev_init(rtc_cdev, &rtc_fops);

        // 申请设备号
#if 0
        dev = MKDEV(222, 0);
        register_chrdev_region(dev, 1, "rtc-demo");
#else
        alloc_chrdev_region(&dev, 0, 1, "rtc-demo");
        printk("major : %d, minor : %d\n", MAJOR(dev), MINOR(dev));
#endif

        // 将cdev加入kobj树
        cdev_add(rtc_cdev, dev, 1);

        // 在/sys/class下创建rtc-class类
        rtc_test_class = class_create(THIS_MODULE, "rtc-class");
        if (IS_ERR(rtc_test_class)) {
                printk("class_create failed\n");
                return -1;
        }

        // 在/sys/class/rtc-class下创建rtc-demo%d设备
        rtc_device = device_create(rtc_test_class, NULL, dev, NULL, "rtc-demo%d", 0);
        if (IS_ERR(rtc_device)) {
                printk("device_create failed\n");
                return -1;
        }

        return 0;
}

static void __exit rtc_exit(void)
{
        // 将cdev从kobj树移除
        cdev_del(rtc_cdev);

        // 释放设备号
        unregister_chrdev_region(dev, 1);

        device_unregister(rtc_device);
        class_destroy(rtc_test_class);
}

module_init(rtc_init);
module_exit(rtc_exit);

加载模块后,发现/sys/class已经创建了rtc-class类

yangxr@vexpress:/root # ls /sys/class/rtc-class/
rtc-demo0
yangxr@vexpress:/root # ls /sys/class/rtc-class/rtc-demo0/
dev        power      subsystem  uevent
yangxr@vexpress:/root # cat /sys/class/rtc-class/rtc-demo0/dev
248:0
yangxr@vexpress:/root # cat /sys/class/rtc-class/rtc-demo0/uevent
MAJOR=248
MINOR=0
DEVNAME=rtc-demo0

运行mdev -s ,创建设备节点

yangxr@vexpress:/root # mdev -s
yangxr@vexpress:/root # ls /dev/rtc-demo0
/dev/rtc-demo0

填充fops

需要注意 用户空间和内核空间基于指针输入输出数据时,相关指向用户空间的指针要用 __user 描述。

填充read write

  inline static unsigned long rtc_tm_to_time(struct rtc_time_s *ptm)
  {
      return ptm->hour * 3600 + ptm->min * 60 + ptm->sec;
  }

  static ssize_t rtc_write (struct file *fp, const char __user *buf, size_t sz, loff_t *pos)
  {
      struct rtc_time_s tm;
      int len = sizeof(tm);

      if ( !access_ok(buf, len))
          return -1;
      if (copy_from_user(&tm, buf, len) != 0) {
          printk("rtc_write failed\n");
          return -1;
      }
      regs->RTCLR = rtc_tm_to_time(&tm);

      return len;
  }

  static void rtc_time_s_trans(unsigned long n, struct rtc_time_s *p)
  {
      p->hour = (n % 86400) / 3600;
      p->min = (n % 3600) / 60;
      p->sec = (n % 60);
  }

  static ssize_t rtc_read (struct file *fp, char __user *buf, size_t sz, loff_t *pos)
  {
      unsigned long cur_time = regs->RTCDR;
      struct rtc_time_s tm;
      int len = sizeof(tm);

      rtc_time_s_trans(cur_time, &tm);
      if (copy_to_user(buf, &tm, len) != 0) {
          printk("rtc_read error\n");
          return -1;
      }

      return len;
  }

ioctl

  struct file_operations {
      ...
      long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
      long (*compat_ioctl) (struct file *, unsigned int, unsigned long);    // 需要兼容不同位宽时使用
  };

ioctl 对应两种回调,常用 unlocked_ioctl
传参都为
long (*unlocked_ioctl) (struct file *fp, unsigned int cmd, unsigned long arg);
其中cmd使用ioctl的宏设置

#define _IO(type,nr)        _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size)  _IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW(type,nr,size)  _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))

比如定义设置RTC时间的命令,和读取RTC时间的命令

  #define RTC_CMD_MAGIC 'R'
  #define RTC_CMD_GET _IOR(RTC_CMD_MAGIC, 0, struct rtc_time_s *)
  #define RTC_CMD_SET _IOW(RTC_CMD_MAGIC, 1, struct rtc_time_s *)

ioctl实现

  static long rtc_ioctl (struct file *fp, unsigned int cmd, unsigned long arg)
  {
      struct rtc_time_s __user *buf = (struct rtc_time_s __user *)arg;
      unsigned long cur_time;
      struct rtc_time_s tm;

      switch (cmd) {
          case RTC_CMD_GET:
              cur_time = regs->RTCDR;
              rtc_time_s_trans(cur_time, &tm);
              if (copy_to_user(buf, &tm, sizeof(tm)) != 0) {
                  return -1;
              }
              break;

          case RTC_CMD_SET:
              if (copy_from_user(&tm, buf, sizeof(tm)) != 0) {
                  return -1;
              }
              cur_time = rtc_tm_to_time(&tm);
              regs->RTCLR = cur_time;
              break;

          default:
              return -1;
      }
      return 0;
  }

private_data

  struct file {
      ...
      /* needed for tty driver, and maybe others */
      void            *private_data;
  };

fp->private_data可用于实现驱动会话。
如在 open 操作时分配会话上下文,挂在 private_data上,其他操作调用时 fp->private_data 可以获得当前用户的会话信息。

提高驱动的安全性

为了避免用户非法输入导致内核挂掉,需要先检查用户数据。
常见的检查方法:
检查ioctl命令
_IOC_TYPE(cmd) 判断命令type是否合法
_IOC_DIR(cmd) 判断命令是读还是写
检查用户内存地址是否合法
access_ok(addr, sz) 判断用户传递的内存是否合法
返回值:1 成功,0 失败
有些函数内部自带检测:copy_from_user, copy_to_user, get_user, put_user
分支预测优化:likely, unlikely
比如

      if (copy_from_user(&tm, buf, len) != 0) {  // 由于这里出错可能性低
          printk("rtc_write failed\n");          // 所以这里不应该先缓存
          return -1;
      }
      regs->RTCLR = rtc_tm_to_time(&tm);         // 应该先缓存这里

使用 unlikely 告诉编译器为真的分支大概率不会执行,于是cache缓存时会跳过那部分指令

      if (unlikely(copy_from_user(&tm, buf, len) != 0)) {
          printk("rtc_write failed\n");
          return -1;
      }
      regs->RTCLR = rtc_tm_to_time(&tm);

制作库

由于直接基于文件系统导出的驱动接口不能直观描述接口功能,所以还需要做一个库,库直接操作驱动接口,用户使用库实现应用。
如 rtc的库可以这样声明

#ifndef __RTCLIB_H
#define __RTCLIB_H

#include <sys/ioctl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

struct rtc_time {
        unsigned int year;
        unsigned int mon;
        unsigned int day;
        unsigned int hour;
        unsigned int min;
        unsigned int sec;
};

int rtc_open(const char *pathname);
int rtc_close(int fd);
int rtc_set_time(int fd, struct rtc_time *ptm);
int rtc_get_time(int fd, struct rtc_time *ptm);

#endif

标签:__,kernel,struct,rtc,字符,int,dev,驱动,class
From: https://www.cnblogs.com/yangxinrui/p/16859312.html

相关文章