采用chardev gpiov2/libgpiod读GPIO获修改后值,疑与ioctl的_IOWR有关?
问题描述
我用以下两种方式读取GPIO状态,但获取到的结果始终是修改后的值:
- 使用chardev gpiov2,代码如下:
int get_gpio_status(const int gpio_offset) { int gfd = -1, lfd = -1, ret = 0, rc, offset = -1; struct gpio_v2_line_request req; struct gpio_v2_line_values value; char gpio_dev[20] = "\/dev\/gpiochip3"; if (offset == -1) offset = gpio_offset; memset(&req, 0, sizeof(req)); /* Configure input mode */ req.config.flags = GPIO_V2_LINE_FLAG_INPUT; req.config.num_attrs = 0; /* open GPIO group device, get the fd */ gfd = open(gpio_dev, O_WRONLY); if (gfd == -1) { ret = -errno; return ret; } /* line offset in requested GPIO group */ req.offsets[0] = offset; /* just request one line */ req.num_lines = 1; ret = ioctl(gfd, GPIO_V2_GET_LINE_IOCTL, &req); if (ret == -1) { goto __exit_gfd_close; } else { lfd = req.fd; } ret = ioctl(lfd, GPIO_V2_LINE_GET_VALUES_IOCTL, &value); if (ret == -1) { goto __exit_gfd_close; } rc = (int)value.bits; // close if ((lfd > 2) && (close(lfd) == -1)) perror("Failed to close lfd "); if ((gfd > 2) && close(gfd) == -1) perror("Failed to close gfd "); __exit_gfd_close: if ((gfd > 2) && close(gfd) == -1) perror("Failed to close gfd"); __exit_lfd_close: if ((lfd > 2) && (close(lfd) == -1)) perror("Failed to close lfd"); return rc; }
- 使用libgpiod,尝试设置
GPIOD_LINE_DIRECTION_AS_IS后结果无变化,代码如下:
static struct gpiod_line_request *request_input_line(const char *chip_path, unsigned int offset, const char *consumer) { struct gpiod_request_config *req_cfg = NULL; struct gpiod_line_request *request = NULL; struct gpiod_line_settings *settings; struct gpiod_line_config *line_cfg; struct gpiod_chip *chip; int ret; chip = gpiod_chip_open(chip_path); if (!chip) return NULL; settings = gpiod_line_settings_new(); if (!settings) goto close_chip; gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT); line_cfg = gpiod_line_config_new(); if (!line_cfg) goto free_settings; ret = gpiod_line_config_add_line_settings(line_cfg, &offset, 1, settings); if (ret) goto free_line_config; if (consumer) { req_cfg = gpiod_request_config_new(); if (!req_cfg) goto free_line_config; gpiod_request_config_set_consumer(req_cfg, consumer); } request = gpiod_chip_request_lines(chip, req_cfg, line_cfg); gpiod_request_config_free(req_cfg); free_line_config: gpiod_line_config_free(line_cfg); free_settings: gpiod_line_settings_free(settings); close_chip: gpiod_chip_close(chip); return request; } int read_gpio_status(const int gpio_chip, const int gpio_offset) { const char *chip_path = gpio_chip == 0 ? GPIO_CHIP_3 : GPIO_CHIP_5; struct gpiod_line_request *request; enum gpiod_line_value value; int ret; request = request_input_line(chip_path, gpio_offset, "get-line-value"); if (!request) { fprintf(stderr, "failed to request line: %s\n", strerror(errno)); return EXIT_FAILURE; } value = gpiod_line_request_get_value(request, gpio_offset); /* not strictly required here, but if the app wasn't exiting... */ gpiod_line_request_release(request); return value; }
我怀疑该问题是否与Linux 5.10内核头文件include/uapi/linux/gpio.h中定义的以下ioctl命令均为_IOWR类型有关:
// 来自linux-5.10/include/uapi/linux/gpio.h /* * v2 ioctl()s */ #define GPIO_V2_GET_LINEINFO_IOCTL _IOWR(0xB4, 0x05, struct gpio_v2_line_info) #define GPIO_V2_GET_LINEINFO_WATCH_IOCTL _IOWR(0xB4, 0x06, struct gpio_v2_line_info) #define GPIO_V2_GET_LINE_IOCTL _IOWR(0xB4, 0x07, struct gpio_v2_line_request) #define GPIO_V2_LINE_SET_CONFIG_IOCTL _IOWR(0xB4, 0x0D, struct gpio_v2_line_config) #define GPIO_V2_LINE_GET_VALUES_IOCTL _IOWR(0xB4, 0x0E, struct gpio_v2_line_values) #define GPIO_V2_LINE_SET_VALUES_IOCTL _IOWR(0xB4, 0x0F, struct gpio_v2_line_values)
问题分析与解决
1. chardev gpiov2代码的核心问题
打开gpiochip设备时使用O_WRONLY模式是错误的,GPIO v2字符设备需要双向访问权限才能执行输入读取操作,应修改为:
gfd = open(gpio_dev, O_RDWR);
另外,退出分支存在重复关闭文件描述符的问题:正常流程已关闭lfd和gfd,但__exit_gfd_close分支会再次尝试关闭,可能触发EBADF错误,建议调整跳转逻辑避免重复操作。
2. libgpiod代码的问题
当前代码中设置的是GPIOD_LINE_DIRECTION_INPUT,而非你提到的GPIOD_LINE_DIRECTION_AS_IS。若要保留原有方向,需修改为:
gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_AS_IS);
更关键的是:如果GPIO线之前被配置为输出模式,即使使用AS_IS请求,读取到的依然是输出寄存器的值(即你所说的“修改后的值”),而非外部输入电平。这是硬件特性:当GPIO设为输出时,读取操作返回的是驱动写入的值,不是引脚实际输入。
3. 关于_IOWR类型的疑问
内核头文件中这些ioctl使用_IOWR是合理的:
_IOWR表示该ioctl既需要向内核写入参数,也需要从内核读取返回数据。例如GPIO_V2_GET_LINE_IOCTL需要传入GPIO偏移等参数(写操作),内核返回分配的文件描述符(读操作);GPIO_V2_LINE_GET_VALUES_IOCTL需要传入存放值的结构体地址,内核将读取结果写入该结构体(对用户空间是读操作,但ioctl本身需要双向传递)。- 该类型定义与你读取到“修改后的值”的问题无关,核心原因是GPIO方向配置和设备打开权限错误。
总结
- 针对chardev gpiov2:将
O_WRONLY改为O_RDWR,修复重复关闭文件描述符的逻辑。 - 针对libgpiod:若要读取外部输入,必须将GPIO配置为输入模式(
GPIOD_LINE_DIRECTION_INPUT);若使用AS_IS,需确保GPIO之前的方向确实是输入。 - GPIO处于输出模式时,读取操作返回驱动设置的输出值,而非引脚实际输入,这是正常硬件行为。
内容的提问来源于stack exchange,提问作者黄光华
相关产品推荐
相关产品推荐

