【分析笔记】Linux 4.9 单总线驱动框架分析
文章介绍
本文主要是基于 T507 Android 10 Linux 4.9 的源代码,对 Linux W1 总线框架的分析记录,便于了解整个框架的基本实现机制。
驱动框架
这张图基本上将内部的各个源文件相互关系表述出来了。
简析说明
master
linux-4.9/drivers/w1/masters/
-
对应的是 SOC 内部具体的 W1 控制器,也可以是通过 GPIO 模拟,还可以是外部 W1 转换芯片,如 DS2490 USB 转单总线芯片。
-
实现具体的硬件控制器数据发送接收,通过实现 struct w1_bus_master 里面的回调接口,通过 w1_init.h->w1_add_master_device() 提供给 core 层使用。
-
最简单的一个 master ,只需实现 read_bit()\write_bit() 两个接口即可,core 层的 w1_io.c 发现其它接口没有实现,会通过这两个接口模拟读写时序。
-
可以参考 drivers/w1/masters/w1-gpio.c 来实现自己的 master 驱动程序。
// linux-4.9/drivers/w1/w1.h
struct w1_bus_master
{
void *data;
u8 (*read_bit)(void *data);
void (*write_bit)(void *data, u8 bit);
u8 (*touch_bit)(void *data, u8 bit);
u8 (*read_byte)(void *data);
void (*write_byte)(void *data, u8 byte);
u8 (*read_block)(void *data, u8 *buf, int len);
void (*write_block)(void *data, const u8 *buf, int len);
u8 (*triplet)(void *data, u8 dbit);
u8 (*reset_bus)(void *data);
u8 (*set_pullup)(void *data, int delay);
void (*search)(void *, struct w1_master *, u8, w1_slave_found_callback);
};
core
linux-4.9/drivers/w1/
- 与具体硬件无关的功能逻辑实现,目的是将硬件控制器与外设解耦,使实现 master 还是 slave ,都变得极其简单。
- 会为每个 master 创建内核线程,周期性发起 0xF0 搜索设备指令,通过读取到的 family id 来创建 slave,关联 master。
linux-4.9/drivers/w1/w1_init.c
根据 bus_master 添加删除来创建和销毁 master,并通过链表维护,为每个 master 创建内核线程用于探测设备和关联 slave 。
linux-4.9/drivers/w1/w1.c
内核线程的具体实现,实现周期性探测设备和关联 slave,通过 slist 链表来维护所有的 slave,便于被用户态使用,为 master 创建 sys 接口实现可动态配置。
linux-4.9/drivers/w1/w1_io.c
提供数据读写接口给 w1.c 使用,当某些接口并未被 master 实现时,会基于 master 基础的 read_bit()/write_bit() 接口通过模拟实现。
linux-4.9/drivers/w1/w1_family.c
当一个 family 驱动通过 w1_register_family() 注册进来,就会被加入到 w1_families 链表内,用于探测设备时匹配关联,从而得到一个 slave。
linux-4.9/drivers/w1/w1_netlink.c
基于 netlink 实现与用户态交互,可参考:https://github.com/bioothod/w1/blob/master/w1d.c。
变量配置 | 功能说明 |
---|---|
w1_search_cout | 指定搜索设备的次数,即内核线程搜索设备的次数。 |
w1_enable_pullup | 是否启用内部上拉电阻。 |
w1_timeout、w1_timeout_us | 每一次搜索设备的时间间隔。 |
w1_max_slave_count | 每次搜索设备的数量。 |
w1_max_slave_ttl | 设备生存时间(时间间隔 x TTL),当设备超过多少 ttl 次没有响应则认为设备离线。 |
slave
linux-4.9/drivers/w1/slaves/
- 具体的外设器件,不同的外设可能会有不同指令,这些都是与外设强关联的,如 DS18B20 温度传感器。
- 主要填充 struct w1_family 结构体的 fid 和 fops,前者用于适配 family id,后者用于拿到 slave 对象用于操作外设。
- 最简单的一个 slave 只需要设置 fid 成员,并通过 w1_family.c->w1_register_family() 注册到 core 层即可,core 层会有一个默认的 fops 可用。
- 可以参考 drivers/w1/slaves/w1_bq27000.c 或者 drivers/w1/slaves/w1_ds2780.c 实现 slave 设备驱动程序。
// linux-4.9/drivers/w1/w1.h
struct w1_family
{
struct list_head family_entry;
u8 fid;
struct w1_family_ops *fops;
atomic_t refcnt;
};
基本用法
内核配置
使用 GPIO 模拟 W1,因此选择 GPIO 1-wire busmaster。
make ARCH=arm64 menuconfig
Device Drivers --->
<*> Connector - unified userspace <-> kernelspace linker --->
[*] Report process events to userspace (NEW)
<*> Dallas's 1-wire support --->
[*] Userspace communication over connector (NEW)
1-wire Bus Masters --->
<*> GPIO 1-wire busmaster
全志平台可以在 sys_config.fex 增加如下配置,其它平台修改对应的 dts 即可(可参考 Documentation\devicetree\bindings\w1\w1-gpio.txt)。
[w1_master]
compatible = "w1-gpio"
gpios = port:PH8<1><default><default><default>
设备驱动
- 这是最简单的设备驱动,主要填充 struct w1_family 的两个 .fid 和 .fops 成员。
- .fid 适配的是 family 不同的设备的 family 不一样,多个设备可以挂接同一条 w1 总线上。
- .fops 指定当总线驱动检测到有新设备,或设备不在线时的回调处理,会传入一个 struct w1_slave。
- struct w1_family_ops 的 .groups 可以指定 sys 接口属性,会在适配到 family 的时候由 core 创建。
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/types.h>
#include <linux/mutex.h>
#include "../w1.h"
#include "../w1_int.h"
#include "../w1_family.h"
static int w1_test_add_slave(struct w1_slave *sl)
{
printk("w1_test_add_slave.......\n");
mutex_lock(&sl->master->bus_mutex);
if (w1_reset_select_slave(sl) == 0) {
w1_write_block(sl->master, ...);
w1_read_block(sl->master, ...);
}
mutex_unlock(&sl->master->bus_mutex);
return 0;
}
static void w1_test_remove_slave(struct w1_slave *sl)
{
printk("w1_test_remove_slave.......\n");
}
static struct w1_family_ops w1_test_fops = {
.add_slave = w1_test_add_slave,
.remove_slave = w1_test_remove_slave,
};
static struct w1_family w1_test_family = {
.fid = 1,
.fops = &w1_test_fops,
};
static int __init w1_test_init(void)
{
return w1_register_family(&w1_test_family);
}
static void __exit w1_test_exit(void)
{
w1_unregister_family(&w1_test_family);
}
module_init(w1_test_init);
module_exit(w1_test_exit);
MODULE_LICENSE("GPL");
应用程序
基于 Netlink
W1 的驱动框架中已经基于 Netlink Connector 实现了与用户空间交互的命令接口,可以参考 https://github.com/bioothod/w1/blob/master/w1d.c。
基于 Sys 节点
- 也可以在自己的 slave 驱动中通过指定 struct w1_family.fops.groups,会在匹配成功后由 core 自动创建文件。
- 如果检测到的 family 没有被 w1_register_family(),会有一个默认的 w1_default_family 指定二进制读写属性。
可以通过二进制读写的方式来控制总线发出读写指令:/sys/bus/w1/devices/{family}-{serial}/rw
// linux-4.9/drivers/w1/w1.c
int w1_attach_slave_device(struct w1_master *dev, struct w1_reg_num *rn)
{
......
spin_lock(&w1_flock);
f = w1_family_registered(rn->family);
if (!f) {
f= &w1_default_family;
dev_info(&dev->dev, "Family %x for %02x.%012llx.%02x is not registered.\n",
rn->family, rn->family,
(unsigned long long)rn->id, rn->crc);
}
__w1_family_get(f);
spin_unlock(&w1_flock);
......
return 0;
}
高效用法
在一些比较特殊的应用场景,例如需要读取 TM DS1990A 电子钥匙的场景。不像 DS18B20 器件会直接焊接在主板上,电子钥匙只在需要开锁的时候, 触碰探头来连接 gnd 和 data,才能实现数据通信。但何时开锁具有不确定性,且又需要相对较高的实时性,可以通过以下方式绕开原有的检测机制,直接控制总线读写。
导出接口
diff --git a/longan/kernel/linux-4.9/drivers/w1/w1.c b/longan/kernel/linux-4.9/drivers/w1/w1.c
--- a/longan/kernel/linux-4.9/drivers/w1/w1.c
+++ b/longan/kernel/linux-4.9/drivers/w1/w1.c
@@ -840,6 +840,7 @@ struct w1_master *w1_search_master_id(u32 id)
return (found)?dev:NULL;
}
+EXPORT_SYMBOL(w1_search_master_id);
struct w1_slave *w1_search_slave(struct w1_reg_num *id)
{
使用方法
// 通过 id 找到对应的 master , 并停止周期性搜索设备
static struct w1_master *get_master(int w1_master_id)
{
struct w1_master *master;
if((master = w1_search_master_id(1))){
mutex_lock(&master->mutex);
master->search_count = 0;
mutex_unlock(&master->mutex);
return master;
}
return NULL;
}
// 通过指定的 master 发出读写指令
static unsigned long long tm_ds1990a_read_serial_number(struct w1_master *master)
{
u64 rn_le = 0x00, data = 0x00;
struct w1_reg_num *tmp = NULL;
unsigned long long id = 0x00;
mutex_lock(&master->bus_mutex);
w1_reset_bus(master);
w1_write_8(master, W1_READ_ROM);
if(w1_read_block(master, (u8 *)&data, sizeof(data)) == sizeof(data)){
rn_le = cpu_to_le64(data);
tmp = (struct w1_reg_num *)&data;
if(data && tmp->crc == w1_calc_crc8((u8 *)&rn_le, sizeof(data) - 1)){
id = (unsigned long long)tmp->id;
}
}
mutex_unlock(&master->bus_mutex);
return id;
}