Linux内核GPIO工具概述

Linux内核GPIO工具概述

本文档对Linux内核GPIO工具目录中的所有C文件进行简要分析,包括主要函数、执行流程、生成工具名称和使用示例。

工具概述

  1. lsgpio: 列出GPIO信息
  2. gpio-hammer: 闪烁GPIO管脚
  3. gpio-event-mon: 监控GPIO事件

目录结构

复制代码
gpio/
├── Makefile          # 构建配置
├── lsgpio.c          # 列出GPIO工具
├── gpio-hammer.c     # 闪烁GPIO工具
├── gpio-event-mon.c  # GPIO事件监控工具
├── gpio-utils.c      # 共享实用函数库
└── gpio-utils.h      # 共享头文件

1. lsgpio.c - 列出GPIO信息

1.1 主要函数

c 复制代码
void print_flags(unsigned long flags)
{
	int i;
	int printed = 0;

	for (i = 0; i < ARRAY_SIZE(flagnames); i++) {
		if (flags & flagnames[i].mask) {
			if (printed)
				fprintf(stdout, " ");
			fprintf(stdout, "%s", flagnames[i].name);
			printed++;
		}
	}
}

功能: 将GPIO标志位转换为可读字符串并打印。
参数:

  • flags: GPIO标志位掩码
    支持的标志:
  • kernel: 内核使用
  • output: 输出模式
  • active-low: 低电平有效
  • open-drain: 开漏输出
  • open-source: 开源输出
1.1.2 list_device()
c 复制代码
int list_device(const char *device_name)
{
	struct gpiochip_info cinfo;
	char *chrdev_name;
	int fd;
	int ret;
	int i;

	ret = asprintf(&chrdev_name, "/dev/%s", device_name);
	if (ret < 0)
		return -ENOMEM;

	fd = open(chrdev_name, 0);
	if (fd == -1) {
		ret = -errno;
		fprintf(stderr, "Failed to open %s\n", chrdev_name);
		goto exit_close_error;
	}

	/* Inspect this GPIO chip */
	ret = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &cinfo);
	if (ret == -1) {
		ret = -errno;
		perror("Failed to issue CHIPINFO IOCTL\n");
		goto exit_close_error;
	}
	fprintf(stdout, "GPIO chip: %s, \"%s\", %u GPIO lines\n",
		cinfo.name, cinfo.label, cinfo.lines);

	/* Loop over the lines and print info */
	for (i = 0; i < cinfo.lines; i++) {
		struct gpioline_info linfo;

		memset(&linfo, 0, sizeof(linfo));
		linfo.line_offset = i;

		ret = ioctl(fd, GPIO_GET_LINEINFO_IOCTL, &linfo);
		if (ret == -1) {
			ret = -errno;
			perror("Failed to issue LINEINFO IOCTL\n");
			goto exit_close_error;
		}
		fprintf(stdout, "\tline %2d:", linfo.line_offset);
		if (linfo.name[0])
			fprintf(stdout, " \"%s\"", linfo.name);
		else
			fprintf(stdout, " unnamed");
		if (linfo.consumer[0])
			fprintf(stdout, " \"%s\"", linfo.consumer);
		else
			fprintf(stdout, " unused");
		if (linfo.flags) {
			fprintf(stdout, " [");
			print_flags(linfo.flags);
			fprintf(stdout, "]");
		}
		fprintf(stdout, "\n");

	}

exit_close_error:
	if (close(fd) == -1)
		perror("Failed to close GPIO character device file");
	free(chrdev_name);
	return ret;
}

功能: 列出指定GPIO设备的所有管脚信息。
参数:

  • device_name: GPIO设备名称(如"gpiochip0")
    执行流程:
  1. 打开GPIO设备文件
  2. 使用GPIO_GET_CHIPINFO_IOCTL获取芯片信息
  3. 打印芯片名称、标签和管脚数量
  4. 遍历所有管脚,使用GPIO_GET_LINEINFO_IOCTL获取每条管脚信息
  5. 打印管脚偏移、名称、消费者标签和标志
