英特尔(R) 管理引擎 (ME) 客户端总线 API¶
基本原理¶
MEI 字符设备对于专用应用程序从用户空间向英特尔 ME 中发现的许多 FW 设备发送和接收数据非常有用。然而,对于某些 ME 功能,利用现有的软件堆栈并通过现有的内核子系统公开它们是有意义的。
为了无缝地插入内核设备驱动程序模型,我们在 MEI 驱动程序之上添加了内核虚拟总线抽象。这允许为各种 MEI 功能实现 Linux 内核驱动程序,作为其各自子系统中找到的独立实体。通过向现有代码添加 MEI CL 总线层,甚至可以潜在地重用现有的设备驱动程序。
MEI CL 总线 API¶
MEI 客户端的驱动程序实现与任何其他现有的基于总线的设备驱动程序非常相似。驱动程序通过 struct mei_cl_driver
结构(在 include/linux/mei_cl_bus.c
中定义)将其自身注册为 MEI CL 总线驱动程序。
struct mei_cl_driver {
struct device_driver driver;
const char *name;
const struct mei_cl_device_id *id_table;
int (*probe)(struct mei_cl_device *dev, const struct mei_cl_id *id);
int (*remove)(struct mei_cl_device *dev);
};
include/linux/mod_devicetable.h
中定义的 mei_cl_device_id 结构允许驱动程序将其自身绑定到设备名称。
struct mei_cl_device_id {
char name[MEI_CL_NAME_SIZE];
uuid_le uuid;
__u8 version;
kernel_ulong_t driver_info;
};
要在 ME 客户端总线上实际注册驱动程序,必须调用 mei_cl_add_driver()
API。这通常在模块初始化时调用。
一旦驱动程序被注册并绑定到设备,驱动程序通常会尝试在此总线上执行一些 I/O,这应该通过 mei_cl_send()
和 mei_cl_recv()
函数完成。更多详细信息请参见API:部分。
为了让驱动程序收到关于挂起流量或事件的通知,驱动程序应分别通过 mei_cl_devev_register_rx_cb()
和 mei_cldev_register_notify_cb()
函数注册回调。
API:¶
-
ssize_t mei_cldev_send_vtag(struct mei_cl_device *cldev, const u8 *buf, size_t length, u8 vtag)¶
使用 vtag (写入) 的 me 设备发送
参数
struct mei_cl_device *cldev
me 客户端设备
const u8 *buf
要发送的缓冲区
size_t length
缓冲区长度
u8 vtag
虚拟标签
返回
以字节为单位写入的大小
错误时 < 0
-
ssize_t mei_cldev_send_vtag_timeout(struct mei_cl_device *cldev, const u8 *buf, size_t length, u8 vtag, unsigned long timeout)¶
使用 vtag 和超时 (写入) 的 me 设备发送
参数
struct mei_cl_device *cldev
me 客户端设备
const u8 *buf
要发送的缓冲区
size_t length
缓冲区长度
u8 vtag
虚拟标签
unsigned long timeout
发送超时时间(以毫秒为单位),0 表示无限超时
返回
以字节为单位写入的大小
错误时 < 0
-
ssize_t mei_cldev_recv_vtag(struct mei_cl_device *cldev, u8 *buf, size_t length, u8 *vtag)¶
使用 vtag (读取) 的客户端接收
参数
struct mei_cl_device *cldev
me 客户端设备
u8 *buf
要接收的缓冲区
size_t length
缓冲区长度
u8 *vtag
虚拟标签
返回
以字节为单位读取的大小
错误时 < 0
-
ssize_t mei_cldev_recv_nonblock_vtag(struct mei_cl_device *cldev, u8 *buf, size_t length, u8 *vtag)¶
使用 vtag (读取) 的非阻塞客户端接收
参数
struct mei_cl_device *cldev
me 客户端设备
u8 *buf
要接收的缓冲区
size_t length
缓冲区长度
u8 *vtag
虚拟标签
返回
以字节为单位读取的大小
如果函数将阻塞,则返回 -EAGAIN。
其他错误时 < 0
-
ssize_t mei_cldev_recv_timeout(struct mei_cl_device *cldev, u8 *buf, size_t length, unsigned long timeout)¶
使用超时 (读取) 的客户端接收
参数
struct mei_cl_device *cldev
me 客户端设备
u8 *buf
要接收的缓冲区
size_t length
缓冲区长度
unsigned long timeout
发送超时时间(以毫秒为单位),0 表示无限超时
返回
以字节为单位读取的大小
错误时 < 0
-
ssize_t mei_cldev_recv_vtag_timeout(struct mei_cl_device *cldev, u8 *buf, size_t length, u8 *vtag, unsigned long timeout)¶
使用 vtag (读取) 的客户端接收
参数
struct mei_cl_device *cldev
me 客户端设备
u8 *buf
要接收的缓冲区
size_t length
缓冲区长度
u8 *vtag
虚拟标签
unsigned long timeout
接收超时时间,单位为毫秒,0 表示无限超时
返回
以字节为单位读取的大小
错误时 < 0
-
ssize_t mei_cldev_send(struct mei_cl_device *cldev, const u8 *buf, size_t length)¶
ME 设备发送 (写入)
参数
struct mei_cl_device *cldev
me 客户端设备
const u8 *buf
要发送的缓冲区
size_t length
缓冲区长度
返回
以字节为单位写入的大小
错误时 < 0
-
ssize_t mei_cldev_send_timeout(struct mei_cl_device *cldev, const u8 *buf, size_t length, unsigned long timeout)¶
ME 设备发送(带超时)(写入)
参数
struct mei_cl_device *cldev
me 客户端设备
const u8 *buf
要发送的缓冲区
size_t length
缓冲区长度
unsigned long timeout
发送超时时间(以毫秒为单位),0 表示无限超时
返回
以字节为单位写入的大小
错误时 < 0
-
ssize_t mei_cldev_recv(struct mei_cl_device *cldev, u8 *buf, size_t length)¶
客户端接收(读取)
参数
struct mei_cl_device *cldev
me 客户端设备
u8 *buf
要接收的缓冲区
size_t length
缓冲区长度
返回
读取的字节数,出错时小于 0
-
ssize_t mei_cldev_recv_nonblock(struct mei_cl_device *cldev, u8 *buf, size_t length)¶
非阻塞客户端接收(读取)
参数
struct mei_cl_device *cldev
me 客户端设备
u8 *buf
要接收的缓冲区
size_t length
缓冲区长度
返回
- 读取的字节数,出错时小于 0
如果函数将阻塞,则返回 -EAGAIN。
-
int mei_cldev_register_rx_cb(struct mei_cl_device *cldev, mei_cldev_cb_t rx_cb)¶
注册 Rx 事件回调
参数
struct mei_cl_device *cldev
ME 客户端设备
mei_cldev_cb_t rx_cb
回调函数
返回
- 成功时返回 0
如果已注册回调,则返回 -EALREADY,其他错误返回小于 0 的值
-
int mei_cldev_register_notif_cb(struct mei_cl_device *cldev, mei_cldev_cb_t notif_cb)¶
注册 FW 通知事件回调
参数
struct mei_cl_device *cldev
ME 客户端设备
mei_cldev_cb_t notif_cb
回调函数
返回
- 成功时返回 0
如果已注册回调,则返回 -EALREADY,其他错误返回小于 0 的值
-
void *mei_cldev_get_drvdata(const struct mei_cl_device *cldev)¶
驱动程序数据获取器
参数
const struct mei_cl_device *cldev
ME 客户端设备
返回
驱动程序私有数据
-
void mei_cldev_set_drvdata(struct mei_cl_device *cldev, void *data)¶
驱动程序数据设置器
参数
struct mei_cl_device *cldev
ME 客户端设备
void *data
要存储的数据
-
const uuid_le *mei_cldev_uuid(const struct mei_cl_device *cldev)¶
返回底层 ME 客户端的 UUID
参数
const struct mei_cl_device *cldev
ME 客户端设备
返回
ME 客户端 UUID
-
u8 mei_cldev_ver(const struct mei_cl_device *cldev)¶
返回底层 ME 客户端的协议版本
参数
const struct mei_cl_device *cldev
ME 客户端设备
返回
ME 客户端协议版本
-
bool mei_cldev_enabled(const struct mei_cl_device *cldev)¶
检查设备是否已启用
参数
const struct mei_cl_device *cldev
ME 客户端设备
返回
如果 ME 客户端已初始化并已连接,则返回 true
-
int mei_cldev_enable(struct mei_cl_device *cldev)¶
启用 ME 客户端设备,创建与 ME 客户端的连接
参数
struct mei_cl_device *cldev
me 客户端设备
返回
成功时返回 0,出错时返回小于 0 的值
-
int mei_cldev_disable(struct mei_cl_device *cldev)¶
禁用 ME 客户端设备,断开与 ME 客户端的连接
参数
struct mei_cl_device *cldev
me 客户端设备
返回
成功时返回 0,出错时返回小于 0 的值
-
ssize_t mei_cldev_send_gsc_command(struct mei_cl_device *cldev, u8 client_id, u32 fence_id, struct scatterlist *sg_in, size_t total_in_len, struct scatterlist *sg_out)¶
发送 GSC 命令,通过向 GSC 发送 GSL MEI 消息并从 GSC 接收回复
参数
struct mei_cl_device *cldev
me 客户端设备
u8 client_id
要将命令发送到的客户端 ID
u32 fence_id
要将命令发送到的 fence ID
struct scatterlist *sg_in
包含 rx 消息缓冲区地址的散列表
size_t total_in_len
‘in’ sg 中数据的总长度,可以小于缓冲区大小的总和
struct scatterlist *sg_out
包含 tx 消息缓冲区地址的散列表
返回
以字节为单位写入的大小
错误时 < 0
示例¶
作为一个理论示例,让我们假设 ME 自带一个“接触式”NFC IP。此设备的驱动程序初始化和退出例程如下所示:
#define CONTACT_DRIVER_NAME "contact"
static struct mei_cl_device_id contact_mei_cl_tbl[] = {
{ CONTACT_DRIVER_NAME, },
/* required last entry */
{ }
};
MODULE_DEVICE_TABLE(mei_cl, contact_mei_cl_tbl);
static struct mei_cl_driver contact_driver = {
.id_table = contact_mei_tbl,
.name = CONTACT_DRIVER_NAME,
.probe = contact_probe,
.remove = contact_remove,
};
static int contact_init(void)
{
int r;
r = mei_cl_driver_register(&contact_driver);
if (r) {
pr_err(CONTACT_DRIVER_NAME ": driver registration failed\n");
return r;
}
return 0;
}
static void __exit contact_exit(void)
{
mei_cl_driver_unregister(&contact_driver);
}
module_init(contact_init);
module_exit(contact_exit);
驱动程序的简化探测例程如下所示:
int contact_probe(struct mei_cl_device *dev, struct mei_cl_device_id *id)
{
[...]
mei_cldev_enable(dev);
mei_cldev_register_rx_cb(dev, contact_rx_cb);
return 0;
}
在探测例程中,驱动程序首先启用 MEI 设备,然后注册一个 rx 处理程序,该处理程序尽可能接近于注册一个线程化的 IRQ 处理程序。处理程序实现通常会调用 mei_cldev_recv()
,然后处理接收到的数据。
#define MAX_PAYLOAD 128
#define HDR_SIZE 4
static void conntact_rx_cb(struct mei_cl_device *cldev)
{
struct contact *c = mei_cldev_get_drvdata(cldev);
unsigned char payload[MAX_PAYLOAD];
ssize_t payload_sz;
payload_sz = mei_cldev_recv(cldev, payload, MAX_PAYLOAD)
if (reply_size < HDR_SIZE) {
return;
}
c->process_rx(payload);
}