Hi, On 12/3/20 10:26 PM, Maximilian Luz wrote: > The Surface ACPI Notify (SAN) device provides an ACPI interface to the > Surface Aggregator EC, specifically the Surface Serial Hub interface. > This interface allows EC requests to be made from ACPI code and can > convert a subset of EC events back to ACPI notifications. > > Specifically, this interface provides a GenericSerialBus operation > region ACPI code can execute a request by writing the request command > data and payload to this operation region and reading back the > corresponding response via a write-then-read operation. Furthermore, > this interface provides a _DSM method to be called when certain events > from the EC have been received, essentially turning them into ACPI > notifications. > > The driver provided in this commit essentially takes care of translating > the request data written to the operation region, executing the request, > waiting for it to finish, and finally writing and translating back the > response (if the request has one). Furthermore, this driver takes care > of enabling the events handled via ACPI _DSM calls. Lastly, this driver > also exposes an interface providing discrete GPU (dGPU) power-on > notifications on the Surface Book 2, which are also received via the > operation region interface (but not handled by the SAN driver directly), > making them accessible to other drivers (such as a dGPU hot-plug driver > that may be added later on). > > On 5th and 6th generation Surface devices (Surface Pro 5/2017, Pro 6, > Book 2, Laptop 1 and 2), the SAN interface provides full battery and > thermal subsystem access, as well as other EC based functionality. On > those models, battery and thermal sensor devices are implemented as > standard ACPI devices of that type, however, forward ACPI calls to the > corresponding Surface Aggregator EC request via the SAN interface and > receive corresponding notifications (e.g. battery information change) > from it. This interface is therefore required to provide said > functionality on those devices. > > Signed-off-by: Maximilian Luz <luzmaximilian@xxxxxxxxx> Thanks, patch looks good to me: Reviewed-by: Hans de Goede <hdegoede@xxxxxxxxxx> Regards, Hans > --- > > Changes in v1 (from RFC): > - add copyright lines > - change SPDX identifier to GPL-2.0+ (was GPL-2.0-or-later) > - fix invalid pointer calculation in san_evt_tmp > - add explicit dependency on CONFIG_ACPI > - remove default in Kconfig > > Changes in v2: > - drop explicit dependency on ACPI in Kconfig > - use printk specifier for hex prefix instead of hard-coding it > - spell check comments and strings, fix typos > - unify comment style > - run checkpatch --strict, fix warnings and style issues > > --- > .../surface_aggregator/clients/index.rst | 1 + > .../surface_aggregator/clients/san.rst | 44 + > MAINTAINERS | 2 + > drivers/platform/surface/Kconfig | 19 + > drivers/platform/surface/Makefile | 1 + > .../platform/surface/surface_acpi_notify.c | 886 ++++++++++++++++++ > include/linux/surface_acpi_notify.h | 39 + > 7 files changed, 992 insertions(+) > create mode 100644 Documentation/driver-api/surface_aggregator/clients/san.rst > create mode 100644 drivers/platform/surface/surface_acpi_notify.c > create mode 100644 include/linux/surface_acpi_notify.h > > diff --git a/Documentation/driver-api/surface_aggregator/clients/index.rst b/Documentation/driver-api/surface_aggregator/clients/index.rst > index ab260ec82cfb..3ccabce23271 100644 > --- a/Documentation/driver-api/surface_aggregator/clients/index.rst > +++ b/Documentation/driver-api/surface_aggregator/clients/index.rst > @@ -11,6 +11,7 @@ This is the documentation for client drivers themselves. Refer to > :maxdepth: 1 > > cdev > + san > > .. only:: subproject and html > > diff --git a/Documentation/driver-api/surface_aggregator/clients/san.rst b/Documentation/driver-api/surface_aggregator/clients/san.rst > new file mode 100644 > index 000000000000..38c2580e7758 > --- /dev/null > +++ b/Documentation/driver-api/surface_aggregator/clients/san.rst > @@ -0,0 +1,44 @@ > +.. SPDX-License-Identifier: GPL-2.0+ > + > +.. |san_client_link| replace:: :c:func:`san_client_link` > +.. |san_dgpu_notifier_register| replace:: :c:func:`san_dgpu_notifier_register` > +.. |san_dgpu_notifier_unregister| replace:: :c:func:`san_dgpu_notifier_unregister` > + > +=================== > +Surface ACPI Notify > +=================== > + > +The Surface ACPI Notify (SAN) device provides the bridge between ACPI and > +SAM controller. Specifically, ACPI code can execute requests and handle > +battery and thermal events via this interface. In addition to this, events > +relating to the discrete GPU (dGPU) of the Surface Book 2 can be sent from > +ACPI code (note: the Surface Book 3 uses a different method for this). The > +only currently known event sent via this interface is a dGPU power-on > +notification. While this driver handles the former part internally, it only > +relays the dGPU events to any other driver interested via its public API and > +does not handle them. > + > +The public interface of this driver is split into two parts: Client > +registration and notifier-block registration. > + > +A client to the SAN interface can be linked as consumer to the SAN device > +via |san_client_link|. This can be used to ensure that the a client > +receiving dGPU events does not miss any events due to the SAN interface not > +being set up as this forces the client driver to unbind once the SAN driver > +is unbound. > + > +Notifier-blocks can be registered by any device for as long as the module is > +loaded, regardless of being linked as client or not. Registration is done > +with |san_dgpu_notifier_register|. If the notifier is not needed any more, it > +should be unregistered via |san_dgpu_notifier_unregister|. > + > +Consult the API documentation below for more details. > + > + > +API Documentation > +================= > + > +.. kernel-doc:: include/linux/surface_acpi_notify.h > + > +.. kernel-doc:: drivers/platform/surface/surface_acpi_notify.c > + :export: > diff --git a/MAINTAINERS b/MAINTAINERS > index f5a788f445a4..894c9ef1b8d6 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -11699,7 +11699,9 @@ W: https://github.com/linux-surface/surface-aggregator-module > C: irc://chat.freenode.net/##linux-surface > F: Documentation/driver-api/surface_aggregator/ > F: drivers/platform/surface/aggregator/ > +F: drivers/platform/surface/surface_acpi_notify.c > F: drivers/platform/surface/surface_aggregator_cdev.c > +F: include/linux/surface_acpi_notify.h > F: include/linux/surface_aggregator/ > F: include/uapi/linux/surface_aggregator/ > > diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig > index cecad7a0cb7b..88818320105e 100644 > --- a/drivers/platform/surface/Kconfig > +++ b/drivers/platform/surface/Kconfig > @@ -40,6 +40,25 @@ config SURFACE_3_POWER_OPREGION > This driver provides support for ACPI operation > region of the Surface 3 battery platform driver. > > +config SURFACE_ACPI_NOTIFY > + tristate "Surface ACPI Notify Driver" > + depends on SURFACE_AGGREGATOR > + help > + Surface ACPI Notify (SAN) driver for Microsoft Surface devices. > + > + This driver provides support for the ACPI interface (called SAN) of > + the Surface System Aggregator Module (SSAM) EC. This interface is used > + on 5th- and 6th-generation Microsoft Surface devices (including > + Surface Pro 5 and 6, Surface Book 2, Surface Laptops 1 and 2, and in > + reduced functionality on the Surface Laptop 3) to execute SSAM > + requests directly from ACPI code, as well as receive SSAM events and > + turn them into ACPI notifications. It essentially acts as a > + translation layer between the SSAM controller and ACPI. > + > + Specifically, this driver may be needed for battery status reporting, > + thermal sensor access, and real-time clock information, depending on > + the Surface device in question. > + > config SURFACE_AGGREGATOR_CDEV > tristate "Surface System Aggregator Module User-Space Interface" > depends on SURFACE_AGGREGATOR > diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile > index 161f0ad05795..3eb971006877 100644 > --- a/drivers/platform/surface/Makefile > +++ b/drivers/platform/surface/Makefile > @@ -7,6 +7,7 @@ > obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o > obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o > obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o > +obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o > obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ > obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o > obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o > diff --git a/drivers/platform/surface/surface_acpi_notify.c b/drivers/platform/surface/surface_acpi_notify.c > new file mode 100644 > index 000000000000..8cd67a669c86 > --- /dev/null > +++ b/drivers/platform/surface/surface_acpi_notify.c > @@ -0,0 +1,886 @@ > +// 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-2020 Maximilian Luz <luzmaximilian@xxxxxxxxx> > + */ > + > +#include <asm/unaligned.h> > +#include <linux/acpi.h> > +#include <linux/delay.h> > +#include <linux/jiffies.h> > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/notifier.h> > +#include <linux/platform_device.h> > +#include <linux/rwsem.h> > + > +#include <linux/surface_aggregator/controller.h> > +#include <linux/surface_acpi_notify.h> > + > +struct san_data { > + struct device *dev; > + struct ssam_controller *ctrl; > + > + struct acpi_connection_info info; > + > + struct ssam_event_notifier nf_bat; > + struct ssam_event_notifier nf_tmp; > +}; > + > +#define to_san_data(ptr, member) \ > + container_of(ptr, struct san_data, member) > + > + > +/* -- dGPU notifier interface. ---------------------------------------------- */ > + > +struct san_rqsg_if { > + struct rw_semaphore lock; > + struct device *dev; > + struct blocking_notifier_head nh; > +}; > + > +static struct san_rqsg_if san_rqsg_if = { > + .lock = __RWSEM_INITIALIZER(san_rqsg_if.lock), > + .dev = NULL, > + .nh = BLOCKING_NOTIFIER_INIT(san_rqsg_if.nh), > +}; > + > +static int 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; > + } > + > + up_read(&san_rqsg_if.lock); > + return 0; > +} > +EXPORT_SYMBOL_GPL(san_client_link); > + > +/** > + * 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); > + > +static int 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); > +} > + > + > +/* -- ACPI _DSM event relay. ------------------------------------------------ */ > + > +#define SAN_DSM_REVISION 0 > + > +/* 93b666c5-70c6-469f-a215-3d487c91ab3c */ > +static const guid_t SAN_DSM_UUID = > + GUID_INIT(0x93b666c5, 0x70c6, 0x469f, 0xa2, 0x15, 0x3d, > + 0x48, 0x7c, 0x91, 0xab, 0x3c); > + > +enum san_dsm_event_fn { > + SAN_DSM_EVENT_FN_BAT1_STAT = 0x03, > + SAN_DSM_EVENT_FN_BAT1_INFO = 0x04, > + SAN_DSM_EVENT_FN_ADP1_STAT = 0x05, > + SAN_DSM_EVENT_FN_ADP1_INFO = 0x06, > + SAN_DSM_EVENT_FN_BAT2_STAT = 0x07, > + SAN_DSM_EVENT_FN_BAT2_INFO = 0x08, > + SAN_DSM_EVENT_FN_THERMAL = 0x09, > + SAN_DSM_EVENT_FN_DPTF = 0x0a, > +}; > + > +enum sam_event_cid_bat { > + SAM_EVENT_CID_BAT_BIX = 0x15, > + SAM_EVENT_CID_BAT_BST = 0x16, > + SAM_EVENT_CID_BAT_ADP = 0x17, > + SAM_EVENT_CID_BAT_PROT = 0x18, > + SAM_EVENT_CID_BAT_DPTF = 0x4f, > +}; > + > +enum sam_event_cid_tmp { > + SAM_EVENT_CID_TMP_TRIP = 0x0b, > +}; > + > +struct san_event_work { > + struct delayed_work work; > + struct device *dev; > + struct ssam_event event; /* must be last */ > +}; > + > +static int 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, 1 << func)) > + return 0; > + > + dev_dbg(dev, "notify event %#04llx\n", func); > + > + obj = acpi_evaluate_dsm_typed(san, &SAN_DSM_UUID, SAN_DSM_REVISION, > + func, param, ACPI_TYPE_BUFFER); > + if (!obj) > + return -EFAULT; > + > + 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; > +} > + > +static int san_evt_bat_adp(struct device *dev, const struct 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; > + > + return san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_BAT2_STAT, NULL); > +} > + > +static int san_evt_bat_bix(struct device *dev, const struct ssam_event *event) > +{ > + enum san_dsm_event_fn fn; > + > + if (event->instance_id == 0x02) > + fn = SAN_DSM_EVENT_FN_BAT2_INFO; > + else > + fn = SAN_DSM_EVENT_FN_BAT1_INFO; > + > + return san_acpi_notify_event(dev, fn, NULL); > +} > + > +static int san_evt_bat_bst(struct device *dev, const struct ssam_event *event) > +{ > + enum san_dsm_event_fn fn; > + > + if (event->instance_id == 0x02) > + fn = SAN_DSM_EVENT_FN_BAT2_STAT; > + else > + fn = SAN_DSM_EVENT_FN_BAT1_STAT; > + > + return san_acpi_notify_event(dev, fn, NULL); > +} > + > +static int san_evt_bat_dptf(struct device *dev, const struct ssam_event *event) > +{ > + union acpi_object payload; > + > + /* > + * 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]; > + > + return san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_DPTF, &payload); > +} > + > +static unsigned long 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; > + } > +} > + > +static bool san_evt_bat(const struct 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. > + */ > + return true; > + > + case SAM_EVENT_CID_BAT_DPTF: > + status = san_evt_bat_dptf(dev, event); > + break; > + > + default: > + return false; > + } > + > + if (status) { > + dev_err(dev, "error handling power event (cid = %#04x)\n", > + event->command_id); > + } > + > + return true; > +} > + > +static void san_evt_bat_workfn(struct work_struct *work) > +{ > + struct san_event_work *ev; > + > + ev = container_of(work, struct san_event_work, work.work); > + san_evt_bat(&ev->event, ev->dev); > + kfree(ev); > +} > + > +static u32 san_evt_bat_nf(struct ssam_event_notifier *nf, > + const struct ssam_event *event) > +{ > + struct san_data *d = to_san_data(nf, nf_bat); > + struct san_event_work *work; > + unsigned long delay = san_evt_bat_delay(event->command_id); > + > + if (delay == 0) > + return san_evt_bat(event, d->dev) ? SSAM_NOTIF_HANDLED : 0; > + > + work = kzalloc(sizeof(*work) + event->length, GFP_KERNEL); > + if (!work) > + return ssam_notifier_from_errno(-ENOMEM); > + > + INIT_DELAYED_WORK(&work->work, san_evt_bat_workfn); > + work->dev = d->dev; > + > + memcpy(&work->event, event, sizeof(struct ssam_event) + event->length); > + > + schedule_delayed_work(&work->work, delay); > + return SSAM_NOTIF_HANDLED; > +} > + > +static int san_evt_tmp_trip(struct device *dev, const struct ssam_event *event) > +{ > + union acpi_object param; > + > + /* > + * 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; > + > + return san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_THERMAL, ¶m); > +} > + > +static bool san_evt_tmp(const struct ssam_event *event, struct device *dev) > +{ > + int status; > + > + switch (event->command_id) { > + case SAM_EVENT_CID_TMP_TRIP: > + status = san_evt_tmp_trip(dev, event); > + break; > + > + default: > + return false; > + } > + > + if (status) { > + dev_err(dev, "error handling thermal event (cid = %#04x)\n", > + event->command_id); > + } > + > + return true; > +} > + > +static u32 san_evt_tmp_nf(struct ssam_event_notifier *nf, > + const struct ssam_event *event) > +{ > + struct san_data *d = to_san_data(nf, nf_tmp); > + > + return san_evt_tmp(event, d->dev) ? SSAM_NOTIF_HANDLED : 0; > +} > + > + > +/* -- ACPI GSB OperationRegion handler -------------------------------------- */ > + > +struct gsb_data_in { > + u8 cv; > +} __packed; > + > +struct gsb_data_rqsx { > + u8 cv; /* Command value (san_gsb_request_cv). */ > + u8 tc; /* Target category. */ > + u8 tid; /* Target ID. */ > + u8 iid; /* Instance ID. */ > + u8 snc; /* Expect-response-flag. */ > + u8 cid; /* Command ID. */ > + u16 cdl; /* Payload length. */ > + u8 pld[]; /* Payload. */ > +} __packed; > + > +struct gsb_data_etwl { > + u8 cv; /* Command value (should be 0x02). */ > + u8 etw3; /* Unknown. */ > + u8 etw4; /* Unknown. */ > + u8 msg[]; /* Error message (ASCIIZ). */ > +} __packed; > + > +struct gsb_data_out { > + u8 status; /* _SSH communication status. */ > + u8 len; /* _SSH payload length. */ > + u8 pld[]; /* _SSH payload. */ > +} __packed; > + > +union gsb_buffer_data { > + struct gsb_data_in in; /* Common input. */ > + struct gsb_data_rqsx rqsx; /* RQSX input. */ > + struct gsb_data_etwl etwl; /* ETWL input. */ > + struct gsb_data_out out; /* Output. */ > +}; > + > +struct gsb_buffer { > + u8 status; /* GSB AttribRawProcess status. */ > + u8 len; /* GSB AttribRawProcess length. */ > + union gsb_buffer_data data; > +} __packed; > + > +#define SAN_GSB_MAX_RQSX_PAYLOAD (U8_MAX - 2 - sizeof(struct gsb_data_rqsx)) > +#define SAN_GSB_MAX_RESPONSE (U8_MAX - 2 - sizeof(struct gsb_data_out)) > + > +#define SAN_GSB_COMMAND 0 > + > +enum san_gsb_request_cv { > + SAN_GSB_REQUEST_CV_RQST = 0x01, > + SAN_GSB_REQUEST_CV_ETWL = 0x02, > + SAN_GSB_REQUEST_CV_RQSG = 0x03, > +}; > + > +#define SAN_REQUEST_NUM_TRIES 5 > + > +static acpi_status san_etwl(struct san_data *d, struct gsb_buffer *b) > +{ > + struct gsb_data_etwl *etwl = &b->data.etwl; > + > + if (b->len < sizeof(struct gsb_data_etwl)) { > + dev_err(d->dev, "invalid ETWL package (len = %d)\n", b->len); > + return AE_OK; > + } > + > + dev_err(d->dev, "ETWL(%#04x, %#04x): %.*s\n", etwl->etw3, etwl->etw4, > + (unsigned int)(b->len - sizeof(struct gsb_data_etwl)), > + (char *)etwl->msg); > + > + /* Indicate success. */ > + b->status = 0x00; > + b->len = 0x00; > + > + return AE_OK; > +} > + > +static > +struct gsb_data_rqsx *san_validate_rqsx(struct device *dev, const char *type, > + struct gsb_buffer *b) > +{ > + struct gsb_data_rqsx *rqsx = &b->data.rqsx; > + > + if (b->len < sizeof(struct gsb_data_rqsx)) { > + dev_err(dev, "invalid %s package (len = %d)\n", type, b->len); > + return NULL; > + } > + > + if (get_unaligned(&rqsx->cdl) != b->len - sizeof(struct gsb_data_rqsx)) { > + dev_err(dev, "bogus %s package (len = %d, cdl = %d)\n", > + type, b->len, get_unaligned(&rqsx->cdl)); > + return NULL; > + } > + > + 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; > + } > + > + return rqsx; > +} > + > +static void gsb_rqsx_response_error(struct gsb_buffer *gsb, int status) > +{ > + gsb->status = 0x00; > + gsb->len = 0x02; > + gsb->data.out.status = (u8)(-status); > + gsb->data.out.len = 0x00; > +} > + > +static void gsb_rqsx_response_success(struct gsb_buffer *gsb, u8 *ptr, size_t len) > +{ > + gsb->status = 0x00; > + gsb->len = len + 2; > + gsb->data.out.status = 0x00; > + gsb->data.out.len = len; > + > + if (len) > + memcpy(&gsb->data.out.pld[0], ptr, len); > +} > + > +static acpi_status san_rqst_fixup_suspended(struct san_data *d, > + struct ssam_request *rqst, > + struct gsb_buffer *gsb) > +{ > + if (rqst->target_category == SSAM_SSH_TC_BAS && rqst->command_id == 0x0D) { > + u8 base_state = 1; > + > + /* 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. > + */ > + > + dev_dbg(d->dev, "rqst: fixup: base-state quirk\n"); > + > + gsb_rqsx_response_success(gsb, &base_state, sizeof(base_state)); > + return AE_OK; > + } > + > + gsb_rqsx_response_error(gsb, -ENXIO); > + return AE_OK; > +} > + > +static acpi_status san_rqst(struct san_data *d, struct gsb_buffer *buffer) > +{ > + u8 rspbuf[SAN_GSB_MAX_RESPONSE]; > + struct gsb_data_rqsx *gsb_rqst; > + struct ssam_request rqst; > + struct ssam_response rsp; > + int status = 0; > + > + gsb_rqst = san_validate_rqsx(d->dev, "RQST", buffer); > + if (!gsb_rqst) > + return AE_OK; > + > + rqst.target_category = gsb_rqst->tc; > + rqst.target_id = gsb_rqst->tid; > + rqst.command_id = gsb_rqst->cid; > + rqst.instance_id = gsb_rqst->iid; > + rqst.flags = gsb_rqst->snc ? SSAM_REQUEST_HAS_RESPONSE : 0; > + rqst.length = get_unaligned(&gsb_rqst->cdl); > + rqst.payload = &gsb_rqst->pld[0]; > + > + rsp.capacity = ARRAY_SIZE(rspbuf); > + rsp.length = 0; > + rsp.pointer = &rspbuf[0]; > + > + /* Handle suspended device. */ > + if (d->dev->power.is_suspended) { > + dev_warn(d->dev, "rqst: device is suspended, not executing\n"); > + return san_rqst_fixup_suspended(d, &rqst, buffer); > + } > + > + status = __ssam_retry(ssam_request_sync_onstack, SAN_REQUEST_NUM_TRIES, > + d->ctrl, &rqst, &rsp, SAN_GSB_MAX_RQSX_PAYLOAD); > + > + if (!status) { > + gsb_rqsx_response_success(buffer, rsp.pointer, rsp.length); > + } else { > + dev_err(d->dev, "rqst: failed with error %d\n", status); > + gsb_rqsx_response_error(buffer, status); > + } > + > + return AE_OK; > +} > + > +static acpi_status san_rqsg(struct san_data *d, struct gsb_buffer *buffer) > +{ > + struct gsb_data_rqsx *gsb_rqsg; > + struct san_dgpu_event evt; > + int status; > + > + gsb_rqsg = san_validate_rqsx(d->dev, "RQSG", buffer); > + if (!gsb_rqsg) > + return AE_OK; > + > + evt.category = gsb_rqsg->tc; > + evt.target = gsb_rqsg->tid; > + evt.command = gsb_rqsg->cid; > + evt.instance = gsb_rqsg->iid; > + evt.length = get_unaligned(&gsb_rqsg->cdl); > + evt.payload = &gsb_rqsg->pld[0]; > + > + status = san_dgpu_notifier_call(&evt); > + if (!status) { > + gsb_rqsx_response_success(buffer, NULL, 0); > + } else { > + dev_err(d->dev, "rqsg: failed with error %d\n", status); > + gsb_rqsx_response_error(buffer, status); > + } > + > + return AE_OK; > +} > + > +static acpi_status san_opreg_handler(u32 function, acpi_physical_address command, > + u32 bits, u64 *value64, void *opreg_context, > + void *region_context) > +{ > + struct san_data *d = to_san_data(opreg_context, info); > + struct gsb_buffer *buffer = (struct gsb_buffer *)value64; > + int accessor_type = (function & 0xFFFF0000) >> 16; > + > + if (command != SAN_GSB_COMMAND) { > + dev_warn(d->dev, "unsupported command: %#04llx\n", command); > + return AE_OK; > + } > + > + if (accessor_type != ACPI_GSB_ACCESS_ATTRIB_RAW_PROCESS) { > + dev_err(d->dev, "invalid access type: %#04x\n", accessor_type); > + return AE_OK; > + } > + > + /* Buffer must have at least contain the command-value. */ > + if (buffer->len == 0) { > + dev_err(d->dev, "request-package too small\n"); > + return AE_OK; > + } > + > + switch (buffer->data.in.cv) { > + case SAN_GSB_REQUEST_CV_RQST: > + return san_rqst(d, buffer); > + > + case SAN_GSB_REQUEST_CV_ETWL: > + return san_etwl(d, buffer); > + > + case SAN_GSB_REQUEST_CV_RQSG: > + return san_rqsg(d, buffer); > + > + default: > + dev_warn(d->dev, "unsupported SAN0 request (cv: %#04x)\n", > + buffer->data.in.cv); > + return AE_OK; > + } > +} > + > + > +/* -- Driver setup. --------------------------------------------------------- */ > + > +static int san_events_register(struct platform_device *pdev) > +{ > + struct san_data *d = platform_get_drvdata(pdev); > + int status; > + > + d->nf_bat.base.priority = 1; > + d->nf_bat.base.fn = san_evt_bat_nf; > + d->nf_bat.event.reg = SSAM_EVENT_REGISTRY_SAM; > + d->nf_bat.event.id.target_category = SSAM_SSH_TC_BAT; > + d->nf_bat.event.id.instance = 0; > + d->nf_bat.event.mask = SSAM_EVENT_MASK_TARGET; > + d->nf_bat.event.flags = SSAM_EVENT_SEQUENCED; > + > + d->nf_tmp.base.priority = 1; > + d->nf_tmp.base.fn = san_evt_tmp_nf; > + d->nf_tmp.event.reg = SSAM_EVENT_REGISTRY_SAM; > + d->nf_tmp.event.id.target_category = SSAM_SSH_TC_TMP; > + d->nf_tmp.event.id.instance = 0; > + d->nf_tmp.event.mask = SSAM_EVENT_MASK_TARGET; > + d->nf_tmp.event.flags = SSAM_EVENT_SEQUENCED; > + > + status = ssam_notifier_register(d->ctrl, &d->nf_bat); > + if (status) > + return status; > + > + status = ssam_notifier_register(d->ctrl, &d->nf_tmp); > + if (status) > + ssam_notifier_unregister(d->ctrl, &d->nf_bat); > + > + return status; > +} > + > +static void san_events_unregister(struct platform_device *pdev) > +{ > + struct san_data *d = platform_get_drvdata(pdev); > + > + ssam_notifier_unregister(d->ctrl, &d->nf_bat); > + ssam_notifier_unregister(d->ctrl, &d->nf_tmp); > +} > + > +#define san_consumer_printk(level, dev, handle, fmt, ...) \ > +do { \ > + char *path = "<error getting consumer path>"; \ > + struct acpi_buffer buffer = { \ > + .length = ACPI_ALLOCATE_BUFFER, \ > + .pointer = NULL, \ > + }; \ > + \ > + if (ACPI_SUCCESS(acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer))) \ > + path = buffer.pointer; \ > + \ > + dev_##level(dev, "[%s]: " fmt, path, ##__VA_ARGS__); \ > + kfree(buffer.pointer); \ > +} while (0) > + > +#define san_consumer_dbg(dev, handle, fmt, ...) \ > + san_consumer_printk(dbg, dev, handle, fmt, ##__VA_ARGS__) > + > +#define san_consumer_warn(dev, handle, fmt, ...) \ > + san_consumer_printk(warn, dev, handle, fmt, ##__VA_ARGS__) > + > +static bool is_san_consumer(struct platform_device *pdev, acpi_handle handle) > +{ > + struct acpi_handle_list dep_devices; > + acpi_handle supplier = ACPI_HANDLE(&pdev->dev); > + acpi_status status; > + int i; > + > + if (!acpi_has_method(handle, "_DEP")) > + return false; > + > + status = acpi_evaluate_reference(handle, "_DEP", NULL, &dep_devices); > + if (ACPI_FAILURE(status)) { > + san_consumer_dbg(&pdev->dev, handle, "failed to evaluate _DEP\n"); > + return false; > + } > + > + for (i = 0; i < dep_devices.count; i++) { > + if (dep_devices.handles[i] == supplier) > + return true; > + } > + > + return false; > +} > + > +static acpi_status san_consumer_setup(acpi_handle handle, u32 lvl, > + void *context, void **rv) > +{ > + const u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_SUPPLIER; > + struct platform_device *pdev = context; > + struct acpi_device *adev; > + struct device_link *link; > + > + if (!is_san_consumer(pdev, handle)) > + return AE_OK; > + > + /* Ignore ACPI devices that are not present. */ > + if (acpi_bus_get_device(handle, &adev) != 0) > + return AE_OK; > + > + san_consumer_dbg(&pdev->dev, handle, "creating device link\n"); > + > + /* 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; > + } > + > + return AE_OK; > +} > + > +static int san_consumer_links_setup(struct platform_device *pdev) > +{ > + acpi_status status; > + > + status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, > + ACPI_UINT32_MAX, san_consumer_setup, NULL, > + pdev, NULL); > + > + return status ? -EFAULT : 0; > +} > + > +static int san_probe(struct platform_device *pdev) > +{ > + acpi_handle san = ACPI_HANDLE(&pdev->dev); > + struct ssam_controller *ctrl; > + struct san_data *data; > + acpi_status astatus; > + int status; > + > + ctrl = ssam_client_bind(&pdev->dev); > + if (IS_ERR(ctrl)) > + return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); > + > + status = san_consumer_links_setup(pdev); > + if (status) > + return status; > + > + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); > + if (!data) > + return -ENOMEM; > + > + data->dev = &pdev->dev; > + data->ctrl = ctrl; > + > + platform_set_drvdata(pdev, data); > + > + astatus = acpi_install_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, > + &san_opreg_handler, NULL, > + &data->info); > + if (ACPI_FAILURE(astatus)) > + return -ENXIO; > + > + status = san_events_register(pdev); > + if (status) > + goto err_enable_events; > + > + status = san_set_rqsg_interface_device(&pdev->dev); > + if (status) > + goto err_install_dev; > + > + acpi_walk_dep_device_list(san); > + return 0; > + > +err_install_dev: > + san_events_unregister(pdev); > +err_enable_events: > + acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, > + &san_opreg_handler); > + return status; > +} > + > +static int san_remove(struct platform_device *pdev) > +{ > + acpi_handle san = ACPI_HANDLE(&pdev->dev); > + > + san_set_rqsg_interface_device(NULL); > + acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, > + &san_opreg_handler); > + san_events_unregister(pdev); > + > + /* > + * We have unregistered our event sources. Now we need to ensure that > + * all delayed works they may have spawned are run to completion. > + */ > + flush_scheduled_work(); > + > + return 0; > +} > + > +static const struct acpi_device_id san_match[] = { > + { "MSHW0091" }, > + { }, > +}; > +MODULE_DEVICE_TABLE(acpi, san_match); > + > +static struct platform_driver surface_acpi_notify = { > + .probe = san_probe, > + .remove = san_remove, > + .driver = { > + .name = "surface_acpi_notify", > + .acpi_match_table = san_match, > + .probe_type = PROBE_PREFER_ASYNCHRONOUS, > + }, > +}; > +module_platform_driver(surface_acpi_notify); > + > +MODULE_AUTHOR("Maximilian Luz <luzmaximilian@xxxxxxxxx>"); > +MODULE_DESCRIPTION("Surface ACPI Notify driver for Surface System Aggregator Module"); > +MODULE_LICENSE("GPL"); > diff --git a/include/linux/surface_acpi_notify.h b/include/linux/surface_acpi_notify.h > new file mode 100644 > index 000000000000..8e3e86c7d78c > --- /dev/null > +++ b/include/linux/surface_acpi_notify.h > @@ -0,0 +1,39 @@ > +/* SPDX-License-Identifier: GPL-2.0+ */ > +/* > + * Interface for Surface ACPI Notify (SAN) driver. > + * > + * Provides access to discrete GPU notifications sent from ACPI via the SAN > + * driver, which are not handled by this driver directly. > + * > + * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@xxxxxxxxx> > + */ > + > +#ifndef _LINUX_SURFACE_ACPI_NOTIFY_H > +#define _LINUX_SURFACE_ACPI_NOTIFY_H > + > +#include <linux/notifier.h> > +#include <linux/types.h> > + > +/** > + * struct san_dgpu_event - Discrete GPU ACPI event. > + * @category: Category of the event. > + * @target: Target ID of the event source. > + * @command: Command ID of the event. > + * @instance: Instance ID of the event source. > + * @length: Length of the event's payload data (in bytes). > + * @payload: Pointer to the event's payload data. > + */ > +struct san_dgpu_event { > + u8 category; > + u8 target; > + u8 command; > + u8 instance; > + u16 length; > + u8 *payload; > +}; > + > +int san_client_link(struct device *client); > +int san_dgpu_notifier_register(struct notifier_block *nb); > +int san_dgpu_notifier_unregister(struct notifier_block *nb); > + > +#endif /* _LINUX_SURFACE_ACPI_NOTIFY_H */ > -- > 2.29.2 >