c 复制代码
void print_usage(void)
{
	fprintf(stderr, "Usage: lsgpio [options]...\n"
		"List GPIO chips, lines and states\n"
		"  -n <name>  List GPIOs on a named device\n"
		"  -?         This helptext\n"
	);
}

功能: 打印工具使用说明。

1.1.4 main()
c 复制代码
int main(int argc, char **argv)
{
	const char *device_name = NULL;
	int ret;
	int c;

	while ((c = getopt(argc, argv, "n:")) != -1) {
		switch (c) {
		case 'n':
			device_name = optarg;
			break;
		case '?':
			print_usage();
			return -1;
		}
	}

	if (device_name)
		ret = list_device(device_name);
	else {
		const struct dirent *ent;
		DIR *dp;

		/* List all GPIO devices one at a time */
		dp = opendir("/dev");
		if (!dp) {
			ret = -errno;
			goto error_out;
		}

		ret = -ENOENT;
		while (ent = readdir(dp), ent) {
			if (check_prefix(ent->d_name, "gpiochip")) {
				ret = list_device(ent->d_name);
				if (ret)
					break;
			}
		}

		ret = 0;
		if (closedir(dp) == -1) {
			perror("scanning devices: Failed to close directory");
			ret = -errno;
		}
	}
error_out:
	return ret;
}

功能: 程序入口点,处理命令行参数并执行相应操作。
执行流程:

  1. 解析命令行参数(-n指定设备,-?显示帮助)
  2. 如果指定了设备,则列出该设备的GPIO管脚
  3. 否则,遍历/dev目录下所有以"gpiochip"开头的设备并列出

1.2 命令参数详解

bash 复制代码
lsgpio [-n device-name]

参数说明:

  • -n <name>: 指定要列出的GPIO设备名称(如"gpiochip0"),如果不指定则列出所有GPIO设备
  • -?: 显示帮助信息

1.3 使用示例

bash 复制代码
# 列出所有GPIO设备
lsgpio

# 列出指定GPIO设备
lsgpio -n gpiochip0

2. gpio-hammer.c - 闪烁GPIO

2.1 主要函数

2.1.1 hammer_device()
c 复制代码
int hammer_device(const char *device_name, unsigned int *lines, int nlines,
		  unsigned int loops)
{
	struct gpiohandle_data data;
	char swirr[] = "-\\|/";
	int fd;
	int ret;
	int i, j;
	unsigned int iteration = 0;

	memset(&data.values, 0, sizeof(data.values));
	ret = gpiotools_request_linehandle(device_name, lines, nlines,
				   GPIOHANDLE_REQUEST_OUTPUT, &data,
				   "gpio-hammer");
	if (ret < 0)
		goto exit_error;
	else
		fd = ret;

	ret = gpiotools_get_values(fd, &data);
	if (ret < 0)
		goto exit_close_error;

	fprintf(stdout, "Hammer lines [");
	for (i = 0; i < nlines; i++) {
		fprintf(stdout, "%d", lines[i]);
		if (i != (nlines - 1))
			fprintf(stdout, ", ");
	}
	fprintf(stdout, "] on %s, initial states: [", device_name);
	for (i = 0; i < nlines; i++) {
		fprintf(stdout, "%d", data.values[i]);
		if (i != (nlines - 1))
			fprintf(stdout, ", ");
	}
	fprintf(stdout, "]\n");

	/* Hammertime! */
	j = 0;
	while (1) {
		/* Invert all lines so we blink */
		for (i = 0; i < nlines; i++)
			data.values[i] = !data.values[i];

		ret = gpiotools_set_values(fd, &data);
		if (ret < 0)
			goto exit_close_error;

		/* Re-read values to get status */
		ret = gpiotools_get_values(fd, &data);
		if (ret < 0)
			goto exit_close_error;

		fprintf(stdout, "[%c] ", swirr[j]);
		j++;
		if (j == sizeof(swirr)-1)
			j = 0;

		fprintf(stdout, "[");
		for (i = 0; i < nlines; i++) {
			fprintf(stdout, "%d: %d", lines[i], data.values[i]);
			if (i != (nlines - 1))
				fprintf(stdout, ", ");
		}
		fprintf(stdout, "]\r");
		fflush(stdout);
		sleep(1);
		iteration++;
		if (loops && iteration == loops)
			break;
	}
	fprintf(stdout, "\n");
	ret = 0;

exit_close_error:
	gpiotools_release_linehandle(fd);
exit_error:
	return ret;
}

