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文件系统下创建节点
- 数据结构