1. 车载Linux内核模块最小骨架搭建

车载Linux内核模块最小骨架搭建 学习卡片

任务目标

在车载Linux开发板上实现一个GPIO LED字符设备驱动,通过sysfs接口控制LED的亮灭。驱动注册后,用户可通过/sys/class/leds/car_led/brightness写入0或1来控制LED状态。

环境与依赖

  • Linux内核源码树(已配置交叉编译环境)
  • 开发板:任意带GPIO引脚的ARM开发板(本文以GPIO编号49为例)
  • 内核配置需开启:CONFIG_LEDS_CLASS、CONFIG_LEDS_GPIO

驱动实现

使用Linux内核LED子系统(leds-class-gpio),驱动只需定义GPIO引脚、注册LED设备,无需手动操作字符设备或实现sysfs属性——子系统自动生成brightness等属性文件。

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/leds.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/slab.h>

#define DRIVER_NAME "car_gpio_led"

struct gpio_led_data {
    struct led_classdev cdev;
    int gpio;
};

static void car_led_set(struct led_classdev *led_cdev,
                        enum led_brightness value)
{
    struct gpio_led_data *led_data =
        container_of(led_cdev, struct gpio_led_data, cdev);

    gpio_set_value(led_data->gpio, value ? 1 : 0);
}

static int gpio_led_probe(struct platform_device *pdev)
{
    struct device_node *np = pdev->dev.of_node;
    struct gpio_led_data *led_data;
    enum of_gpio_flags flags;
    int ret;

    led_data = devm_kzalloc(&pdev->dev, sizeof(*led_data), GFP_KERNEL);
    if (!led_data)
        return -ENOMEM;

    led_data->gpio = of_get_named_gpio_flags(np, "gpios", 0, &flags);
    if (!gpio_is_valid(led_data->gpio)) {
        dev_err(&pdev->dev, "invalid GPIO\n");
        return -EINVAL;
    }

    ret = devm_gpio_request_one(&pdev->dev, led_data->gpio,
                                GPIOF_OUT_INIT_LOW, "car_led");
    if (ret < 0)
        return ret;

    led_data->cdev.name = "car_led";
    led_data->cdev.brightness_set = car_led_set;
    led_data->cdev.max_brightness = 1;

    ret = devm_led_classdev_register(&pdev->dev, &led_data->cdev);
    if (ret < 0) {
        dev_err(&pdev->dev, "failed to register LED\n");
        return ret;
    }

    platform_set_drvdata(pdev, led_data);
    dev_info(&pdev->dev, "car LED registered on GPIO %d\n",
             led_data->gpio);
    return 0;
}

static const struct of_device_id gpio_led_of_match[] = {
    { .compatible = "vendor,car-gpio-led" },
    { }
};
MODULE_DEVICE_TABLE(of, gpio_led_of_match);

static struct platform_driver gpio_led_driver = {
    .probe  = gpio_led_probe,
    .driver = {
        .name = DRIVER_NAME,
        .of_match_table = gpio_led_of_match,
    },
};
module_platform_driver(gpio_led_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("AutoSAR Lab");
MODULE_DESCRIPTION("Car GPIO LED driver");

设备树配置

在开发板设备树源文件(.dts)的根节点下添加LED节点:

/ {
    car_led {
        compatible = "vendor,car-gpio-led";
        gpios = <&gpio0 17 GPIO_ACTIVE_HIGH>;  /* GPIO 49 = bank0 pin17 */
    };
};

编译与加载

  1. 将驱动源码放入内核drivers/leds/目录,并在该目录Makefile添加:obj-$(CONFIG_LEDS_GPIO) += car_gpio_led.o
  2. 编译内核或单独编译模块:make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules
  3. 将生成的car_gpio_led.ko拷贝到开发板,加载:insmod car_gpio_led.ko

验证方法

  1. 加载驱动后检查设备节点:ls /sys/class/leds/,应看到car_led目录
  2. 点亮LED:echo 1 > /sys/class/leds/car_led/brightness,观察LED灯亮起
  3. 熄灭LED:echo 0 > /sys/class/leds/car_led/brightness,观察LED灯熄灭
  4. 读取当前状态:cat /sys/class/leds/car_led/brightness,输出应为最后写入的0或1

通过条件:写入1时LED物理点亮,写入0时熄灭,且读取值与写入值一致。若LED无反应,用dmesg | tail查看驱动日志确认probe是否成功及GPIO号是否正确。