功能: 反复闪烁指定的GPIO管脚。
参数:

  • device_name: GPIO设备名称
  • lines: GPIO管脚偏移数组
  • nlines: 管脚数量
  • loops: 循环次数(0表示无限循环)
    执行流程:
  1. 请求GPIO管脚句柄(输出模式)
  2. 获取初始状态
  3. 循环翻转GPIO状态
  4. 打印当前状态
  5. 延迟1秒
  6. 获取初始管脚状态并打印
  7. 进入循环:
    • 反转所有管脚状态
    • 设置新的管脚状态
    • 读取并打印当前状态
    • 显示旋转动画
    • 等待1秒
    • 检查是否达到循环次数
  8. 释放GPIO管脚句柄
c 复制代码
void print_usage(void)
{
	fprintf(stderr, "Usage: gpio-hammer [options]...\n"
		"Hammer GPIO lines, 0->1->0->1...\n"
		"  -n <name>  Hammer GPIOs on a named device (must be stated)\n"
		"  -o <n>     Offset[s] to hammer, at least one, several can be stated\n"
		" [-c <n>]    Do <n> loops (optional, infinite loop if not stated)\n"
		"  -?         This helptext\n"
		"\n"
		"Example:\n"
		"gpio-hammer -n gpiochip0 -o 4\n"
	);
}

功能: 打印工具使用说明。

2.1.3 main()
c 复制代码
int main(int argc, char **argv)
{
	const char *device_name = NULL;
	unsigned int lines[GPIOHANDLES_MAX];
	unsigned int loops = 0;
	int nlines;
	int c;
	int i;

	i = 0;
	while ((c = getopt(argc, argv, "c:n:o:?")) != -1) {
		switch (c) {
		case 'c':
			loops = strtoul(optarg, NULL, 10);
			break;
		case 'n':
			device_name = optarg;
			break;
		case 'o':
			lines[i] = strtoul(optarg, NULL, 10);
			i++;
			break;
		case '?':
			print_usage();
			return -1;
		}
	}
	nlines = i;

	if (!device_name || !nlines) {
		print_usage();
		return -1;
	}
	return hammer_device(device_name, lines, nlines, loops);
}

功能: 程序入口点,处理命令行参数并执行闪烁操作。
执行流程:

  1. 解析命令行参数(-n指定设备,-o指定管脚,-c指定循环次数)
  2. 验证参数有效性
  3. 调用hammer_device()执行闪烁操作

2.2 命令参数详解

bash 复制代码
gpio-hammer -n <device-name> -o <offset1> [-o <offset2> ...] [-c <loops>]

参数说明:

  • -n <name>: 指定要操作的GPIO设备名称(必须参数)
  • -o <n>: 指定要闪烁的GPIO管脚偏移,可以指定多个(至少一个必须参数)
  • -c <n>: 指定闪烁循环次数,0表示无限循环(可选参数)
  • -?: 显示帮助信息

2.3 使用示例

bash 复制代码
# 无限闪烁gpiochip0的第4号线
gpio-hammer -n gpiochip0 -o 4

# 闪烁gpiochip0的第4和5号线10次
gpio-hammer -n gpiochip0 -o 4 -o 5 -c 10

