Skip to content

Linux驱动开发

学习资料

为硬件编译系统

  • 需要用到的包括 uboot 内核 文件系统
  • u-boot
    • ./make.sh
    • 得到 uboot.img rk356x_spl_loader_v1.18.112.bin
  • kernal
    • 加载默认配置
    • 编译Image 内核镜像
    • 编译dtbs 设备树
    • 编译 modules 模块
    • make modules_install 把编译好的模块拷贝到系统目录
    • 生成boot.img
      • extlinux dtbs Image rk-kernel.dtb modules
  • debian

shell
dd if=/dev/zero of=boot.img bs=1M count=256
mkfs.vfat -F 32 -n boot boot.img
mkdir ./tmp
sudo mount -o loop ./boot.img ./tmp
sudo rm -rf tmp_modules/lib/modules/5.10.*/build
sudo rm -rf tmp_modules/lib/modules/5.10.*/source
cd ./tmp
sudo mkdir extlinux
sudo mkdir dtbs
sudo cp ../extlinux.conf ./extlinux/
sudo cp ../kernel/arch/arm64/boot/Image ./
sudo cp ../kernel/arch/arm64/boot/dts/rockchip/rk3568-atk-atompi-ca1.dtb ./rk-kernel.dtb
sudo cp ../kernel/arch/arm64/boot/dts/rockchip/overlay/*.dtbo ./dtbs/
sudo cp -rf ../tmp_modules/lib/modules/5.10.* ./
cd ..
sudo rm -rf tmp_modules
sleep 1
sync
sleep 1
sudo umount ./tmp
sudo rm -rf ./tmp
sleep 1
sync

echo Making bootfs finsh!

内核模块

  • 编译内核源码
    • tar -xvf sdk 解压sdk压缩包
    • cd kernal 进入内核源码目录
    • make ARCH=arm64 menuconfig 配置并保存到.config
    • 进入Makefile 填写ARCH CROSS_COMPILE CROSS_COMPILE通常在 prebuilts
  • 编写并编译驱动代码
  • 加载卸载驱动
    • insmod name.ko 载入
    • modprobe name.ko 依赖同时载入
    • rmmod name.ko 移除内核模块
    • lsmod 查看载入的模块 cat /proc/modules
    • modinfo name.ko 查看模块信息
  • 模块传参
    • module_param
  • 符号导出
    • EXPORT_SYMBOL(name) 编译时会生成内核符号表
c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>

extern int add(int a, int b);

// insmod hello.ko a=1 arr=1,2,3 str=hello

static int a = 0;
static int arr[5] = {0};
static int len;
static char str1[10] = {0};

module_param(a, int, S_IRUGO);
MODULE_PARM_DESC(a, "e.g:a=1");

module_param_array(arr, int, &len, S_IRUGO);
MODULE_PARM_DESC(arr, "e.g:arr=1,2,3");

module_param_string(str, str1, sizeof(str1), S_IRUGO);
MODULE_PARM_DESC(str, "e.g:str=hello");

static int hello_init(void) {
  int i = 0;

  printk("a: %d\n", a);
  printk("a: %d\n", add(i, a));

  for (i = 0; i < len; i++) {
    printk("arr[%d]: %d\n", i, arr[i]);
  }
  printk("len: %d\n", len);

  printk("str1: %s\n", str1);

  return 0;
}
static void hello_exit(void) {
  printk("hello_exit\n");
  // return 0;
}

module_init(hello_init);

module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("brgzz");
MODULE_VERSION("V1.0");
c
#include <linux/init.h>
#include <linux/module.h>

// 先加载后卸载

int add(int a, int b) {
  return a + b;
}
EXPORT_SYMBOL(add);

static int hello_init(void) {
  int i = 0;

  printk("hello_init\n");

  return 0;
}
static void hello_exit(void) {
  printk("hello_exit\n");
  // return 0;
}

module_init(hello_init);

module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("brgzz");
MODULE_VERSION("V1.0");

字符设备

  • 注册字符设备
    • 得到设备号
      • 静态获取
      • 动态获取
      • cat /proc/devices 查看设备号
    • 初始化init/注册add cdev
    • 构建ops结构体
      • open read write close
    • 注册字符类设备
      • cdev_add(cdev,devNumm, count)
      • cdev_del(cdev) 删除字符设备
  • 生成设备节点
    • 手动创建 mknod /dev/test c 236 0 路径 类型(c/b) 主设备号 次设备号
    • 自动创建
      • 创建class class_create()
      • 创建设备 device_create()
    • 查看
      • ls /dev/test -al
      • ls /sys/class/test/
  • 卸载设备
    • 释放设备号unregister_chrdev_region
    • 卸载cdevcdev_del
    • 卸载设备device_destroy
    • 卸载class class_destroy
  • 内核/用户空间
    • 数据交换
    • copy_to_user(userP,kernalP,n)
    • copy_from_user(kernalP,userP,n)
  • 文件私有数据
    • 面向对象
    • 顺藤摸瓜 obj=container_of(ptr,type,member)
  • 杂项设备
    • 固定主设备号10
    • 自动创建设备节点
    • misc_register()
    • misc_deregister()
    • 构建杂项设备结构体
    • 卸载
  • 错误处理
    • 先进后出
    • IS_ERR(obj) 返回结构体的判错方法
    • ret = PTR_ERR(obj) 错误码
    • goto err;
    • class_destory(obj)
  • led驱动
    • io -r -4 0xfdc2000c 查看寄存器的值
    • io -w -4 0xfdd60000 0x80004040 寄存器写值
c
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/uaccess.h>
#include <errno.h>

struct Device
{
  dev_t devNum;
  static int major;
  static int minor;

  struct cdev cdevice; // 字符设备
  struct class *class;
  struct device *device;
  char kbuf[32];
}

struct Device dev1;

static int cdev_open(struct inode *inode, struct file *file)
{
  file->private_data = &dev1;
  // 多设备时自动找是哪个
  // file->private_data = container_of(inode->i_cdev, struct Device, cdevice);
  printk("open\n");
  return 0;
}

static ssize_t cdev_read(struct file *file, char __user *ubuf, size_t size, loff_t *off)
{

  struct Device *devT = (struct Device *)file->private_data;
  // 多设备时判断是哪个
  // if(devT->minor==0){}else if(devT->minor==1){}

  if (copy_to_user(ubuf, devt->kbuf, strlen(devt->kbuf)) != 0)
  {
    printk("copy error");
    return -1;
  }

  return 0;
}
static ssize_t cdev_write(struct file *file, char __user *ubuf, size_t size, loff_t *off)
{
  struct Device *devT = (struct Device *)file->private_data;
  if (copy_from_user(devt->kbuf, ubuf, size) != 0)
  {
    printk("copy error");
    return -1;
  }

  printk("kbuf is %s\n", devt->kbuf);

  printk("cdev_write\n");
  return 0;
}
static int cdev_release(struct inode *inode, struct file *file)
{
  printk("cdev_release\n");
  return 0;
}

static struct file_operations cdev_ops =
    {
        .owner = THIS_MODULE,
        .open = cdev_open,
        .read = cdev_read,
        .write = cdev_write,
        .release = cdev_release,
};

module_param(dev1.major, int, S_IRUGO);
module_param(dev1.minor, int, S_IRUGO);

static int init(void)
{
  int ret;

  if (dev1.major)
  {
    printk("%d-%d", dev1.major, dev1.minor);
    dev1.devNum = MKDEV(dev1.major, dev1.minor);
    ret = register_chrdev_region(dev1.devNum, 1, "name");
    if (ret < 0)
      printk("error\n");

    printk("success\n");
  }
  else
  {
    ret = alloc_chrdev_region(&dev1.devNum, 0, 1, "name");
    if (ret)
      printk("error\n");

    printk("success\n");
    dev1.major = MAJOR(dev1.devNum);
    dev1.minor = MINOR(dev1.devNum);
    printk("%d-%d", dev1.major, dev1.minor);
  }
  // 添加字符设备
  dev1.cdevice.owner = THIS_MODULE;
  cdev_init(&dev1.cdevice, &cdev_ops);
  cdev_add(&dev1.cdevice, dev1.devNum, 1);

  // 添加设备节点
  dev1.class = class_create(THIS_MODULE, 'hello');
  dev1.device = device_create(dev1.class, NULL, dev1.devNum, NULL, "hello");
  if (IS_ERR(dev1.device))
  {
    ret = PTR_ERR(dev1.device);
    goto err;
  }
  return 0;
err:
  class_destroy(dev1.class);
}

static void exit(void)
{
  unregister_chrdev_region(dev1.devNum, 1);
  cdev_del(&dev1.cdevice);
  device_destroy(dev1.class, dev1.devNum);
  class_destroy(dev1.class);
  printk("hello_exit\n");
}

module_init(init);
module_exit(exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("brgzz");
MODULE_VERSION("V1.0");
c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
  int fd;
  char buf1[32] = {0};
  char buf2[32] = "nihao";
  fd = open("/dev/hello", O_RDWR);
  if (fd < 0)
  {
    perror("error\n");
    return fd;
  }
  read(fd, buf1, sizeof(buf1));
  printf("buf1: %s\n", buf1);

  // 点灯led
  // buf2[0] = atoi(argv[1]);
  write(fd, buf2, sizeof(buf2));

  close(fd);
  return 0;
}
c
#include <linux/miscdevice.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/uaccess.h>

static int cdev_open(struct inode *inode, struct file *file)
{
  printk("open\n");
  return 0;
}

static ssize_t cdev_read(struct file *file, char __user *ubuf, size_t size, loff_t *off)
{

  struct Device *devT = (struct Device *)file->private_data;
  // 多设备时判断是哪个
  // if(devT->minor==0){}else if(devT->minor==1){}

  if (copy_to_user(ubuf, devt->kbuf, strlen(devt->kbuf)) != 0)
  {
    printk("copy error");
    return -1;
  }

  return 0;
}
static ssize_t cdev_write(struct file *file, char __user *ubuf, size_t size, loff_t *off)
{
  struct Device *devT = (struct Device *)file->private_data;
  if (copy_from_user(devt->kbuf, ubuf, size) != 0)
  {
    printk("copy error");
    return -1;
  }

  printk("kbuf is %s\n", devt->kbuf);

  printk("cdev_write\n");
  return 0;
}
static int cdev_release(struct inode *inode, struct file *file)
{
  printk("cdev_release\n");
  return 0;
}

static struct file_operations ops =
    {
        .owner = THIS_MODULE,
        .open = cdev_open,
        .read = cdev_read,
        .write = cdev_write,
        .release = cdev_release};

struct miscdevice miscDev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "test",
    .fops = &ops,
}

static int
init(void)
{
  int ret;
  ret = misc_register(&miscDev);
  if (ret < 0)
  {
    printf("misc_register error\n");
    return -1;
  }

  return 0;
}

static void exit(void)
{
  misc_deregister(&miscDev);
  printk("hello_exit\n");
}

module_init(init);
module_exit(exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("brgzz");
MODULE_VERSION("V1.0");
c
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/uaccess.h>
#include <linux/errno.h>
#include <linux/io.h>
#define GPIO 0xFDD60000

struct Device
{
  dev_t devNum;
  static int major;
  static int minor;

  struct cdev cdevice; // 字符设备
  struct class *class;
  struct device *device;
  char kbuf[32];
  unsigned int *gpioPtr;
}

struct Device dev1;

static int cdev_open(struct inode *inode, struct file *file)
{
  file->private_data = &dev1;
  // 多设备时自动找是哪个
  // file->private_data = container_of(inode->i_cdev, struct Device, cdevice);
  printk("open\n");
  return 0;
}

static ssize_t cdev_read(struct file *file, char __user *ubuf, size_t size, loff_t *off)
{

  struct Device *devT = (struct Device *)file->private_data;
  // 多设备时判断是哪个
  // if(devT->minor==0){}else if(devT->minor==1){}

  if (copy_to_user(ubuf, devt->kbuf, strlen(devt->kbuf)) != 0)
  {
    printk("copy error");
    return -1;
  }

  return 0;
}
static ssize_t cdev_write(struct file *file, char __user *ubuf, size_t size, loff_t *off)
{
  struct Device *devT = (struct Device *)file->private_data;
  if (copy_from_user(devt->kbuf, ubuf, size) != 0)
  {
    printk("copy error");
    return -1;
  }
  if (devT->kbuf[0] == 1)
  {
    *(devT->gpioPtr) = 0x8000c040;
  }
  else if (devT->kbuf[0] == 0)
  {
    *(devT->gpioPtr) = 0x80004040;
  }

  printk("kbuf is %s\n", devt->kbuf);

  printk("cdev_write\n");
  return 0;
}
static int cdev_release(struct inode *inode, struct file *file)
{
  printk("cdev_release\n");
  return 0;
}

static struct file_operations cdev_ops =
    {
        .owner = THIS_MODULE,
        .open = cdev_open,
        .read = cdev_read,
        .write = cdev_write,
        .release = cdev_release,
};

module_param(dev1.major, int, S_IRUGO);
module_param(dev1.minor, int, S_IRUGO);

static int init(void)
{
  int ret;

  if (dev1.major)
  {
    printk("%d-%d", dev1.major, dev1.minor);
    dev1.devNum = MKDEV(dev1.major, dev1.minor);
    ret = register_chrdev_region(dev1.devNum, 1, "name");
    if (ret < 0)
      printk("error\n");

    printk("success\n");
  }
  else
  {
    ret = alloc_chrdev_region(&dev1.devNum, 0, 1, "name");
    if (ret)
      printk("error\n");

    printk("success\n");
    dev1.major = MAJOR(dev1.devNum);
    dev1.minor = MINOR(dev1.devNum);
    printk("%d-%d", dev1.major, dev1.minor);
  }
  // 添加字符设备
  dev1.cdevice.owner = THIS_MODULE;
  cdev_init(&dev1.cdevice, &cdev_ops);
  cdev_add(&dev1.cdevice, dev1.devNum, 1);

  // 添加设备节点
  dev1.class = class_create(THIS_MODULE, 'hello');
  dev1.device = device_create(dev1.class, NULL, dev1.devNum, NULL, "hello");
  if (IS_ERR(dev1.device))
  {
    ret = PTR_ERR(dev1.device);
    goto err;
  }

  dev1.gpioPtr = ioremap(GPIO, 4);
  if (IS_ERR(dev1.gpioPtr))
  {
    ret = PTR_ERR(dev1.gpioPtr);
    goto err_ioremap;
  }

  return 0;
err_ioremap:
  iounmap(dev1.gpioPtr);
err:
  class_destroy(dev1.class);
}

static void exit(void)
{
  unregister_chrdev_region(dev1.devNum, 1);
  cdev_del(&dev1.cdevice);
  device_destroy(dev1.class, dev1.devNum);
  class_destroy(dev1.class);
  iounmap(dev1.gpioPtr);
  printk("hello_exit\n");
}

module_init(init);
module_exit(exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("brgzz");
MODULE_VERSION("V1.0");

并发与竞争

  • 原子操作
    • 想象这一操作像原子一样不可拆分
    • 在驱动里面使用原子操作,应用竞争
    • 常用于保护一个简单的整形数据
    • 使用 atomic_t atomic64_t结构体来描述原子变量
    • api init read set add sub inc dec and_test negative return
  • 自旋锁
    • 原地等待直到获取成功 可以用于中断内 适用于短暂任务
    • 加锁进入临界区 解锁退出
    • 临界区内不能有线程休眠代码 会死锁
    • 如果获取后发生中断 中断也要获取就会死锁 要先用irqsave关闭中断
    • spinlock_t 描述结构体
    • api define init lock unlock trylock is_locked irq
  • 信号量 睡眠锁
    • 进程休眠等待
    • 适用于加锁时间较长的场合
    • semaphore
    • define init down down_interruptible up down_trylock
  • 互斥锁
    • 类似信号量1 优先考虑
    • 不可以用于中断
    • 不能递归上锁
    • mutex
    • define init lock unlock is_locked
c
#include <asm/atomic.h>
#include <linux/atmioc.h>

static atomic64_t v = ATOMIC_INIT(1);

static int cdev_open()
{
  if (!atomic64_dec_and_test(&v))
  {
    atomic64_inc(&v);
    return -EBUSY;
  }
  //...
}
static int cdev_release()
{
  iatomic64_inc(&v);
  return 0;
}
c
#include <linux/spinlock.h>

static spinlock_t spinlock;
static int flag = 1;

static int cdev_open()
{
  spin_lock(&spinlock);
  // 临界区
  if (flag != 1)
  {
    spin_unlock(&spinlock);
    return -EBUSY;
  }
  flag = 0;
  //...
  spin_unlock(&spinlock);
}
static int cdev_release()
{
  spin_lock(&spinlock);
  flag = 1;
  spin_unlock(&spinlock);
  return 0;
}
c
#include <linux/semaphore.h>
// #include <semaphore.h>

static struct semaphore semlock;
sema_init(&semlock, 1);

static int cdev_open()
{
  if (down(&semlock))
  {
    return -EINTR;
  }

  //...
}
static int cdev_release()
{
  up(&semlock);
  return 0;
}
c
#include <linux/mutex.h>

static struct mutex lock;
static int flag = 1;

static int cdev_open()
{
  mutex_lock(&lock);
  // 临界区
  if (flag != 1)
  {
    mutex_unlock(&lock);
    return -EBUSY;
  }
  flag = 0;
  //...
  mutex_unlock(&lock);
}
static int cdev_release()
{
  flag = 1;
  return 0;
}

中断

  • 中断上文
    • request_irq
    • irq_desc 中断描述结构体
  • 中断下文 tasklet
  • 软中断 类型较少
    • open_softirq(中断号,中断函数)
    • raise_softirq(中断号)
    • raise_softirq_irqoff(中断号)
    • 其实tasklet运行在软中断的中断服务函数里面
  • 工作队列
    • 适用于更加耗时的场合 可以休眠
  • 中断线程化
c
#include <linux/gpio.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>

int irq;

struct tasklet_struct tk;

// 中断下文
void tk_func(unsigned long data)
{
  printk("this is tk_func\n");
  printk("data is %ld\n", data); // 打印1
}

// 静态初始化
// DECLARE_TASKLET(tk, tk_func, 1);

// 中断上文
irqreturn_t irq_func(int irq, void *args)
{
  printk("this is irq_func\n");
  tasklet_schedule(&tk);
  return IRQ_RETVAL(IRQ_HANDLED);
}

static int init(void)
{
  int ret;
  // 分配中断号
  irq = gpio_to_irq(13);
  // 绑定中断处理函数
  ret = request_irq(irq, irq_func, IRQF_TRIGGER_RISING, "gpioIrq", NULL);
  if (ret < 0)
  {
    printk("request_irq err");
    return -1;
  }

  // 动态初始化
  tasklet_init(&tk, tk_func, 1);

  return 0;
}

static void mod_exit(void)
{
  free_irq(irq, NULL);
  tasklet_enable();
  tasklet_kill(&tk);
  printk("bye\n");
}
c
#include <linux/gpio.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>

int irq;

// 中断下文
void softirq_func(unsigned long data)
{
  printk("this is softirq_func\n");
  printk("data is %ld\n", data); // 打印1
}

// 中断上文
irqreturn_t irq_func(int irq, void *args)
{
  printk("this is irq_func\n");
  raise_softirq(TIMER_SOFTIRQ);
  return IRQ_RETVAL(IRQ_HANDLED);
}

static int init(void)
{
  int ret;
  // 分配中断号
  irq = gpio_to_irq(13);
  // 绑定中断处理函数
  ret = request_irq(irq, irq_func, IRQF_TRIGGER_RISING, "gpioIrq", NULL);
  if (ret < 0)
  {
    printk("request_irq err");
    return -1;
  }
  open_softirq(TIMER_SOFTIRQ, softirq_func);

  return 0;
}

static void mod_exit(void)
{
  free_irq(irq, NULL);
  raise_softirq_irqoff(TIMER_SOFTIRQ);
  printk("bye\n");
}

平台总线

  • 平台总线模型
    • device.c 硬件相关
    • driver.c 控制相关
    • 两者通过平台总线连接,通过name一致匹配
  • platform 设备
  • probe 函数

设备树

设备模型

热插拔

子系统

单总线

I2C

SPI

V4L2

  • 应用层使用 参考
    • 打开设备 open("/dev/video0", O_RDWR[| O_NONBLOCK]);
    • 检查和设置设备属性
      • struct v4l2_capability capability;
      • int ret = ioctl(fd, VIDIOC_QUERYCAP, &capability);
    • 设置帧格式
      • ioctl(fd, VIDIOC_QUERYCAP, &input);
      • ioctl(fd, VIDIOC_S_FMT, &fmt)
    • 设置一种输入输出方法(缓冲 区管理)
      • struct v4l2_requestbuffers req; 一般设置4个缓冲区
      • ioctl(dev->fd, VIDIOC_REQBUFS, &req)
    • 循环获取数据
      • 可以使用read/mmap/用户指针 常用mmap内存映射
      • struct v4l2_buffer buf;
      • ioctl(fd, VIDIOC_QUERYBUF, &buf)
      • buffers[numBufs].start = mmap(NULL, buf.length,PROT_READ | PROT_WRITE,MAP_SHARED,fd, buf.m.offset)
      • ioctl(dev->fd, VIDIOC_STREAMON, &type)
      • struct v4l2_buffer capture_buf;
      • memset(&capture_buf, 0, sizeof(capture_buf));
      • ioctl(dev.fd, VIDIOC_DQBUF, &capture_buf)
      • ioctl(fd, VIDIOC_QBUF, &buf)
    • 关闭设备
      • ioctl(fd, VIDIOC_STREAMOFF, &buf_type);
      • munmap(buffer[j].start, buffer[j].length);
      • close(fd);
  • 内核层 参考
    • 数据结构
      • v4l2_device代表整个输入设备
      • v4l2_subdev 代表子模块,比如CSI、Sensor等
      • video_device 用于向系统注册字符设备节点
    • 驱动实现
      • 驱动结构体中内嵌struct video_device,同时实现struct v4l2_file_operations结构体中的函数,最终通过video_register_device向提供注册
      • v4l2_register_device函数通过cdev_add向系统注册字符设备,并指定了file_operations,用户空间调用open/read/write/ioctl等接口,便可回调到驱动实现中
      • v4l2_register_device函数中,通过device_register向系统注册设备,会在/sys文件系统下创建节点