寄存器映射及内存访问
内存基础概念

用户空间和内核空间

IO空间:
CPU通过IO操作设备上的寄存器实现对设备的控制,一般厂商按照IO空间性质将IO划分为IO端口和IO内存
IO端口:
采用独立编址方式。外部设备中的寄存器(IO端口)地址与内存地址是相互独立、互不影响的,它们分别位于不同的地址空间内
IO内存:
采用统一编址方式。外部设备中的寄存器和内存中的存储单元被同等看待,每个IO端口占用一个内存存储单元的地址,将内存地址的一部分划出来用作IO地址空间。外设寄存器占用了内存的地址空间,使内存的存储容量减小

申请/释放IO内存


地址映射
我们访问一个寄存器时,需要将物理地址转换成虚拟地址才能够访问,这个过程就是映射,映射分为动态映射和静态映射
**静态映射:**静态映射在内核启动过程中就建立好,整个内核的生命周期都存在
**动态映射:**动态映射在模块需要的时候才进行,使用完后要解除映射,只有映射时才占用地址资源
IO内存映射/释放映射


内存分配
申请/释放内存


gfp_mask

申请/释放内存(可申请任意大小)


申请/释放内存(申请更大内存)


IO内存读写
读IO内存

写IO内存

LED基础设备驱动
寄存器介绍


模式寄存器配置


输出类型寄存器


置位复位寄存器


LED灯驱动实现流程
1、搭建基础模块框架
2、模块加载函数
注册字符设备实现GPIOZ组基地址映射修改GPIOZ_MODER,配置PZ5、PZ6、PZ7模式为输出模式修改GPIOZ_OTYPER,配置PZ5、PZ6、PZ7为推挽输出修改GPIOZ_BSRR,配置PZ5、PZ6、PZ7输出低电平,关闭LED实现加载函数出错处理,出错则释放之前申请的资源,返回错误码
2、模块卸载函数
注销字符设备、取消地址映射
3、定义命令
创建头文件,将命令定义放在头文件中
4、实现struct file_operations结构体的unlocked_ioctl成员
获取用户的命令和参数将用户参数和命令组合成寄存器位偏移将0x1左移组合后的偏移量,设置复位置位寄存器,实现LED灯控制
led.h
cs
#ifndef LED_HHHHH
#define LED_HHHHH
#define LED_TYPE 'H'
#define LED1_SET _IOW(LED_TYPE, 0, int)
#define LED2_SET _IOW(LED_TYPE, 1, int)
#define LED3_SET _IOW(LED_TYPE, 2, int)
#define LED_ON 0x0
#define LED_OFF 0x1
#endif
led.c
cs
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include "led.h"
MODULE_LICENSE("GPL");
static int led_major = 300;
static int led_minor = 0;
static int device_number = 1;
static struct cdev cdev;
#define GPIOZ_BASE_ADDR 0x54004000
#define GPIOZ_MODER 0x00
#define GPIOZ_OTYPER 0x04
#define GPIOZ_BSRR 0x18
void __iomem *gpioz_base;
static int led_open(struct inode *inodep, struct file *filp)
{
printk("led open\n");
return 0;
}
static int led_close(struct inode *inodep, struct file *filp)
{
printk("led close\n");
return 0;
}
static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int state = 0;
int offset = 0;
int tmp = 0;
printk("led ioctl\n");
/* 获取用户传递过来的参数存储在state变量中 */
if(copy_from_user(&state, (void *)arg, sizeof(int)))
return -EFAULT;
/* 如果用户设置的LED灯状态为LED_OFF则操作BSRR寄存器的高16位 */
if(state == LED_OFF) {
offset += 16;
}
/* 判断命令执行对应操作 */
switch(cmd) {
case LED1_SET:
/* LED1/PZ5位偏移 */
offset += 5;
break;
case LED2_SET:
/* LED2/PZ6位偏移 */
offset += 6;
break;
case LED3_SET:
/* LED3/PZ7位偏移 */
offset += 7;
break;
default:
return -EINVAL;
}
/* 设置指定GPIO输出状态 */
tmp = 0x1 << offset;
writel(tmp, gpioz_base + GPIOZ_BSRR);
return 0;
}
static struct file_operations led_fops = {
.open = led_open,
.release = led_close,
.unlocked_ioctl = led_ioctl
};
int led_drv_init(void)
{
int ret;
unsigned int tmp;
/* 构建设备号 */
dev_t devno = MKDEV(led_major, led_minor);
printk("led driver loading\n");
/* 申请设备号 */
ret = register_chrdev_region(devno, device_number, "led");
if (ret < 0) {
printk("failed: register_chrdev_region\n");
return ret;
}
/* cdev初始化 */
cdev_init(&cdev, &led_fops);
/* 设备注册 */
ret = cdev_add(&cdev, devno, device_number);
if (ret < 0) {
printk("faile: cdev_add\n");
goto err1;
}
/* 寄存器映射 */
gpioz_base = ioremap(GPIOZ_BASE_ADDR, 0xff);
if (!gpioz_base) {
printk("failed: ioremap\n");
goto err2;
}
/* 设置GPIO口工作模式 */
tmp = readl(gpioz_base + GPIOZ_MODER);
tmp = (tmp & ~(0x3 << 10)) | 0x1 << 10; //设置PZ5为输出
tmp = (tmp & ~(0x3 << 12)) | 0x1 << 12; //设置PZ6为输出
tmp = (tmp & ~(0x3 << 14)) | 0x1 << 14; //设置PZ7为输出
writel(tmp, gpioz_base + GPIOZ_MODER);
/* 设置GPIO输出类型 */
tmp = readl(gpioz_base + GPIOZ_OTYPER);
tmp &= ~(0x7 << 5); //设置PZ5 PZ6 PZ7为推挽输出
writel(tmp, gpioz_base + GPIOZ_OTYPER);
/* 设置GPIO默认输出状态 */
tmp = 0x7 << (5 + 16); //设置PZ5 PZ6 PZ7输出低电平,关闭LED灯
writel(tmp, gpioz_base + GPIOZ_BSRR);
return 0;
/* 出错处理依次释放掉前文申请的资源 */
err2:
cdev_del(&cdev);
err1:
unregister_chrdev_region(devno, device_number);
return ret;
}
void led_drv_exit(void)
{
/* 构建设备号 */
dev_t devno = MKDEV(led_major, led_minor);
printk("led driver unloading\n");
/* 去掉寄存器地址映射 */
iounmap(gpioz_base);
/* 设备注销 */
cdev_del(&cdev);
/* 释放设备号 */
unregister_chrdev_region(devno, device_number);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_AUTHOR("led <caozm_yf@hqyj.com>");
LED灯应用层实现流程
1、应用程序
打开字符设备
设置向模块传递的参数,用于开关灯
通过ioctl循环向模块发送命令和参数,延时1s
test.c
cs
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <string.h>
#include "led.h"
int main(int argc, const char *argv[])
{
int fd, state;
fd = open("/dev/led", O_RDWR);
if (fd < 0) {
perror("open");
return -1;
}
while(1) {
state = LED_ON;
ioctl(fd, LED1_SET, &state);
ioctl(fd, LED2_SET, &state);
ioctl(fd, LED3_SET, &state);
sleep(1);
state = LED_OFF;
ioctl(fd, LED1_SET, &state);
ioctl(fd, LED2_SET, &state);
ioctl(fd, LED3_SET, &state);
sleep(1);
}
close(fd);
return 0;
}