3. gpio-event-mon.c - GPIO事件监控

3.1 主要函数

3.1.1 monitor_device()
c 复制代码
int monitor_device(const char *device_name,
		   unsigned int line,
		   uint32_t handleflags,
		   uint32_t eventflags,
		   unsigned int loops)
{
	struct gpioevent_request req;
	struct gpiohandle_data data;
	char *chrdev_name;
	int fd;
	int ret;
	int i = 0;

	ret = asprintf(&chrdev_name, "/dev/%s", device_name);
	if (ret < 0)
		return -ENOMEM;

	fd = open(chrdev_name, 0);
	if (fd == -1) {
		ret = -errno;
		fprintf(stderr, "Failed to open %s\n", chrdev_name);
		goto exit_close_error;
	}

	req.lineoffset = line;
	req.handleflags = handleflags;
	req.eventflags = eventflags;
	strcpy(req.consumer_label, "gpio-event-mon");

	ret = ioctl(fd, GPIO_GET_LINEEVENT_IOCTL, &req);
	if (ret == -1) {
		ret = -errno;
		fprintf(stderr, "Failed to issue GET EVENT "
			"IOCTL (%d)\n",
			ret);
		goto exit_close_error;
	}

	/* Read initial states */
	ret = ioctl(req.fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data);
	if (ret == -1) {
		ret = -errno;
		fprintf(stderr, "Failed to issue GPIOHANDLE GET LINE "
			"VALUES IOCTL (%d)\n",
			ret);
		goto exit_close_error;
	}

	fprintf(stdout, "Monitoring line %d on %s\n", line, device_name);
	fprintf(stdout, "Initial line value: %d\n", data.values[0]);

	while (1) {
		struct gpioevent_data event;

		ret = read(req.fd, &event, sizeof(event));
		if (ret == -1) {
			if (errno == -EAGAIN) {
				fprintf(stderr, "nothing available\n");
				continue;
			} else {
				ret = -errno;
				fprintf(stderr, "Failed to read event (%d)\n",
					ret);
				break;
			}
		}

		if (ret != sizeof(event)) {
			fprintf(stderr, "Reading event failed\n");
			ret = -EIO;
			break;
		}
		fprintf(stdout, "GPIO EVENT %llu: ", event.timestamp);
		switch (event.id) {
		case GPIOEVENT_EVENT_RISING_EDGE:
			fprintf(stdout, "rising edge");
			break;
		case GPIOEVENT_EVENT_FALLING_EDGE:
			fprintf(stdout, "falling edge");
			break;
		default:
			fprintf(stdout, "unknown event");
		}
		fprintf(stdout, "\n");

		i++;
		if (i == loops)
			break;
	}

exit_close_error:
	if (close(fd) == -1)
		perror("Failed to close GPIO character device file");
	free(chrdev_name);
	return ret;
}

功能: 监控指定GPIO管脚的事件(上升沿/下降沿)。
参数:

  • device_name: GPIO设备名称
  • line: GPIO管脚偏移
  • handleflags: 管脚处理标志
  • eventflags: 事件标志(上升沿/下降沿)
  • loops: 监控次数(0表示无限监控)
    执行流程:
  1. 打开GPIO设备文件
  2. 使用GPIO_GET_LINEEVENT_IOCTL请求事件监控
  3. 获取初始管脚状态并打印
  4. 进入循环:
    • 读取事件数据
    • 打印事件时间戳和类型(上升沿/下降沿)
    • 检查是否达到监控次数
  5. 关闭设备文件
c 复制代码
void print_usage(void)
{
	fprintf(stderr, "Usage: gpio-event-mon [options]...\n"
		"Listen to events on GPIO lines, 0->1 1->0\n"
		"  -n <name>  Listen on GPIOs on a named device (must be stated)\n"
		"  -o <n>     Offset to monitor\n"
		"  -d         Set line as open drain\n"
		"  -s         Set line as open source\n"
		"  -r         Listen for rising edges\n"
		"  -f         Listen for falling edges\n"
		" [-c <n>]    Do <n> loops (optional, infinite loop if not stated)\n"
		"  -?         This helptext\n"
		"\n"
		"Example:\n"
		"gpio-event-mon -n gpiochip0 -o 4 -r -f\n"
	);
}

