linux驱动笔记
led驱动实验(直接操作寄存器)
在linux中应用层操作的都是虚拟地址,物理地址是实际芯片中寄存器的地址,例如:可以在datesheet中查到具体GPIO8的起始地址和偏移地址。
驱动中主要就是注册和卸载,字符设备驱动中有file_operations操作集,封装有open、write、release等函数
linux驱动层操作register
在驱动中使用ioremap函数
/* 寄存器物理地址 */
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)
/* 地址映射后的虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
/* 入口 */
static int __init led_init(void)
{
int ret = 0;
unsigned int val = 0;
/* 1,初始化LED灯,地址映射 */
IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);
}
/* 出口 */
static void __exit led_exit(void)
{
unsigned int val = 0;
val = readl(GPIO1_DR);
val |= (1 << 3); /* bit3清零,打开LED灯 */
writel(val, GPIO1_DR);
/* 1,取消地址映射 */
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
/* 注销字符设备 */
unregister_chrdev(LED_MAJOR, LED_NAME);
printk("led_exit\r\n");
}
驱动层有提供读写寄存器的函数
readb、readw 和 readl 这三个函数分别对应 8bit、16bit 和 32bit 读操作,参数 addr 就是要读取写内存地址,返回值就是读取到的数据。
u8 readb(const volatile void iomem *addr)
u16 readw(const volatile void iomem *addr)
u32 readl(const volatile void iomem *addr)
writeb、writew 和 writel 这三个函数分别对应 8bit、16bit 和 32bit 写操作,参数 value 是要写入的数值,addr 是要写入的地址。
void writeb(u8 value,volatile void __iomem*addr)
void writew(ul6 value, volatile void __iomem*addr)
void writel(u32 value,volatile void __iomem*addr)
linux应用层操作register
-
可以使用的devmem2命令直接操作物理地址
-
在linux中/dev/mem对应用层开放了物理地址,可以使用open函数打开/dev/mem,配合c库中的mmap函数映射到内存区,实现在linux的应用层对物理地址寄存器操作
#include <stdio.h> #include <stdbool.h> #include <libgen.h> #include <signal.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include <unistd.h> #include <errno.h> #include <fcntl.h> #include <getopt.h> #include <sys/select.h> #include <linux/input.h> #include <sys/mman.h> #include <stdint.h> /* GPIO register */ #define AM62X_GPIO0_BASE 0x00600000 #define AM62X_GPIO1_BASE 0x00601000 #define AM62X_MCU_GPIO0_BASE 0x04201000 // bank 0 -1 #define AM62X_GPIO_DIR01_OFFSET 0x10 // direction #define AM62X_GPIO_OUT_DATA01_OFFSET 0x14 // output status #define AM62X_GPIO_SET_DATA01_OFFSET 0x18 // output data #define AM62X_GPIO_CLR_DATA01_OFFSET 0x1C // clear output #define AM62X_GPIO_IN_DATA01_OFFSET 0x20 // input status // 定义一个宏来读取特定地址的 32 位寄存器值 #define readl(addr) (*(volatile uint32_t *)(addr)) // 定义一个宏来写入特定地址的 32 位寄存器值 #define writel(val, addr) (*(volatile uint32_t *)(addr) = (val)) int resetbit_gpio(void) { // 打开 /dev/mem 设备文件 int mem_fd = open("/dev/mem", O_RDWR | O_SYNC); if (mem_fd < 0) { perror("Failed to open /dev/mem"); return -1; } // 将 GPIO 寄存器映射到用户空间 volatile unsigned int *mcu_gpio_base = (volatile unsigned int *)mmap(NULL,0x1000 & ~(sysconf(_SC_PAGE_SIZE) - 1), PROT_READ | PROT_WRITE, MAP_SHARED, mem_fd, AM62X_MCU_GPIO0_BASE); if (mcu_gpio_base == MAP_FAILED) { perror("mmap failed"); close(mem_fd); return -1; } printf("resetbit_gpio successful, mcu_gpio_base=%x \n",*mcu_gpio_base); unsigned int val; /* SOM-TL62x: A53 u-boot: MCU- gpio0_1 */ val = readl(mcu_gpio_base + AM62X_GPIO_DIR01_OFFSET / sizeof(unsigned int)); val &= ~(1 << 1); writel(val, mcu_gpio_base + AM62X_GPIO_DIR01_OFFSET / sizeof(unsigned int)); printf("AM62X_GPIO_DIR01_OFFSET=%x \n",*(mcu_gpio_base + AM62X_GPIO_DIR01_OFFSET / sizeof(unsigned int))); writel((1 << 1) , mcu_gpio_base + AM62X_GPIO_CLR_DATA01_OFFSET / sizeof(unsigned int)); printf("AM62X_GPIO_CLR_DATA01_OFFSET=%x \n",*(mcu_gpio_base + AM62X_GPIO_CLR_DATA01_OFFSET / sizeof(unsigned int))); val = readl(mcu_gpio_base + AM62X_GPIO_OUT_DATA01_OFFSET / sizeof(unsigned int)); val &= ~(1 << 1); writel(val, mcu_gpio_base + AM62X_GPIO_OUT_DATA01_OFFSET / sizeof(unsigned int)); printf("AM62X_GPIO_OUT_DATA01_OFFSET=%x \n",*(mcu_gpio_base + AM62X_GPIO_OUT_DATA01_OFFSET / sizeof(unsigned int))); // 解除内存映射并关闭文件 munmap((void *)mcu_gpio_base, 0x1000 & ~(sysconf(_SC_PAGE_SIZE) - 1)); close(mem_fd); return 0; } int setbit_gpio(void) { // 打开 /dev/mem 设备文件 int mem_fd = open("/dev/mem", O_RDWR | O_SYNC); if (mem_fd < 0) { perror("Failed to open /dev/mem"); return -1; } // 将 GPIO 寄存器映射到用户空间 volatile unsigned int *mcu_gpio_base = (volatile unsigned int *)mmap(NULL,0x1000 & ~(sysconf(_SC_PAGE_SIZE) - 1), PROT_READ | PROT_WRITE, MAP_SHARED, mem_fd, AM62X_MCU_GPIO0_BASE); if (mcu_gpio_base == MAP_FAILED) { perror("mmap failed"); close(mem_fd); return -1; } printf("setbit_gpio successful, mcu_gpio_base=%x \n",*mcu_gpio_base); volatile unsigned int val; /* SOM-TL62x: A53 u-boot: MCU- gpio0_1 */ val = readl(mcu_gpio_base + AM62X_GPIO_DIR01_OFFSET / sizeof(unsigned int)); val &= ~(1 << 1); writel(val, mcu_gpio_base + AM62X_GPIO_DIR01_OFFSET / sizeof(unsigned int)); printf("AM62X_GPIO_DIR01_OFFSET=%x \n",*(mcu_gpio_base + AM62X_GPIO_DIR01_OFFSET / sizeof(unsigned int))); writel((1 << 1), mcu_gpio_base + AM62X_GPIO_SET_DATA01_OFFSET / sizeof(unsigned int)); printf("AM62X_GPIO_SET_DATA01_OFFSET=%x \n",*(mcu_gpio_base + AM62X_GPIO_SET_DATA01_OFFSET / sizeof(unsigned int))); val = readl(mcu_gpio_base + AM62X_GPIO_OUT_DATA01_OFFSET / sizeof(unsigned int)); val |= (1 << 1); writel(val, mcu_gpio_base + AM62X_GPIO_OUT_DATA01_OFFSET / sizeof(unsigned int)); printf("AM62X_GPIO_OUT_DATA01_OFFSET=%x \n",*(mcu_gpio_base + AM62X_GPIO_OUT_DATA01_OFFSET / sizeof(unsigned int))); // 解除内存映射并关闭文件 munmap((void *)mcu_gpio_base, 0x1000 & ~(sysconf(_SC_PAGE_SIZE) - 1)); close(mem_fd); return 0; } int main() { while(1){ sleep(1); resetbit_gpio(); sleep(1); setbit_gpio(); } }
新字符设备驱动
使用alloc_chrdev_region代替原来的register_chrdev函数进行字符设备注册,引入了cdev结构体进行管理。
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
} __randomize_layout;
通过cdev结构体将,fop,major,devid等绑定在一起,使用cdec_init后使用cdev_add添加到linux内核。如果指定设备号进行注册,使用register_chrdev_region进行注册固定的设备号。
ret = alloc_chrdev_region(&devid, 0, BSG_MAX_DEVS, "bsg");
if (ret)
goto destroy_bsg_class;
bsg_major = MAJOR(devid);
cdev_init(&bsg_cdev, &bsg_fops);
ret = cdev_add(&bsg_cdev, MKDEV(bsg_major, 0), BSG_MAX_DEVS);
if (ret)
goto unregister_chrdev;
卸载使用unregister_chrdev_region函数释放申请的主设备号。
创建设备节点
使用class_creat创建设备节点
/* 4,自动创建设备节点 */
newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
if (IS_ERR(newchrled.class)) {
ret = PTR_ERR(newchrled.class);
goto fail_class;
}
newchrled.device = device_create(newchrled.class, NULL,
newchrled.devid, NULL, NEWCHRLED_NAME);
if (IS_ERR(newchrled.device)) {
ret = PTR_ERR(newchrled.device);
goto fail_device;
}
设置私有数据
经常将设备变量赋予到设备私有数据结构体中,后续使用直接操作私有变量操作。
static int newchrled_open(struct inode *inode, struct file *filp)
{
filp->private_data = &newchrled;
return 0;
}
static int newchrled_release(struct inode *inode, struct file *filp)
{
struct newchrled_dev *dev = (struct newchrled_dev*)filp->private_data;
return 0;
}
使用goto处理错误
处理返回值ret,需要对之前成功申请和注册的东西进行释放。
ret = cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_COUNT);
if(ret < 0) {
goto fail_cdev;
}
goto点后的操作也会执行,例如执行goto fail_class后,fail_cdev之后都会执行到。可以将驱动注册过程中先遇到的代码放在goto处的最后。
fail_device:
class_destroy(newchrled.class);
fail_class:
cdev_del(&newchrled.cdev);
fail_cdev:
unregister_chrdev_region(newchrled.devid, NEWCHRLED_COUNT);
fail_devid:
return ret;
goto点后的操作也会执行,例如执行goto fail_class后,fail_cdev之后都会执行到。可以将驱动注册过程中先遇到的代码放在goto处的最后。
fail_device:
class_destroy(newchrled.class);
fail_class:
cdev_del(&newchrled.cdev);
fail_cdev:
unregister_chrdev_region(newchrled.devid, NEWCHRLED_COUNT);
fail_devid:
return ret;