4. 车载设备号动态分配与udev规则适配
任务说明
本案例实现一个车载电池电压采集驱动:通过ADC控制器读取电池电压原始值,转换为毫伏电压后,通过字符设备接口提供给用户空间程序读取。驱动基于Linux内核的IIO子系统实现,用户空间可通过cat /sys/bus/iio/devices/iio:device0/in_voltage0_raw读取原始值,或通过自定义字符设备节点读取转换后的毫伏值。
环境依赖
- Linux内核源码树(本案例基于5.10版本验证)
- 交叉编译工具链(ARM平台)或本机gcc(x86模拟验证)
- 内核已启用CONFIG_IIO和CONFIG_IIO_BUFFER
驱动实现
以下代码为完整驱动,包含平台驱动框架、IIO设备注册、ADC原始值读取及毫伏转换逻辑。驱动通过设备树获取ADC通道信息,并注册一个misc设备提供read接口返回电压值(单位:mV)。
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/iio/iio.h>
#include <linux/iio/consumer.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/of.h>
#define DRIVER_NAME "battery_adc"
struct batt_adc_dev {
struct iio_channel *channel;
struct miscdevice misc;
};
static ssize_t batt_adc_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
struct batt_adc_dev *dev = container_of(file->private_data,
struct batt_adc_dev, misc);
int ret, val, uv;
char kbuf[32];
int len;
if (*ppos != 0)
return 0;
ret = iio_read_channel_processed(dev->channel, &val);
if (ret < 0) {
dev_err(&dev->misc.this_device, "read ADC failed: %d\n", ret);
return ret;
}
/* iio_read_channel_processed 返回微伏(uV),转换为毫伏(mV) */
uv = val;
len = snprintf(kbuf, sizeof(kbuf), "%d\n", uv / 1000);
if (copy_to_user(buf, kbuf, len))
return -EFAULT;
*ppos = len;
return len;
}
static const struct file_operations batt_adc_fops = {
.owner = THIS_MODULE,
.read = batt_adc_read,
};
static int batt_adc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct batt_adc_dev *bdev;
struct iio_channel *chan;
int ret;
bdev = devm_kzalloc(dev, sizeof(*bdev), GFP_KERNEL);
if (!bdev)
return -ENOMEM;
chan = devm_iio_channel_get(dev, "battery-volt");
if (IS_ERR(chan)) {
ret = PTR_ERR(chan);
if (ret == -EPROBE_DEFER)
return ret;
dev_err(dev, "failed to get IIO channel: %d\n", ret);
return ret;
}
bdev->channel = chan;
bdev->misc.minor = MISC_DYNAMIC_MINOR;
bdev->misc.name = "battery_voltage";
bdev->misc.fops = &batt_adc_fops;
bdev->misc.parent = dev;
ret = misc_register(&bdev->misc);
if (ret) {
dev_err(dev, "failed to register misc device: %d\n", ret);
return ret;
}
platform_set_drvdata(pdev, bdev);
dev_info(dev, "battery ADC driver probed\n");
return 0;
}
static int batt_adc_remove(struct platform_device *pdev)
{
struct batt_adc_dev *bdev = platform_get_drvdata(pdev);
misc_deregister(&bdev->misc);
return 0;
}
static const struct of_device_id batt_adc_of_match[] = {
{ .compatible = "vendor,battery-adc" },
{ }
};
MODULE_DEVICE_TABLE(of, batt_adc_of_match);
static struct platform_driver batt_adc_driver = {
.probe = batt_adc_probe,
.remove = batt_adc_remove,
.driver = {
.name = DRIVER_NAME,
.of_match_table = batt_adc_of_match,
},
};
module_platform_driver(batt_adc_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kernel Course");
MODULE_DESCRIPTION("Battery voltage ADC driver");
设备树配置
在设备树中,ADC控制器节点需已定义(如adc@ff650000),驱动节点通过io-channels属性绑定到具体ADC通道:
&battery_adc_node {
compatible = "vendor,battery-adc";
io-channels = <&adc 0>; /* 使用ADC通道0 */
io-channel-names = "battery-volt";
status = "okay";
};
编译与加载
- 将驱动源码放入内核
drivers/iio/adc/目录,并在Kconfig/Makefile中添加对应条目。 - 编译内核或编译为模块:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules - 加载模块:
insmod battery_adc.ko(若编译为模块)
验证方法
- 检查驱动加载日志:
dmesg | grep "battery ADC",应看到battery ADC driver probed。 - 确认设备节点存在:
ls -l /dev/battery_voltage。 - 读取电压值:
cat /dev/battery_voltage,预期输出一个整数(单位mV),例如12400表示12.4V。 - 与IIO原始节点对比:
cat /sys/bus/iio/devices/iio:device0/in_voltage0_raw,该值乘以通道缩放系数(由ADC驱动提供)应等于字符设备输出的毫伏值。
通过判定标准:cat /dev/battery_voltage能稳定输出合理电压值(如车载电池12V对应约12000mV),且连续读取数值波动在ADC量化误差范围内(通常±10mV以内),即视为驱动工作正常。