功能: 打印工具使用说明。

3.1.3 main()
c 复制代码
int main(int argc, char **argv)
{
	const char *device_name = NULL;
	unsigned int line = -1;
	unsigned int loops = 0;
	uint32_t handleflags = GPIOHANDLE_REQUEST_INPUT;
	uint32_t eventflags = 0;
	int c;

	while ((c = getopt(argc, argv, "c:n:o:dsrf?")) != -1) {
		switch (c) {
		case 'c':
			loops = strtoul(optarg, NULL, 10);
			break;
		case 'n':
			device_name = optarg;
			break;
		case 'o':
			line = strtoul(optarg, NULL, 10);
			break;
		case 'd':
			handleflags |= GPIOHANDLE_REQUEST_OPEN_DRAIN;
			break;
		case 's':
			handleflags |= GPIOHANDLE_REQUEST_OPEN_SOURCE;
			break;
		case 'r':
			eventflags |= GPIOEVENT_REQUEST_RISING_EDGE;
			break;
		case 'f':
			eventflags |= GPIOEVENT_REQUEST_FALLING_EDGE;
			break;
		case '?':
			print_usage();
			return -1;
		}
	}

	if (!device_name || line == -1) {
		print_usage();
		return -1;
	}
	if (!eventflags) {
		printf("No flags specified, listening on both rising and "
		       "falling edges\n");
		eventflags = GPIOEVENT_REQUEST_BOTH_EDGES;
	}
	return monitor_device(device_name, line, handleflags,
			eventflags, loops);
}

功能: 程序入口点,处理命令行参数并执行事件监控。
执行流程:

  1. 解析命令行参数(-n指定设备,-o指定管脚,-r监控上升沿,-f监控下降沿,-c指定次数)
  2. 验证参数有效性
  3. 如果未指定事件标志,则默认监控上升沿和下降沿
  4. 调用monitor_device()执行事件监控

3.2 命令参数详解

bash 复制代码
gpio-event-mon -n <device-name> -o <offset> [-d] [-s] [-r] [-f] [-c <loops>]

参数说明:

  • -n <name>: 指定要监控的GPIO设备名称(必须参数)
  • -o <n>: 指定要监控的GPIO管脚偏移(必须参数)
  • -d: 设置管脚为开漏模式
  • -s: 设置管脚为开源模式
  • -r: 监控上升沿事件
  • -f: 监控下降沿事件
  • -c <n>: 指定监控事件次数,0表示无限监控(可选参数)
  • -?: 显示帮助信息

3.3 使用示例

bash 复制代码
# 监控gpiochip0的第4号线的所有事件
gpio-event-mon -n gpiochip0 -o 4

# 监控gpiochip0的第4号线的上升沿
gpio-event-mon -n gpiochip0 -o 4 -r

# 监控gpiochip0的第4号线的上升沿和下降沿5次
gpio-event-mon -n gpiochip0 -o 4 -r -f -c 5

4. gpio-utils.c - 共享实用函数库

4.1 主要函数

