openUBMC Chip Abstract Layer V0.1
载入中...
搜索中...
未找到
HAL设计

本次定义的HAL(硬件抽象层),其目标是总结openUBMC对BMC SOC硬件的依赖,提供一层抽象,屏蔽BMC芯片差异。 达成的效果:用户(这里指驱动开发者,后文同)在实现完HAL层制定的API后,能够把openUBMC的核心服务拉起。

  • 核心服务
    • 框架,包括:
      • 设备驱动框架:hwproxy/hwdiscovery/devmon/component_drivers,基本的资源树接口(Chip, Bus)
      • 应用管理框架:libmcpp/libmc4lua
    • 基础服务子系统
    • 闭源组件
  • 对硬件的哪些依赖应该纳入HAL层?
    • 没有硬性规则,当前以libsoc_adapter为蓝本。 如果用户需要扩展HAL层定义,应当在社区进行评审。
    • 核心服务的依赖应该纳入
    • 只服务于特定硬件实现的,不应当纳入,比如SD RAID
  • 未被纳入HAL层的部分,在移植时,应当由使用这部分依赖的组件负责移植

HAL定义的方针

针对现有的使用情况,简化API接口(明确输入输出,参数正交化),去除和特定实现强相关的形式。

注意,本HAL层的目标不是定义通用的接口(如Linux内核驱动),理由:

  • HAL的基础目标是明确openUBMC对硬件的依赖 通用驱动面对的业务场景是不确定的,因此接口定义尽可能地通用; openUBMC的业务已经确定,因此仅针对现有业务定义接口; 如果定义通用的接口:
    • 没有使用场景 比如,Linux 定义的 Canbus 驱动可以配置传输的bit timing,openUBMC根本不会使用这个特性
    • 增加移植的工作量
  • 驱动必须支撑资源树接口的实现。 有些资源树接口定义本就不通用,比如Uart的串口互联功能,事实上不是一个通用的特性,但因为bmc.kepler.Managers.Uart接口定义了这个特性,必须有驱动支撑,所以有HAL定义。
  • 针对通用驱动有利于未来扩展这一点:
    • 如果是扩展的框架特性需要某一接口,则可以在增加框架功能时扩充HAL层定义
    • 如果是特定业务组件(非核心业务)需要某一接口,则用户自行扩展驱动实现即可,openUBMC的核心业务仍旧使用原来的接口。

类图

驱动接口:IDriver 具体驱动的接口示例:Adc

classDiagram
%% ================================
%% 驱动工厂 - 单例+工厂方法模式
%% ================================
class IDriverFactory {
<<interface>>
<<factory>>
+set_driver_path(driver_path: string_t&) void
+get_driver(const string_t& driver_name) IDriver&
+get_driver_load_info(void) string_t void
+remove_driver(driver_name: string_t&) void
get_instance()$ IDriverFactory&
}
class driver_factory {
<<singleton>>
<<factory>>
-m_drivers: map~string_t,driver_ptr~
-m_driver_path: string_t
+set_driver_path(driver_path: string_t&) void
+get_driver(const string_t& driver_name) IDriver&
+get_driver_load_info(void) string_t void
+remove_driver(driver_name: string_t&) void
+get_instance()$ driver_factory&
}
%% ================================
%% ABI接口层
%% ================================
class IDriver {
<<interface>>
+driver_name: char*
+init(args: void*) void
+config(config: void*) void
+lock() void
+unlock() void
}
%% ================================
%% ABI接口层 - 驱动抽象类
%% ================================
class Adc {
<<interface>>
+read(index: uint32_t) uint32_t
}
class Bt {
<<interface>>
+read(index: uint32_t, len: uint32_t, timeout: uint32_t) string_t
+write(index: uint32_t, val: string_view&, timeout: uint32_t) void
}
class AdcImpl {
+read() uint32_t
}
class BtImpl {
+read(index: uint32_t, len: uint32_t, timeout: uint32_t) string_t
+write(index: uint32_t, val: string_view&, timeout: uint32_t) void
}
%% ================================
%% 接口实现关系
%% ================================
IDriverFactory <|.. driver_factory : 继承
IDriver <|.. Adc : 继承
IDriver <|.. Bt : 继承
Adc <|.. AdcImpl : 实现
Bt <|.. BtImpl : 实现
%% ================================
%% 组合关系
%% ================================
driver_factory *-- IDriver : 管理
%% ================================
%% 依赖关系
%% ================================
driver_factory --> IDriver : 创建
%% ================================
%% 样式定义
%% ================================
style IDriverFactory fill:#fff3e0
style driver_factory fill:#ff9800
style IDriver fill:#fff3e0
style Adc fill:#fff3e0
style Bt fill:#fff3e0
style AdcImpl fill:#c9f2c7
style BtImpl fill:#c9f2c7

