// SPDX-License-Identifier: GPL-2.0+ /* * Driver for the Surface ACPI Notify (SAN) interface/shim. * * Translates communication from ACPI to Surface System Aggregator Module * (SSAM/SAM) requests and back, specifically SAM-over-SSH. Translates SSAM * events back to ACPI notifications. Allows handling of discrete GPU * notifications sent from ACPI via the SAN interface by providing them to any * registered external driver. * * Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>
*/
staticint san_set_rqsg_interface_device(struct device *dev)
{ int status = 0;
down_write(&san_rqsg_if.lock); if (!san_rqsg_if.dev && dev)
san_rqsg_if.dev = dev; else
status = -EBUSY;
up_write(&san_rqsg_if.lock);
return status;
}
/** * san_client_link() - Link client as consumer to SAN device. * @client: The client to link. * * Sets up a device link between the provided client device as consumer and * the SAN device as provider. This function can be used to ensure that the * SAN interface has been set up and will be set up for as long as the driver * of the client device is bound. This guarantees that, during that time, all * dGPU events will be received by any registered notifier. * * The link will be automatically removed once the client device's driver is * unbound. * * Return: Returns zero on success, %-ENXIO if the SAN interface has not been * set up yet, and %-ENOMEM if device link creation failed.
*/ int san_client_link(struct device *client)
{ const u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER; struct device_link *link;
down_read(&san_rqsg_if.lock);
if (!san_rqsg_if.dev) {
up_read(&san_rqsg_if.lock); return -ENXIO;
}
link = device_link_add(client, san_rqsg_if.dev, flags); if (!link) {
up_read(&san_rqsg_if.lock); return -ENOMEM;
}
if (READ_ONCE(link->status) == DL_STATE_SUPPLIER_UNBIND) {
up_read(&san_rqsg_if.lock); return -ENXIO;
}
/** * san_dgpu_notifier_register() - Register a SAN dGPU notifier. * @nb: The notifier-block to register. * * Registers a SAN dGPU notifier, receiving any new SAN dGPU events sent from * ACPI. The registered notifier will be called with &struct san_dgpu_event * as notifier data and the command ID of that event as notifier action.
*/ int san_dgpu_notifier_register(struct notifier_block *nb)
{ return blocking_notifier_chain_register(&san_rqsg_if.nh, nb);
}
EXPORT_SYMBOL_GPL(san_dgpu_notifier_register);
/** * san_dgpu_notifier_unregister() - Unregister a SAN dGPU notifier. * @nb: The notifier-block to unregister.
*/ int san_dgpu_notifier_unregister(struct notifier_block *nb)
{ return blocking_notifier_chain_unregister(&san_rqsg_if.nh, nb);
}
EXPORT_SYMBOL_GPL(san_dgpu_notifier_unregister);
staticint san_dgpu_notifier_call(struct san_dgpu_event *evt)
{ int ret;
ret = blocking_notifier_call_chain(&san_rqsg_if.nh, evt->command, evt); return notifier_to_errno(ret);
}
struct san_event_work { struct delayed_work work; struct device *dev; struct ssam_event event; /* must be last */
};
staticint san_acpi_notify_event(struct device *dev, u64 func, union acpi_object *param)
{
acpi_handle san = ACPI_HANDLE(dev); union acpi_object *obj; int status = 0;
if (!acpi_check_dsm(san, &SAN_DSM_UUID, SAN_DSM_REVISION, BIT_ULL(func))) return 0;
if (obj->buffer.length != 1 || obj->buffer.pointer[0] != 0) {
dev_err(dev, "got unexpected result from _DSM\n");
status = -EPROTO;
}
ACPI_FREE(obj); return status;
}
staticint san_evt_bat_adp(struct device *dev, conststruct ssam_event *event)
{ int status;
status = san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_ADP1_STAT, NULL); if (status) return status;
/* * Ensure that the battery states get updated correctly. When the * battery is fully charged and an adapter is plugged in, it sometimes * is not updated correctly, instead showing it as charging. * Explicitly trigger battery updates to fix this.
*/
status = san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_BAT1_STAT, NULL); if (status) return status;
/* * The Surface ACPI expects a buffer and not a package. It specifically * checks for ObjectType (Arg3) == 0x03. This will cause a warning in * acpica/nsarguments.c, but that warning can be safely ignored.
*/
payload.type = ACPI_TYPE_BUFFER;
payload.buffer.length = event->length;
payload.buffer.pointer = (u8 *)&event->data[0];
staticunsignedlong san_evt_bat_delay(u8 cid)
{ switch (cid) { case SAM_EVENT_CID_BAT_ADP: /* * Wait for battery state to update before signaling adapter * change.
*/ return msecs_to_jiffies(5000);
case SAM_EVENT_CID_BAT_BST: /* Ensure we do not miss anything important due to caching. */ return msecs_to_jiffies(2000);
default: return 0;
}
}
staticbool san_evt_bat(conststruct ssam_event *event, struct device *dev)
{ int status;
switch (event->command_id) { case SAM_EVENT_CID_BAT_BIX:
status = san_evt_bat_bix(dev, event); break;
case SAM_EVENT_CID_BAT_BST:
status = san_evt_bat_bst(dev, event); break;
case SAM_EVENT_CID_BAT_ADP:
status = san_evt_bat_adp(dev, event); break;
case SAM_EVENT_CID_BAT_PROT: /* * TODO: Implement support for battery protection status change * event.
*/ returntrue;
case SAM_EVENT_CID_BAT_DPTF:
status = san_evt_bat_dptf(dev, event); break;
default: returnfalse;
}
if (status) {
dev_err(dev, "error handling power event (cid = %#04x)\n",
event->command_id);
}
/* * The Surface ACPI expects an integer and not a package. This will * cause a warning in acpica/nsarguments.c, but that warning can be * safely ignored.
*/
param.type = ACPI_TYPE_INTEGER;
param.integer.value = event->instance_id;
if (get_unaligned(&rqsx->cdl) > SAN_GSB_MAX_RQSX_PAYLOAD) {
dev_err(dev, "payload for %s package too large (cdl = %d)\n",
type, get_unaligned(&rqsx->cdl)); return NULL;
}
/* Base state quirk: * The base state may be queried from ACPI when the EC is still * suspended. In this case it will return '-EPERM'. This query * will only be triggered from the ACPI lid GPE interrupt, thus * we are either in laptop or studio mode (base status 0x01 or * 0x02). Furthermore, we will only get here if the device (and * EC) have been suspended. * * We now assume that the device is in laptop mode (0x01). This * has the drawback that it will wake the device when unfolding * it in studio mode, but it also allows us to avoid actively * waiting for the EC to wake up, which may incur a notable * delay.
*/
/* Try to set up device links, ignore but log errors. */
link = device_link_add(&adev->dev, &pdev->dev, flags); if (!link) {
san_consumer_warn(&pdev->dev, handle, "failed to create device link\n"); return AE_OK;
}
/* * We have unregistered our event sources. Now we need to ensure that * all delayed works they may have spawned are run to completion.
*/
flush_workqueue(san_wq);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.