1.在单片机和裸机中我们操作硬件,
//操作寄存器
unsigned int *p = 0x12345678;
*p = 0x87654321;
2.在Linux上使能了MMU,不能直接操作物理地址,需要把物理地址转换成虚拟地址。
3.内核提供了相关的函数:
ioremap:把物理地址转换成虚拟地址
iounmap:释放掉ioremap映射的地址
static inline void __iomem *ioremap(phys_addr_t offset, unsigned long size)
参数:映射物理地址的起始地址,要映射多大的内存空间
返回值:成功返回虚拟地址的首地址,失败返回NULL
static inline void iounmap(void __iomem *addr)
参数:要取消映射的虚拟地址的首地址
注意:物理地址只能被映射一次,多次映射会失败
4.cat /proc/iomem 查看哪些物理地址被映射过了。
5.一个相对完整的驱动实践编写,以蜂鸣器为例,要先根据硬件原理图和数据手册确定相关的寄存器地址。还是直接上代码了。
app:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int fd;
char buf[64] = {0};
fd = open("/dev/beep_drv",O_RDWR);
if(fd < 0)
{
perror("open error\n");
return fd;
}
//接收来自命令行的数据
//atoi将字符串转换成整形
buf[0] = atoi(argv[1]);
write(fd,buf,sizeof(buf));
close(fd);
return 0;
}
驱动:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
//物理地址
#define PWM 0xC0018000
//映射完的虚拟地址首地址
unsigned int *vir_pwm_dr;
int beep_open(struct inode * inode, struct file * file)
{
printk("beep_open\n");
return 0;
}
int beep_release (struct inode *inode, struct file *file)
{
printk("beep_release\n");
return 0;
}
ssize_t beep_read (struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{
char kbuf[64] = "hahaha";
if(copy_to_user(ubuf, kbuf, strlen(kbuf)) != 0)
{
printk("copy_to_user error\n");
return -1;
}
return 0;
}
ssize_t beep_write (struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t)
{
char kbuf[64] = {0};
if(copy_from_user(kbuf, ubuf, size) != 0)
{
printk("copy_from_user error\n");
return -1;
}
printk("beep_write\n");
if(kbuf[0] == 1)
//左移一位
*vir_pwm_dr |= (1<<1);
else if(kbuf[0] == 0)
*vir_pwm_dr &= ~(1<<1);
return 0;
}
struct file_operations beep_fops = {
.owner = THIS_MODULE,
.open = beep_open,
.release = beep_release,
.read = beep_read,
.write = beep_write
};
struct miscdevice beep_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "beep_drv",
.fops = &beep_fops
};
static int __init beep_init(void)
{
int ret;
ret = misc_register(&beep_dev);
if(ret < 0)
{
printk("beep register is error\n");
return -1;
}
printk("beep_init\n");
//映射虚拟地址(物理地址,要映射的大小)
vir_pwm_dr = ioremap(PWM, 4);
if(vir_pwm_dr == NULL)
{
printk("PWM ioremap error\n");
//Linux标准错误
return -EBUSY;
}
printk("PWM ioremap ok\n");
return 0;
}
static void __exit beep_exit(void)
{
misc_deregister(&beep_dev);
//取消映射(映射完的虚拟地址的首地址)
iounmap(vir_pwm_dr);
printk("beep_exit\n");
}
module_init(beep_init);
module_exit(beep_exit);
MODULE_LICENSE("GPL");
6.驱动传参,例如安装驱动时:insmod beep.ko a=1
驱动传参的作用:
(1)设置驱动的相关参数,比如设置缓冲区的大小
(2)设置安全校验,防止我们写的驱动被别人盗用
7.驱动传参使用的函数,
(1)传递普通的参数,比如 char, int 类型的
//参数:参数名,类型,参数读写权限
module_param(name,type,perm);
(2)传递数组
//参数:参数名、类型、实际传入的个数、参数读写权限
module_param_array(name, type, nump, perm);
8.关于驱动传参的驱动代码:
//包含宏定义的头文件
#include <linux/init.h>
//包含初始化加载模块的头文件
#include <linux/module.h>
static int a;
static int b[5];
//实际传入的个数
static int count;
//int、char类型参数传递
//参数名、类型、读写权限
module_param(a, int, S_IRUSR);
//传递数组
//参数名、类型、实际传入的个数、参数读写权限
module_param_array(b, int, &count, S_IRUSR);
//装载卸载函数入口
static int __init hello_init(void)
{
int i;
for(i=0;i<count;i++)
{
printk("b[%d] = %d \n",i,b[i]);
}
printk("count = %d\n",count);
printk("a = %d\n",a);
return 0;
}
static void __exit hello_exit(void)
{
printk("a = %d\n",a);
printk("hello_exit\n");
}
//驱动模块装载卸载声明
module_init(hello_init);
module_exit(hello_exit);
//开源许可证GPL声明
MODULE_LICENSE("GPL");