IDriver的设计

  • 语言,沿用libsoc_adapter,使用C++作为接口语言
  • 类命名,统一使用Xyz作为Xyz驱动的接口类名,采用大驼峰
  • namespace,沿用libsoc_adapter的处理,驱动Xyz的namespace为DRIVER_XYZ
  • 每个Xyz实例,都是xyz设备的总管,可以操作所有的xyz设备
    • 设备操作接口,统一增加一个index入参,用于区分xyz设备。 index的分配由驱动实现决定;
    • 对比 libsoc_adapter的处理,Xyz实例
      • 有的是指单个xyz设备,或者单例(比如Ipmb, Efuse)
      • 有的是xyz设备的总管(比如I2c)
  • 不提供ioctl方式的API ioctl(fd, cmdcode, param) 该形式和定义函数接口没有本质差别 且API面向的业务场景是确定的,可以明确定义函数接口
  • ctor 取消在构造函数传递device的做法,要不要打开设备、打开什么设备、打开几个设备,这些均取决于实现
  • init 用于驱动的初始化,允许传递任意入参
    • 框架本身对驱动总管并没有配置诉求
    • 业务组件开发者在知晓具体驱动实现的情况下,可以传递配置给驱动 这里仅支持通过初始化配置驱动的行为,不支持在具体操作接口,比如read,传递自定义参数。因为
      • 框架对驱动的调用是固定的
      • 如果用户自定义的业务想要传参,可以自己扩展接口
  • config 用于设备实例的初始化,允许传递任意入参。
    • 框架使用的入参形式,在每个接口类处定义,其命名为XyzConfig。比如AdcConfig 该结构体主要是为了支撑MDS属性的实际生效
  • lock/unlock 锁操作,但不限定锁的形式,也不限定锁和设备的对应关系
  • get_version 为了兼容考虑

锁、并发控制

对驱动API的调用,遵循以下原则:

  • 初始类API,如 init, config, open,在读写操作、读写设置动作之前,且这些初始化动作一般是单线程
  • 销毁类API,close, free,在读写操作、读写设置动作完全停止之后,且一般是单线程
  • 写设置API,在读写操作完全停止之后。写设置 通常由修改发起者 进行并发控制
  • 读设置API,一般没有限定

因此,只需要对读写动作进行加锁,由IDriverlock()/unlock()方法提供。 由于openUBMC的基本运行单元是组件,不是进程/线程,无法断定两个组件是否运行于同一个进程,因此,要求使用进程锁。

HAL定义手法

  • 接口基本沿用libsoc_adapter
  • 入参和返回值的形式:尽量删除为数据传递而定义的结构体,比如I2c的读操作定义的I2C_READ_S
  • 对于读写操作(读出的报文、写入的报文),如果BMC对其有格式要求,明确作出结构体定义。比如I2c::write,写入的内容实际上是有格式的,定义I2cWriteMsg结构,并标注在注释中。
  • 删除/调整部分接口,但是确保MDS和MDB接口可用

用户开发示例

class AdcImpl : Adc {
private:
std::mutex m_mutex;
int32_t m_fd;
public:
AdcImpl(){}
uint32_t read(int32_t index) override;
};
void AdcImpl::init(void *args)
{
m_mutex = new std::mutex;
}
void AdcImpl::config(int32_t index, void *config)
{
AdcConfig *cfg = (AdcConfig *)config;
}
void AdcImpl::lock(int32_t index)
{
m_mutex.lock;
}
void AdcImpl::unlock(int32_t index)
{
m_mutex.unlock;
}
uint32_t AdcImpl::read(int32_t index)
{
uint32_t ret = ioctl(m_fd, ADC_CMD_READ, param)
return ret;
}
Driver* create_driver()
{
AdcImpl *drv = new AdcImpl();
return (Driver*)drv;
}
void destroy_driver(Driver* drv)
{
delete (AdcImpl *)drv;
}

组件使用

#include "adc.h"
int main()
{
// 用户需要在一个集中的地方把想要的驱动加载完
// 如果想分散加载,则大家都得知道so的路径以及对应的驱动名称
Adc drv = (Adc &)hal::IDriverFactory::get_instance().get_driver("Adc");
ret = drv.read();
}
ADC 驱动接口
static IDriverFactory & get_instance()
获取驱动工厂实例单例
Definition driver.cpp:207
virtual IDriver & get_driver(const string_t &driver_name)=0
获取驱动实例 加载driver_path下的 lib{driver_name}.so,返回驱动实例

HAL层应用处理策略

  • 用户自行增加驱动定义 如果一个驱动需要用户定义,由两种可能:
    • 驱动涉及openUBMC核心组件/闭源组件。这个属于HAL定义的缺陷,应当避免。如有该情况,应当尽快扩展HAL定义。
    • 非核心业务组件依赖特定驱动,只需要业务组件和驱动实现协商好接口即可。 用户在任意位置从IDriver类派生出一个新的接口类并实现,并将生成的so放置在指定路径下,即可使用。
  • 不支持的驱动 如果一个驱动不支持,则用户不提供对应的so,IDriverFactory::get_driver接口抛错,使用方必须处理错误 通过IDriverFactory::get_driver_load_info检查已经加载的驱动
  • 驱动中有不支持的接口 应提供打桩

HAL的纳入范围

libsoc_adapter驱动名称 HAL纳入 备注
adc Y
bt Y
btc N 和BT是同类,没有必要要求开发者提供两种实现
canbus Y
comm N 引脚复用,非通用;接口形式难以确定
cp N 协处理器,非通用
dboot_info N uboot信息,不同soc形式不同
dfx N 不同soc不同
edma Y
efuse Y
gpio Y
hisport Y
i2c Y
innerbus Y
ipmb Y
jtag Y
kcs Y
localbus Y
localbus_mmap N 和localbus是同类
mctp Y
mdio Y
mem Y
mmc Y
peci Y
pwm Y
rmii N 实际上是一个lib,不是硬件驱动
sd_raid N 没有人使用
sol Y
spi Y
trng Y
uart Y
usb_drd Y
usb_driver Y
wdt Y

关于<tt>read</tt>/<tt>write</tt>的设计

read/write不作为基类IDriver的方法,每个类读写操作的入参、返回值都不一样。 类似string_t read(void *args)的定义,用户仍需自己进行类型转换,本质上没有对read/write做任何抽象,徒增理解难度。

判断写操作是否成功