目录
一、led字符设备驱动实验
二、驱动模块初始化
三、虚拟地址读写
四、自定义led的file_operation接口
五、拷贝数据
六、register_chrdev函数
七、 __register_chrdev函数
八、编译执行
一、led字符设备驱动实验
驱动模块=内核模块(.ko)+驱动接口(file_operations)
- 在内核模块入口函数里获得gpio相关寄存器并初始化
- 构造file_operations接口,并注册到内核
- 创建设备文件,绑定自定义file_operations接口
- 应用程序echo通过写设备文件控制硬件led
二、驱动模块初始化
文件夹:/home/geralt/linux_driver/part_3/chrdev.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#define DEV_MAJOR 0 /* 动态申请主设备号 */
#define DEV_NAME "red_led" /*led设备名字 */
/* GPIO虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO04;
static void __iomem *SW_PAD_GPIO1_IO04;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
static int led_open(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
return -EFAULT;
}
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
unsigned char databuf[10];
if(cnt >10)
cnt =10;
/*从用户空间拷贝数据到内核空间*/
if(copy_from_user(databuf, buf, cnt)){
return -EIO;
}
if(!memcmp(databuf,"on",2)) {
iowrite32(0 << 4, GPIO1_DR);
} else if(!memcmp(databuf,"off",3)) {
iowrite32(1 << 4, GPIO1_DR);
}
/*写成功后,返回写入的字数*/
return cnt;
}
static int led_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* 自定义led的file_operations 接口*/
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
int major = 0;
static int __init led_init(void)
{
/* GPIO相关寄存器映射 */
IMX6U_CCM_CCGR1 = ioremap(0x20c406c, 4);
SW_MUX_GPIO1_IO04 = ioremap(0x20e006c, 4);
SW_PAD_GPIO1_IO04 = ioremap(0x20e02f8, 4);
GPIO1_GDIR = ioremap(0x0209c004, 4);
GPIO1_DR = ioremap(0x0209c000, 4);
/* 使能GPIO1时钟 */
iowrite32(0xffffffff, IMX6U_CCM_CCGR1);
/* 设置GPIO1_IO04复用为普通GPIO*/
iowrite32(5, SW_MUX_GPIO1_IO04);
/*设置GPIO属性*/
iowrite32(0x10B0, SW_PAD_GPIO1_IO04);
/* 设置GPIO1_IO04为输出功能 */
iowrite32(1 << 4, GPIO1_GDIR);
/* LED输出高电平 */
iowrite32(1<< 4, GPIO1_DR);
/* 注册字符设备驱动 */
major = register_chrdev(DEV_MAJOR, DEV_NAME, &led_fops);
printk(KERN_ALERT "led major:%d\n",major);
return 0;
}
static void __exit led_exit(void)
{
/* 取消映射 */
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO04);
iounmap(SW_PAD_GPIO1_IO04);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
/* 注销字符设备驱动 */
unregister_chrdev(major, DEV_NAME);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL2");
MODULE_AUTHOR("embedfire ");
MODULE_DESCRIPTION("led_module");
MODULE_ALIAS("led_module");
地址映射
GPIO寄存器物理地址和虚拟地址映射
文件夹:ebf-buster-linux/arch/arm/include/asm/io.h()
void __iomem *ioremap(resource_size_t res_cookie, size_t size)
参数
- res_cookie:物理地址
- size:映射长度
返回值
- void *类型的指针:指向被映射的虚拟地址
- __iomem:主要用于编译器检查地址在内核空间的有效性
三、虚拟地址读写
readl()/ writel() //过时
void iowrite32(u32 b, void __iomem *addr) //写入一个双字(32bit)
unsigned int ioread32(void __iomem *addr) //读取一个双字(32bit)
内核会检查cpu大小端,调整字节序,以提高驱动的可移植性
四、自定义led的file_operation接口
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
-
owner:设置驱动接口关联的内核模块,防止驱动程序运行时内核模块被卸载
-
release:文件引用数为0时调用
五、拷贝数据
文件夹:include/linux/uaccess.h
copy_from_user(void *to, const void __user *from, unsigned long n)
参数:
- *to:将数据拷贝到内核的地址
- *from:需要拷贝数据的用户空间地址
- n:拷贝数据的长度(字节)
返回值
- 失败:没有被拷贝的字节数
- 成功:0
六、register_chrdev函数
文件夹:ebf-buster-linux/include/linux/fs.h
作用:注册到内核
static inline int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
{
return __register_chrdev(major, 0, 256, name, fops);
}
七、 __register_chrdev函数
源码文件夹:kernel/ebf-buster-linux/fs/char_dev.c
int __register_chrdev(unsigned int major, unsigned int baseminor,unsigned int count, const char *name,const struct file_operations *fops)
{
struct char_device_struct *cd;
struct cdev *cdev;
int err = -ENOMEM;
cd = __register_chrdev_region(major, baseminor, count, name);
...
cdev = cdev_alloc();
...
cdev->owner = fops->owner;
cdev->ops = fops;
...
err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
...
}
- 次设备号为0,次设备号数量为256
八、编译执行
- 进入到目录文件夹:执行make命令
- 执行make copy命令将生成的ko文件移动到共享文件夹
- 打开mobaxterm软件,用SSH登录的方法登录开饭啊
- 将共享文件夹里的ko文件拉到板子里面
- 执行加载模块命令:sudo insmod /home/ubuntu/MyWork/chrdev.ko
6、执行 cat /proc/devices 查看内核中被加载的设备文件
7、执行mknod命令创建设备文件:sudo mknod /dev/xxx c 244 0
8、执行echo命令点亮LED灯:
提示没有权限是因为:sudo命令只给了echo命令权限,并没有给重定向字符权限;
sh -c 可以使得后面的字符串变成一个整体的命令;
9、执行echo命令关闭LED灯: