mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-21 03:21:56 +07:00
136 lines
3.9 KiB
Plaintext
136 lines
3.9 KiB
Plaintext
|
Intel(R) Management Engine (ME) Client bus API
|
||
|
===============================================
|
||
|
|
||
|
|
||
|
Rationale
|
||
|
=========
|
||
|
MEI misc character device is useful for dedicated applications to send and receive
|
||
|
data to the many FW appliance found in Intel's ME from the user space.
|
||
|
However for some of the ME functionalities it make sense to leverage existing software
|
||
|
stack and expose them through existing kernel subsystems.
|
||
|
|
||
|
In order to plug seamlessly into the kernel device driver model we add kernel virtual
|
||
|
bus abstraction on top of the MEI driver. This allows implementing linux kernel drivers
|
||
|
for the various MEI features as a stand alone entities found in their respective subsystem.
|
||
|
Existing device drivers can even potentially be re-used by adding an MEI CL bus layer to
|
||
|
the existing code.
|
||
|
|
||
|
|
||
|
MEI CL bus API
|
||
|
===========
|
||
|
A driver implementation for an MEI Client is very similar to existing bus
|
||
|
based device drivers. The driver registers itself as an MEI CL bus driver through
|
||
|
the mei_cl_driver structure:
|
||
|
|
||
|
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);
|
||
|
};
|
||
|
|
||
|
struct mei_cl_id {
|
||
|
char name[MEI_NAME_SIZE];
|
||
|
kernel_ulong_t driver_info;
|
||
|
};
|
||
|
|
||
|
The mei_cl_id structure allows the driver to bind itself against a device name.
|
||
|
|
||
|
To actually register a driver on the ME Client bus one must call the mei_cl_add_driver()
|
||
|
API. This is typically called at module init time.
|
||
|
|
||
|
Once registered on the ME Client bus, a driver will typically try to do some I/O on
|
||
|
this bus and this should be done through the mei_cl_send() and mei_cl_recv()
|
||
|
routines. The latter is synchronous (blocks and sleeps until data shows up).
|
||
|
In order for drivers to be notified of pending events waiting for them (e.g.
|
||
|
an Rx event) they can register an event handler through the
|
||
|
mei_cl_register_event_cb() routine. Currently only the MEI_EVENT_RX event
|
||
|
will trigger an event handler call and the driver implementation is supposed
|
||
|
to call mei_recv() from the event handler in order to fetch the pending
|
||
|
received buffers.
|
||
|
|
||
|
|
||
|
Example
|
||
|
=======
|
||
|
As a theoretical example let's pretend the ME comes with a "contact" NFC IP.
|
||
|
The driver init and exit routines for this device would look like:
|
||
|
|
||
|
#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);
|
||
|
|
||
|
And the driver's simplified probe routine would look like that:
|
||
|
|
||
|
int contact_probe(struct mei_cl_device *dev, struct mei_cl_device_id *id)
|
||
|
{
|
||
|
struct contact_driver *contact;
|
||
|
|
||
|
[...]
|
||
|
mei_cl_register_event_cb(dev, contact_event_cb, contact);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
In the probe routine the driver basically registers an ME bus event handler
|
||
|
which is as close as it can get to registering a threaded IRQ handler.
|
||
|
The handler implementation will typically call some I/O routine depending on
|
||
|
the pending events:
|
||
|
|
||
|
#define MAX_NFC_PAYLOAD 128
|
||
|
|
||
|
static void contact_event_cb(struct mei_cl_device *dev, u32 events,
|
||
|
void *context)
|
||
|
{
|
||
|
struct contact_driver *contact = context;
|
||
|
|
||
|
if (events & BIT(MEI_EVENT_RX)) {
|
||
|
u8 payload[MAX_NFC_PAYLOAD];
|
||
|
int payload_size;
|
||
|
|
||
|
payload_size = mei_recv(dev, payload, MAX_NFC_PAYLOAD);
|
||
|
if (payload_size <= 0)
|
||
|
return;
|
||
|
|
||
|
/* Hook to the NFC subsystem */
|
||
|
nfc_hci_recv_frame(contact->hdev, payload, payload_size);
|
||
|
}
|
||
|
}
|