From 5b7c4cabbb65f5c469464da6c5f614cbd7f730f2 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 21 Feb 2023 18:24:12 -0800 Subject: Merge tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next Pull networking updates from Jakub Kicinski: "Core: - Add dedicated kmem_cache for typical/small skb->head, avoid having to access struct page at kfree time, and improve memory use. - Introduce sysctl to set default RPS configuration for new netdevs. - Define Netlink protocol specification format which can be used to describe messages used by each family and auto-generate parsers. Add tools for generating kernel data structures and uAPI headers. - Expose all net/core sysctls inside netns. - Remove 4s sleep in netpoll if carrier is instantly detected on boot. - Add configurable limit of MDB entries per port, and port-vlan. - Continue populating drop reasons throughout the stack. - Retire a handful of legacy Qdiscs and classifiers. Protocols: - Support IPv4 big TCP (TSO frames larger than 64kB). - Add IP_LOCAL_PORT_RANGE socket option, to control local port range on socket by socket basis. - Track and report in procfs number of MPTCP sockets used. - Support mixing IPv4 and IPv6 flows in the in-kernel MPTCP path manager. - IPv6: don't check net.ipv6.route.max_size and rely on garbage collection to free memory (similarly to IPv4). - Support Penultimate Segment Pop (PSP) flavor in SRv6 (RFC8986). - ICMP: add per-rate limit counters. - Add support for user scanning requests in ieee802154. - Remove static WEP support. - Support minimal Wi-Fi 7 Extremely High Throughput (EHT) rate reporting. - WiFi 7 EHT channel puncturing support (client & AP). BPF: - Add a rbtree data structure following the "next-gen data structure" precedent set by recently added linked list, that is, by using kfunc + kptr instead of adding a new BPF map type. - Expose XDP hints via kfuncs with initial support for RX hash and timestamp metadata. - Add BPF_F_NO_TUNNEL_KEY extension to bpf_skb_set_tunnel_key to better support decap on GRE tunnel devices not operating in collect metadata. - Improve x86 JIT's codegen for PROBE_MEM runtime error checks. - Remove the need for trace_printk_lock for bpf_trace_printk and bpf_trace_vprintk helpers. - Extend libbpf's bpf_tracing.h support for tracing arguments of kprobes/uprobes and syscall as a special case. - Significantly reduce the search time for module symbols by livepatch and BPF. - Enable cpumasks to be used as kptrs, which is useful for tracing programs tracking which tasks end up running on which CPUs in different time intervals. - Add support for BPF trampoline on s390x and riscv64. - Add capability to export the XDP features supported by the NIC. - Add __bpf_kfunc tag for marking kernel functions as kfuncs. - Add cgroup.memory=nobpf kernel parameter option to disable BPF memory accounting for container environments. Netfilter: - Remove the CLUSTERIP target. It has been marked as obsolete for years, and we still have WARN splats wrt races of the out-of-band /proc interface installed by this target. - Add 'destroy' commands to nf_tables. They are identical to the existing 'delete' commands, but do not return an error if the referenced object (set, chain, rule...) did not exist. Driver API: - Improve cpumask_local_spread() locality to help NICs set the right IRQ affinity on AMD platforms. - Separate C22 and C45 MDIO bus transactions more clearly. - Introduce new DCB table to control DSCP rewrite on egress. - Support configuration of Physical Layer Collision Avoidance (PLCA) Reconciliation Sublayer (RS) (802.3cg-2019). Modern version of shared medium Ethernet. - Support for MAC Merge layer (IEEE 802.3-2018 clause 99). Allowing preemption of low priority frames by high priority frames. - Add support for controlling MACSec offload using netlink SET. - Rework devlink instance refcounts to allow registration and de-registration under the instance lock. Split the code into multiple files, drop some of the unnecessarily granular locks and factor out common parts of netlink operation handling. - Add TX frame aggregation parameters (for USB drivers). - Add a new attr TCA_EXT_WARN_MSG to report TC (offload) warning messages with notifications for debug. - Allow offloading of UDP NEW connections via act_ct. - Add support for per action HW stats in TC. - Support hardware miss to TC action (continue processing in SW from a specific point in the action chain). - Warn if old Wireless Extension user space interface is used with modern cfg80211/mac80211 drivers. Do not support Wireless Extensions for Wi-Fi 7 devices at all. Everyone should switch to using nl80211 interface instead. - Improve the CAN bit timing configuration. Use extack to return error messages directly to user space, update the SJW handling, including the definition of a new default value that will benefit CAN-FD controllers, by increasing their oscillator tolerance. New hardware / drivers: - Ethernet: - nVidia BlueField-3 support (control traffic driver) - Ethernet support for imx93 SoCs - Motorcomm yt8531 gigabit Ethernet PHY - onsemi NCN26000 10BASE-T1S PHY (with support for PLCA) - Microchip LAN8841 PHY (incl. cable diagnostics and PTP) - Amlogic gxl MDIO mux - WiFi: - RealTek RTL8188EU (rtl8xxxu) - Qualcomm Wi-Fi 7 devices (ath12k) - CAN: - Renesas R-Car V4H Drivers: - Bluetooth: - Set Per Platform Antenna Gain (PPAG) for Intel controllers. - Ethernet NICs: - Intel (1G, igc): - support TSN / Qbv / packet scheduling features of i226 model - Intel (100G, ice): - use GNSS subsystem instead of TTY - multi-buffer XDP support - extend support for GPIO pins to E823 devices - nVidia/Mellanox: - update the shared buffer configuration on PFC commands - implement PTP adjphase function for HW offset control - TC support for Geneve and GRE with VF tunnel offload - more efficient crypto key management method - multi-port eswitch support - Netronome/Corigine: - add DCB IEEE support - support IPsec offloading for NFP3800 - Freescale/NXP (enetc): - support XDP_REDIRECT for XDP non-linear buffers - improve reconfig, avoid link flap and waiting for idle - support MAC Merge layer - Other NICs: - sfc/ef100: add basic devlink support for ef100 - ionic: rx_push mode operation (writing descriptors via MMIO) - bnxt: use the auxiliary bus abstraction for RDMA - r8169: disable ASPM and reset bus in case of tx timeout - cpsw: support QSGMII mode for J721e CPSW9G - cpts: support pulse-per-second output - ngbe: add an mdio bus driver - usbnet: optimize usbnet_bh() by avoiding unnecessary queuing - r8152: handle devices with FW with NCM support - amd-xgbe: support 10Mbps, 2.5GbE speeds and rx-adaptation - virtio-net: support multi buffer XDP - virtio/vsock: replace virtio_vsock_pkt with sk_buff - tsnep: XDP support - Ethernet high-speed switches: - nVidia/Mellanox (mlxsw): - add support for latency TLV (in FW control messages) - Microchip (sparx5): - separate explicit and implicit traffic forwarding rules, make the implicit rules always active - add support for egress DSCP rewrite - IS0 VCAP support (Ingress Classification) - IS2 VCAP filters (protos, L3 addrs, L4 ports, flags, ToS etc.) - ES2 VCAP support (Egress Access Control) - support for Per-Stream Filtering and Policing (802.1Q, 8.6.5.1) - Ethernet embedded switches: - Marvell (mv88e6xxx): - add MAB (port auth) offload support - enable PTP receive for mv88e6390 - NXP (ocelot): - support MAC Merge layer - support for the the vsc7512 internal copper phys - Microchip: - lan9303: convert to PHYLINK - lan966x: support TC flower filter statistics - lan937x: PTP support for KSZ9563/KSZ8563 and LAN937x - lan937x: support Credit Based Shaper configuration - ksz9477: support Energy Efficient Ethernet - other: - qca8k: convert to regmap read/write API, use bulk operations - rswitch: Improve TX timestamp accuracy - Intel WiFi (iwlwifi): - EHT (Wi-Fi 7) rate reporting - STEP equalizer support: transfer some STEP (connection to radio on platforms with integrated wifi) related parameters from the BIOS to the firmware. - Qualcomm 802.11ax WiFi (ath11k): - IPQ5018 support - Fine Timing Measurement (FTM) responder role support - channel 177 support - MediaTek WiFi (mt76): - per-PHY LED support - mt7996: EHT (Wi-Fi 7) support - Wireless Ethernet Dispatch (WED) reset support - switch to using page pool allocator - RealTek WiFi (rtw89): - support new version of Bluetooth co-existance - Mobile: - rmnet: support TX aggregation" * tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next: (1872 commits) page_pool: add a comment explaining the fragment counter usage net: ethtool: fix __ethtool_dev_mm_supported() implementation ethtool: pse-pd: Fix double word in comments xsk: add linux/vmalloc.h to xsk.c sefltests: netdevsim: wait for devlink instance after netns removal selftest: fib_tests: Always cleanup before exit net/mlx5e: Align IPsec ASO result memory to be as required by hardware net/mlx5e: TC, Set CT miss to the specific ct action instance net/mlx5e: Rename CHAIN_TO_REG to MAPPED_OBJ_TO_REG net/mlx5: Refactor tc miss handling to a single function net/mlx5: Kconfig: Make tc offload depend on tc skb extension net/sched: flower: Support hardware miss to tc action net/sched: flower: Move filter handle initialization earlier net/sched: cls_api: Support hardware miss to tc action net/sched: Rename user cookie and act cookie sfc: fix builds without CONFIG_RTC_LIB sfc: clean up some inconsistent indentings net/mlx4_en: Introduce flexible array to silence overflow warning net: lan966x: Fix possible deadlock inside PTP net/ulp: Remove redundant ->clone() test in inet_clone_ulp(). ... --- drivers/platform/surface/surface_acpi_notify.c | 910 +++++++++++++++++++++++++ 1 file changed, 910 insertions(+) create mode 100644 drivers/platform/surface/surface_acpi_notify.c (limited to 'drivers/platform/surface/surface_acpi_notify.c') diff --git a/drivers/platform/surface/surface_acpi_notify.c b/drivers/platform/surface/surface_acpi_notify.c new file mode 100644 index 000000000..897cdd9c3 --- /dev/null +++ b/drivers/platform/surface/surface_acpi_notify.c @@ -0,0 +1,910 @@ +// 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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +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 = ""; \ + 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. */ + 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 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_workqueue(san_wq); + + 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, + }, +}; + +static int __init san_init(void) +{ + int ret; + + san_wq = alloc_workqueue("san_wq", 0, 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 "); +MODULE_DESCRIPTION("Surface ACPI Notify driver for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3