| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884 |
- // 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;
- }
- 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_do_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 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 (!acpi_device_dep(handle, ACPI_HANDLE(&pdev->dev)))
- return AE_OK;
- /* Ignore ACPI devices that are not present. */
- adev = acpi_fetch_acpi_dev(handle);
- if (!adev)
- 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)
- {
- struct acpi_device *san = ACPI_COMPANION(&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->handle,
- 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_dev_clear_dependencies(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 void 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_workqueue(san_wq);
- }
- 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,
- },
- };
- static int __init san_init(void)
- {
- int ret;
- san_wq = alloc_workqueue("san_wq", WQ_PERCPU, 0);
- if (!san_wq)
- return -ENOMEM;
- ret = platform_driver_register(&surface_acpi_notify);
- if (ret)
- destroy_workqueue(san_wq);
- return ret;
- }
- module_init(san_init);
- static void __exit san_exit(void)
- {
- platform_driver_unregister(&surface_acpi_notify);
- destroy_workqueue(san_wq);
- }
- module_exit(san_exit);
- MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
- MODULE_DESCRIPTION("Surface ACPI Notify driver for Surface System Aggregator Module");
- MODULE_LICENSE("GPL");
|