版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_16777851/article/details/82226543
本节在上一节的驱动程序中添加基本硬件操作函数。
这一节我们开始增加一个led灯的驱动程序。
点亮一个led灯只需要两个寄存器,即配置io口的控制寄存器和状态寄存器。
基础知识:
在linux系统中,开启mmu后,我们不能直接使用寄存器的硬件地址(或者说我们不知道,寄存器硬件地址被映射到那块内存了),所以我们只能使用虚拟地址来操纵寄存器。而目前我们不知道虚拟地址,只知道物理地址。
所以系统给我们提供了一个接口函数,用来通过寄存器的物理地址得到寄存器的虚拟地址。
void __iomem *ioremap(phys_addr_t offset, unsigned long size)
它的第一个输入参数是物理地址,第二个输入参数是请求的寄存器的长度。输出参数是虚拟地址。
其功能是建立新的一个页表,把输入的物理地址映射到内核空间的一块虚拟地址。(记住是映射一页)
所以使用完一定要释放掉,否则块内核的内存就不能再被使用了。
static inline void iounmap(void __iomem *addr)
取消映射,使用的是要释放的虚拟地址。
/* 内核空间和用户空间使用的不同的地址,所以它们之间的指针里面的内容使用都需要转换 */
static inline int copy_from_user(void *to, const void __user volatile *from,
unsigned long n) /* 从用户空间向内核空间传参 */
这里的from是用户空间的地址
/* 内核空间和用户空间使用的不同的地址,所以它们之间的指针里面的内容使用都需要转换 */
static inline int copy_to_user(void __user volatile *to, const void *from,
unsigned long n) /* 从内核空间向用户空间传参 */
这里的to是用户空间的地址
注:上面用到的四个函数,后面我会分析。
我们先操纵一个GPH0_3端口
控制寄存器和数据寄存器的地址相连,所以我们申请8个字节。
控制寄存器的12-15bit初始化为1
下面就是在上一节的框架基础上填充了,io寄存器申请,操作。
#include <linux/fs.h> /* 包含file_operation结构体 */
#include <linux/init.h> /* 包含module_init module_exit */
#include <linux/module.h> /* 包含LICENSE的宏 */
#include <asm/uaccess.h> /* 包含copy_to_user之类 */
#include <linux/io.h> /* 包含ioremap之类 */
/* GPJ0CON寄存器的物理地址 */
#define GPJ0CON_PA 0xe0200240
struct gpj0 {
unsigned int gpj0con;
unsigned int gpj0dat;
};
static struct gpj0* p_gpj0 = NULL;
/* 定义一个打开设备的,open函数 */
static int first_drv_open(struct inode *inodep, struct file *filep)
{
printk("first_drv_open\n");
/* 初始化gpj0_3为输出 */
p_gpj0->gpj0con &= ~(0xf << 12);
p_gpj0->gpj0con |= (1 << 12);
/* 默认熄灭led灯 */
p_gpj0->gpj0dat |= (1<<3);
return 0;
}
/* 定义一个打开设备的,write函数 */
static ssize_t first_drv_write(struct file *filep, const char __user * buf, size_t len, loff_t *ppos)
{
char buf[10];
int ret;
printk("first_drv_write\n");
memset(buf, 0,10);
ret = copy_from_user(buf, from, 1); /* 内核空间和用户空间使用的不同的地址,所以它们之间的指针参数都需要转换 */
if(ret) /* 错误校验 */
{
printk("copy_from_user fail\n");
}
if('0' == buf[0])
{
p_gpj0->gpj0dat |= (1<<3); /* 灭灯 */
}
else if('1' == buf[0])
{
p_gpj0->gpj0dat &= ~(1<<3); /* 亮灯 */
}
else
{
printk("please input 1/0 ?"); /* 使用提示 */
}
}
/* 把自己定义的函数接口使用file_operations结构体封装起来,方便管理和使用 */
static const struct file_operations first_drv_file_operation = {
.owner = THIS_MODULE,
.open = first_drv_open,
.write = first_drv_write,
};
/* 注册驱动打包好的驱动程序 */
static int __init first_drv_init(void)
{
printk("first_drv_init\n");
register_chrdev(111,"first_drv",&first_drv_file_operation);
/* 映射io寄存器 */
p_gpj0 = ioremap(GPJ0CON_PA, 0x08);
if(NULL == p_gpj0)
{
printk("ioremap fail\n");
return -1;
}
return 0;
}
/* 卸载打包好的驱动程序 */
static void __exit first_drv_exit(void)
{
printk("first_drv_exit\n");
unregister_chrdev(111,"first_drv_init");
/* 使用完取消映射 */
iounmap(p_gpj0);
}
/* 声明函数属性 */
module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");
接下来看一下应用程序
应用程序很简单,和上一节没什么区别
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(void)
{
char buf[10] = "1";
int fd = open("/dev/xxx", O_RDWR);
if(fd < 0)
{
printf("open /dev/xxx fail\n");
return -1;
}
while('q' != buf[0]) /* 退出 */
{
scanf("%s",buf); /* 读参数 */
write(fd, buf, 5);
printf("\n");
}
return 0;
}
调试过程就可以看到led等的变化。
驱动程序不好的地方?
1.字符设备号需要自己先查看内核的/proc/devices里面没被使用的设备号,然后才能写代码,非常不具有可移植性
2.每次安装驱动后都需要手动创建设备节点
下一节,我们就来解决上面的两个问题