在单片机和裸机中操作硬件直接访问物理寄存器即可
例如 unsigned int *p = 0x12345678;
*p = 0x87654321;
但是在Linux上不行,在Linux上,如果要想操作硬件,需要先把物理地址转换为虚拟地址,因为Linux使能了MMU(物理地址转换为虚拟地址),所以我们在Linux上不能直接操作物理地址。
MMU的好处?
(1) 让虚拟地址成了可能
(2) 可以让系统更加安全,因为有了MMU,我们上层应用看到的内存都是虚拟内存,我们的应用就不能直接访问硬件,这样就保证了系统的安全。
MMU非常复杂**,我们如何完成物理地址到虚拟地址的转换?**
内核给我们提供了函数
ioremap,iounmap
ioremap : 把物理地址转换成虚拟地址
static inline void __iomem *ioremap(unsigned long port, unsigned long size)
{
return IO_CONCAT(__IO_PREFIX,ioremap) (port, size);
}
port 映射物理地址的起始地址。
size : 映射多大的内存空间
返回值: 成功返回虚拟地址的首地址、 失败: 返回NULL
iounmap: 释放掉ioremap映射的地址
static inline void iounmap(volatile void __iomem *addr)
{
IO_CONCAT(__IO_PREFIX,iounmap)(addr);
}
addr 要取消映射的虚拟地址的首地址
如何查看那些物理地址被映射过了?
cat /proc/iomem
– 通用输入输出口
– 单片机或处理器对外设进行操作的主要方式
– 通过设置GPIO的高电平或者低电平来实现
对外设的操作
– GPIO很多是复用的
•程序对寄存器操作即可实现对GPIO的操作
编写IED驱动,主要是对GPIO的操作
在datasheet中找到GPL2
GPL2_0的控制寄存器,主要是控制输入/输出(电平方向)
数据寄存器,写入0x1 为高电平、0为低电平 因为这里使用的是GPL2_0 第0位对应着GPL2_0 , 如果是2_1
就是对应着第1位 第1位为1 则为高电平、0位低电平
这里使用的是物理地址和虚拟地址映射的方式来操作GPIO
#include
#include
#include //杂项设备的头文件struct miscdevice
#include //文件操作的头文件 struct file_operations
#include //这个是copy_to/from_user 的头文件
#include // ioremap 函数的头文件
#define GPIO_LED_CON 0x11000100 //GPL2CON
#define GPIO_LED_DAT 0x11000104 //GPL2DAT
unsigned char * LED_CON;
unsigned char * LED_DAT;
ssize_t misc_write (struct file * file, const char __user *user, size_t n, loff_t *loff_t)
{
char buf[64] = {0};
printk("misc_write!\n");
if (copy_from_user(buf, user, n) != 0)
{
printk("copy from user is error!\n");
return -1;
}
printk("copy from user is success!\n");
//应用层写1 为 打开LED 0为关闭LED
if (buf[0] == 1)
{
*LED_DAT = 0x1;
}
else if (buf[0] == 0)
{
*LED_DAT = 0x0;
}
printk("misc_write %c\n", buf[0]);
return 0;
}
int misc_open (struct inode *inode, struct file *file)
{
printk("misc_open!\n");
return 0;
}
int misc_release (struct inode *inode, struct file *file)
{
printk("misc_release!\n");
return 0;
}
//第二步填充file_operations结构体
struct file_operations misc_fops = {
.owner = THIS_MODULE, // 当前用户拥有
.open = misc_open,
.write = misc_write,
.read = misc_read
};
//第一步填充miscdevice结构体
struct miscdevice miscdev = {
.minor = MISC_DYNAMIC_MINOR, //自动获取次设备号
.name = "my_led", //杂项设备名称 在/dev下可以看到my_led设备节点
.fops = &misc_fops //文件操作集
};
static int misc_init(void)
{
int ret;
//第三步 注册杂项设备并生成设备节点
ret = misc_register(&miscdev);
if (0 > ret)
{
printk("misc register is error!\n");
return -1;
}
printk("misc register is success!\n");
// 物理地址映射到虚拟地址
LED_DAT = ioremap(GPIO_LED_DAT, 1); //数据寄存器只有一个字节 映射一个字节
if (NULL == LED_DAT)
{
printk("LED_DAT ioremap is error!\n");
return -EBUSY;
}
LED_CON = ioremap(GPIO_LED_CON, 4); //控制寄存器有4个字节
if (NULL == LED_CON)
{
printk("LED_CON ioremap is error!\n");
return -EBUSY;
}
*LED_CON |= 0x1; //设置为output
return 0;
}
static void misc_exit(void)
{
misc_deregister(&miscdev); //卸载
//取消映射
iounmap(LED_CON);
iounmap(LED_DAT);
printk("byb byb\n"); // 这里打印函数需要用printk 因为printf是C库函数
}
module_init(misc_init); //入口
module_exit(misc_exit); //出口
MODULE_LICENSE("GPL"); //协议声明
/*************************************************************************
> File Name: app.c
> 作者:YJK
> Mail: [email protected]
> Created Time: 2021年04月11日 星期日 21时31分17秒
************************************************************************/
#include
#include
#include
#include
#include
#include
int main(int argc,char *argv[])
{
int fd;
char buf[64] ={0};
char *str = "hello world";
fd = open(argv[1], O_RDWR);
if (fd == -1)
{
perror("open");
return -1;
}
char dat = 0x1;
ret = write(fd, &dat, 1);
if (ret == -1)
{
perror("write");
return -1;
}
sleep(1);
dat = 0x0;
write(fd, &dat, 1);
sleep(1);
dat = 0x1;
write(fd, &dat, 1);
close(fd);
return 0;
}
执行make之后生成.ko 模块文件,然后copy到开发板,使用insmod xx.ko 导入模块,然后执行app程序