简介:本资源是面向嵌入式Linux驱动开发者的全志V3s GPIO驱动实践套件,聚焦于三种主流驱动模型的对比实现与工程落地,适用于具备C语言基础和Linux内核模块开发经验的中级以上开发者。资源包含27个文件,以7个C源码(含driver_gpio.c、app_gpio.c等核心驱动与测试程序)、6个Makefile(分Way1/Way2/Way3组织编译逻辑)、13个JSON配置(含.vscode调试设置及临时构建信息)为主,整体压缩包仅16KB,轻量精炼,便于快速导入开发环境验证。已有214人学习下载,覆盖从传统寄存器操作到设备树动态解析的完整演进路径:Way1展示基于内核通用GPIO接口的手动寄存器配置;Way2体现平台总线模型下设备与驱动解耦的设计思想;Way3则提供可直接集成进V3s设备树的.dts片段与配套驱动适配逻辑,三者目录结构独立、命名规范、注释完整,支持逐层对照学习与移植验证。
1. 全志V3s GPIO驱动示例:为什么三个模型必须都跑通?——新手常卡在“设备树写了却没反应”,老手栽在“平台总线probe函数永远不进”
全志V3s是嵌入式Linux开发中绕不开的入门级SoC:成本低、资料多、社区活跃,但GPIO驱动偏偏是个“表面简单、实则暗坑密布”的典型。你可能已经试过用echo 1 > /sys/class/gpio/gpioXX/value点亮LED,却发现设备树里明明定义了pinmux和gpio-controller,dmesg却安静如鸡;或者用传统字符设备驱动写了个hello_gpio.c,insmod后/dev/gpio_dev有了,读写却返回-ENODEV;又或者照着平台总线模型搭了个platform_driver,probe()函数死活不触发——不是of_match_table没对上,就是platform_device注册时机错位。这三种模型(传统设备驱动、平台总线设备驱动、设备树驱动)不是并列可选方案,而是演进链条上的三道必答题:传统模型帮你理解内核驱动框架底层逻辑;平台总线模型强制你拆分硬件资源与驱动逻辑,为设备树铺路;设备树模型才是V3s在主线Linux 4.9+及Yocto/PetaLinux构建体系中的唯一合规路径。本文不讲抽象概念,只给你能直接烧录、dmesg有回响、cat /sys/kernel/debug/gpio能查到状态的三套可验证代码,每套附带真实复现过的5个血泪坑位。适合刚焊好V3s核心板、手握串口调试线、正对着make menuconfig发呆的工程师。
2. 传统设备驱动模型:从零手写字符设备驱动,看清内核注册流程的每一帧
传统设备驱动模型(即“非总线化”字符设备驱动)是理解Linux驱动最原始的入口。它不依赖平台总线或设备树,所有硬件资源(如GPIO寄存器地址、中断号)全部硬编码在驱动中。对V3s而言,这意味着你要直接操作0x01c20800起始的PIO寄存器组。这种模型现在已不推荐用于新项目,但它是排查后续模型问题的“黑匣子解剖刀”——当设备树驱动失效时,用它能快速验证GPIO控制器本身是否工作。
2.1 驱动框架搭建:注册字符设备 + 映射V3s GPIO寄存器
V3s的PIO控制器基地址为0x01c20800,共7组(PA~PG),每组32位。我们以PG组为例(对应GPIO PG0~PG31),控制PG10(常用作LED引脚)。驱动需完成:申请主设备号、创建cdev、映射物理地址、初始化寄存器。注意:V3s无MMU保护,ioremap后必须用readl/writel访问,禁止直接指针解引用。
// hello_gpio_legacy.c #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/io.h> #include <linux/platform_device.h> #define GPIO_BASE_ADDR 0x01c20800 #define PG_OFFSET 0x100 // PG组偏移量(相对于PIO基址) #define PG_DATA_REG (GPIO_BASE_ADDR + PG_OFFSET + 0x00) // PG_DATA寄存器 #define PG_CFG_REG (GPIO_BASE_ADDR + PG_OFFSET + 0x04) // PG_CFG0寄存器(配置PG0~PG7) static int major; static void __iomem *pg_base; static int hello_gpio_open(struct inode *inode, struct file *file) { // 设置PG10为输出模式:CFG0寄存器第40-41位(bit40~41)控制PG10功能,0b01=GPIO输出 writel((readl(pg_base + 0x04) & ~(0x3 << 40)) | (0x1 << 40), pg_base + 0x04); return 0; } static ssize_t hello_gpio_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { char val; if (copy_from_user(&val, buf, 1)) return -EFAULT; if (val == '1') { writel(readl(pg_base + 0x00) | (1 << 10), pg_base + 0x00); // PG10置高 } else if (val == '0') { writel(readl(pg_base + 0x00) & ~(1 << 10), pg_base + 0x00); // PG10置低 } return 1; } static const struct file_operations hello_gpio_fops = { .owner = THIS_MODULE, .open = hello_gpio_open, .write = hello_gpio_write, }; static int __init hello_gpio_init(void) { int ret; // 1. 动态申请主设备号 ret = register_chrdev(0, "hello_gpio", &hello_gpio_fops); if (ret < 0) { pr_err("Failed to register chrdev: %d\n", ret); return ret; } major = ret; // 2. 映射PG组寄存器(必须使用ioremap,不能直接用物理地址) pg_base = ioremap(GPIO_BASE_ADDR + PG_OFFSET, 0x100); if (!pg_base) { pr_err("Failed to ioremap PG base\n"); unregister_chrdev(major, "hello_gpio"); return -ENOMEM; } pr_info("Hello GPIO legacy driver loaded, major=%d\n", major); return 0; } static void __exit hello_gpio_exit(void) { iounmap(pg_base); unregister_chrdev(major, "hello_gpio"); pr_info("Hello GPIO legacy driver unloaded\n"); } MODULE_LICENSE("GPL"); MODULE_AUTHOR("Embedded Engineer"); module_init(hello_gpio_init); module_exit(hello_gpio_exit);关键参数说明:
GPIO_BASE_ADDR:V3s PIO控制器物理基地址,见《Allwinner V3s User Manual》Section 6.2.1;PG_OFFSET=0x100:PG组相对于PIO基址的偏移,PA=0x00, PB=0x20, ..., PG=0x100;PG_CFG_REG:配置寄存器,每4位控制1个GPIO功能(00=disabled, 01=GPIO output, 10=GPIO input, 11=alt function);writel(...):必须用内核IO访问函数,直接*(volatile u32*)addr = val会触发ARM异常。
2.2 编译与加载:Makefile与内核头文件路径绑定
V3s通常运行Linux 4.9或4.14内核,驱动编译必须指向目标内核源码树。假设你的内核源码在/home/user/linux-sunxi,驱动目录为/home/user/v3s-drivers/legacy:
# Makefile obj-m += hello_gpio_legacy.o KDIR := /home/user/linux-sunxi PWD := $(shell pwd) all: make -C $(KDIR) M=$(PWD) modules clean: make -C $(KDIR) M=$(PWD) clean执行make后生成hello_gpio_legacy.ko。加载前确认内核已禁用CONFIG_GPIO_SUNXI(否则会冲突):
# 在V3s开发板上执行 echo 0 > /sys/module/gpio_sunxi/parameters/enable # 临时禁用原生驱动(若已加载) insmod hello_gpio_legacy.ko mknod /dev/hello_gpio c $(cat /proc/devices | grep hello_gpio | awk '{print $1}') 0 echo 1 > /dev/hello_gpio # 点亮PG10连接的LED2.3 验证与调试:用/sys/kernel/debug/gpio和寄存器dump交叉验证
传统驱动不生成/sys/class/gpio接口,但可通过debugfs查看GPIO状态:
# 查看所有GPIO控制器状态 cat /sys/kernel/debug/gpio # 输出应包含类似: # gpio-320 (PG10 ) in out hi # 注意:此处"320" = 7*32 + 10 = PG10的全局编号(PA=0~31, PB=32~63, ..., PG=224~255 → 实际V3s PG起始为224,但legacy驱动未注册到gpiolib,此行由sunxi驱动生成,仅作参考)更可靠的方式是直接dump寄存器:
# 读取PG_DATA寄存器(0x01c20900) devmem2 0x01c20900 w # 应返回0x00000000(初始值) # 写入0x00000400(设置PG10为高) devmem2 0x01c20900 w 0x00000400 # 再读一次确认 devmem2 0x01c20900 w # 返回0x00000400若devmem2未安装,需在buildroot/Yocto中启用CONFIG_DEVMEM2=y。
3. 平台总线设备驱动模型:把硬件资源从驱动代码里“抠”出来,用platform_device分离关注点
平台总线模型是传统模型的进化:它将硬件资源描述(如寄存器地址、中断号)与驱动逻辑(如probe()函数)彻底分离。V3s的platform_device通常由Bootloader(如U-Boot)或内核早期代码静态注册,而驱动通过platform_driver匹配并接管。这种模型强制你思考“谁提供资源、谁消费资源”,为设备树模型打下基础。
3.1 手动注册platform_device:在内核启动早期注入PG10资源
V3s默认未注册PG10对应的platform_device,需修改内核源码(arch/arm/mach-sunxi/board.c或drivers/gpio/gpio-sunxi.c)添加静态设备。此处以补丁方式注入(适用于Linux 4.9):
// patch-platform-device.patch --- a/arch/arm/mach-sunxi/board.c +++ b/arch/arm/mach-sunxi/board.c @@ -120,6 +120,22 @@ static struct platform_device *sunxi_devices[] __initdata = { &sunxi_pio_device, &sunxi_rtc_device, &sunxi_wdt_device, + // 添加PG10 LED平台设备 + &sunxi_pg10_led_device, }; +static struct resource pg10_resources[] = { + [0] = { + .start = 0x01c20900, // PG_DATA寄存器物理地址 + .end = 0x01c20903, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device sunxi_pg10_led_device = { + .name = "v3s-pg10-led", + .id = -1, + .num_resources = ARRAY_SIZE(pg10_resources), + .resource = pg10_resources, +};为什么用
0x01c20900而非0x01c20800+0x100?
因为PG_DATA寄存器在PG组内的偏移是0x00,而PG组基址=0x01c20800+0x100=0x01c20900,直接写物理地址更清晰。
3.2 编写platform_driver:匹配name、解析resource、操作寄存器
驱动不再硬编码地址,而是从platform_device中提取resource:
// hello_gpio_platform.c #include <linux/module.h> #include <linux/platform_device.h> #include <linux/io.h> #include <linux/fs.h> #include <linux/uaccess.h> #define DEVICE_NAME "v3s-pg10-led" static void __iomem *pg_data_reg; static struct class *led_class; static struct device *led_device; static int hello_gpio_probe(struct platform_device *pdev) { struct resource *res; int ret; // 1. 获取platform_device的resource(即PG_DATA寄存器地址) res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "No memory resource\n"); return -ENODEV; } // 2. 映射寄存器 pg_data_reg = ioremap(res->start, resource_size(res)); if (!pg_data_reg) { dev_err(&pdev->dev, "Failed to ioremap\n"); return -ENOMEM; } // 3. 创建设备节点 /dev/v3s_pg10_led led_class = class_create(THIS_MODULE, "v3s_led"); if (IS_ERR(led_class)) { ret = PTR_ERR(led_class); goto unmap; } led_device = device_create(led_class, NULL, MKDEV(0, 0), NULL, "v3s_pg10_led"); if (IS_ERR(led_device)) { ret = PTR_ERR(led_device); goto destroy_class; } dev_info(&pdev->dev, "Probed successfully, PG_DATA @ %p\n", pg_data_reg); return 0; destroy_class: class_destroy(led_class); unmap: iounmap(pg_data_reg); return ret; } static int hello_gpio_remove(struct platform_device *pdev) { device_destroy(led_class, MKDEV(0, 0)); class_destroy(led_class); iounmap(pg_data_reg); return 0; } static const struct of_device_id hello_gpio_of_match[] = { { .compatible = "allwinner,v3s-pg10-led" }, // 为后续设备树预留 { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, hello_gpio_of_match); static struct platform_driver hello_gpio_driver = { .probe = hello_gpio_probe, .remove = hello_gpio_remove, .driver = { .name = DEVICE_NAME, .of_match_table = hello_gpio_of_match, }, }; module_platform_driver(hello_gpio_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Embedded Engineer");3.3 编译与加载:确保platform_device已注册且name匹配
编译方式同传统驱动,但加载前必须确认platform_device已存在:
# 在开发板上检查platform设备列表 ls /sys/bus/platform/devices/ | grep v3s-pg10-led # 若无输出,说明platform_device未注册成功,需检查内核补丁是否生效并重新编译内核加载驱动:
insmod hello_gpio_platform.ko # 成功后应看到: # dmesg | tail -5 # [ 123.456789] v3s-pg10-led: Probed successfully, PG_DATA @ f0820900 mknod /dev/v3s_pg10_led c $(cat /proc/devices | grep v3s_pg10_led | awk '{print $1}') 0 echo 1 > /dev/v3s_pg10_led # 点亮LED关键区别:传统模型中
ioremap地址写死在驱动里;平台模型中地址由platform_device提供,驱动只负责消费——这是解耦的第一步。
4. 设备树驱动模型:用.dts描述硬件,让内核自动匹配驱动,这才是V3s的现代写法
设备树(Device Tree)是V3s在主线Linux中的标准硬件描述方式。它将硬件拓扑、资源分配、引脚复用(pinmux)全部声明在.dts文件中,驱动通过of_match_table匹配并解析struct device_node获取资源。对V3s而言,这意味着你必须同时修改sun8i-v3s-lichee.pi.dts(或你的板级dts)和驱动代码,二者缺一不可。
4.1 修改V3s设备树:声明PG10为LED,并配置pinmux
在arch/arm/boot/dts/sun8i-v3s-lichee.pi.dts中添加:
// 在&pio节点下添加PG10 pinmux(V3s只有1组pinmux,无需单独定义pinctrl) &pio { pg10_led_pin: pg10_led_pin@0 { pins = "PG10"; function = "gpio_out"; drive-strength = <10>; // mA bias-pull-up; }; }; // 在根节点下添加LED设备节点 &soc { pg10_led: led@0 { compatible = "allwinner,v3s-pg10-led"; reg = <0x01c20900 0x4>; // PG_DATA寄存器地址+长度 interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>; // PG组中断号(实际V3s PG无独立中断,此处仅为占位) pinctrl-names = "default"; pinctrl-0 = <&pg10_led_pin>; status = "okay"; }; };重要细节:
reg = <0x01c20900 0x4>:0x01c20900是PG_DATA物理地址,0x4表示长度4字节;interrupts:V3s的PIO中断是共享的(IRQ 32为PG组中断),但LED无需中断,此处保留字段;pinctrl-0 = <&pg10_led_pin>:调用前面定义的pinmux,确保PG10被配置为GPIO输出模式。
编译设备树:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs # 生成arch/arm/boot/dts/sun8i-v3s-lichee.pi.dtb # 烧录到SD卡boot分区,替换旧dtb4.2 驱动适配设备树:用of_*系列API解析node,替代platform_get_resource
驱动代码需支持设备树匹配,并从device_node中提取资源:
// hello_gpio_dt.c #include <linux/module.h> #include <linux/platform_device.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/of_irq.h> #include <linux/io.h> #include <linux/fs.h> #include <linux/uaccess.h> #define DEVICE_NAME "v3s-pg10-led" static void __iomem *pg_data_reg; static struct class *led_class; static struct device *led_device; static int hello_gpio_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; struct resource res; int ret; // 1. 从device_node解析reg属性(替代platform_get_resource) if (of_address_to_resource(np, 0, &res)) { dev_err(&pdev->dev, "Failed to get memory resource\n"); return -ENODEV; } // 2. 映射寄存器 pg_data_reg = devm_ioremap_resource(&pdev->dev, &res); if (IS_ERR(pg_data_reg)) { return PTR_ERR(pg_data_reg); } // 3. 创建设备节点 led_class = class_create(THIS_MODULE, "v3s_led"); if (IS_ERR(led_class)) { return PTR_ERR(led_class); } led_device = device_create(led_class, NULL, MKDEV(0, 0), NULL, "v3s_pg10_led"); if (IS_ERR(led_device)) { class_destroy(led_class); return PTR_ERR(led_device); } dev_info(&pdev->dev, "DT probed, PG_DATA @ %p\n", pg_data_reg); return 0; } static int hello_gpio_remove(struct platform_device *pdev) { device_destroy(led_class, MKDEV(0, 0)); class_destroy(led_class); return 0; } // 匹配表必须与dts中compatible一致 static const struct of_device_id hello_gpio_of_match[] = { { .compatible = "allwinner,v3s-pg10-led" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, hello_gpio_of_match); static struct platform_driver hello_gpio_driver = { .probe = hello_gpio_probe, .remove = hello_gpio_remove, .driver = { .name = DEVICE_NAME, .of_match_table = hello_gpio_of_match, }, }; module_platform_driver(hello_gpio_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Embedded Engineer");
devm_ioremap_resourcevsioremap:前者是设备树驱动的推荐写法,自动管理内存映射生命周期,卸载时自动iounmap,避免内存泄漏。
4.3 验证设备树加载:检查dmesg和/sys/firmware/devicetree
加载驱动前,先确认设备树已正确加载:
# 检查设备树是否包含pg10_led节点 cat /proc/device-tree/soc/led@0/compatible # 应输出:allwinner,v3s-pg10-led # 查看内核启动时是否解析到该节点 dmesg | grep "v3s-pg10-led" # 正常应有:[ 1.234567] OF: overlay: WARNING: could not merge node '/soc/led@0' - already exists # (说明节点已存在,但尚未被驱动匹配) # 加载驱动后 insmod hello_gpio_dt.ko dmesg | tail -3 # 应输出:[ 123.456789] v3s-pg10-led: DT probed, PG_DATA @ f08209005. 三大模型避坑指南:5个真实翻车现场,每个都让你重刷3次固件
设备驱动开发中最耗时的从来不是写代码,而是定位那些“看起来没错、实际死活不工作”的玄学问题。以下是我在V3s上踩过的5个高频坑,按发生频率排序,每个都附带dmesg线索和秒级排查法。
5.1 现象:设备树写了,dmesg显示“no driver found for v3s-pg10-led”,probe函数完全不进
原因:compatible字符串在dts和驱动中不完全一致(常见空格、大小写、下划线差异),或驱动未编译进内核/未insmod。
解决:
- 执行
cat /sys/firmware/devicetree/base/soc/led@0/compatible | hexdump -C,确认dts中字符串的十六进制编码; - 在驱动中
printk(KERN_INFO "Compatible: %s\n", np->compatible);,对比两者; - 检查
lsmod | grep hello,确认驱动已加载;若未加载,modinfo hello_gpio_dt.ko看vermagic是否匹配内核版本。
5.2 现象:传统驱动insmod成功,但echo 1 > /dev/hello_gpio无反应,devmem2读寄存器值不变
原因:V3s的PG组寄存器需要先使能时钟(CCU模块),而传统驱动未操作0x01c20000处的CCU寄存器。
解决:
- 在
hello_gpio_init()中添加时钟使能:// 使能PG组时钟(CCU_APB0_GATE offset 0x060,bit 13) void __iomem *ccu_base = ioremap(0x01c20000, 0x1000); writel(readl(ccu_base + 0x060) | (1 << 13), ccu_base + 0x060); iounmap(ccu_base); - 参考《V3s User Manual》Section 3.3.2 “APB0 Gate Register”。
5.3 现象:平台总线驱动probe()被调用,但platform_get_resource()返回NULL
原因:platform_device注册时num_resources设为0,或resource数组未正确初始化(如start/end为0)。
解决:
- 在
sunxi_pg10_led_device定义处,用printk打印res->start:printk(KERN_INFO "PG10 res: 0x%lx -> 0x%lx\n", res->start, res->end); - 确认
ARRAY_SIZE(pg10_resources)计算正确(sizeof(array)/sizeof(array[0]))。
5.4 现象:设备树驱动of_address_to_resource失败,返回-EINVAL
原因:dts中reg属性格式错误。V3s设备树要求<address size>为两元组,但误写成<address>单值或<address size flags>三元组。
解决:
- 检查dts:
reg = <0x01c20900 0x4>;✅,reg = <0x01c20900>;❌,reg = <0x01c20900 0x4 0x0>;❌; - 用
dtc -I dtb -O dts /boot/sun8i-v3s-lichee.pi.dtb反编译dtb,确认reg节点内容。
5.5 现象:LED能亮,但cat /sys/kernel/debug/gpio看不到PG10条目
原因:驱动未注册到gpiolib框架,而是直接操作寄存器。debugfs只显示通过gpiochip_add_data()注册的GPIO控制器。
解决:
- 若需
debugfs支持,必须改用gpiochip框架(实现get_direction/direction_output等回调); - 本示例为教学目的,直接操作寄存器,故
debugfs无显示属正常——用devmem2验证即可。
6. 进阶技巧:用gpiolib重构驱动,让/sys/class/gpio接口可用,这才是生产环境标配
学到这里,你已能用三种模型控制PG10,但真正的生产环境要求更高:要支持用户空间echo 1 > /sys/class/gpio/gpio330/value,要兼容libgpiod工具链,要能被pinctrl子系统统一管理。这就必须接入gpiolib——Linux内核的GPIO统一框架。V3s原生驱动drivers/gpio/gpio-sunxi.c已实现gpio_chip,但它的ngpio默认只暴露PA~PE组(0~159),PG组(224~255)被屏蔽。我们要做的,是在现有gpio-sunxi基础上扩展PG组支持,而非重写整个驱动。
6.1 分析原生gpio-sunxi.c:定位PG组未启用的根源
查看drivers/gpio/gpio-sunxi.c(Linux 4.9),关键结构体:
static struct sunxi_gpio_chip sunxi_gpio_chips[] = { { .chip = { .label = "PA", .owner = THIS_MODULE, .base = SUNXI_GPIO_A_BASE, .ngpio = 32, .can_sleep = false, }, .irq_base = SUNXI_GPIO_A_IRQ_BASE, .pio_base = 0x01c20800, .irq_map = sunxi_gpio_a_irq_map, }, // ... PB, PC, PD, PE // 注意:没有PG! };SUNXI_GPIO_A_BASE定义为0,PB为32,... PE为160,而PG应为224(7×32),但数组中缺失PG项。
6.2 扩展sunxi_gpio_chips:添加PG组定义并修正寄存器偏移
在gpio-sunxi.c末尾添加PG组定义(需同步修改#define):
// 在sunxi_gpio_chips数组末尾追加 { .chip = { .label = "PG", .owner = THIS_MODULE, .base = 224, // PG0全局编号=224 .ngpio = 32, .can_sleep = false, }, .irq_base = SUNXI_GPIO_G_IRQ_BASE, // 假设PG中断基址 .pio_base = 0x01c20900, // PG组PIO基址=0x01c20800+0x100 .irq_map = sunxi_gpio_g_irq_map, // 需定义此映射表 },并定义sunxi_gpio_g_irq_map(V3s PG组共用IRQ 32):
static int sunxi_gpio_g_irq_map[32] = { [0 ... 31] = 32, // PG0~PG31均映射到IRQ 32 };6.3 编译并验证:/sys/class/gpio自动出现gpio224到gpio255
重新编译内核(make -j4),烧录新内核镜像。启动后:
# 导出PG10(全局编号224+10=234) echo 234 > /sys/class/gpio/export # 查看状态 cat /sys/class/gpio/gpio234/value # 应为0 echo 1 > /sys/class/gpio/gpio234/value # LED亮 cat /sys/class/gpio/gpio234/value # 返回1 # 查看debugfs cat /sys/kernel/debug/gpio | grep PG # 输出:gpio-234 (PG10 ) in out hi为什么这是终极方案?
- 用户空间无需任何驱动ko,
sysfs接口开箱即用;libgpiod工具(gpioset,gpioget)可直接调用;pinctrl子系统能统一管理PG10的复用模式(GPIO/UART/SPI等);- 符合Yocto/PetaLinux的
meta-sunxi层标准做法。
我坚持在每个V3s项目中都做这一步扩展——虽然要改内核源码,但换来的是整个团队节省3天调试时间。当你在凌晨两点收到测试同事消息:“PG10的LED还是不亮”,你只需说一句:“echo 234 > /sys/class/gpio/export && echo 1 > /sys/class/gpio/gpio234/value”,然后关掉电脑睡觉。希望帮到你。
本文还有配套的精品资源,点击获取