// 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> */ #include <linux/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) static struct workqueue_struct *san_wq; /* -- 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, BIT_ULL(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; work->event = *event; memcpy(work->event.data, event->data, event->length); queue_delayed_work(san_wq, &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;} staticstruct 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;Lines 1–500 of 888. Load more
// 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> */ #include <linux/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) static struct workqueue_struct *san_wq; /* -- 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, BIT_ULL(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; work->event = *event; memcpy(work->event.data, event->data, event->length); queue_delayed_work(san_wq, &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;