资讯详情

Linux下的驱动学习笔记(1)

笔记目录

  • 重要结构体和宏定义:
  • 一、调用设备树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. 进入对应的异常向量表
  3. 进入异常处理函数
  4. 读控制器的位(标志宏)
  5. 进入中断处理函数
  6. 回复现场

1、两种中断流程

2、tasklet编程流程

  1. 创造结构体变量:

    struct tasklet_struct tasklet;
    
  2. 初始化结构体变量(probe):

    //参数1:中断结构体赋值,参数2:下半部处理函数的函数指针,参数3:下半部处理函数的参数
    	tasklet_init(&key->tasklet, key_drv_func, 0);
    
  3. 在上半部分处理函数(key_drv_handler)进行调度

    //调度,启动下半部分函数
    	tasklet_schedule(&key->tasklet);
    

3、workqueue编程流程(第一个进程跑到下半部分调度后,中断唤醒进程再继续跑未完成的下半部分函数(绿实线))

  1. 创建work结构体变量:

    struct work_struct work;
    
  2. 初始化结构体变量(probe):

    //参数1:work结构体指针,参数2:下半部处理函数的函数指针
    	INIT_WORK(&key->work, key_drv_work);
    
  3. 在上半部分处理函数(key_drv_handler)进行调度

    schedule_work(&key-> work);
    

4、thread_handler编程流程

  1. 写后半段处理函数作为申请线程中断处理的参数

    irqreturn_t key_drv_irq_thread(int irq_no,void * data)
    { 
              
    	printk("-----------start---%s---------\n",__FUNCTION__);
    	return IRQ_HANDLED;
    }
    
  2. 申请线程中断处理

    //可以再带一个线程处理中断函数
    	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]);
    
  3. 在上半部分处理函数(key_drv_handler)返回唤醒进程的宏

    return IRQ_WAKE_THREAD;//唤醒线程,启动线程中断处理函数5、
    

5、thread阻塞唤醒(可大大增加CPU利用率)

  1. 创建等待队列头以及资源标志位

    struct wait_queue_head wq_head;//等待队列头
    int have_data;//是否有资源标志位
    
  2. 初始化等待队列头以及资源标志位

    key->have_data = 0;//初始化标志位
    init_waitqueue_head(&key->wq_head);//初始化等待队列头
    
  3. 在key_drv_read中设置一个可以被打断的休眠(阻塞)

    //一个可以被打断的休眠(阻塞),即进程被挂在了休眠队列上。参数1:等待队列头,参数2:休眠条件
    wait_event_interruptible(key->wq_head, key->have_data);
    
  4. 在中断函数中拿到资源并唤醒休眠

    key->have_data = 1;
    wake_up_interruptible(&(key->wq_head));//中断唤醒休眠
    

6、多路IO复用(read函数要求阻塞)

  1. 创建监听事件结构体

    //struct pollfd
    //{ 
              
    // int fd;
    // short events;//输入值:被监视的的事件
    // short revents;//输出值:返回真正发生的事件
    //};
    struct pollfd fds[2];
    
  2. 初始化监听事件结构体

    	fds[0].fd = fd;
    	fds[0].events = POLLIN;
    
    	fds[1].fd = 0;
    	fds[1].events = POLLIN;
    
  3. 开启轮询监听

    while(1)
    	{ 
              	 //参数1:监听事件结构体pollfd,参数2:被监听的事件总数,参数3:设置-1等于没有超时时间
    		//返回值>0表示监听到事件
    		ret = poll(fds, sizeof(fds)/sizeof(fds[0]), -1);
    
  4. 判定监听事件以及动作

    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(

标签: 1a50v三极管d管to251开关三极管24vdc500a继电器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台