1.驱动是什么?
驱动是一个内核调用设备资源的载体,用户通过系统调用进入内核,然后内核操作驱动动作来操作外设寄存器地址。
2.一个cpu的linux框架
内核态:操作系统运行的状态。
用户态:用户编写的应用运行状态。
3.两者之间的沟通:
系统调用。
4.linux下点灯,
...
fd=open("/sys/class/leds/heartbeat/trigger",O_RDWR);
if(fd)
{
write(fd,1);
}
...
在cpp中加这段代码,会发现心跳led点亮。那么这段代码是怎么执行的呢?
1),open,write是c库给我们的系统调用函数,它会执行下面几个步骤:
装载open的调用号;一般是操作系统提供的,可以搜一下。open的调用号为5
触发系统调用中断:int 0x80
存储当前进程的所有执行数据,一般是程序地址,堆栈。
执行系统调用中断,进入内核态。
判断调用号是否可用,执行内核open函数
open函数的参数有驱动文件指针参数,根据驱动文件指针,执行驱动文件中的open函数。
驱动文件中的open函数会操作物理地址映射的虚拟地址,进而操作外设寄存器。
操作完成后,退出内核,加载原先进程的堆栈。
2)write也是一样,也会通过系统调用陷入内核,然后操作芯片物理外设,点亮led。
3)那么驱动的编写流程是什么
了解芯片的物理外设地址。
通过ioremap函数将需要操作物理地址映射到虚拟地址。
通过提供的函数分配或者申请设备的主次设备号,主次设备号是设备树的标志,使得内核管理更加方便。
动作函数定义,比如open函数,write函数
文件操作结构体初始化
模块初始化
模块卸载方式
证书权限,这个可以写个人名字和一些备注。
驱动文件;
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/device.h>
static int major;
static struct class *led_class;
/* registers */
// RCC_PLL4CR地址:0x50000000 + 0x894
static volatile unsigned int *RCC_PLL4CR;
// RCC_MP_AHB4ENSETR 地址:0x50000000 + 0xA28
static volatile unsigned int *RCC_MP_AHB4ENSETR;
// GPIOA_MODER 地址:0x50002000 + 0x00
static volatile unsigned int *GPIOA_MODER;
// GPIOA_BSRR 地址: 0x50002000 + 0x18
static volatile unsigned int *GPIOA_BSRR;
static ssize_t led_write(struct file *filp, const char __user *buf,
size_t count, loff_t *ppos)
{
char val;
/* copy_from_user : get data from app */
copy_from_user(&val, buf, 1);
/* to set gpio register: out 1/0 */
if (val)
{
/* set gpa10 to let led on */
*GPIOA_BSRR = (1<<26);
}
else
{
/* set gpa10 to let led off */
*GPIOA_BSRR = (1<<10);
}
return 1;
}
static int led_open(struct inode *inode, struct file *filp)
{
/* enalbe PLL4, it is clock source for all gpio */
*RCC_PLL4CR |= (1<<0);
while ((*RCC_PLL4CR & (1<<1)) == 0);
/* enable gpioA */
*RCC_MP_AHB4ENSETR |= (1<<0);
/*
* configure gpa10 as gpio
* configure gpio as output
* 01:General purpose output mode
*/
*GPIOA_MODER &= ~(3<<20);
*GPIOA_MODER |= (1<<20);
return 0;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.write = led_write,
.open = led_open,
};
/* 入口函数 */
static int __init led_init(void)
{
printk("%s %d\n", __FUNCTION__, __LINE__);
major = register_chrdev(0, "100ask_led", &led_fops);
/* ioremap(base_phy, size); */
// RCC_PLL4CR地址:0x50000000 + 0x894
RCC_PLL4CR = ioremap(0x50000000 + 0x894, 4);
// RCC_MP_AHB4ENSETR 地址:0x50000000 + 0xA28
RCC_MP_AHB4ENSETR = ioremap(0x50000000 + 0xA28, 4);
// GPIOA_MODER 地址:0x50002000 + 0x00
GPIOA_MODER = ioremap(0x50002000 + 0x00, 4);
// GPIOA_BSRR 地址: 0x50002000 + 0x18
GPIOA_BSRR = ioremap(0x50002000 + 0x18, 4);
led_class = class_create(THIS_MODULE, "myled");
device_create(led_class, NULL, MKDEV(major, 0), NULL, "myled"); /* /dev/myled */
return 0;
}
static void __exit led_exit(void)
{
iounmap(RCC_PLL4CR);
iounmap(RCC_MP_AHB4ENSETR);
iounmap(GPIOA_MODER);
iounmap(GPIOA_BSRR);
device_destroy(led_class, MKDEV(major, 0));
class_destroy(led_class);
unregister_chrdev(major, "100ask_led");
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
4)编译驱动
可以试着写一下makefile,但是基本上有现成模板稍微更改就行。
5)加载驱动
insmod,动态加载。***.ko
静态是直接编译进内核。
6)卸载驱动
rmmode。 ***.ko
5.其实每一步都是无数人花费很长时间逐渐搭建的框架,每一步都值得深入学习,这是我目前所了解的用户如何调用系统资源的流程,会不断更新,不断学习。
6.总结:其实这些东西也没有想像中的可怕,一点一点的搞,将所有的疑惑通过资料查找解决,基本上就没什么问题了。
7.参考:http://t.csdn.cn/QGNuy (这是驱动介绍的,非常详细)
http://t.csdn.cn/TqBxe (这是系统调用的,非常详细)