笔记目录
- 重要结构体和宏定义:
- 一、调用设备树API方法
-
- 1.初始化外设结构和获取次设备号和开时钟的方法
- 2、进入probe函数
-
- 2.1.创建平台总线(外设)资源节点(资源内容)app内:时钟,GPIO)
- 2.2.创造设备树资源
- 2.3.获取平台总线资源
- 2.4.设备树获取函数族
- 2.5、进入remove函数做反操作
- 二、调用GPIO的设备树API
-
- 1.初始外设结构和设备树
- 2、进入probe函数
-
- 2.1、GPIO的设备API
- 三、杂项驱动
-
- 1.杂项结构的初始化
- 2、 注册杂项驱动(直接完成注册、申请、创建数据节点)
- 三、硬件初始化
- 四、中断
-
- 中断过程:
- 两种中断过程
- 2、tasklet编程流程
- 3、workqueue编程流程(第一个进程跑到下半部分调度后,中断唤醒进程再继续跑未完成的下半部分函数(绿实线))
- 4、thread_handler编程流程
- 5、thread阻塞唤醒(可以大大增加)CPU利用率)
- 6、多路IO复用(read函数需要阻塞)
- 7.异步通知(信号量)
- 8、中断延时
- 五、mmap(内存映射操作,比read、write性能更高)
- 六、原子操作
- 七、信号灯(限制一定数量的线程运行)
- 八、锁
- 九、自旋锁(当一个过程与中断一起访问时,谁拿到锁谁跑,谁拿不到锁休眠)
- 十、input子系统
-
- 1.输入子系统框架和重要路径
-
- 1.1 input子系统框架
- 2、重要结构体
- 3、编程思路
- 十一、用户态uinput(未看)
-
- 1. 概念
- 2. 实现过程
- 十二、I2C和smbus总线
-
- 一. I2C协议
-
- 1. 硬件连接
- 2. I2C传输数据的格式
- 3. I2C信号
- 4. 协议细节
- 二. SMBus协议(未见)
-
- 1. SMBus协议简介
- 2. SMBus协议详细(还没看)
- 三. Linux内核中I2C框架
重要结构体和宏定义:
////与虚拟机用网卡通信 ping 192.168.44.194 if pxe get; then pxe boot; fi //添加编译工具 3> 导入sdk linux@ubuntu:~/farsight/sdk$ source /opt/stm32_sdk/environment-setup-cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi
//设备层 struct platform_device { //pedv const char * name; // 数组资源(结构体)"名称"---用来匹配 int id; // 不同寄存器组的编号 struct device dev; // 父类 u32 num_resources; // 资源数量一般=ARRAY_SIZE(led_main) struct resource * resource; //资源(直接给资源结构体数组名称[]) }; extern int platform_device_register(struct platform_device *);//设备层led_dev_init里注册 extern void platform_device_unregister(struct platform_device *);//在设备层led_dev_exit里注销 ---------------------------------------------- //资源结构体 struct resource { resource_size_t start; //起始 resource_size_t end; //结束 const char *name; //名字,
自定义 unsigned long flags; //地址资源还是中断资源 }; ---------------------------------------------- //驱动层操作 struct platform_driver { int (*probe)(struct platform_device *); int (*remove)(struct platform_device *); void (*shutdown)(struct platform_device *); int (*suspend)(struct platform_device *, pm_message_t state); int (*resume)(struct platform_device *); struct device_driver driver; //父类 const struct platform_device_id *id_table; //名字列表 }; extern int platform_driver_register(struct platform_driver *); extern void platform_driver_unregister(struct platform_driver *); ---------------------------------------------- //stm32mp157GPIO结构体 typedef struct { volatile unsigned int MODER; // 0x00 volatile unsigned int OTYPER; // 0x04 volatile unsigned int OSPEEDR; // 0x08 volatile unsigned int PUPDR; // 0x0C volatile unsigned int IDR; // 0x10 volatile unsigned int ODR; // 0x14 volatile unsigned int BSRR; // 0x18 volatile unsigned int LCKR; // 0x1C volatile unsigned int AFRL; // 0x20 volatile unsigned int AFRH; // 0x24 volatile unsigned int BRR; // 0x28 volatile unsigned int res; volatile unsigned int SECCFGR; // 0x30 }gpio_t; #define GPIOA 0x50002000 #define GPIOB 0x50003000 #define GPIOC 0x50004000 #define GPIOD 0x50005000 #define GPIOE 0x50006000 #define GPIOF 0x50007000 #define GPIOG 0x50008000 #define GPIOH 0x50009000 #define GPIOI 0x5000A000 #define GPIOJ 0x5000B000 #define GPIOK 0x5000C000 #define GPIOZ 0x54004000 #define RCC 0x50000000 //平台总线 const struct platform_device_id led_table[] = { //参数1:设备名字,参数2:占内核大小 { "led_main", 0x1234}, { }, };
一、调用设备树API方法
1、初始化外设结构体以及获取次设备号和开时钟方法
struct stm32mp157{
int major;//主设备号
struct class * clazz;//类
struct device * dev;//数据节点
gpio_t * gpioz;
gpio_t * gpioe;
rcc_t *rcc;
};
struct stm32mp157 * stm32_led;
--------------------------------------------------
//获取次设备号的方法,在ssize_t led_write(struct file * filp, const char __user * buff, size_t count, loff_t * pos)
int minor;
struct inode * node;
node = filp->f_path.dentry->d_inode;
minor = iminor(node);//获得次设备号
--------------------------------------------------
//打开锁相环
key->rcc->PLL4CR|= (1<<0);
//等待锁相环进入ready状态
while((key->rcc->PLL4CR & (1<<1)) == 0);
key->rcc->MP_AHB4ENSETR |= (1 << 5);
return 0;
2、进入probe函数
2.1、创建平台总线(外设)资源节点(资源内容在app内:时钟,GPIO)
struct resource * res1;
struct resource * res2;
2.2、创建设备树资源
zhangsan:stm32mp157_led@54004000 {
compatible = "stm32mp157,led_test";
reg = <0x50005400 0x400 0x50000000 0x1000>;
shen_name = "daoge","farsight";
age = <33>;
ages = <33 44>, <55 66>;
test = [06 06 06];
special{
spec = "linux";
};
2.3、获取平台总线资源
ps:在应用程序(app)操作之前,内核里的驱动层和设备层就已经匹配完毕。应用层只是调用匹配完后的内核里的接口。进程只是应用层里的概念,而这些进程公用驱动层的一套程序而已。
struct resource led_main[] = {
[0] = {
.start = RCC_S,
.end = RCC_P,
.name = "led_main_rcc",
.flags = IORESOURCE_MEM,
},
[1] = {
.start = GPIOZ_S,
.end = GPIOZ_P,
.name = "led_main_gpio",
.flags = IORESOURCE_MEM,
},
//========以下为测试=========
[2] = {
.start = 11,
.end = 11,
.name = "led_main_irq",
.flags = IORESOURCE_IRQ,
},
};
struct platform_device pdev_main=
{
.name = "led_main",
.id = -1,
.dev = {
.release = led_dev_release,
},
.num_resources = ARRAY_SIZE(led_main),//资源的个数,ARRAY_SIZE(数组)计算数组长度
.resource = led_main,//资源(地址,中断)
};
res1 = platform_get_resource(pdev, IORESOURCE_MEM, 0);
//参数2为资源类别,参数3为以该资源类的下标(从0开始)
2.4、设备树获取函数族
//根据给的属性名去找属性并返回对应的值
shen_name = (char *)of_get_property(pdev->dev.of_node, "shen_name", &lenp);
//带参宏,定义如下
/*#define of_property_for_each_string(np, propname, prop, s) \ for (prop = of_find_property(np, propname, NULL), \ s = of_prop_next_string(prop, NULL); \ s; \ s = of_prop_next_string(prop, s)) */
//该函数会遍历属性数组"shen_name",把每个读到的内容存到s
of_property_for_each_string(pdev->dev.of_node, "shen_name", prop, s)
{
printk("s=%s\n",s);
}
//计算该属性有几个元素
lenp = of_property_count_u32_elems(pdev->dev.of_node, "ages");
//从属性读取一个32位数组的值,返回值放out_value数组里,参数4为读取的个数,其他类似
of_property_read_u32_array(pdev->dev.of_node, "ages", out_value,4);
of_property_read_string_helper(pdev->dev.of_node,"shen_name",&shen_name, 2, 1);
of_property_read_u32(pdev->dev.of_node, "age", &value);
of_property_read_u8_array(pdev->dev.of_node, "test", data, 3);
node = of_find_node_by_name(pdev->dev.of_node, "special");
of_property_read_string(node,"spec",&special);
2.5、进入remove函数做反操作
二、调用GPIO的设备树API
1、初始化外设结构体和设备树
struct stm32mp157{
int major;
struct class * clazz;
struct device * dev;
int gpioz_5;
int gpioz_6;
int gpioz_7;
};
stm32mp157_led_test{
//设置引脚有效电平
compatible = "stm32mp157,led_test2";
led-gpios = <&gpioz 5 GPIO_ACTIVE_HIGH>,
<&gpioz 6 GPIO_ACTIVE_HIGH>,
<&gpioz 7 GPIO_ACTIVE_HIGH>;
};
2、进入probe函数
2.1、GPIO的设备API
方法1:
//物理地址转化成虚拟地址
stm32_led->gpioz = ioremap(res3->start, resource_size(res3));
//获得GPIO号
stm32_led->gpioz_5 = of_get_gpio(pdev->dev.of_node, 0);
if(stm32_led->gpioz_5 < 0)
{
ret = stm32_led->gpioz_5;
printk("of_get_gpio error1\n");
goto device_create_err;
}
//请求操作GPIO权限
ret = gpio_request(stm32_led->gpioz_5, "led1");
if(ret < 0)
{
printk("gpio_request error1\n");
goto device_create_err;
}
//设置GPIO输出,低电平
gpio_direction_output(stm32_led->gpioz_5, 0);
//释放GPIO
gpio_free(stm32_led->gpioz_5);
方法2:
//物理地址转化成虚拟地址
stm32_led->gpioz = ioremap(res3->start, resource_size(res3));
//获得GPIO号(使用devm前缀不用自己释放)
stm32_led->led1 = devm_gpiod_get_index(&pdev->dev,"led",0,GPIOD_OUT_HIGH);
//设置引脚为输出模式,且初始值value为0
gpiod_direction_output(stm32_led->led1,0);
//改变电平的值
gpiod_set_value(stm32_led->led1,1);
三、杂项驱动
1、初始化杂项结构体
/*struct miscdevice {//杂项驱动结构体的major默认是10 int minor; const char *name; const struct file_operations *fops; struct list_head list; struct device *parent; struct device *this_device; const struct attribute_group **groups; const char *nodename; umode_t mode; };*/
struct miscdevice misc = {
.minor = 199,
.name = "key_drv",
.fops = &key_fops,
};
2、 注册杂项驱动(注册、申请类、创造数据节点直接做完)
ret = misc_register(&misc);
if(ret < 0)
{
printk("misc_register error\n");
goto kzalloc_err2;
}
3、硬件初始化
//外设结构体
struct key_info{
//按键地名称
char name[10];
//按键的ID
int id;
//按键中断号
int irq_no;
//按键的值
int value;
//触发方式
int flag;
};
//自动获取外设结构体并赋值和注册中断
for(i = 0; i < pdev->num_resources; i++)
{
sprintf(key->info[i].name, "KEY_%d", i+1);
key->info[i].id = i+1;
key->info[i].flag = IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING;//中断判定方式
key->info[i].irq_no = platform_get_irq(pdev, i);//从设备树获取设备号
ret = request_irq(key->info[i].irq_no, key_drv_handler, key->info[i].flag, key->info[i].name, &key->info[i]);
if(ret < 0)
{
printk("request_irq error\n");
goto misc_register_err;
}
}
request_irq 中断注册函数
//参数1:虚拟中断号(设备树种获取),参数2:中断处理函数,参数3:触发方式,参数4:描述,参数5:传递给中断处理函数的参数
四、中断
中断过程:
- 保存现场
- 进入对应的异常向量表
- 进入异常处理函数
- 读控制器的位(标志宏)
- 进入中断处理函数
- 回复现场
1、两种中断流程
2、tasklet编程流程
-
创造结构体变量:
struct tasklet_struct tasklet;
-
初始化结构体变量(probe):
//参数1:中断结构体赋值,参数2:下半部处理函数的函数指针,参数3:下半部处理函数的参数 tasklet_init(&key->tasklet, key_drv_func, 0);
-
在上半部分处理函数(key_drv_handler)进行调度
//调度,启动下半部分函数 tasklet_schedule(&key->tasklet);
3、workqueue编程流程(第一个进程跑到下半部分调度后,中断唤醒进程再继续跑未完成的下半部分函数(绿实线))
-
创建work结构体变量:
struct work_struct work;
-
初始化结构体变量(probe):
//参数1:work结构体指针,参数2:下半部处理函数的函数指针 INIT_WORK(&key->work, key_drv_work);
-
在上半部分处理函数(key_drv_handler)进行调度
schedule_work(&key-> work);
4、thread_handler编程流程
-
写后半段处理函数作为申请线程中断处理的参数
irqreturn_t key_drv_irq_thread(int irq_no,void * data) { printk("-----------start---%s---------\n",__FUNCTION__); return IRQ_HANDLED; }
-
申请线程中断处理
//可以再带一个线程处理中断函数 ret = request_threaded_irq(key->info[i].irq_no, key_drv_handler,key_drv_irq_thread, key>info[i].flag, key->info[i].name, &key->info[i]);
-
在上半部分处理函数(key_drv_handler)返回唤醒进程的宏
return IRQ_WAKE_THREAD;//唤醒线程,启动线程中断处理函数5、
5、thread阻塞唤醒(可大大增加CPU利用率)
-
创建等待队列头以及资源标志位
struct wait_queue_head wq_head;//等待队列头 int have_data;//是否有资源标志位
-
初始化等待队列头以及资源标志位
key->have_data = 0;//初始化标志位 init_waitqueue_head(&key->wq_head);//初始化等待队列头
-
在key_drv_read中设置一个可以被打断的休眠(阻塞)
//一个可以被打断的休眠(阻塞),即进程被挂在了休眠队列上。参数1:等待队列头,参数2:休眠条件 wait_event_interruptible(key->wq_head, key->have_data);
-
在中断函数中拿到资源并唤醒休眠
key->have_data = 1; wake_up_interruptible(&(key->wq_head));//中断唤醒休眠
6、多路IO复用(read函数要求阻塞)
-
创建监听事件结构体
//struct pollfd //{ // int fd; // short events;//输入值:被监视的的事件 // short revents;//输出值:返回真正发生的事件 //}; struct pollfd fds[2];
-
初始化监听事件结构体
fds[0].fd = fd; fds[0].events = POLLIN; fds[1].fd = 0; fds[1].events = POLLIN;
-
开启轮询监听
while(1) { //参数1:监听事件结构体pollfd,参数2:被监听的事件总数,参数3:设置-1等于没有超时时间 //返回值>0表示监听到事件 ret = poll(fds, sizeof(fds)/sizeof(fds[0]), -1);
-
判定监听事件以及动作
if(ret > 0) { if(fds[0].revents & POLLIN) //有按键数据了 { read(fd, &info, sizeof(info)); // if(info.value) printf("KEY--->%s, Pressed...\n",info.name); else printf("KEY--->%s, Release...\n",info.name); } if(fds[1].revents & POLLIN)//有标准输出了 { int len; printf("---------start3----------\n"); len = read(0, buff, sizeof(buff)); buff[len] = '\0'; printf(