一、前言
之前写过一篇文章,
《需要多久才能看完linux内核源码?》
数以千亿级的电子设备都在运行linux,最核心的Linux内核代码总计有xxxx行,它汇集了全球几百万顶尖程序员的代码,博大精深:
- 一套代码支持不同指令集不同厂家不同系列的SoC可以驱动数以万计的外设芯片支持各种文件系统可以稳定的调度并发任务可以支持mipi、usb、uart、iic、spi、sdio、pcie、mdio等不同类型不同速率的总线通信
可以说linux内核代码是人类智慧的结晶,要想写出高效、稳定的c代码,大家可以从linux内核中寻找灵感!
彭老师,平时在做项目中,很多小模块都是从参考的linux内核代码。
本文通过一个小例子,来教大家如何借鉴(抄袭)内核代码。
二、我的需求
用C语言实现语音压缩算法用于话音通信,要支持不同的压缩比,比如8k、2.4k等,每一种压缩比都会有一组配套的算法参数,
用C语言实现,最常见的就是通过结构体数组来管理这些参数,
那么内核大佬是如何用数组来实现的呢?
三、参考内核代码
JZ4740、JZ4760、JZ4775、JZ4780是北京君正推出的嵌入式微处理器系列,主要用于智能硬件、物联网等领域。
该系列处理器的uart控制器的ip核比较雷同,所以驱动大部分功能是相通的,
但是也有一些差别比如uart的fifo大小,有16、32、64的区别,
那么如何让同一个驱动同时支持不同的uart控制器 ?
该驱动是基于platform总线的,
所以可以根据设备树的compatible的属性信息,来区分当前的uart控制器版本。
参考驱动文件路径如下:
8250_ingenic.c driversttyserial8250
驱动信息:
static const struct ingenic_uart_config jz4740_uart_config = {
.tx_loadsz = 8,
.fifosize = 16,
};
static const struct ingenic_uart_config jz4760_uart_config = {
.tx_loadsz = 16,
.fifosize = 32,
};
static const struct ingenic_uart_config jz4780_uart_config = {
.tx_loadsz = 32,
.fifosize = 64,
};
static const struct of_device_id of_match[] = {
{ .compatible = "ingenic,jz4740-uart", .data = &jz4740_uart_config },
{ .compatible = "ingenic,jz4760-uart", .data = &jz4760_uart_config },
{ .compatible = "ingenic,jz4775-uart", .data = &jz4760_uart_config },
{ .compatible = "ingenic,jz4780-uart", .data = &jz4780_uart_config },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, of_match);
static int ingenic_uart_probe(struct platform_device *pdev)
{
struct uart_8250_port uart = {};
const struct ingenic_uart_config *cdata;
const struct of_device_id *match;
match = of_match_device(of_match, &pdev->dev);
if (!match) {
dev_err(&pdev->dev, "Error: No device match foundn");
return -ENODEV;
}
cdata = match->data;
……
}
内核结构体数组的初始化赋值,都是通过这种 . 加上成员名的方法,
为了保护数组内容不被其他代码修改,数组定义为const类型
强烈建议大家使用这种方法来写c代码
用来表示fifo细节的结构体:
struct ingenic_uart_config {
int tx_loadsz;
int fifosize;
};
设备树信息:
uart0: serial@10030000 {
compatible = "ingenic,jz4780-uart";
reg = <0x10030000 0x100>;
interrupt-parent = <&intc>;
interrupts = <51>;
clocks = <&ext>, <&cgu JZ4780_CLK_UART0>;
clock-names = "baud", "module";
status = "disabled";
};
注意其中的compatible属性信息,ingenic,jz4780-uart
驱动大致原理:
-
- 设备树节点
uart0: serial@10030000
-
- 的compatible属性信息最终被内核存放到
pdev->dev->of_node->properties
- 数组**of_match[]**,
struct of_device_id {
char name[32];
char type[32];
char compatible[128];// 存放外设名字
const void *data; //存放私有信息
};
函数**of_match_device()**是内核提供的设备树匹配函数,
该函数会遍历数组of_match[],根据比较数组的每一个元素的compatible和pdev->dev->of_node->properties,如何相同,则返回struct of_device_id类型指针,该指针指向数组的某一个元素
用于保存uart控制器的私有信息就保存在数组of_match->data中,函数**of_match_device()**会根据设备传入的名字,帮助我们找到该元素
通过变量cdata;保存私有信息,对于ingenic,jz4780-uart来说,就是变量jz4780_uart_config,
后面就可以通过该变量
这种方法是内核最通用的处理方法,
有的外设驱动还会先通过控制总线先从外设读取一个预存在某个内部寄存器的id值,然后再根据这个id值来区分外设硬件版本。
四、我的例子
根据该思想,我们编写自己的代码:
#include <stdio.h>
#include <string.h>
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
struct my_private_s{
int comp_type;
int cfg1;
int cfg2;
int cfg3;
char compatible[32];
};
#define COMPRESS_TYPE1 0
#define COMPRESS_TYPE2 1
#define COMPRESS_TYPE3 3
#define COMPRESS_TYPE4 4
//比较下面两种定义区别
//struct my_private_s of_match[] = {
static const struct my_private_s of_match[] = {
{
.comp_type = COMPRESS_TYPE1,
.cfg1 = 11,
.cfg2 = 111,
.cfg3 = 1111,
.compatible = "com_type1",
},
{
.comp_type = COMPRESS_TYPE2,
.cfg1 = 22,
.cfg2 = 222,
.cfg3 = 2222,
.compatible = "com_type2"
},
{
.comp_type = COMPRESS_TYPE3,
.cfg1 = 33,
.cfg2 = 333,
.cfg3 = 3333,
.compatible = "com_type3"
},
{
.comp_type = COMPRESS_TYPE4,
.cfg1 = 44,
.cfg2 = 444,
.cfg3 = 444,
.compatible = "com_type4"
},
{},
};
struct my_private_s const* of_match_device(int comp_type)
{
int i = 0;
for(i=0;i<ARRAY_SIZE(of_match);i++)
{
if(of_match[i].comp_type == comp_type)
{
return &of_match[i];
}
}
return NULL;
}
void dump_compress_info(const struct my_private_s *comp_type)
{
printf("ntcomp type:%dntcfg1:%dntcfg2:%dntcfg3:%dntname:%sn",
comp_type->comp_type,
comp_type->cfg1,
comp_type->cfg2,
comp_type->cfg3,
comp_type->compatible);
}
const struct my_private_s *my_dev = NULL;
int main()
{
my_dev = of_match_device(COMPRESS_TYPE1);
if(my_dev == NULL)
{
printf("[yikou] not find valid compress typen");
return -1;
}else{
dump_compress_info(my_dev);
}
//思考下面代码
//my_dev->cfg1 = 88;
return 1;
}
编译测试:
注意
为了保护我们的配置信息不被恶意修改,我们必须把数组定义为const类型,防止后面维护的老铁给修改了,
结尾
更多嵌入式软件编程技巧,请关注彭老师!
end
179