4.1.1 gpiotools_request_linehandle()
c 复制代码
int gpiotools_request_linehandle(const char *device_name, unsigned int *lines,
				 unsigned int nlines, unsigned int flag,
				 struct gpiohandle_data *data,
				 const char *consumer_label)
{
	struct gpiohandle_request req;
	char *chrdev_name;
	int fd;
	int i;
	int ret;

	ret = asprintf(&chrdev_name, "/dev/%s", device_name);
	if (ret < 0)
		return -ENOMEM;

	fd = open(chrdev_name, 0);
	if (fd == -1) {
		ret = -errno;
		fprintf(stderr, "Failed to open %s, %s\n",
			chrdev_name, strerror(errno));
		goto exit_close_error;
	}

	for (i = 0; i < nlines; i++)
		req.lineoffsets[i] = lines[i];

	req.flags = flag;
	strcpy(req.consumer_label, consumer_label);
	req.lines = nlines;
	if (flag & GPIOHANDLE_REQUEST_OUTPUT)
		memcpy(req.default_values, data, sizeof(req.default_values));

	ret = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &req);
	if (ret == -1) {
		ret = -errno;
		fprintf(stderr, "Failed to issue %s (%d), %s\n",
			"GPIO_GET_LINEHANDLE_IOCTL", ret, strerror(errno));
	}

exit_close_error:
	if (close(fd) == -1)
		perror("Failed to close GPIO character device file");
	free(chrdev_name);
	return ret < 0 ? ret : req.fd;
}

功能: 请求GPIO管脚句柄,用于后续操作。
参数:

  • device_name: GPIO设备名称
  • lines: GPIO管脚偏移数组
  • nlines: 管脚数量
  • flag: 管脚标志(输入/输出等)
  • data: 初始数据(输出模式时使用)
  • consumer_label: 消费者标签
    返回值: 成功返回文件描述符,失败返回错误码
4.1.2 gpiotools_set_values()
c 复制代码
int gpiotools_set_values(const int fd, struct gpiohandle_data *data)
{
	int ret;

	ret = ioctl(fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, data);
	if (ret == -1) {
		ret = -errno;
		fprintf(stderr, "Failed to issue %s (%d), %s\n",
			"GPIOHANDLE_SET_LINE_VALUES_IOCTL", ret,
			strerror(errno));
	}

	return ret;
}

功能: 设置GPIO管脚的值。
参数:

  • fd: GPIO管脚句柄文件描述符
  • data: 要设置的值
4.1.3 gpiotools_get_values()
c 复制代码
int gpiotools_get_values(const int fd, struct gpiohandle_data *data)
{
	int ret;

	ret = ioctl(fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, data);
	if (ret == -1) {
		ret = -errno;
		fprintf(stderr, "Failed to issue %s (%d), %s\n",
			"GPIOHANDLE_GET_LINE_VALUES_IOCTL", ret,
			strerror(errno));
	}

	return ret;
}

功能: 获取GPIO管脚的值。
参数:

  • fd: GPIO管脚句柄文件描述符
  • data: 存储获取的值
4.1.4 gpiotools_release_linehandle()
c 复制代码
int gpiotools_release_linehandle(const int fd)
{
	int ret;

	ret = close(fd);
	if (ret == -1) {
		perror("Failed to close GPIO LINEHANDLE device file");
		ret = -errno;
	}

	return ret;
}

功能: 释放GPIO管脚句柄。
参数:

  • fd: GPIO管脚句柄文件描述符
4.1.5 gpiotools_get()
c 复制代码
int gpiotools_get(const char *device_name, unsigned int line)
{
	struct gpiohandle_data data;
	unsigned int lines[] = {line};

	gpiotools_gets(device_name, lines, 1, &data);
	return data.values[0];
}

功能: 获取单个GPIO管脚的值。
参数:

  • device_name: GPIO设备名称
  • line: GPIO管脚偏移
4.1.6 gpiotools_set()
c 复制代码
int gpiotools_set(const char *device_name, unsigned int line,
		 unsigned int value)
{
	struct gpiohandle_data data;
	unsigned int lines[] = {line};

	data.values[0] = value;
	return gpiotools_sets(device_name, lines, 1, &data);
}

功能: 设置单个GPIO管脚的值。
参数:

  • device_name: GPIO设备名称
  • line: GPIO管脚偏移
  • value: 要设置的值(0或1)
