linux驱动笔记

linux驱动笔记

led驱动实验(直接操作寄存器)

在linux中应用层操作的都是虚拟地址,物理地址是实际芯片中寄存器的地址,例如:可以在datesheet中查到具体GPIO8的起始地址和偏移地址。

image-20240715130508285

驱动中主要就是注册和卸载,字符设备驱动中有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; 
  • 12
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值