首先聊一下linux中的软件工作岗位,有专门负责BSP的,负责把uboot、kernel、文件系统都搞定,这些都是比较复杂的;打包好了之后,基本万年不动,除非有问题暴露需要解决;内核驱动里面有基于总线架构和一些子系统实现,比如input子系统、pinctrl子系统、i2c、spi总线模型驱动;还有一些块驱动,比如,Nand、EMMC驱动;网络驱动,有网卡芯片、PHY芯片,和spi接口的网卡芯片驱动都算是网络驱动;最后介绍一下字符驱动,这是最常更改的一类驱动,也通常是自己实现的驱动。
驱动的范围包含:LED、DO控制、DI读取、按键读取等;
网上教学的LED字符驱动有很多,大多数从iormap,或者调用底层的地址去点灯,那是想让大家了解与单片机操作地址的区别;那些博主都非常的棒,我现在直接上干货,项目和工作中直接的用法。
关注微信公众号,回复“led字符驱动”,下载源代码。
1、模块驱动的入口函数
module_init(at91_led_init);//加载模块先运行init函数
module_exit(at91_led_cleanup);//卸载模块进入cleanup函数
//符合GPL规范,开源,否则无法加载进内核运行
MODULE_DESCRIPTION ("AT91 led DRIVER");
MODULE_VERSION(DRV_VERSION);
MODULE_AUTHOR ("Jack");
MODULE_LICENSE ("GPL");
//驱动模块的描述信息,可帮助使用者了解这个模块的功能
#define DRV_VERSION "1.0.0"
static int led_major;
module_param(led_major, int, 0);
MODULE_PARM_DESC(led_major, "led Major device number");
2、创建init和cleanup函数
定义初始化信息,4个LED灯
struct led_pin
{
ulong led_pin_run; /* 运行灯 */
ulong led_pin_fault; /* 故障灯 */
ulong led_pin_can5; /* CAN5 */
ulong led_pin_can6; /* CAN6 */
};
struct St_led
{
dev_t dev_major;
struct led_pin st_pin;
atomic_t open_count;
struct mutex led_mudex;//互斥锁
struct cdev led_dev;//
struct class *led_class;
struct device *led_device;
};
注册驱动流程
static int __init at91_led_init(void)
{
int retval = -1;
dev_t dev_id;
// 分配一个结构
pled = (struct St_led *)kzalloc(sizeof (struct St_led),GFP_KERNEL);
if(!pled)
{
printk("%s:kzalloc failded from:%s\n", __func__,LED_NAME);
return -ENOMEM;
}
//初始化锁,指定具体的PIN脚,根据数据手册定义的地址自定义宏
pled->st_pin.led_pin_run = AT91_PIN_PD26; /* 运行灯 */
pled->st_pin.led_pin_fault= AT91_PIN_PD27; /* 故障灯 */
pled->st_pin.led_pin_can5= AT91_PIN_PA29; /* CAN5灯 */
pled->st_pin.led_pin_can6= AT91_PIN_PA6; /* CAN6灯 */
//加了互斥锁,只能同时被一个应用程序调用
//printk("led_pin:green:%d\n",pwdt->wdg_pin);
mutex_init(&(pled->led_mudex));
//pwdt->open_count = ATOMIC_INIT(0);//初始化为0
//分配主设备号,注册字符驱动
if (led_major) {
dev_id = MKDEV(led_major, 0);
retval = register_chrdev_region(dev_id, MAX_LED_NB,
LED_NAME);
} else {
retval = alloc_chrdev_region(&dev_id, 0, MAX_LED_NB,
LED_NAME);
led_major = MAJOR(dev_id);
}
if(retval<0)
{
printk("led: register error!\n");
goto error_malloc;
}
else
printk("led:led_major:%d\n",led_major);
//绑定操作方法at91_led_fops
cdev_init(&(pled->led_dev), &at91_led_fops);
retval = cdev_add(&(pled->led_dev), dev_id,MAX_LED_NB);
if(retval<0)
{
printk("led:cdev_add failed!!!\n");
goto error_reg;
}
//创建类
pled->led_class = class_create(THIS_MODULE,LED_NAME);
if(!pled->led_class)
{
printk("led:class_create error!\n");
goto error_cdev;
}
//驱动和设备匹配,使用设备号匹配,MKDEV(led_major, 0),驱动中唯一的
pled->led_device= device_create(pled->led_class, NULL,
MKDEV(led_major, 0),
NULL, LED_NAME);
if(!pled->led_device)
{
printk("led:device_create error!\n");
goto error_class;
}
return 0;
error_class:
class_destroy(pled->led_class);
error_cdev:
cdev_del(&(pled->led_dev));
error_reg:
unregister_chrdev_region(dev_id,MAX_LED_NB);
error_malloc:
kfree(pled);
return retval;
}
注销驱动流程
注意观察,这个顺序和注册流程刚好是反的;这是个多层嵌套;
static void __exit at91_led_cleanup(void)
{
dev_t dev_id;
gpio_set_value(pled->st_pin.led_pin_run, 1); /* 关闭指示灯 */
gpio_set_value(pled->st_pin.led_pin_fault, 1);
gpio_set_value(pled->st_pin.led_pin_can5, 1);
gpio_set_value(pled->st_pin.led_pin_can6, 1);
dev_id = MKDEV(led_major,0);
if(pled->led_device)
{
device_destroy(pled->led_class,MKDEV(led_major, 0));
}
if(pled->led_class)
{
class_destroy(pled->led_class);
}
// del cdev
cdev_del(&(pled->led_dev));
if(led_major)
{
unregister_chrdev_region(dev_id,MAX_LED_NB);
}
//mutex destory
mutex_destroy(&pled->led_mudex);
//释放内存
kfree(pled);
printk("led:mod is cleanup.\n");
}
2、驱动的操作方法
绑定次驱动的操作方法
static struct file_operations at91_led_fops={
.owner = THIS_MODULE,
.llseek = NULL,
.read = NULL,
.write = NULL,
.unlocked_ioctl = at91_led_ioctl, //点灯和灭灯操作
.open = at91_led_open, //申请io资源,io初始化操作
.release = at91_led_close, //释放io资源
};
我们需要实现三个操作方法,所以需要写三个函数at91_led_open、at91_led_close、at91_led_ioctl;
static int at91_led_open(struct inode *inode, struct file *filp)
{
int ret = 0;
struct St_led *pled;
pled = container_of(inode->i_cdev, struct St_led,led_dev);
filp->private_data = (void *)pled;
//原子操作,防止有多个应用打开
if(atomic_read(&(pled->open_count))>0)
{
printk("at91_led_open:busy!!!\n");
return -EAGAIN;
}
else
{
atomic_add(1,&(pled->open_count));
}
/*
at91_set_GPIO_periph(pled->st_pin.led_pin_run, 1);
at91_set_GPIO_periph(pled->st_pin.led_pin_fault, 1);
at91_set_GPIO_periph(pled->st_pin.led_pin_can5, 1);
at91_set_GPIO_periph(pled->st_pin.led_pin_can6, 1);
*/
ret = gpio_request(pled->st_pin.led_pin_run, "led_pin_run");
if (ret < 0) {
printk("led led_pin_run %ld\n", pled->st_pin.led_pin_run);
}
ret = gpio_request(pled->st_pin.led_pin_fault, "led_pin_fault");
if (ret < 0) {
printk("led GPIO %ld\n", pled->st_pin.led_pin_fault);
}
ret = gpio_request(pled->st_pin.led_pin_can5, "led_pin_can5");
if (ret < 0) {
printk("led GPIO %ld\n", pled->st_pin.led_pin_can5);
}
ret = gpio_request(pled->st_pin.led_pin_can6, "led_pin_can6");
if (ret < 0) {
printk("led GPIO %ld ret=%d\n", pled->st_pin.led_pin_can6,ret);
}
/*最基本的硬件初始化,gpio为标准输入输出设备*/
//printk("led:gpio_g value:%d gpio_r value:%d\n",at91_get_gpio_value(pled->st_pin.led_pin_green),at91_get_gpio_value(pled->st_pin.led_pin_red));
gpio_direction_output(pled->st_pin.led_pin_run, 1); /* 关闭指示灯 */
gpio_direction_output(pled->st_pin.led_pin_fault, 1);
gpio_direction_output(pled->st_pin.led_pin_can5, 1);
gpio_direction_output(pled->st_pin.led_pin_can6, 1);
gpio_set_value(pled->st_pin.led_pin_run, 1); /* 关闭指示灯 */
gpio_set_value(pled->st_pin.led_pin_fault, 1);
gpio_set_value(pled->st_pin.led_pin_can5, 1);
gpio_set_value(pled->st_pin.led_pin_can6, 1);
return nonseekable_open(inode,filp);
}
static int at91_led_close(struct inode *inode, struct file *filp)
{
struct St_led *pled;
pled = container_of(inode->i_cdev, struct St_led, led_dev);
gpio_set_value(pled->st_pin.led_pin_run, 1); /* 关闭指示灯 */
gpio_set_value(pled->st_pin.led_pin_fault, 1);
gpio_set_value(pled->st_pin.led_pin_can5, 1);
gpio_set_value(pled->st_pin.led_pin_can6, 1);
atomic_sub(1,&(pled->open_count));
gpio_free(pled->st_pin.led_pin_run);
gpio_free(pled->st_pin.led_pin_fault);
gpio_free(pled->st_pin.led_pin_can5);
gpio_free(pled->st_pin.led_pin_can6);
return 0;
}
gpio_set_value(pled->st_pin.led_pin_run, 1); /* 关闭指示灯 */
gpio_set_value(pled->st_pin.led_pin_fault, 1);
gpio_set_value(pled->st_pin.led_pin_can5, 1);
gpio_set_value(pled->st_pin.led_pin_can6, 1);
atomic_sub(1,&(pled->open_count));
gpio_free(pled->st_pin.led_pin_run);
gpio_free(pled->st_pin.led_pin_fault);
gpio_free(pled->st_pin.led_pin_can5);
gpio_free(pled->st_pin.led_pin_can6);
return 0;
}
//驱动名字,应用程序调用的时候使用
#define LED_NAME "at91_led"
#define LED_MAGIC_NB 'l'
#define LED_IOC_MAXNR 2
//定义应用调用的灯的功能编码
#define LED_RUN 0 /* 运行灯 */
#define LED_FAULT 1 /* 故障灯 */
#define LED_CAN5 2 /* CAN5 */
#define LED_CAN6 3 /* CAN6 */
//定义命令功能
#define LED_LIGHTS_ON _IOW(LED_MAGIC_NB,1,char) /* 亮 */
#define LED_LIGHTS_OFF _IOW(LED_MAGIC_NB,2,char) /* 灭 */
static long at91_led_ioctl(struct file *filp, unsigned int cmd, unsigned long args)
{
int ret = 0;
unsigned char r_or_g;
struct St_led *pled;
pled = (struct St_led *)filp->private_data;
//检查命令的有效性
if ((_IOC_TYPE(cmd) != LED_MAGIC_NB) || (_IOC_NR(cmd) > LED_IOC_MAXNR))
{
printk("%s:CMD error\n",__func__);
return -EINVAL;
}
//判断空间是否可访问
if(_IOC_DIR(cmd) & _IOC_READ)
ret = !access_ok(VERIFY_WRITE,(void *)args,_IOC_SIZE(cmd));
else if(_IOC_DIR(cmd) & _IOC_WRITE)
ret = !access_ok(VERIFY_READ,(void *)args,_IOC_SIZE(cmd));
if(ret)
return -EFAULT;
ret = __get_user(r_or_g, (__u8 __user *)args);
if(ret)
{
printk("led:__get_user error!!!\n");
return -EFAULT;
}
else
{
//printk("r_or_g:%d\n",r_or_g);
}
//互斥锁
if(mutex_lock_interruptible(&(pled->led_mudex)))
{
return -ERESTARTSYS;
}
//命令处理
switch(cmd)
{
case LED_LIGHTS_ON://亮灯
if( LED_RUN == r_or_g )
gpio_set_value(pled->st_pin.led_pin_run,0);
else if( LED_FAULT== r_or_g )
gpio_set_value(pled->st_pin.led_pin_fault,0);
else if( LED_CAN5 == r_or_g )
gpio_set_value(pled->st_pin.led_pin_can5,0);
else if( LED_CAN6 == r_or_g )
gpio_set_value(pled->st_pin.led_pin_can6,0);
break;
case LED_LIGHTS_OFF://灭灯
if( LED_RUN == r_or_g )
gpio_set_value(pled->st_pin.led_pin_run,1);
else if( LED_FAULT== r_or_g )
gpio_set_value(pled->st_pin.led_pin_fault,1);
else if( LED_CAN5 == r_or_g )
gpio_set_value(pled->st_pin.led_pin_can5,1);
else if( LED_CAN6 == r_or_g )
gpio_set_value(pled->st_pin.led_pin_can6,1);
break;
default:
printk("led:error cmd %d.\n", cmd);
mutex_unlock(&(pled->led_mudex));
return -EINVAL;
}
mutex_unlock(&(pled->led_mudex));
return ret;
}
3、应用程序编写
#include
#include
#include
#include
#include
#include
#include "led.h"
int main()
{
int iCnt=0;
int value =0;
int fd = 0;
int i;
while(1)
{
//open函数会调用驱动层的at91_led_open
fd = open("/dev/"LED_NAME, 0);
if (fd < 0)
{
printf("led:failed to open led.\n");
return 0;
}
else
{
printf("led:open led sucess!!!\n");
}
//red off
value = 0;
//会调用驱动层的at91_led_ioctl
ioctl(fd,LED_LIGHTS_OFF,&value);
for(i=0;i<4;i++)
ioctl(fd,LED_LIGHTS_OFF,&i);
sleep(1);
for(i=0;i<4;i++)
ioctl(fd,LED_LIGHTS_ON,&i);
sleep(1);
close(fd);
}
//会调用驱动层的at91_led_close,可以通过打印查看是否对应
close(fd);
}
4、Makefile文件中,编译模块指定的内核路径,必须是已经编译成功的内核,因为要依赖于已经编译的文件调用里面的库函数;
更多linux知识点推荐:
[Linux 驱动]模块加载RTX8025驱动
[linux kernel] 内核下RX8025对接系统时钟
[linux kernel]内核启动阶段控制IO口时序输出
[职场吐槽]如何缓解焦虑
[linux kernel] 内核下ksz8081驱动调试
[linux kernel] 内核下ksz9031驱动调试
[linux kernel]内核图形化裁剪配置
[linux kernel]内核移植过程记录
[linux kernel] 内核启动流程梳理
[linux 底层]u-boot EMMC驱动
[linux 底层]u-boot图形化裁剪配置
[Linux 底层]U-boot ksz9031网络驱动调试
[Linux 底层]U-boot调试命令使用技巧
[Linux 底层]U-boot编译移植
[Linux 底层]U-boot烧录脚本介绍SecureCRT
[Linux 底层]bootstrap移植裁剪及编译
[Linux 底层] 平台软件分层介绍
[Linux 驱动] RS485测试程序编写
[Linux 驱动] CAN测试程序编写
推荐阅读:
芯片手册解读 | Linux底层 | 职场吐槽 | C语言视频