4.1.7 gpiotools_gets()
c 复制代码
int gpiotools_gets(const char *device_name, unsigned int *lines,
		   unsigned int nlines, struct gpiohandle_data *data)
{
	int fd;
	int ret;
	int ret_close;

	ret = gpiotools_request_linehandle(device_name, lines, nlines,
				   GPIOHANDLE_REQUEST_INPUT, data,
				   COMSUMER);
	if (ret < 0)
		return ret;

	fd = ret;
	ret = gpiotools_get_values(fd, data);
	ret_close = gpiotools_release_linehandle(fd);
	return ret < 0 ? ret : ret_close;
}

功能: 获取多个GPIO管脚的值。
参数:

  • device_name: GPIO设备名称
  • lines: GPIO管脚偏移数组
  • nlines: 管脚数量
  • data: 存储获取的值
4.1.8 gpiotools_sets()
c 复制代码
int gpiotools_sets(const char *device_name, unsigned int *lines,
		   unsigned int nlines, struct gpiohandle_data *data)
{
	int ret;

	ret = gpiotools_request_linehandle(device_name, lines, nlines,
				   GPIOHANDLE_REQUEST_OUTPUT, data,
				   COMSUMER);
	if (ret < 0)
		return ret;

	return gpiotools_release_linehandle(ret);
}

功能: 设置多个GPIO管脚的值。
参数:

  • device_name: GPIO设备名称
  • lines: GPIO管脚偏移数组
  • nlines: 管脚数量
  • data: 要设置的值

5. gpio-utils.h - 共享头文件

定义了共享实用函数库的接口,包括:

  • 常量定义
  • 结构体声明
  • 函数原型

6. 构建系统

6.1 Makefile文件分析

以下是完整的Makefile内容及详细分析:

makefile 复制代码
# SPDX-License-Identifier: GPL-2.0  # 许可证声明
include ../scripts/Makefile.include  # 包含通用脚本

bindir ?= /usr/bin  # 默认安装目录

# 自动检测内核源代码树根目录
ifndef building_out_of_srctree
# 递归向上查找内核源代码树
srctree := $(patsubst %/,%,$(dir $(CURDIR)))
srctree := $(patsubst %/,%,$(dir $(srctree)))
endif

# 禁用make内置规则,提高构建性能并避免意外行为
MAKEFLAGS += -r

# 编译选项配置
override CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include
# -O2: 优化级别
# -Wall: 开启所有警告
# -g: 包含调试信息
# -D_GNU_SOURCE: 启用GNU扩展
# -I$(OUTPUT)include: 包含构建目录头文件

# 定义构建目标
ALL_TARGETS := lsgpio gpio-hammer gpio-event-mon
ALL_PROGRAMS := $(patsubst %,$(OUTPUT)%,$(ALL_TARGETS))

# 默认目标:构建所有工具
all: $(ALL_PROGRAMS)

# 导出构建环境变量
export srctree OUTPUT CC LD CFLAGS
# 包含内核工具构建框架
include $(srctree)/tools/build/Makefile.include

# 头文件准备:创建符号链接到内核头文件
$(OUTPUT)include/linux/gpio.h: ../../include/uapi/linux/gpio.h
	mkdir -p $(OUTPUT)include/linux 2>&1 || true  # 创建目录,忽略错误
	ln -sf $(CURDIR)/../../include/uapi/linux/gpio.h $@  # 创建符号链接

# 准备目标:确保头文件存在
prepare: $(OUTPUT)include/linux/gpio.h

# 构建gpio-utils共享库
GPIO_UTILS_IN := $(OUTPUT)gpio-utils-in.o
$(GPIO_UTILS_IN): prepare FORCE
	$(Q)$(MAKE) $(build)=gpio-utils  # 子目录构建

# 构建lsgpio工具
LSGPIO_IN := $(OUTPUT)lsgpio-in.o
$(LSGPIO_IN): prepare FORCE $(OUTPUT)gpio-utils-in.o
	$(Q)$(MAKE) $(build)=lsgpio
