Jetson Nano内核模块无法正确访问GPIO寄存器问题求助
我出于学习目的开发了一个控制GPIO的内核模块,设计逻辑是向/proc/lll-gpio写入4,1时点亮GPIO引脚37。因为没有内核模块和寄存器访问经验,我先通过读取寄存器值验证内存地址是否正确:用Jetson.GPIO Python库切换GPIO37状态(引脚确实能正常切换),但内核模块读取的寄存器值完全没变化,求排查问题。
#include <asm/io.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/proc_fs.h> #include <linux/slab.h> #include <linux/uaccess.h> #define LLL_MAX_USER_SIZE 1024 #define GPIO_ADDR 0x6000d000 #define GPIO_CONTROLLER_1_OFFSET 0x0 #define GPIO_CONTROLLER_2_OFFSET 0x100 #define GPIO_CONTROLLER_3_OFFSET 0x200 #define GPIO_CONTROLLER_4_OFFSET 0x300 #define GPIO_CONTROLLER_5_OFFSET 0x400 #define GPIO_CONTROLLER_6_OFFSET 0x500 #define GPIO_CONTROLLER_7_OFFSET 0x600 #define GPIO_PORT_1_OFFSET 0x00 #define GPIO_PORT_2_OFFSET 0x04 #define GPIO_PORT_3_OFFSET 0x08 #define GPIO_PORT_4_OFFSET 0x0C #define GPIO_REG_GPIO_CNF 0x00 #define GPIO_REG_GPIO_OE 0x10 #define GPIO_REG_GPIO_OUT 0x20 #define GPIO_REG_GPIO_IN 0x30 #define GPIO_REG_GPIO_INT_STAT 0x40 #define GPIO_REG_GPIO_ENB 0x50 #define GPIO_REG_GPIO_LVL 0x60 #define GPIO_REG_GPIO_CLR 0x70 static unsigned int *gpio_registers = NULL; static struct proc_dir_entry *lll_proc = NULL; static char data_buffer[LLL_MAX_USER_SIZE]; ssize_t lll_read(struct file *file, char __user *user, size_t size, loff_t *off); ssize_t lll_write(struct file *file, const char __user *user, size_t size, loff_t *off); static void gpio_on(unsigned int pin) { volatile unsigned int *GPIO_CNF_REG = gpio_registers + GPIO_CONTROLLER_1_OFFSET + GPIO_PORT_2_OFFSET + GPIO_REG_GPIO_CNF; volatile unsigned int *GPIO_OE_REG = gpio_registers + GPIO_CONTROLLER_1_OFFSET + GPIO_PORT_2_OFFSET + GPIO_REG_GPIO_OE; volatile unsigned int *GPIO_ENB_REG = gpio_registers + GPIO_CONTROLLER_1_OFFSET + GPIO_PORT_2_OFFSET + GPIO_REG_GPIO_ENB; volatile unsigned int *GPIO_IN_REG = gpio_registers + GPIO_CONTROLLER_1_OFFSET + GPIO_PORT_2_OFFSET + GPIO_REG_GPIO_IN; // *GPIO_CNF_REG = *GPIO_CNF_REG | 1 << 4; // *GPIO_ENB_REG = *GPIO_ENB_REG | 1 << 4; // *GPIO_OE_REG = *GPIO_OE_REG | 1 << 4; printk("GPIO CNG addr : %X\n", *GPIO_CNF_REG); printk("GPIO OE addr : %X\n", *GPIO_OE_REG); printk("GPIO ENB addr : %X\n", *GPIO_ENB_REG); printk("GPIO IN addr : %X\n", *GPIO_IN_REG ); } static void gpio_off(unsigned int pin) { } ssize_t lll_read(struct file *file, char __user *user, size_t size, loff_t *off) { int ret = 0; ret = copy_to_user(user, "Hello!\n", 7); return 7; } ssize_t lll_write(struct file *file, const char __user *user, size_t size, loff_t *off) { int ret = 0; unsigned int pin = UINT_MAX; unsigned int value = UINT_MAX; memset(data_buffer, 0x0, sizeof(data_buffer)); if (size > LLL_MAX_USER_SIZE) size = LLL_MAX_USER_SIZE; ret = copy_from_user(data_buffer, user, size); printk("You said '%s' !", data_buffer); if (sscanf(data_buffer, "%d,%d", &pin, &value) != 2) { printk("Improper format\n"); } printk("You said pin %d value %d!\n", pin, value); if (pin > 0 && pin < 8) { gpio_on(pin); } return size; } static const struct file_operations lll_proc_fops = { .read = lll_read, .write = lll_write, }; static int __init gpio_driver_init(void) { printk("Welcome to my driver!\n"); gpio_registers = (int *)ioremap(GPIO_ADDR, 1024); if (gpio_registers == NULL) { printk("Failed to initialize GPIO"); return -1; } printk("Successfully GPIO"); // Create proc file lll_proc = proc_create("lll-gpio", 0666, NULL, &lll_proc_fops); if (lll_proc == NULL) return -1; return 0; } static void __exit gpio_driver_exit(void) { printk("Leaving to my driver!\n"); iounmap(gpio_registers); proc_remove(lll_proc); return; } module_init(gpio_driver_init); module_exit(gpio_driver_exit); MODULE_LICENSE("GPL");
[ +0.000007] GPIO CNG addr : 40 [ +0.000005] GPIO OE addr : 0 [ +0.000005] GPIO ENB addr : 0 [ +0.000004] GPIO IN addr : 0
寄存器地址计算错误
代码中unsigned int *gpio_registers是32位指针,直接加十六进制偏移值会按4字节为单位计算偏移,而非字节偏移。比如0x4的偏移会实际访问GPIO_ADDR + 0x4*4 = GPIO_ADDR + 0x10的地址,和预期的字节偏移完全不符。
正确做法是先将基地址转为char *类型做字节偏移,再转成unsigned int *访问寄存器:volatile char *gpio_base = ioremap(GPIO_ADDR, 1024); volatile unsigned int *GPIO_CNF_REG = (volatile unsigned int *)(gpio_base + GPIO_CONTROLLER_1_OFFSET + GPIO_PORT_2_OFFSET + GPIO_REG_GPIO_CNF);GPIO引脚映射错误
代码假设GPIO37对应控制器1的端口2第4位,但Jetson的GPIO编号与寄存器组的映射需严格参考官方技术参考手册(TRM),比如Jetson Nano的GPIO37实际对应GPIO Controller 5的Port B第5位,地址偏移完全不同。寄存器定义与实际硬件不匹配
代码中定义的GPIO_REG_GPIO_CNF、GPIO_REG_GPIO_ENB等寄存器偏移,可能和Jetson实际的GPIO寄存器布局不符。需参考官方TRM确认寄存器的正确偏移和功能。内核GPIO子系统的独占访问限制
Jetson.GPIO库基于内核GPIO子系统实现,当它占用GPIO37时,内核可能禁止直接寄存器访问,或该引脚被复用为其他功能。可通过cat /sys/kernel/debug/gpio查看GPIO37的状态,确认是否被标记为"used",以及是否处于GPIO功能模式。
内容的提问来源于stack exchange,提问作者ForgottenTale

