sdei的全称是software delegated exception interface。首先明确在bios 看来这就是一个apci table
这样在os中就可以解析并注册一个设备
static int __init sdei_init(void)
{
if (sdei_present_dt() || sdei_present_acpi())
platform_driver_register(&sdei_driver);
return 0;
}
可以看到这里调用platform_driver_register 注册了一个总线设备,我们重点看看这个设备的probe函数
static int sdei_probe(struct platform_device *pdev)
{
int err;
u64 ver = 0;
int conduit;
conduit = sdei_get_conduit(pdev);
if (!sdei_firmware_call)
return 0;
#检查sdei的版本
err = sdei_api_get_version(&ver);
if (err == -EOPNOTSUPP)
pr_err("advertised but not implemented in platform firmware\n");
if (err) {
pr_err("Failed to get SDEI version: %d\n", err);
sdei_mark_interface_broken();
return err;
}
pr_info("SDEIv%d.%d (0x%x) detected in firmware.\n",
(int)SDEI_VERSION_MAJOR(ver), (int)SDEI_VERSION_MINOR(ver),
(int)SDEI_VERSION_VENDOR(ver));
if (SDEI_VERSION_MAJOR(ver) != 1) {
pr_warn("Conflicting SDEI version detected.\n");
sdei_mark_interface_broken();
return -EINVAL;
}
err = sdei_platform_reset();
if (err)
return err;
#拿到entry_point
sdei_entry_point = sdei_arch_get_entry_point(conduit);
if (!sdei_entry_point) {
/* Not supported due to hardware or boot configuration */
sdei_mark_interface_broken();
return 0;
}
#下面分别注册pm和reboot相关的通知链
err = cpu_pm_register_notifier(&sdei_pm_nb);
if (err) {
pr_warn("Failed to register CPU PM notifier...\n");
goto error;
}
err = register_reboot_notifier(&sdei_reboot_nb);
if (err) {
pr_warn("Failed to register reboot notifier...\n");
goto remove_cpupm;
}
err = cpuhp_setup_state(CPUHP_AP_ARM_SDEI_STARTING, "SDEI",
&sdei_cpuhp_up, &sdei_cpuhp_down);
if (err) {
pr_warn("Failed to register CPU hotplug notifier...\n");
goto remove_reboot;
}
return 0;
remove_reboot:
unregister_reboot_notifier(&sdei_reboot_nb);
remove_cpupm:
cpu_pm_unregister_notifier(&sdei_pm_nb);
error:
sdei_mark_interface_broken();
return err;
}
到这里可以看到kernel 会为sdei注册一个总线设备
在kernel中会调用sdei_event_register来注册一些事件,
int sdei_event_register(u32 event_num, sdei_event_callback *cb, void *arg)
{
int err;
struct sdei_event *event;
WARN_ON(in_nmi());
mutex_lock(&sdei_events_lock);
do {
#首先检测事件是否已经注册,这里的时间随意数字为标识符,且不能重复注册
if (sdei_event_find(event_num)) {
pr_warn("Event %u already registered\n", event_num);
err = -EBUSY;
break;
}
#根据event_num 和callback 注册一个sdei的event
event = sdei_event_create(event_num, cb, arg);
if (IS_ERR(event)) {
err = PTR_ERR(event);
pr_warn("Failed to create event %u: %d\n", event_num,
err);
break;
}
#注册这个事件到sdei_list中
err = _sdei_event_register(event);
if (err) {
sdei_event_destroy(event);
pr_warn("Failed to register event %u: %d\n", event_num,
err);
}
} while (0);
mutex_unlock(&sdei_events_lock);
return err;
}
_sdei_event_register代码如下:
static int _sdei_event_register(struct sdei_event *event)
{
int err;
lockdep_assert_held(&sdei_events_lock);
spin_lock(&sdei_list_lock);
event->reregister = true;
spin_unlock(&sdei_list_lock);
#可以看到event根据type可以分为是否共享的还是私有的event
if (event->type == SDEI_EVENT_TYPE_SHARED)
return sdei_api_event_register(event->event_num,
sdei_entry_point,
event->registered,
SDEI_EVENT_REGISTER_RM_ANY, 0);
#共享或者私有都会走到这里
err = sdei_do_cross_call(_local_event_register, event);
if (err) {
spin_lock(&sdei_list_lock);
event->reregister = false;
event->reenable = false;
spin_unlock(&sdei_list_lock);
sdei_do_cross_call(_local_event_unregister, event);
}
return err;
}
sdei_do_cross_call基本上是为每个cpu调用一遍fn
static inline int sdei_do_cross_call(void *fn, struct sdei_event * event)
{
struct sdei_crosscall_args arg;
CROSSCALL_INIT(arg, event);
#调用内核接口在每个cpu上fn
on_each_cpu(fn, &arg, true);
return arg.first_error;
}
我们重点看看fn
static void _local_event_register(void *data)
{
int err;
struct sdei_registered_event *reg;
struct sdei_crosscall_args *arg = data;
WARN_ON(preemptible());
reg = per_cpu_ptr(arg->event->private_registered, smp_processor_id());
#这里的第二个参数就是从bios的acpi 表中获得的,调用这个函数就会陷入到atf中
err = sdei_api_event_register(arg->event->event_num, sdei_entry_point,
reg, 0, 0);
sdei_cross_call_return(arg, err);
}
static int sdei_api_event_register(u32 event_num, unsigned long entry_point,
void *arg, u64 flags, u64 affinity)
{
return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_REGISTER, event_num,
(unsigned long)entry_point, (unsigned long)arg,
flags, affinity, NULL);
}
static int invoke_sdei_fn(unsigned long function_id, unsigned long arg0,
unsigned long arg1, unsigned long arg2,
unsigned long arg3, unsigned long arg4,
u64 *result)
{
int err = 0;
struct arm_smccc_res res;
if (sdei_firmware_call) {
#最终调用sdei_fireware_call 进入到atf中
sdei_firmware_call(function_id, arg0, arg1, arg2, arg3, arg4,
&res);
if (sdei_is_err(&res))
err = sdei_to_linux_errno(res.a0);
} else {
/*
* !sdei_firmware_call means we failed to probe or called
* sdei_mark_interface_broken(). -EIO is not an error returned
* by sdei_to_linux_errno() and is used to suppress messages
* from this driver.
*/
err = -EIO;
res.a0 = SDEI_NOT_SUPPORTED;
}
if (result)
*result = res.a0;
return err;
}