$(OUTPUT)lsgpio: $(LSGPIO_IN)
	$(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@  # 链接可执行文件

# 构建gpio-hammer工具
GPIO_HAMMER_IN := $(OUTPUT)gpio-hammer-in.o
$(GPIO_HAMMER_IN): prepare FORCE $(OUTPUT)gpio-utils-in.o
	$(Q)$(MAKE) $(build)=gpio-hammer
$(OUTPUT)gpio-hammer: $(GPIO_HAMMER_IN)
	$(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@

# 构建gpio-event-mon工具
GPIO_EVENT_MON_IN := $(OUTPUT)gpio-event-mon-in.o
$(GPIO_EVENT_MON_IN): prepare FORCE $(OUTPUT)gpio-utils-in.o
	$(Q)$(MAKE) $(build)=gpio-event-mon
$(OUTPUT)gpio-event-mon: $(GPIO_EVENT_MON_IN)
	$(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@

# 清理目标:删除构建产物
clean:
	rm -f $(ALL_PROGRAMS)  # 删除可执行文件
	rm -f $(OUTPUT)include/linux/gpio.h  # 删除头文件链接
	# 删除所有.o文件和.d依赖文件
	find $(if $(OUTPUT),$(OUTPUT),.) -name '*.o' -delete -o -name '\.*.d' -delete

# 安装目标:将工具安装到系统目录
install: $(ALL_PROGRAMS)
	install -d -m 755 $(DESTDIR)$(bindir);        # 创建安装目录
	for program in $(ALL_PROGRAMS); do        # 安装每个工具
		install $$program $(DESTDIR)$(bindir);    
	done

# 强制目标:确保每次都执行
FORCE:

# 伪目标声明
.PHONY: all install clean FORCE prepare

6.2 主要目标

bash 复制代码
make all      # 构建所有三个工具
make install  # 将工具安装到/usr/bin
make clean    # 清理构建产物

6.3 生成的工具

  1. lsgpio: 列出GPIO信息
  2. gpio-hammer: 闪烁GPIO管脚
  3. gpio-event-mon: 监控GPIO事件

7. 总结

这些GPIO工具提供了从用户空间操作GPIO管脚的便捷方式,展示了Linux GPIO字符设备接口的使用方法。工具之间通过共享实用函数库实现代码复用,提高了可维护性。

每个工具都有明确的职责:

  • lsgpio: 用于查看GPIO系统状态
  • gpio-hammer: 用于测试GPIO输出功能
  • gpio-event-mon: 用于监控GPIO输入事件

这些工具对于调试和测试GPIO功能非常有用,特别是在嵌入式系统开发中。

相关推荐
松涛和鸣1 小时前
DAY69 Practical Guide to Linux Character Device Drivers
linux·服务器·arm开发·数据库·单片机·嵌入式硬件
程序猿编码2 小时前
实战Linux内核模块:终止ptrace跟踪程序与被跟踪进程
linux·网络·内核·内核模块·ptrace
咩咩不吃草2 小时前
Linux环境下MySQL的安装与使用与Navicat
linux·运维·数据库·mysql·navicat
好好学习天天向上~~2 小时前
3_Linux学习总结_基础指令
linux·服务器·学习
郝学胜-神的一滴2 小时前
Linux网络编程之Socket函数:构建通信的桥梁
linux·服务器·网络·c++·程序人生
regret~2 小时前
【笔记】Nginx 核心操作 + 配置解析笔记(适配 Linux+FastAPI / 前端代理场景)
linux·笔记·nginx
理智.6292 小时前
Windows 本地文件上传到 Linux 服务器的完整实践(scp/ssh),以及常见踩坑总结
linux·服务器·ssh
老兵发新帖2 小时前
Ubuntu版本nvidia-smi提示版本不匹配问题,解决办法
linux·chrome·ubuntu
酉鬼女又兒2 小时前
Linux快速入门指南:常用快捷键➕命令行高效操作
linux·运维·服务器