From c167b9c7e3d6131b4a4865c112a3dbc86d2e997d Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Mon, 21 Dec 2020 19:39:51 +0100 Subject: platform/surface: Add Surface Aggregator subsystem Add Surface System Aggregator Module core and Surface Serial Hub driver, required for the embedded controller found on Microsoft Surface devices. The Surface System Aggregator Module (SSAM, SAM or Surface Aggregator) is an embedded controller (EC) found on 4th and later generation Microsoft Surface devices, with the exception of the Surface Go series. This EC provides various functionality, depending on the device in question. This can include battery status and thermal reporting (5th and later generations), but also HID keyboard (6th+) and touchpad input (7th+) on Surface Laptop and Surface Book 3 series devices. This patch provides the basic necessities for communication with the SAM EC on 5th and later generation devices. On these devices, the EC provides an interface that acts as serial device, called the Surface Serial Hub (SSH). 4th generation devices, on which the EC interface is provided via an HID-over-I2C device, are not supported by this patch. Specifically, this patch adds a driver for the SSH device (device HID MSHW0084 in ACPI), as well as a controller structure and associated API. This represents the functional core of the Surface Aggregator kernel subsystem, introduced with this patch, and will be expanded upon in subsequent commits. The SSH driver acts as the main attachment point for this subsystem and sets-up and manages the controller structure. The controller in turn provides a basic communication interface, allowing to send requests from host to EC and receiving the corresponding responses, as well as managing and receiving events, sent from EC to host. It is structured into multiple layers, with the top layer presenting the API used by other kernel drivers and the lower layers modeled after the serial protocol used for communication. Said other drivers are then responsible for providing the (Surface model specific) functionality accessible through the EC (e.g. battery status reporting, thermal information, ...) via said controller structure and API, and will be added in future commits. Signed-off-by: Maximilian Luz Link: https://lore.kernel.org/r/20201221183959.1186143-2-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 546aa66428c9..845a245f562c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11813,6 +11813,14 @@ L: platform-driver-x86@vger.kernel.org S: Supported F: drivers/platform/surface/surfacepro3_button.c +MICROSOFT SURFACE SYSTEM AGGREGATOR SUBSYSTEM +M: Maximilian Luz +S: Maintained +W: https://github.com/linux-surface/surface-aggregator-module +C: irc://chat.freenode.net/##linux-surface +F: drivers/platform/surface/aggregator/ +F: include/linux/surface_aggregator/ + MICROTEK X6 SCANNER M: Oliver Neukum S: Maintained -- cgit v1.2.3 From 8d7792823da4abd799d63aaceb23805203a5419e Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Mon, 21 Dec 2020 19:39:57 +0100 Subject: docs: driver-api: Add Surface Aggregator subsystem documentation Add documentation for the Surface Aggregator subsystem and its client drivers, giving an overview of the subsystem, its use-cases, its internal structure and internal API, as well as its external API for writing client drivers. Signed-off-by: Maximilian Luz Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20201221183959.1186143-8-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- Documentation/driver-api/index.rst | 1 + .../driver-api/surface_aggregator/client-api.rst | 38 ++ .../driver-api/surface_aggregator/client.rst | 393 ++++++++++++++ .../surface_aggregator/clients/index.rst | 10 + .../driver-api/surface_aggregator/index.rst | 21 + .../driver-api/surface_aggregator/internal-api.rst | 67 +++ .../driver-api/surface_aggregator/internal.rst | 577 +++++++++++++++++++++ .../driver-api/surface_aggregator/overview.rst | 77 +++ .../driver-api/surface_aggregator/ssh.rst | 344 ++++++++++++ MAINTAINERS | 1 + 10 files changed, 1529 insertions(+) create mode 100644 Documentation/driver-api/surface_aggregator/client-api.rst create mode 100644 Documentation/driver-api/surface_aggregator/client.rst create mode 100644 Documentation/driver-api/surface_aggregator/clients/index.rst create mode 100644 Documentation/driver-api/surface_aggregator/index.rst create mode 100644 Documentation/driver-api/surface_aggregator/internal-api.rst create mode 100644 Documentation/driver-api/surface_aggregator/internal.rst create mode 100644 Documentation/driver-api/surface_aggregator/overview.rst create mode 100644 Documentation/driver-api/surface_aggregator/ssh.rst (limited to 'MAINTAINERS') diff --git a/Documentation/driver-api/index.rst b/Documentation/driver-api/index.rst index 2456d0a97ed8..9d9af54d68c5 100644 --- a/Documentation/driver-api/index.rst +++ b/Documentation/driver-api/index.rst @@ -99,6 +99,7 @@ available subsections can be seen below. rfkill serial/index sm501 + surface_aggregator/index switchtec sync_file vfio-mediated-device diff --git a/Documentation/driver-api/surface_aggregator/client-api.rst b/Documentation/driver-api/surface_aggregator/client-api.rst new file mode 100644 index 000000000000..8e0b000d0e64 --- /dev/null +++ b/Documentation/driver-api/surface_aggregator/client-api.rst @@ -0,0 +1,38 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +=============================== +Client Driver API Documentation +=============================== + +.. contents:: + :depth: 2 + + +Serial Hub Communication +======================== + +.. kernel-doc:: include/linux/surface_aggregator/serial_hub.h + +.. kernel-doc:: drivers/platform/surface/aggregator/ssh_packet_layer.c + :export: + + +Controller and Core Interface +============================= + +.. kernel-doc:: include/linux/surface_aggregator/controller.h + +.. kernel-doc:: drivers/platform/surface/aggregator/controller.c + :export: + +.. kernel-doc:: drivers/platform/surface/aggregator/core.c + :export: + + +Client Bus and Client Device API +================================ + +.. kernel-doc:: include/linux/surface_aggregator/device.h + +.. kernel-doc:: drivers/platform/surface/aggregator/bus.c + :export: diff --git a/Documentation/driver-api/surface_aggregator/client.rst b/Documentation/driver-api/surface_aggregator/client.rst new file mode 100644 index 000000000000..26d13085a117 --- /dev/null +++ b/Documentation/driver-api/surface_aggregator/client.rst @@ -0,0 +1,393 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +.. |ssam_controller| replace:: :c:type:`struct ssam_controller ` +.. |ssam_device| replace:: :c:type:`struct ssam_device ` +.. |ssam_device_driver| replace:: :c:type:`struct ssam_device_driver ` +.. |ssam_client_bind| replace:: :c:func:`ssam_client_bind` +.. |ssam_client_link| replace:: :c:func:`ssam_client_link` +.. |ssam_get_controller| replace:: :c:func:`ssam_get_controller` +.. |ssam_controller_get| replace:: :c:func:`ssam_controller_get` +.. |ssam_controller_put| replace:: :c:func:`ssam_controller_put` +.. |ssam_device_alloc| replace:: :c:func:`ssam_device_alloc` +.. |ssam_device_add| replace:: :c:func:`ssam_device_add` +.. |ssam_device_remove| replace:: :c:func:`ssam_device_remove` +.. |ssam_device_driver_register| replace:: :c:func:`ssam_device_driver_register` +.. |ssam_device_driver_unregister| replace:: :c:func:`ssam_device_driver_unregister` +.. |module_ssam_device_driver| replace:: :c:func:`module_ssam_device_driver` +.. |SSAM_DEVICE| replace:: :c:func:`SSAM_DEVICE` +.. |ssam_notifier_register| replace:: :c:func:`ssam_notifier_register` +.. |ssam_notifier_unregister| replace:: :c:func:`ssam_notifier_unregister` +.. |ssam_request_sync| replace:: :c:func:`ssam_request_sync` +.. |ssam_event_mask| replace:: :c:type:`enum ssam_event_mask ` + + +====================== +Writing Client Drivers +====================== + +For the API documentation, refer to: + +.. toctree:: + :maxdepth: 2 + + client-api + + +Overview +======== + +Client drivers can be set up in two main ways, depending on how the +corresponding device is made available to the system. We specifically +differentiate between devices that are presented to the system via one of +the conventional ways, e.g. as platform devices via ACPI, and devices that +are non-discoverable and instead need to be explicitly provided by some +other mechanism, as discussed further below. + + +Non-SSAM Client Drivers +======================= + +All communication with the SAM EC is handled via the |ssam_controller| +representing that EC to the kernel. Drivers targeting a non-SSAM device (and +thus not being a |ssam_device_driver|) need to explicitly establish a +connection/relation to that controller. This can be done via the +|ssam_client_bind| function. Said function returns a reference to the SSAM +controller, but, more importantly, also establishes a device link between +client device and controller (this can also be done separate via +|ssam_client_link|). It is important to do this, as it, first, guarantees +that the returned controller is valid for use in the client driver for as +long as this driver is bound to its device, i.e. that the driver gets +unbound before the controller ever becomes invalid, and, second, as it +ensures correct suspend/resume ordering. This setup should be done in the +driver's probe function, and may be used to defer probing in case the SSAM +subsystem is not ready yet, for example: + +.. code-block:: c + + static int client_driver_probe(struct platform_device *pdev) + { + struct ssam_controller *ctrl; + + ctrl = ssam_client_bind(&pdev->dev); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); + + // ... + + return 0; + } + +The controller may be separately obtained via |ssam_get_controller| and its +lifetime be guaranteed via |ssam_controller_get| and |ssam_controller_put|. +Note that none of these functions, however, guarantee that the controller +will not be shut down or suspended. These functions essentially only operate +on the reference, i.e. only guarantee a bare minimum of accessibility +without any guarantees at all on practical operability. + + +Adding SSAM Devices +=================== + +If a device does not already exist/is not already provided via conventional +means, it should be provided as |ssam_device| via the SSAM client device +hub. New devices can be added to this hub by entering their UID into the +corresponding registry. SSAM devices can also be manually allocated via +|ssam_device_alloc|, subsequently to which they have to be added via +|ssam_device_add| and eventually removed via |ssam_device_remove|. By +default, the parent of the device is set to the controller device provided +for allocation, however this may be changed before the device is added. Note +that, when changing the parent device, care must be taken to ensure that the +controller lifetime and suspend/resume ordering guarantees, in the default +setup provided through the parent-child relation, are preserved. If +necessary, by use of |ssam_client_link| as is done for non-SSAM client +drivers and described in more detail above. + +A client device must always be removed by the party which added the +respective device before the controller shuts down. Such removal can be +guaranteed by linking the driver providing the SSAM device to the controller +via |ssam_client_link|, causing it to unbind before the controller driver +unbinds. Client devices registered with the controller as parent are +automatically removed when the controller shuts down, but this should not be +relied upon, especially as this does not extend to client devices with a +different parent. + + +SSAM Client Drivers +=================== + +SSAM client device drivers are, in essence, no different than other device +driver types. They are represented via |ssam_device_driver| and bind to a +|ssam_device| via its UID (:c:type:`struct ssam_device.uid `) +member and the match table +(:c:type:`struct ssam_device_driver.match_table `), +which should be set when declaring the driver struct instance. Refer to the +|SSAM_DEVICE| macro documentation for more details on how to define members +of the driver's match table. + +The UID for SSAM client devices consists of a ``domain``, a ``category``, +a ``target``, an ``instance``, and a ``function``. The ``domain`` is used +differentiate between physical SAM devices +(:c:type:`SSAM_DOMAIN_SERIALHUB `), i.e. devices that can +be accessed via the Surface Serial Hub, and virtual ones +(:c:type:`SSAM_DOMAIN_VIRTUAL `), such as client-device +hubs, that have no real representation on the SAM EC and are solely used on +the kernel/driver-side. For physical devices, ``category`` represents the +target category, ``target`` the target ID, and ``instance`` the instance ID +used to access the physical SAM device. In addition, ``function`` references +a specific device functionality, but has no meaning to the SAM EC. The +(default) name of a client device is generated based on its UID. + +A driver instance can be registered via |ssam_device_driver_register| and +unregistered via |ssam_device_driver_unregister|. For convenience, the +|module_ssam_device_driver| macro may be used to define module init- and +exit-functions registering the driver. + +The controller associated with a SSAM client device can be found in its +:c:type:`struct ssam_device.ctrl ` member. This reference is +guaranteed to be valid for at least as long as the client driver is bound, +but should also be valid for as long as the client device exists. Note, +however, that access outside of the bound client driver must ensure that the +controller device is not suspended while making any requests or +(un-)registering event notifiers (and thus should generally be avoided). This +is guaranteed when the controller is accessed from inside the bound client +driver. + + +Making Synchronous Requests +=========================== + +Synchronous requests are (currently) the main form of host-initiated +communication with the EC. There are a couple of ways to define and execute +such requests, however, most of them boil down to something similar as shown +in the example below. This example defines a write-read request, meaning +that the caller provides an argument to the SAM EC and receives a response. +The caller needs to know the (maximum) length of the response payload and +provide a buffer for it. + +Care must be taken to ensure that any command payload data passed to the SAM +EC is provided in little-endian format and, similarly, any response payload +data received from it is converted from little-endian to host endianness. + +.. code-block:: c + + int perform_request(struct ssam_controller *ctrl, u32 arg, u32 *ret) + { + struct ssam_request rqst; + struct ssam_response resp; + int status; + + /* Convert request argument to little-endian. */ + __le32 arg_le = cpu_to_le32(arg); + __le32 ret_le = cpu_to_le32(0); + + /* + * Initialize request specification. Replace this with your values. + * The rqst.payload field may be NULL if rqst.length is zero, + * indicating that the request does not have any argument. + * + * Note: The request parameters used here are not valid, i.e. + * they do not correspond to an actual SAM/EC request. + */ + rqst.target_category = SSAM_SSH_TC_SAM; + rqst.target_id = 0x01; + rqst.command_id = 0x02; + rqst.instance_id = 0x03; + rqst.flags = SSAM_REQUEST_HAS_RESPONSE; + rqst.length = sizeof(arg_le); + rqst.payload = (u8 *)&arg_le; + + /* Initialize request response. */ + resp.capacity = sizeof(ret_le); + resp.length = 0; + resp.pointer = (u8 *)&ret_le; + + /* + * Perform actual request. The response pointer may be null in case + * the request does not have any response. This must be consistent + * with the SSAM_REQUEST_HAS_RESPONSE flag set in the specification + * above. + */ + status = ssam_request_sync(ctrl, &rqst, &resp); + + /* + * Alternatively use + * + * ssam_request_sync_onstack(ctrl, &rqst, &resp, sizeof(arg_le)); + * + * to perform the request, allocating the message buffer directly + * on the stack as opposed to allocation via kzalloc(). + */ + + /* + * Convert request response back to native format. Note that in the + * error case, this value is not touched by the SSAM core, i.e. + * 'ret_le' will be zero as specified in its initialization. + */ + *ret = le32_to_cpu(ret_le); + + return status; + } + +Note that |ssam_request_sync| in its essence is a wrapper over lower-level +request primitives, which may also be used to perform requests. Refer to its +implementation and documentation for more details. + +An arguably more user-friendly way of defining such functions is by using +one of the generator macros, for example via: + +.. code-block:: c + + SSAM_DEFINE_SYNC_REQUEST_W(__ssam_tmp_perf_mode_set, __le32, { + .target_category = SSAM_SSH_TC_TMP, + .target_id = 0x01, + .command_id = 0x03, + .instance_id = 0x00, + }); + +This example defines a function + +.. code-block:: c + + int __ssam_tmp_perf_mode_set(struct ssam_controller *ctrl, const __le32 *arg); + +executing the specified request, with the controller passed in when calling +said function. In this example, the argument is provided via the ``arg`` +pointer. Note that the generated function allocates the message buffer on +the stack. Thus, if the argument provided via the request is large, these +kinds of macros should be avoided. Also note that, in contrast to the +previous non-macro example, this function does not do any endianness +conversion, which has to be handled by the caller. Apart from those +differences the function generated by the macro is similar to the one +provided in the non-macro example above. + +The full list of such function-generating macros is + +- :c:func:`SSAM_DEFINE_SYNC_REQUEST_N` for requests without return value and + without argument. +- :c:func:`SSAM_DEFINE_SYNC_REQUEST_R` for requests with return value but no + argument. +- :c:func:`SSAM_DEFINE_SYNC_REQUEST_W` for requests without return value but + with argument. + +Refer to their respective documentation for more details. For each one of +these macros, a special variant is provided, which targets request types +applicable to multiple instances of the same device type: + +- :c:func:`SSAM_DEFINE_SYNC_REQUEST_MD_N` +- :c:func:`SSAM_DEFINE_SYNC_REQUEST_MD_R` +- :c:func:`SSAM_DEFINE_SYNC_REQUEST_MD_W` + +The difference of those macros to the previously mentioned versions is, that +the device target and instance IDs are not fixed for the generated function, +but instead have to be provided by the caller of said function. + +Additionally, variants for direct use with client devices, i.e. +|ssam_device|, are also provided. These can, for example, be used as +follows: + +.. code-block:: c + + SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, { + .target_category = SSAM_SSH_TC_BAT, + .command_id = 0x01, + }); + +This invocation of the macro defines a function + +.. code-block:: c + + int ssam_bat_get_sta(struct ssam_device *sdev, __le32 *ret); + +executing the specified request, using the device IDs and controller given +in the client device. The full list of such macros for client devices is: + +- :c:func:`SSAM_DEFINE_SYNC_REQUEST_CL_N` +- :c:func:`SSAM_DEFINE_SYNC_REQUEST_CL_R` +- :c:func:`SSAM_DEFINE_SYNC_REQUEST_CL_W` + + +Handling Events +=============== + +To receive events from the SAM EC, an event notifier must be registered for +the desired event via |ssam_notifier_register|. The notifier must be +unregistered via |ssam_notifier_unregister| once it is not required any +more. + +Event notifiers are registered by providing (at minimum) a callback to call +in case an event has been received, the registry specifying how the event +should be enabled, an event ID specifying for which target category and, +optionally and depending on the registry used, for which instance ID events +should be enabled, and finally, flags describing how the EC will send these +events. If the specific registry does not enable events by instance ID, the +instance ID must be set to zero. Additionally, a priority for the respective +notifier may be specified, which determines its order in relation to any +other notifier registered for the same target category. + +By default, event notifiers will receive all events for the specific target +category, regardless of the instance ID specified when registering the +notifier. The core may be instructed to only call a notifier if the target +ID or instance ID (or both) of the event match the ones implied by the +notifier IDs (in case of target ID, the target ID of the registry), by +providing an event mask (see |ssam_event_mask|). + +In general, the target ID of the registry is also the target ID of the +enabled event (with the notable exception being keyboard input events on the +Surface Laptop 1 and 2, which are enabled via a registry with target ID 1, +but provide events with target ID 2). + +A full example for registering an event notifier and handling received +events is provided below: + +.. code-block:: c + + u32 notifier_callback(struct ssam_event_notifier *nf, + const struct ssam_event *event) + { + int status = ... + + /* Handle the event here ... */ + + /* Convert return value and indicate that we handled the event. */ + return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; + } + + int setup_notifier(struct ssam_device *sdev, + struct ssam_event_notifier *nf) + { + /* Set priority wrt. other handlers of same target category. */ + nf->base.priority = 1; + + /* Set event/notifier callback. */ + nf->base.fn = notifier_callback; + + /* Specify event registry, i.e. how events get enabled/disabled. */ + nf->event.reg = SSAM_EVENT_REGISTRY_KIP; + + /* Specify which event to enable/disable */ + nf->event.id.target_category = sdev->uid.category; + nf->event.id.instance = sdev->uid.instance; + + /* + * Specify for which events the notifier callback gets executed. + * This essentially tells the core if it can skip notifiers that + * don't have target or instance IDs matching those of the event. + */ + nf->event.mask = SSAM_EVENT_MASK_STRICT; + + /* Specify event flags. */ + nf->event.flags = SSAM_EVENT_SEQUENCED; + + return ssam_notifier_register(sdev->ctrl, nf); + } + +Multiple event notifiers can be registered for the same event. The event +handler core takes care of enabling and disabling events when notifiers are +registered and unregistered, by keeping track of how many notifiers for a +specific event (combination of registry, event target category, and event +instance ID) are currently registered. This means that a specific event will +be enabled when the first notifier for it is being registered and disabled +when the last notifier for it is being unregistered. Note that the event +flags are therefore only used on the first registered notifier, however, one +should take care that notifiers for a specific event are always registered +with the same flag and it is considered a bug to do otherwise. diff --git a/Documentation/driver-api/surface_aggregator/clients/index.rst b/Documentation/driver-api/surface_aggregator/clients/index.rst new file mode 100644 index 000000000000..31e026d96102 --- /dev/null +++ b/Documentation/driver-api/surface_aggregator/clients/index.rst @@ -0,0 +1,10 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +=========================== +Client Driver Documentation +=========================== + +This is the documentation for client drivers themselves. Refer to +:doc:`../client` for documentation on how to write client drivers. + +.. Place documentation for individual client drivers here. diff --git a/Documentation/driver-api/surface_aggregator/index.rst b/Documentation/driver-api/surface_aggregator/index.rst new file mode 100644 index 000000000000..6f3e1094904d --- /dev/null +++ b/Documentation/driver-api/surface_aggregator/index.rst @@ -0,0 +1,21 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +======================================= +Surface System Aggregator Module (SSAM) +======================================= + +.. toctree:: + :maxdepth: 2 + + overview + client + clients/index + ssh + internal + +.. only:: subproject and html + + Indices + ======= + + * :ref:`genindex` diff --git a/Documentation/driver-api/surface_aggregator/internal-api.rst b/Documentation/driver-api/surface_aggregator/internal-api.rst new file mode 100644 index 000000000000..639a67b5a392 --- /dev/null +++ b/Documentation/driver-api/surface_aggregator/internal-api.rst @@ -0,0 +1,67 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +========================== +Internal API Documentation +========================== + +.. contents:: + :depth: 2 + + +Packet Transport Layer +====================== + +.. kernel-doc:: drivers/platform/surface/aggregator/ssh_parser.h + :internal: + +.. kernel-doc:: drivers/platform/surface/aggregator/ssh_parser.c + :internal: + +.. kernel-doc:: drivers/platform/surface/aggregator/ssh_msgb.h + :internal: + +.. kernel-doc:: drivers/platform/surface/aggregator/ssh_packet_layer.h + :internal: + +.. kernel-doc:: drivers/platform/surface/aggregator/ssh_packet_layer.c + :internal: + + +Request Transport Layer +======================= + +.. kernel-doc:: drivers/platform/surface/aggregator/ssh_request_layer.h + :internal: + +.. kernel-doc:: drivers/platform/surface/aggregator/ssh_request_layer.c + :internal: + + +Controller +========== + +.. kernel-doc:: drivers/platform/surface/aggregator/controller.h + :internal: + +.. kernel-doc:: drivers/platform/surface/aggregator/controller.c + :internal: + + +Client Device Bus +================= + +.. kernel-doc:: drivers/platform/surface/aggregator/bus.c + :internal: + + +Core +==== + +.. kernel-doc:: drivers/platform/surface/aggregator/core.c + :internal: + + +Trace Helpers +============= + +.. kernel-doc:: drivers/platform/surface/aggregator/trace.h diff --git a/Documentation/driver-api/surface_aggregator/internal.rst b/Documentation/driver-api/surface_aggregator/internal.rst new file mode 100644 index 000000000000..72704734982a --- /dev/null +++ b/Documentation/driver-api/surface_aggregator/internal.rst @@ -0,0 +1,577 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +.. |ssh_ptl| replace:: :c:type:`struct ssh_ptl ` +.. |ssh_ptl_submit| replace:: :c:func:`ssh_ptl_submit` +.. |ssh_ptl_cancel| replace:: :c:func:`ssh_ptl_cancel` +.. |ssh_ptl_shutdown| replace:: :c:func:`ssh_ptl_shutdown` +.. |ssh_ptl_rx_rcvbuf| replace:: :c:func:`ssh_ptl_rx_rcvbuf` +.. |ssh_rtl| replace:: :c:type:`struct ssh_rtl ` +.. |ssh_rtl_submit| replace:: :c:func:`ssh_rtl_submit` +.. |ssh_rtl_cancel| replace:: :c:func:`ssh_rtl_cancel` +.. |ssh_rtl_shutdown| replace:: :c:func:`ssh_rtl_shutdown` +.. |ssh_packet| replace:: :c:type:`struct ssh_packet ` +.. |ssh_packet_get| replace:: :c:func:`ssh_packet_get` +.. |ssh_packet_put| replace:: :c:func:`ssh_packet_put` +.. |ssh_packet_ops| replace:: :c:type:`struct ssh_packet_ops ` +.. |ssh_packet_base_priority| replace:: :c:type:`enum ssh_packet_base_priority ` +.. |ssh_packet_flags| replace:: :c:type:`enum ssh_packet_flags ` +.. |SSH_PACKET_PRIORITY| replace:: :c:func:`SSH_PACKET_PRIORITY` +.. |ssh_frame| replace:: :c:type:`struct ssh_frame ` +.. |ssh_command| replace:: :c:type:`struct ssh_command ` +.. |ssh_request| replace:: :c:type:`struct ssh_request ` +.. |ssh_request_get| replace:: :c:func:`ssh_request_get` +.. |ssh_request_put| replace:: :c:func:`ssh_request_put` +.. |ssh_request_ops| replace:: :c:type:`struct ssh_request_ops ` +.. |ssh_request_init| replace:: :c:func:`ssh_request_init` +.. |ssh_request_flags| replace:: :c:type:`enum ssh_request_flags ` +.. |ssam_controller| replace:: :c:type:`struct ssam_controller ` +.. |ssam_device| replace:: :c:type:`struct ssam_device ` +.. |ssam_device_driver| replace:: :c:type:`struct ssam_device_driver ` +.. |ssam_client_bind| replace:: :c:func:`ssam_client_bind` +.. |ssam_client_link| replace:: :c:func:`ssam_client_link` +.. |ssam_request_sync| replace:: :c:type:`struct ssam_request_sync ` +.. |ssam_event_registry| replace:: :c:type:`struct ssam_event_registry ` +.. |ssam_event_id| replace:: :c:type:`struct ssam_event_id ` +.. |ssam_nf| replace:: :c:type:`struct ssam_nf ` +.. |ssam_nf_refcount_inc| replace:: :c:func:`ssam_nf_refcount_inc` +.. |ssam_nf_refcount_dec| replace:: :c:func:`ssam_nf_refcount_dec` +.. |ssam_notifier_register| replace:: :c:func:`ssam_notifier_register` +.. |ssam_notifier_unregister| replace:: :c:func:`ssam_notifier_unregister` +.. |ssam_cplt| replace:: :c:type:`struct ssam_cplt ` +.. |ssam_event_queue| replace:: :c:type:`struct ssam_event_queue ` +.. |ssam_request_sync_submit| replace:: :c:func:`ssam_request_sync_submit` + +===================== +Core Driver Internals +===================== + +Architectural overview of the Surface System Aggregator Module (SSAM) core +and Surface Serial Hub (SSH) driver. For the API documentation, refer to: + +.. toctree:: + :maxdepth: 2 + + internal-api + + +Overview +======== + +The SSAM core implementation is structured in layers, somewhat following the +SSH protocol structure: + +Lower-level packet transport is implemented in the *packet transport layer +(PTL)*, directly building on top of the serial device (serdev) +infrastructure of the kernel. As the name indicates, this layer deals with +the packet transport logic and handles things like packet validation, packet +acknowledgment (ACKing), packet (retransmission) timeouts, and relaying +packet payloads to higher-level layers. + +Above this sits the *request transport layer (RTL)*. This layer is centered +around command-type packet payloads, i.e. requests (sent from host to EC), +responses of the EC to those requests, and events (sent from EC to host). +It, specifically, distinguishes events from request responses, matches +responses to their corresponding requests, and implements request timeouts. + +The *controller* layer is building on top of this and essentially decides +how request responses and, especially, events are dealt with. It provides an +event notifier system, handles event activation/deactivation, provides a +workqueue for event and asynchronous request completion, and also manages +the message counters required for building command messages (``SEQ``, +``RQID``). This layer basically provides a fundamental interface to the SAM +EC for use in other kernel drivers. + +While the controller layer already provides an interface for other kernel +drivers, the client *bus* extends this interface to provide support for +native SSAM devices, i.e. devices that are not defined in ACPI and not +implemented as platform devices, via |ssam_device| and |ssam_device_driver| +simplify management of client devices and client drivers. + +Refer to :doc:`client` for documentation regarding the client device/driver +API and interface options for other kernel drivers. It is recommended to +familiarize oneself with that chapter and the :doc:`ssh` before continuing +with the architectural overview below. + + +Packet Transport Layer +====================== + +The packet transport layer is represented via |ssh_ptl| and is structured +around the following key concepts: + +Packets +------- + +Packets are the fundamental transmission unit of the SSH protocol. They are +managed by the packet transport layer, which is essentially the lowest layer +of the driver and is built upon by other components of the SSAM core. +Packets to be transmitted by the SSAM core are represented via |ssh_packet| +(in contrast, packets received by the core do not have any specific +structure and are managed entirely via the raw |ssh_frame|). + +This structure contains the required fields to manage the packet inside the +transport layer, as well as a reference to the buffer containing the data to +be transmitted (i.e. the message wrapped in |ssh_frame|). Most notably, it +contains an internal reference count, which is used for managing its +lifetime (accessible via |ssh_packet_get| and |ssh_packet_put|). When this +counter reaches zero, the ``release()`` callback provided to the packet via +its |ssh_packet_ops| reference is executed, which may then deallocate the +packet or its enclosing structure (e.g. |ssh_request|). + +In addition to the ``release`` callback, the |ssh_packet_ops| reference also +provides a ``complete()`` callback, which is run once the packet has been +completed and provides the status of this completion, i.e. zero on success +or a negative errno value in case of an error. Once the packet has been +submitted to the packet transport layer, the ``complete()`` callback is +always guaranteed to be executed before the ``release()`` callback, i.e. the +packet will always be completed, either successfully, with an error, or due +to cancellation, before it will be released. + +The state of a packet is managed via its ``state`` flags +(|ssh_packet_flags|), which also contains the packet type. In particular, +the following bits are noteworthy: + +* ``SSH_PACKET_SF_LOCKED_BIT``: This bit is set when completion, either + through error or success, is imminent. It indicates that no further + references of the packet should be taken and any existing references + should be dropped as soon as possible. The process setting this bit is + responsible for removing any references to this packet from the packet + queue and pending set. + +* ``SSH_PACKET_SF_COMPLETED_BIT``: This bit is set by the process running the + ``complete()`` callback and is used to ensure that this callback only runs + once. + +* ``SSH_PACKET_SF_QUEUED_BIT``: This bit is set when the packet is queued on + the packet queue and cleared when it is dequeued. + +* ``SSH_PACKET_SF_PENDING_BIT``: This bit is set when the packet is added to + the pending set and cleared when it is removed from it. + +Packet Queue +------------ + +The packet queue is the first of the two fundamental collections in the +packet transport layer. It is a priority queue, with priority of the +respective packets based on the packet type (major) and number of tries +(minor). See |SSH_PACKET_PRIORITY| for more details on the priority value. + +All packets to be transmitted by the transport layer must be submitted to +this queue via |ssh_ptl_submit|. Note that this includes control packets +sent by the transport layer itself. Internally, data packets can be +re-submitted to this queue due to timeouts or NAK packets sent by the EC. + +Pending Set +----------- + +The pending set is the second of the two fundamental collections in the +packet transport layer. It stores references to packets that have already +been transmitted, but wait for acknowledgment (e.g. the corresponding ACK +packet) by the EC. + +Note that a packet may both be pending and queued if it has been +re-submitted due to a packet acknowledgment timeout or NAK. On such a +re-submission, packets are not removed from the pending set. + +Transmitter Thread +------------------ + +The transmitter thread is responsible for most of the actual work regarding +packet transmission. In each iteration, it (waits for and) checks if the +next packet on the queue (if any) can be transmitted and, if so, removes it +from the queue and increments its counter for the number of transmission +attempts, i.e. tries. If the packet is sequenced, i.e. requires an ACK by +the EC, the packet is added to the pending set. Next, the packet's data is +submitted to the serdev subsystem. In case of an error or timeout during +this submission, the packet is completed by the transmitter thread with the +status value of the callback set accordingly. In case the packet is +unsequenced, i.e. does not require an ACK by the EC, the packet is completed +with success on the transmitter thread. + +Transmission of sequenced packets is limited by the number of concurrently +pending packets, i.e. a limit on how many packets may be waiting for an ACK +from the EC in parallel. This limit is currently set to one (see :doc:`ssh` +for the reasoning behind this). Control packets (i.e. ACK and NAK) can +always be transmitted. + +Receiver Thread +--------------- + +Any data received from the EC is put into a FIFO buffer for further +processing. This processing happens on the receiver thread. The receiver +thread parses and validates the received message into its |ssh_frame| and +corresponding payload. It prepares and submits the necessary ACK (and on +validation error or invalid data NAK) packets for the received messages. + +This thread also handles further processing, such as matching ACK messages +to the corresponding pending packet (via sequence ID) and completing it, as +well as initiating re-submission of all currently pending packets on +receival of a NAK message (re-submission in case of a NAK is similar to +re-submission due to timeout, see below for more details on that). Note that +the successful completion of a sequenced packet will always run on the +receiver thread (whereas any failure-indicating completion will run on the +process where the failure occurred). + +Any payload data is forwarded via a callback to the next upper layer, i.e. +the request transport layer. + +Timeout Reaper +-------------- + +The packet acknowledgment timeout is a per-packet timeout for sequenced +packets, started when the respective packet begins (re-)transmission (i.e. +this timeout is armed once per transmission attempt on the transmitter +thread). It is used to trigger re-submission or, when the number of tries +has been exceeded, cancellation of the packet in question. + +This timeout is handled via a dedicated reaper task, which is essentially a +work item (re-)scheduled to run when the next packet is set to time out. The +work item then checks the set of pending packets for any packets that have +exceeded the timeout and, if there are any remaining packets, re-schedules +itself to the next appropriate point in time. + +If a timeout has been detected by the reaper, the packet will either be +re-submitted if it still has some remaining tries left, or completed with +``-ETIMEDOUT`` as status if not. Note that re-submission, in this case and +triggered by receival of a NAK, means that the packet is added to the queue +with a now incremented number of tries, yielding a higher priority. The +timeout for the packet will be disabled until the next transmission attempt +and the packet remains on the pending set. + +Note that due to transmission and packet acknowledgment timeouts, the packet +transport layer is always guaranteed to make progress, if only through +timing out packets, and will never fully block. + +Concurrency and Locking +----------------------- + +There are two main locks in the packet transport layer: One guarding access +to the packet queue and one guarding access to the pending set. These +collections may only be accessed and modified under the respective lock. If +access to both collections is needed, the pending lock must be acquired +before the queue lock to avoid deadlocks. + +In addition to guarding the collections, after initial packet submission +certain packet fields may only be accessed under one of the locks. +Specifically, the packet priority must only be accessed while holding the +queue lock and the packet timestamp must only be accessed while holding the +pending lock. + +Other parts of the packet transport layer are guarded independently. State +flags are managed by atomic bit operations and, if necessary, memory +barriers. Modifications to the timeout reaper work item and expiration date +are guarded by their own lock. + +The reference of the packet to the packet transport layer (``ptl``) is +somewhat special. It is either set when the upper layer request is submitted +or, if there is none, when the packet is first submitted. After it is set, +it will not change its value. Functions that may run concurrently with +submission, i.e. cancellation, can not rely on the ``ptl`` reference to be +set. Access to it in these functions is guarded by ``READ_ONCE()``, whereas +setting ``ptl`` is equally guarded with ``WRITE_ONCE()`` for symmetry. + +Some packet fields may be read outside of the respective locks guarding +them, specifically priority and state for tracing. In those cases, proper +access is ensured by employing ``WRITE_ONCE()`` and ``READ_ONCE()``. Such +read-only access is only allowed when stale values are not critical. + +With respect to the interface for higher layers, packet submission +(|ssh_ptl_submit|), packet cancellation (|ssh_ptl_cancel|), data receival +(|ssh_ptl_rx_rcvbuf|), and layer shutdown (|ssh_ptl_shutdown|) may always be +executed concurrently with respect to each other. Note that packet +submission may not run concurrently with itself for the same packet. +Equally, shutdown and data receival may also not run concurrently with +themselves (but may run concurrently with each other). + + +Request Transport Layer +======================= + +The request transport layer is represented via |ssh_rtl| and builds on top +of the packet transport layer. It deals with requests, i.e. SSH packets sent +by the host containing a |ssh_command| as frame payload. This layer +separates responses to requests from events, which are also sent by the EC +via a |ssh_command| payload. While responses are handled in this layer, +events are relayed to the next upper layer, i.e. the controller layer, via +the corresponding callback. The request transport layer is structured around +the following key concepts: + +Request +------- + +Requests are packets with a command-type payload, sent from host to EC to +query data from or trigger an action on it (or both simultaneously). They +are represented by |ssh_request|, wrapping the underlying |ssh_packet| +storing its message data (i.e. SSH frame with command payload). Note that +all top-level representations, e.g. |ssam_request_sync| are built upon this +struct. + +As |ssh_request| extends |ssh_packet|, its lifetime is also managed by the +reference counter inside the packet struct (which can be accessed via +|ssh_request_get| and |ssh_request_put|). Once the counter reaches zero, the +``release()`` callback of the |ssh_request_ops| reference of the request is +called. + +Requests can have an optional response that is equally sent via a SSH +message with command-type payload (from EC to host). The party constructing +the request must know if a response is expected and mark this in the request +flags provided to |ssh_request_init|, so that the request transport layer +can wait for this response. + +Similar to |ssh_packet|, |ssh_request| also has a ``complete()`` callback +provided via its request ops reference and is guaranteed to be completed +before it is released once it has been submitted to the request transport +layer via |ssh_rtl_submit|. For a request without a response, successful +completion will occur once the underlying packet has been successfully +transmitted by the packet transport layer (i.e. from within the packet +completion callback). For a request with response, successful completion +will occur once the response has been received and matched to the request +via its request ID (which happens on the packet layer's data-received +callback running on the receiver thread). If the request is completed with +an error, the status value will be set to the corresponding (negative) errno +value. + +The state of a request is again managed via its ``state`` flags +(|ssh_request_flags|), which also encode the request type. In particular, +the following bits are noteworthy: + +* ``SSH_REQUEST_SF_LOCKED_BIT``: This bit is set when completion, either + through error or success, is imminent. It indicates that no further + references of the request should be taken and any existing references + should be dropped as soon as possible. The process setting this bit is + responsible for removing any references to this request from the request + queue and pending set. + +* ``SSH_REQUEST_SF_COMPLETED_BIT``: This bit is set by the process running the + ``complete()`` callback and is used to ensure that this callback only runs + once. + +* ``SSH_REQUEST_SF_QUEUED_BIT``: This bit is set when the request is queued on + the request queue and cleared when it is dequeued. + +* ``SSH_REQUEST_SF_PENDING_BIT``: This bit is set when the request is added to + the pending set and cleared when it is removed from it. + +Request Queue +------------- + +The request queue is the first of the two fundamental collections in the +request transport layer. In contrast to the packet queue of the packet +transport layer, it is not a priority queue and the simple first come first +serve principle applies. + +All requests to be transmitted by the request transport layer must be +submitted to this queue via |ssh_rtl_submit|. Once submitted, requests may +not be re-submitted, and will not be re-submitted automatically on timeout. +Instead, the request is completed with a timeout error. If desired, the +caller can create and submit a new request for another try, but it must not +submit the same request again. + +Pending Set +----------- + +The pending set is the second of the two fundamental collections in the +request transport layer. This collection stores references to all pending +requests, i.e. requests awaiting a response from the EC (similar to what the +pending set of the packet transport layer does for packets). + +Transmitter Task +---------------- + +The transmitter task is scheduled when a new request is available for +transmission. It checks if the next request on the request queue can be +transmitted and, if so, submits its underlying packet to the packet +transport layer. This check ensures that only a limited number of +requests can be pending, i.e. waiting for a response, at the same time. If +the request requires a response, the request is added to the pending set +before its packet is submitted. + +Packet Completion Callback +-------------------------- + +The packet completion callback is executed once the underlying packet of a +request has been completed. In case of an error completion, the +corresponding request is completed with the error value provided in this +callback. + +On successful packet completion, further processing depends on the request. +If the request expects a response, it is marked as transmitted and the +request timeout is started. If the request does not expect a response, it is +completed with success. + +Data-Received Callback +---------------------- + +The data received callback notifies the request transport layer of data +being received by the underlying packet transport layer via a data-type +frame. In general, this is expected to be a command-type payload. + +If the request ID of the command is one of the request IDs reserved for +events (one to ``SSH_NUM_EVENTS``, inclusively), it is forwarded to the +event callback registered in the request transport layer. If the request ID +indicates a response to a request, the respective request is looked up in +the pending set and, if found and marked as transmitted, completed with +success. + +Timeout Reaper +-------------- + +The request-response-timeout is a per-request timeout for requests expecting +a response. It is used to ensure that a request does not wait indefinitely +on a response from the EC and is started after the underlying packet has +been successfully completed. + +This timeout is, similar to the packet acknowledgment timeout on the packet +transport layer, handled via a dedicated reaper task. This task is +essentially a work-item (re-)scheduled to run when the next request is set +to time out. The work item then scans the set of pending requests for any +requests that have timed out and completes them with ``-ETIMEDOUT`` as +status. Requests will not be re-submitted automatically. Instead, the issuer +of the request must construct and submit a new request, if so desired. + +Note that this timeout, in combination with packet transmission and +acknowledgment timeouts, guarantees that the request layer will always make +progress, even if only through timing out packets, and never fully block. + +Concurrency and Locking +----------------------- + +Similar to the packet transport layer, there are two main locks in the +request transport layer: One guarding access to the request queue and one +guarding access to the pending set. These collections may only be accessed +and modified under the respective lock. + +Other parts of the request transport layer are guarded independently. State +flags are (again) managed by atomic bit operations and, if necessary, memory +barriers. Modifications to the timeout reaper work item and expiration date +are guarded by their own lock. + +Some request fields may be read outside of the respective locks guarding +them, specifically the state for tracing. In those cases, proper access is +ensured by employing ``WRITE_ONCE()`` and ``READ_ONCE()``. Such read-only +access is only allowed when stale values are not critical. + +With respect to the interface for higher layers, request submission +(|ssh_rtl_submit|), request cancellation (|ssh_rtl_cancel|), and layer +shutdown (|ssh_rtl_shutdown|) may always be executed concurrently with +respect to each other. Note that request submission may not run concurrently +with itself for the same request (and also may only be called once per +request). Equally, shutdown may also not run concurrently with itself. + + +Controller Layer +================ + +The controller layer extends on the request transport layer to provide an +easy-to-use interface for client drivers. It is represented by +|ssam_controller| and the SSH driver. While the lower level transport layers +take care of transmitting and handling packets and requests, the controller +layer takes on more of a management role. Specifically, it handles device +initialization, power management, and event handling, including event +delivery and registration via the (event) completion system (|ssam_cplt|). + +Event Registration +------------------ + +In general, an event (or rather a class of events) has to be explicitly +requested by the host before the EC will send it (HID input events seem to +be the exception). This is done via an event-enable request (similarly, +events should be disabled via an event-disable request once no longer +desired). + +The specific request used to enable (or disable) an event is given via an +event registry, i.e. the governing authority of this event (so to speak), +represented by |ssam_event_registry|. As parameters to this request, the +target category and, depending on the event registry, instance ID of the +event to be enabled must be provided. This (optional) instance ID must be +zero if the registry does not use it. Together, target category and instance +ID form the event ID, represented by |ssam_event_id|. In short, both, event +registry and event ID, are required to uniquely identify a respective class +of events. + +Note that a further *request ID* parameter must be provided for the +enable-event request. This parameter does not influence the class of events +being enabled, but instead is set as the request ID (RQID) on each event of +this class sent by the EC. It is used to identify events (as a limited +number of request IDs is reserved for use in events only, specifically one +to ``SSH_NUM_EVENTS`` inclusively) and also map events to their specific +class. Currently, the controller always sets this parameter to the target +category specified in |ssam_event_id|. + +As multiple client drivers may rely on the same (or overlapping) classes of +events and enable/disable calls are strictly binary (i.e. on/off), the +controller has to manage access to these events. It does so via reference +counting, storing the counter inside an RB-tree based mapping with event +registry and ID as key (there is no known list of valid event registry and +event ID combinations). See |ssam_nf|, |ssam_nf_refcount_inc|, and +|ssam_nf_refcount_dec| for details. + +This management is done together with notifier registration (described in +the next section) via the top-level |ssam_notifier_register| and +|ssam_notifier_unregister| functions. + +Event Delivery +-------------- + +To receive events, a client driver has to register an event notifier via +|ssam_notifier_register|. This increments the reference counter for that +specific class of events (as detailed in the previous section), enables the +class on the EC (if it has not been enabled already), and installs the +provided notifier callback. + +Notifier callbacks are stored in lists, with one (RCU) list per target +category (provided via the event ID; NB: there is a fixed known number of +target categories). There is no known association from the combination of +event registry and event ID to the command data (target ID, target category, +command ID, and instance ID) that can be provided by an event class, apart +from target category and instance ID given via the event ID. + +Note that due to the way notifiers are (or rather have to be) stored, client +drivers may receive events that they have not requested and need to account +for them. Specifically, they will, by default, receive all events from the +same target category. To simplify dealing with this, filtering of events by +target ID (provided via the event registry) and instance ID (provided via +the event ID) can be requested when registering a notifier. This filtering +is applied when iterating over the notifiers at the time they are executed. + +All notifier callbacks are executed on a dedicated workqueue, the so-called +completion workqueue. After an event has been received via the callback +installed in the request layer (running on the receiver thread of the packet +transport layer), it will be put on its respective event queue +(|ssam_event_queue|). From this event queue the completion work item of that +queue (running on the completion workqueue) will pick up the event and +execute the notifier callback. This is done to avoid blocking on the +receiver thread. + +There is one event queue per combination of target ID and target category. +This is done to ensure that notifier callbacks are executed in sequence for +events of the same target ID and target category. Callbacks can be executed +in parallel for events with a different combination of target ID and target +category. + +Concurrency and Locking +----------------------- + +Most of the concurrency related safety guarantees of the controller are +provided by the lower-level request transport layer. In addition to this, +event (un-)registration is guarded by its own lock. + +Access to the controller state is guarded by the state lock. This lock is a +read/write semaphore. The reader part can be used to ensure that the state +does not change while functions depending on the state to stay the same +(e.g. |ssam_notifier_register|, |ssam_notifier_unregister|, +|ssam_request_sync_submit|, and derivatives) are executed and this guarantee +is not already provided otherwise (e.g. through |ssam_client_bind| or +|ssam_client_link|). The writer part guards any transitions that will change +the state, i.e. initialization, destruction, suspension, and resumption. + +The controller state may be accessed (read-only) outside the state lock for +smoke-testing against invalid API usage (e.g. in |ssam_request_sync_submit|). +Note that such checks are not supposed to (and will not) protect against all +invalid usages, but rather aim to help catch them. In those cases, proper +variable access is ensured by employing ``WRITE_ONCE()`` and ``READ_ONCE()``. + +Assuming any preconditions on the state not changing have been satisfied, +all non-initialization and non-shutdown functions may run concurrently with +each other. This includes |ssam_notifier_register|, |ssam_notifier_unregister|, +|ssam_request_sync_submit|, as well as all functions building on top of those. diff --git a/Documentation/driver-api/surface_aggregator/overview.rst b/Documentation/driver-api/surface_aggregator/overview.rst new file mode 100644 index 000000000000..1e9d57e50063 --- /dev/null +++ b/Documentation/driver-api/surface_aggregator/overview.rst @@ -0,0 +1,77 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +======== +Overview +======== + +The Surface/System Aggregator Module (SAM, SSAM) is an (arguably *the*) +embedded controller (EC) on Microsoft Surface devices. It has been originally +introduced on 4th generation devices (Surface Pro 4, Surface Book 1), but +its responsibilities and feature-set have since been expanded significantly +with the following generations. + + +Features and Integration +======================== + +Not much is currently known about SAM on 4th generation devices (Surface Pro +4, Surface Book 1), due to the use of a different communication interface +between host and EC (as detailed below). On 5th (Surface Pro 2017, Surface +Book 2, Surface Laptop 1) and later generation devices, SAM is responsible +for providing battery information (both current status and static values, +such as maximum capacity etc.), as well as an assortment of temperature +sensors (e.g. skin temperature) and cooling/performance-mode setting to the +host. On the Surface Book 2, specifically, it additionally provides an +interface for properly handling clipboard detachment (i.e. separating the +display part from the keyboard part of the device), on the Surface Laptop 1 +and 2 it is required for keyboard HID input. This HID subsystem has been +restructured for 7th generation devices and on those, specifically Surface +Laptop 3 and Surface Book 3, is responsible for all major HID input (i.e. +keyboard and touchpad). + +While features have not changed much on a coarse level since the 5th +generation, internal interfaces have undergone some rather large changes. On +5th and 6th generation devices, both battery and temperature information is +exposed to ACPI via a shim driver (referred to as Surface ACPI Notify, or +SAN), translating ACPI generic serial bus write-/read-accesses to SAM +requests. On 7th generation devices, this additional layer is gone and these +devices require a driver hooking directly into the SAM interface. Equally, +on newer generations, less devices are declared in ACPI, making them a bit +harder to discover and requiring us to hard-code a sort of device registry. +Due to this, a SSAM bus and subsystem with client devices +(:c:type:`struct ssam_device `) has been implemented. + + +Communication +============= + +The type of communication interface between host and EC depends on the +generation of the Surface device. On 4th generation devices, host and EC +communicate via HID, specifically using a HID-over-I2C device, whereas on +5th and later generations, communication takes place via a USART serial +device. In accordance to the drivers found on other operating systems, we +refer to the serial device and its driver as Surface Serial Hub (SSH). When +needed, we differentiate between both types of SAM by referring to them as +SAM-over-SSH and SAM-over-HID. + +Currently, this subsystem only supports SAM-over-SSH. The SSH communication +interface is described in more detail below. The HID interface has not been +reverse engineered yet and it is, at the moment, unclear how many (and +which) concepts of the SSH interface detailed below can be transferred to +it. + +Surface Serial Hub +------------------ + +As already elaborated above, the Surface Serial Hub (SSH) is the +communication interface for SAM on 5th- and all later-generation Surface +devices. On the highest level, communication can be separated into two main +types: Requests, messages sent from host to EC that may trigger a direct +response from the EC (explicitly associated with the request), and events +(sometimes also referred to as notifications), sent from EC to host without +being a direct response to a previous request. We may also refer to requests +without response as commands. In general, events need to be enabled via one +of multiple dedicated requests before they are sent by the EC. + +See :doc:`ssh` for a more technical protocol documentation and +:doc:`internal` for an overview of the internal driver architecture. diff --git a/Documentation/driver-api/surface_aggregator/ssh.rst b/Documentation/driver-api/surface_aggregator/ssh.rst new file mode 100644 index 000000000000..bf007d6c9873 --- /dev/null +++ b/Documentation/driver-api/surface_aggregator/ssh.rst @@ -0,0 +1,344 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +.. |u8| replace:: :c:type:`u8 ` +.. |u16| replace:: :c:type:`u16 ` +.. |TYPE| replace:: ``TYPE`` +.. |LEN| replace:: ``LEN`` +.. |SEQ| replace:: ``SEQ`` +.. |SYN| replace:: ``SYN`` +.. |NAK| replace:: ``NAK`` +.. |ACK| replace:: ``ACK`` +.. |DATA| replace:: ``DATA`` +.. |DATA_SEQ| replace:: ``DATA_SEQ`` +.. |DATA_NSQ| replace:: ``DATA_NSQ`` +.. |TC| replace:: ``TC`` +.. |TID| replace:: ``TID`` +.. |IID| replace:: ``IID`` +.. |RQID| replace:: ``RQID`` +.. |CID| replace:: ``CID`` + +=========================== +Surface Serial Hub Protocol +=========================== + +The Surface Serial Hub (SSH) is the central communication interface for the +embedded Surface Aggregator Module controller (SAM or EC), found on newer +Surface generations. We will refer to this protocol and interface as +SAM-over-SSH, as opposed to SAM-over-HID for the older generations. + +On Surface devices with SAM-over-SSH, SAM is connected to the host via UART +and defined in ACPI as device with ID ``MSHW0084``. On these devices, +significant functionality is provided via SAM, including access to battery +and power information and events, thermal read-outs and events, and many +more. For Surface Laptops, keyboard input is handled via HID directed +through SAM, on the Surface Laptop 3 and Surface Book 3 this also includes +touchpad input. + +Note that the standard disclaimer for this subsystem also applies to this +document: All of this has been reverse-engineered and may thus be erroneous +and/or incomplete. + +All CRCs used in the following are two-byte ``crc_ccitt_false(0xffff, ...)``. +All multi-byte values are little-endian, there is no implicit padding between +values. + + +SSH Packet Protocol: Definitions +================================ + +The fundamental communication unit of the SSH protocol is a frame +(:c:type:`struct ssh_frame `). A frame consists of the following +fields, packed together and in order: + +.. flat-table:: SSH Frame + :widths: 1 1 4 + :header-rows: 1 + + * - Field + - Type + - Description + + * - |TYPE| + - |u8| + - Type identifier of the frame. + + * - |LEN| + - |u16| + - Length of the payload associated with the frame. + + * - |SEQ| + - |u8| + - Sequence ID (see explanation below). + +Each frame structure is followed by a CRC over this structure. The CRC over +the frame structure (|TYPE|, |LEN|, and |SEQ| fields) is placed directly +after the frame structure and before the payload. The payload is followed by +its own CRC (over all payload bytes). If the payload is not present (i.e. +the frame has ``LEN=0``), the CRC of the payload is still present and will +evaluate to ``0xffff``. The |LEN| field does not include any of the CRCs, it +equals the number of bytes inbetween the CRC of the frame and the CRC of the +payload. + +Additionally, the following fixed two-byte sequences are used: + +.. flat-table:: SSH Byte Sequences + :widths: 1 1 4 + :header-rows: 1 + + * - Name + - Value + - Description + + * - |SYN| + - ``[0xAA, 0x55]`` + - Synchronization bytes. + +A message consists of |SYN|, followed by the frame (|TYPE|, |LEN|, |SEQ| and +CRC) and, if specified in the frame (i.e. ``LEN > 0``), payload bytes, +followed finally, regardless if the payload is present, the payload CRC. The +messages corresponding to an exchange are, in part, identified by having the +same sequence ID (|SEQ|), stored inside the frame (more on this in the next +section). The sequence ID is a wrapping counter. + +A frame can have the following types +(:c:type:`enum ssh_frame_type `): + +.. flat-table:: SSH Frame Types + :widths: 1 1 4 + :header-rows: 1 + + * - Name + - Value + - Short Description + + * - |NAK| + - ``0x04`` + - Sent on error in previously received message. + + * - |ACK| + - ``0x40`` + - Sent to acknowledge receival of |DATA| frame. + + * - |DATA_SEQ| + - ``0x80`` + - Sent to transfer data. Sequenced. + + * - |DATA_NSQ| + - ``0x00`` + - Same as |DATA_SEQ|, but does not need to be ACKed. + +Both |NAK|- and |ACK|-type frames are used to control flow of messages and +thus do not carry a payload. |DATA_SEQ|- and |DATA_NSQ|-type frames on the +other hand must carry a payload. The flow sequence and interaction of +different frame types will be described in more depth in the next section. + + +SSH Packet Protocol: Flow Sequence +================================== + +Each exchange begins with |SYN|, followed by a |DATA_SEQ|- or +|DATA_NSQ|-type frame, followed by its CRC, payload, and payload CRC. In +case of a |DATA_NSQ|-type frame, the exchange is then finished. In case of a +|DATA_SEQ|-type frame, the receiving party has to acknowledge receival of +the frame by responding with a message containing an |ACK|-type frame with +the same sequence ID of the |DATA| frame. In other words, the sequence ID of +the |ACK| frame specifies the |DATA| frame to be acknowledged. In case of an +error, e.g. an invalid CRC, the receiving party responds with a message +containing an |NAK|-type frame. As the sequence ID of the previous data +frame, for which an error is indicated via the |NAK| frame, cannot be relied +upon, the sequence ID of the |NAK| frame should not be used and is set to +zero. After receival of an |NAK| frame, the sending party should re-send all +outstanding (non-ACKed) messages. + +Sequence IDs are not synchronized between the two parties, meaning that they +are managed independently for each party. Identifying the messages +corresponding to a single exchange thus relies on the sequence ID as well as +the type of the message, and the context. Specifically, the sequence ID is +used to associate an ``ACK`` with its ``DATA_SEQ``-type frame, but not +``DATA_SEQ``- or ``DATA_NSQ``-type frames with other ``DATA``- type frames. + +An example exchange might look like this: + +:: + + tx: -- SYN FRAME(D) CRC(F) PAYLOAD CRC(P) ----------------------------- + rx: ------------------------------------- SYN FRAME(A) CRC(F) CRC(P) -- + +where both frames have the same sequence ID (``SEQ``). Here, ``FRAME(D)`` +indicates a |DATA_SEQ|-type frame, ``FRAME(A)`` an ``ACK``-type frame, +``CRC(F)`` the CRC over the previous frame, ``CRC(P)`` the CRC over the +previous payload. In case of an error, the exchange would look like this: + +:: + + tx: -- SYN FRAME(D) CRC(F) PAYLOAD CRC(P) ----------------------------- + rx: ------------------------------------- SYN FRAME(N) CRC(F) CRC(P) -- + +upon which the sender should re-send the message. ``FRAME(N)`` indicates an +|NAK|-type frame. Note that the sequence ID of the |NAK|-type frame is fixed +to zero. For |DATA_NSQ|-type frames, both exchanges are the same: + +:: + + tx: -- SYN FRAME(DATA_NSQ) CRC(F) PAYLOAD CRC(P) ---------------------- + rx: ------------------------------------------------------------------- + +Here, an error can be detected, but not corrected or indicated to the +sending party. These exchanges are symmetric, i.e. switching ``rx`` and +``tx`` results again in a valid exchange. Currently, no longer exchanges are +known. + + +Commands: Requests, Responses, and Events +========================================= + +Commands are sent as payload inside a data frame. Currently, this is the +only known payload type of |DATA| frames, with a payload-type value of +``0x80`` (:c:type:`SSH_PLD_TYPE_CMD `). + +The command-type payload (:c:type:`struct ssh_command `) +consists of an eight-byte command structure, followed by optional and +variable length command data. The length of this optional data is derived +from the frame payload length given in the corresponding frame, i.e. it is +``frame.len - sizeof(struct ssh_command)``. The command struct contains the +following fields, packed together and in order: + +.. flat-table:: SSH Command + :widths: 1 1 4 + :header-rows: 1 + + * - Field + - Type + - Description + + * - |TYPE| + - |u8| + - Type of the payload. For commands always ``0x80``. + + * - |TC| + - |u8| + - Target category. + + * - |TID| (out) + - |u8| + - Target ID for outgoing (host to EC) commands. + + * - |TID| (in) + - |u8| + - Target ID for incoming (EC to host) commands. + + * - |IID| + - |u8| + - Instance ID. + + * - |RQID| + - |u16| + - Request ID. + + * - |CID| + - |u8| + - Command ID. + +The command struct and data, in general, does not contain any failure +detection mechanism (e.g. CRCs), this is solely done on the frame level. + +Command-type payloads are used by the host to send commands and requests to +the EC as well as by the EC to send responses and events back to the host. +We differentiate between requests (sent by the host), responses (sent by the +EC in response to a request), and events (sent by the EC without a preceding +request). + +Commands and events are uniquely identified by their target category +(``TC``) and command ID (``CID``). The target category specifies a general +category for the command (e.g. system in general, vs. battery and AC, vs. +temperature, and so on), while the command ID specifies the command inside +that category. Only the combination of |TC| + |CID| is unique. Additionally, +commands have an instance ID (``IID``), which is used to differentiate +between different sub-devices. For example ``TC=3`` ``CID=1`` is a +request to get the temperature on a thermal sensor, where |IID| specifies +the respective sensor. If the instance ID is not used, it should be set to +zero. If instance IDs are used, they, in general, start with a value of one, +whereas zero may be used for instance independent queries, if applicable. A +response to a request should have the same target category, command ID, and +instance ID as the corresponding request. + +Responses are matched to their corresponding request via the request ID +(``RQID``) field. This is a 16 bit wrapping counter similar to the sequence +ID on the frames. Note that the sequence ID of the frames for a +request-response pair does not match. Only the request ID has to match. +Frame-protocol wise these are two separate exchanges, and may even be +separated, e.g. by an event being sent after the request but before the +response. Not all commands produce a response, and this is not detectable by +|TC| + |CID|. It is the responsibility of the issuing party to wait for a +response (or signal this to the communication framework, as is done in +SAN/ACPI via the ``SNC`` flag). + +Events are identified by unique and reserved request IDs. These IDs should +not be used by the host when sending a new request. They are used on the +host to, first, detect events and, second, match them with a registered +event handler. Request IDs for events are chosen by the host and directed to +the EC when setting up and enabling an event source (via the +enable-event-source request). The EC then uses the specified request ID for +events sent from the respective source. Note that an event should still be +identified by its target category, command ID, and, if applicable, instance +ID, as a single event source can send multiple different event types. In +general, however, a single target category should map to a single reserved +event request ID. + +Furthermore, requests, responses, and events have an associated target ID +(``TID``). This target ID is split into output (host to EC) and input (EC to +host) fields, with the respecting other field (e.g. output field on incoming +messages) set to zero. Two ``TID`` values are known: Primary (``0x01``) and +secondary (``0x02``). In general, the response to a request should have the +same ``TID`` value, however, the field (output vs. input) should be used in +accordance to the direction in which the response is sent (i.e. on the input +field, as responses are generally sent from the EC to the host). + +Note that, even though requests and events should be uniquely identifiable +by target category and command ID alone, the EC may require specific +target ID and instance ID values to accept a command. A command that is +accepted for ``TID=1``, for example, may not be accepted for ``TID=2`` +and vice versa. + + +Limitations and Observations +============================ + +The protocol can, in theory, handle up to ``U8_MAX`` frames in parallel, +with up to ``U16_MAX`` pending requests (neglecting request IDs reserved for +events). In practice, however, this is more limited. From our testing +(although via a python and thus a user-space program), it seems that the EC +can handle up to four requests (mostly) reliably in parallel at a certain +time. With five or more requests in parallel, consistent discarding of +commands (ACKed frame but no command response) has been observed. For five +simultaneous commands, this reproducibly resulted in one command being +dropped and four commands being handled. + +However, it has also been noted that, even with three requests in parallel, +occasional frame drops happen. Apart from this, with a limit of three +pending requests, no dropped commands (i.e. command being dropped but frame +carrying command being ACKed) have been observed. In any case, frames (and +possibly also commands) should be re-sent by the host if a certain timeout +is exceeded. This is done by the EC for frames with a timeout of one second, +up to two re-tries (i.e. three transmissions in total). The limit of +re-tries also applies to received NAKs, and, in a worst case scenario, can +lead to entire messages being dropped. + +While this also seems to work fine for pending data frames as long as no +transmission failures occur, implementation and handling of these seems to +depend on the assumption that there is only one non-acknowledged data frame. +In particular, the detection of repeated frames relies on the last sequence +number. This means that, if a frame that has been successfully received by +the EC is sent again, e.g. due to the host not receiving an |ACK|, the EC +will only detect this if it has the sequence ID of the last frame received +by the EC. As an example: Sending two frames with ``SEQ=0`` and ``SEQ=1`` +followed by a repetition of ``SEQ=0`` will not detect the second ``SEQ=0`` +frame as such, and thus execute the command in this frame each time it has +been received, i.e. twice in this example. Sending ``SEQ=0``, ``SEQ=1`` and +then repeating ``SEQ=1`` will detect the second ``SEQ=1`` as repetition of +the first one and ignore it, thus executing the contained command only once. + +In conclusion, this suggests a limit of at most one pending un-ACKed frame +(per party, effectively leading to synchronous communication regarding +frames) and at most three pending commands. The limit to synchronous frame +transfers seems to be consistent with behavior observed on Windows. diff --git a/MAINTAINERS b/MAINTAINERS index 845a245f562c..41f082551942 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11818,6 +11818,7 @@ M: Maximilian Luz S: Maintained 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: include/linux/surface_aggregator/ -- cgit v1.2.3 From 178f6ab77e617c984d6520b92e747075a12676ff Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Mon, 21 Dec 2020 19:39:58 +0100 Subject: platform/surface: Add Surface Aggregator user-space interface Add a misc-device providing user-space access to the Surface Aggregator EC, mainly intended for debugging, testing, and reverse-engineering. This interface gives user-space applications the ability to send requests to the EC and receive the corresponding responses. The device-file is managed by a pseudo platform-device and corresponding driver to avoid dependence on the dedicated bus, allowing it to be loaded in a minimal configuration. A python library and scripts to access this device can be found at [1]. [1]: https://github.com/linux-surface/surface-aggregator-module/tree/master/scripts/ssam Signed-off-by: Maximilian Luz Link: https://lore.kernel.org/r/20201221183959.1186143-9-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- .../driver-api/surface_aggregator/clients/cdev.rst | 87 ++++++ .../surface_aggregator/clients/index.rst | 12 +- Documentation/userspace-api/ioctl/ioctl-number.rst | 2 + MAINTAINERS | 2 + drivers/platform/surface/Kconfig | 17 ++ drivers/platform/surface/Makefile | 1 + drivers/platform/surface/surface_aggregator_cdev.c | 303 +++++++++++++++++++++ include/uapi/linux/surface_aggregator/cdev.h | 78 ++++++ 8 files changed, 501 insertions(+), 1 deletion(-) create mode 100644 Documentation/driver-api/surface_aggregator/clients/cdev.rst create mode 100644 drivers/platform/surface/surface_aggregator_cdev.c create mode 100644 include/uapi/linux/surface_aggregator/cdev.h (limited to 'MAINTAINERS') diff --git a/Documentation/driver-api/surface_aggregator/clients/cdev.rst b/Documentation/driver-api/surface_aggregator/clients/cdev.rst new file mode 100644 index 000000000000..248c1372d879 --- /dev/null +++ b/Documentation/driver-api/surface_aggregator/clients/cdev.rst @@ -0,0 +1,87 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +.. |u8| replace:: :c:type:`u8 ` +.. |u16| replace:: :c:type:`u16 ` +.. |ssam_cdev_request| replace:: :c:type:`struct ssam_cdev_request ` +.. |ssam_cdev_request_flags| replace:: :c:type:`enum ssam_cdev_request_flags ` + +============================== +User-Space EC Interface (cdev) +============================== + +The ``surface_aggregator_cdev`` module provides a misc-device for the SSAM +controller to allow for a (more or less) direct connection from user-space to +the SAM EC. It is intended to be used for development and debugging, and +therefore should not be used or relied upon in any other way. Note that this +module is not loaded automatically, but instead must be loaded manually. + +The provided interface is accessible through the ``/dev/surface/aggregator`` +device-file. All functionality of this interface is provided via IOCTLs. +These IOCTLs and their respective input/output parameter structs are defined in +``include/uapi/linux/surface_aggregator/cdev.h``. + +A small python library and scripts for accessing this interface can be found +at https://github.com/linux-surface/surface-aggregator-module/tree/master/scripts/ssam. + + +Controller IOCTLs +================= + +The following IOCTLs are provided: + +.. flat-table:: Controller IOCTLs + :widths: 1 1 1 1 4 + :header-rows: 1 + + * - Type + - Number + - Direction + - Name + - Description + + * - ``0xA5`` + - ``1`` + - ``WR`` + - ``REQUEST`` + - Perform synchronous SAM request. + + +``REQUEST`` +----------- + +Defined as ``_IOWR(0xA5, 1, struct ssam_cdev_request)``. + +Executes a synchronous SAM request. The request specification is passed in +as argument of type |ssam_cdev_request|, which is then written to/modified +by the IOCTL to return status and result of the request. + +Request payload data must be allocated separately and is passed in via the +``payload.data`` and ``payload.length`` members. If a response is required, +the response buffer must be allocated by the caller and passed in via the +``response.data`` member. The ``response.length`` member must be set to the +capacity of this buffer, or if no response is required, zero. Upon +completion of the request, the call will write the response to the response +buffer (if its capacity allows it) and overwrite the length field with the +actual size of the response, in bytes. + +Additionally, if the request has a response, this must be indicated via the +request flags, as is done with in-kernel requests. Request flags can be set +via the ``flags`` member and the values correspond to the values found in +|ssam_cdev_request_flags|. + +Finally, the status of the request itself is returned in the ``status`` +member (a negative errno value indicating failure). Note that failure +indication of the IOCTL is separated from failure indication of the request: +The IOCTL returns a negative status code if anything failed during setup of +the request (``-EFAULT``) or if the provided argument or any of its fields +are invalid (``-EINVAL``). In this case, the status value of the request +argument may be set, providing more detail on what went wrong (e.g. +``-ENOMEM`` for out-of-memory), but this value may also be zero. The IOCTL +will return with a zero status code in case the request has been set up, +submitted, and completed (i.e. handed back to user-space) successfully from +inside the IOCTL, but the request ``status`` member may still be negative in +case the actual execution of the request failed after it has been submitted. + +A full definition of the argument struct is provided below: + +.. kernel-doc:: include/uapi/linux/surface_aggregator/cdev.h diff --git a/Documentation/driver-api/surface_aggregator/clients/index.rst b/Documentation/driver-api/surface_aggregator/clients/index.rst index 31e026d96102..ab260ec82cfb 100644 --- a/Documentation/driver-api/surface_aggregator/clients/index.rst +++ b/Documentation/driver-api/surface_aggregator/clients/index.rst @@ -7,4 +7,14 @@ Client Driver Documentation This is the documentation for client drivers themselves. Refer to :doc:`../client` for documentation on how to write client drivers. -.. Place documentation for individual client drivers here. +.. toctree:: + :maxdepth: 1 + + cdev + +.. only:: subproject and html + + Indices + ======= + + * :ref:`genindex` diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst index a4c75a28c839..b5231d7f9200 100644 --- a/Documentation/userspace-api/ioctl/ioctl-number.rst +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst @@ -324,6 +324,8 @@ Code Seq# Include File Comments 0xA3 90-9F linux/dtlk.h 0xA4 00-1F uapi/linux/tee.h Generic TEE subsystem 0xA4 00-1F uapi/asm/sgx.h +0xA5 01 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator + 0xAA 00-3F linux/uapi/linux/userfaultfd.h 0xAB 00-1F linux/nbd.h 0xAC 00-1F linux/raw.h diff --git a/MAINTAINERS b/MAINTAINERS index 41f082551942..9bd55c842898 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11820,7 +11820,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_aggregator_cdev.c F: include/linux/surface_aggregator/ +F: include/uapi/linux/surface_aggregator/ MICROTEK X6 SCANNER M: Oliver Neukum diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig index 2916a47b130e..988b2de6b500 100644 --- a/drivers/platform/surface/Kconfig +++ b/drivers/platform/surface/Kconfig @@ -41,6 +41,23 @@ config SURFACE_3_POWER_OPREGION This driver provides support for ACPI operation region of the Surface 3 battery platform driver. +config SURFACE_AGGREGATOR_CDEV + tristate "Surface System Aggregator Module User-Space Interface" + depends on SURFACE_AGGREGATOR + help + Provides a misc-device interface to the Surface System Aggregator + Module (SSAM) controller. + + This option provides a module (called surface_aggregator_cdev), that, + when loaded, will add a client device (and its respective driver) to + the SSAM controller. Said client device manages a misc-device + interface (/dev/surface/aggregator), which can be used by user-space + tools to directly communicate with the SSAM EC by sending requests and + receiving the corresponding responses. + + The provided interface is intended for debugging and development only, + and should not be used otherwise. + config SURFACE_GPE tristate "Surface GPE/Lid Support Driver" depends on DMI diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile index 034134fe0264..161f0ad05795 100644 --- a/drivers/platform/surface/Makefile +++ b/drivers/platform/surface/Makefile @@ -8,5 +8,6 @@ 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_AGGREGATOR) += aggregator/ +obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c new file mode 100644 index 000000000000..340d15b148b9 --- /dev/null +++ b/drivers/platform/surface/surface_aggregator_cdev.c @@ -0,0 +1,303 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Provides user-space access to the SSAM EC via the /dev/surface/aggregator + * misc device. Intended for debugging and development. + * + * Copyright (C) 2020 Maximilian Luz + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define SSAM_CDEV_DEVICE_NAME "surface_aggregator_cdev" + +struct ssam_cdev { + struct kref kref; + struct rw_semaphore lock; + struct ssam_controller *ctrl; + struct miscdevice mdev; +}; + +static void __ssam_cdev_release(struct kref *kref) +{ + kfree(container_of(kref, struct ssam_cdev, kref)); +} + +static struct ssam_cdev *ssam_cdev_get(struct ssam_cdev *cdev) +{ + if (cdev) + kref_get(&cdev->kref); + + return cdev; +} + +static void ssam_cdev_put(struct ssam_cdev *cdev) +{ + if (cdev) + kref_put(&cdev->kref, __ssam_cdev_release); +} + +static int ssam_cdev_device_open(struct inode *inode, struct file *filp) +{ + struct miscdevice *mdev = filp->private_data; + struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev); + + filp->private_data = ssam_cdev_get(cdev); + return stream_open(inode, filp); +} + +static int ssam_cdev_device_release(struct inode *inode, struct file *filp) +{ + ssam_cdev_put(filp->private_data); + return 0; +} + +static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) +{ + struct ssam_cdev_request __user *r; + struct ssam_cdev_request rqst; + struct ssam_request spec; + struct ssam_response rsp; + const void __user *plddata; + void __user *rspdata; + int status = 0, ret = 0, tmp; + + r = (struct ssam_cdev_request __user *)arg; + ret = copy_struct_from_user(&rqst, sizeof(rqst), r, sizeof(*r)); + if (ret) + goto out; + + plddata = u64_to_user_ptr(rqst.payload.data); + rspdata = u64_to_user_ptr(rqst.response.data); + + /* Setup basic request fields. */ + spec.target_category = rqst.target_category; + spec.target_id = rqst.target_id; + spec.command_id = rqst.command_id; + spec.instance_id = rqst.instance_id; + spec.flags = 0; + spec.length = rqst.payload.length; + spec.payload = NULL; + + if (rqst.flags & SSAM_CDEV_REQUEST_HAS_RESPONSE) + spec.flags |= SSAM_REQUEST_HAS_RESPONSE; + + if (rqst.flags & SSAM_CDEV_REQUEST_UNSEQUENCED) + spec.flags |= SSAM_REQUEST_UNSEQUENCED; + + rsp.capacity = rqst.response.length; + rsp.length = 0; + rsp.pointer = NULL; + + /* Get request payload from user-space. */ + if (spec.length) { + if (!plddata) { + ret = -EINVAL; + goto out; + } + + spec.payload = kzalloc(spec.length, GFP_KERNEL); + if (!spec.payload) { + ret = -ENOMEM; + goto out; + } + + if (copy_from_user((void *)spec.payload, plddata, spec.length)) { + ret = -EFAULT; + goto out; + } + } + + /* Allocate response buffer. */ + if (rsp.capacity) { + if (!rspdata) { + ret = -EINVAL; + goto out; + } + + rsp.pointer = kzalloc(rsp.capacity, GFP_KERNEL); + if (!rsp.pointer) { + ret = -ENOMEM; + goto out; + } + } + + /* Perform request. */ + status = ssam_request_sync(cdev->ctrl, &spec, &rsp); + if (status) + goto out; + + /* Copy response to user-space. */ + if (rsp.length && copy_to_user(rspdata, rsp.pointer, rsp.length)) + ret = -EFAULT; + +out: + /* Always try to set response-length and status. */ + tmp = put_user(rsp.length, &r->response.length); + if (tmp) + ret = tmp; + + tmp = put_user(status, &r->status); + if (tmp) + ret = tmp; + + /* Cleanup. */ + kfree(spec.payload); + kfree(rsp.pointer); + + return ret; +} + +static long __ssam_cdev_device_ioctl(struct ssam_cdev *cdev, unsigned int cmd, + unsigned long arg) +{ + switch (cmd) { + case SSAM_CDEV_REQUEST: + return ssam_cdev_request(cdev, arg); + + default: + return -ENOTTY; + } +} + +static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct ssam_cdev *cdev = file->private_data; + long status; + + /* Ensure that controller is valid for as long as we need it. */ + if (down_read_killable(&cdev->lock)) + return -ERESTARTSYS; + + if (!cdev->ctrl) { + up_read(&cdev->lock); + return -ENODEV; + } + + status = __ssam_cdev_device_ioctl(cdev, cmd, arg); + + up_read(&cdev->lock); + return status; +} + +static const struct file_operations ssam_controller_fops = { + .owner = THIS_MODULE, + .open = ssam_cdev_device_open, + .release = ssam_cdev_device_release, + .unlocked_ioctl = ssam_cdev_device_ioctl, + .compat_ioctl = ssam_cdev_device_ioctl, + .llseek = noop_llseek, +}; + +static int ssam_dbg_device_probe(struct platform_device *pdev) +{ + struct ssam_controller *ctrl; + struct ssam_cdev *cdev; + int status; + + ctrl = ssam_client_bind(&pdev->dev); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); + + cdev = kzalloc(sizeof(*cdev), GFP_KERNEL); + if (!cdev) + return -ENOMEM; + + kref_init(&cdev->kref); + init_rwsem(&cdev->lock); + cdev->ctrl = ctrl; + + cdev->mdev.parent = &pdev->dev; + cdev->mdev.minor = MISC_DYNAMIC_MINOR; + cdev->mdev.name = "surface_aggregator"; + cdev->mdev.nodename = "surface/aggregator"; + cdev->mdev.fops = &ssam_controller_fops; + + status = misc_register(&cdev->mdev); + if (status) { + kfree(cdev); + return status; + } + + platform_set_drvdata(pdev, cdev); + return 0; +} + +static int ssam_dbg_device_remove(struct platform_device *pdev) +{ + struct ssam_cdev *cdev = platform_get_drvdata(pdev); + + misc_deregister(&cdev->mdev); + + /* + * The controller is only guaranteed to be valid for as long as the + * driver is bound. Remove controller so that any lingering open files + * cannot access it any more after we're gone. + */ + down_write(&cdev->lock); + cdev->ctrl = NULL; + up_write(&cdev->lock); + + ssam_cdev_put(cdev); + return 0; +} + +static struct platform_device *ssam_cdev_device; + +static struct platform_driver ssam_cdev_driver = { + .probe = ssam_dbg_device_probe, + .remove = ssam_dbg_device_remove, + .driver = { + .name = SSAM_CDEV_DEVICE_NAME, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; + +static int __init ssam_debug_init(void) +{ + int status; + + ssam_cdev_device = platform_device_alloc(SSAM_CDEV_DEVICE_NAME, + PLATFORM_DEVID_NONE); + if (!ssam_cdev_device) + return -ENOMEM; + + status = platform_device_add(ssam_cdev_device); + if (status) + goto err_device; + + status = platform_driver_register(&ssam_cdev_driver); + if (status) + goto err_driver; + + return 0; + +err_driver: + platform_device_del(ssam_cdev_device); +err_device: + platform_device_put(ssam_cdev_device); + return status; +} +module_init(ssam_debug_init); + +static void __exit ssam_debug_exit(void) +{ + platform_driver_unregister(&ssam_cdev_driver); + platform_device_unregister(ssam_cdev_device); +} +module_exit(ssam_debug_exit); + +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("User-space interface for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); diff --git a/include/uapi/linux/surface_aggregator/cdev.h b/include/uapi/linux/surface_aggregator/cdev.h new file mode 100644 index 000000000000..fbcce04abfe9 --- /dev/null +++ b/include/uapi/linux/surface_aggregator/cdev.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +/* + * Surface System Aggregator Module (SSAM) user-space EC interface. + * + * Definitions, structs, and IOCTLs for the /dev/surface/aggregator misc + * device. This device provides direct user-space access to the SSAM EC. + * Intended for debugging and development. + * + * Copyright (C) 2020 Maximilian Luz + */ + +#ifndef _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H +#define _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H + +#include +#include + +/** + * enum ssam_cdev_request_flags - Request flags for SSAM cdev request IOCTL. + * + * @SSAM_CDEV_REQUEST_HAS_RESPONSE: + * Specifies that the request expects a response. If not set, the request + * will be directly completed after its underlying packet has been + * transmitted. If set, the request transport system waits for a response + * of the request. + * + * @SSAM_CDEV_REQUEST_UNSEQUENCED: + * Specifies that the request should be transmitted via an unsequenced + * packet. If set, the request must not have a response, meaning that this + * flag and the %SSAM_CDEV_REQUEST_HAS_RESPONSE flag are mutually + * exclusive. + */ +enum ssam_cdev_request_flags { + SSAM_CDEV_REQUEST_HAS_RESPONSE = 0x01, + SSAM_CDEV_REQUEST_UNSEQUENCED = 0x02, +}; + +/** + * struct ssam_cdev_request - Controller request IOCTL argument. + * @target_category: Target category of the SAM request. + * @target_id: Target ID of the SAM request. + * @command_id: Command ID of the SAM request. + * @instance_id: Instance ID of the SAM request. + * @flags: Request flags (see &enum ssam_cdev_request_flags). + * @status: Request status (output). + * @payload: Request payload (input data). + * @payload.data: Pointer to request payload data. + * @payload.length: Length of request payload data (in bytes). + * @response: Request response (output data). + * @response.data: Pointer to response buffer. + * @response.length: On input: Capacity of response buffer (in bytes). + * On output: Length of request response (number of bytes + * in the buffer that are actually used). + */ +struct ssam_cdev_request { + __u8 target_category; + __u8 target_id; + __u8 command_id; + __u8 instance_id; + __u16 flags; + __s16 status; + + struct { + __u64 data; + __u16 length; + __u8 __pad[6]; + } payload; + + struct { + __u64 data; + __u16 length; + __u8 __pad[6]; + } response; +} __attribute__((__packed__)); + +#define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request) + +#endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H */ -- cgit v1.2.3 From fc00bc8ac1dada4085f9308f85f2d6359da0faa8 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Mon, 21 Dec 2020 19:39:59 +0100 Subject: platform/surface: Add Surface ACPI Notify driver 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 Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20201221183959.1186143-10-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- .../surface_aggregator/clients/index.rst | 1 + .../driver-api/surface_aggregator/clients/san.rst | 44 + MAINTAINERS | 2 + drivers/platform/surface/Kconfig | 19 + drivers/platform/surface/Makefile | 1 + drivers/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 (limited to 'MAINTAINERS') 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 9bd55c842898..cf250c1365b0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11820,7 +11820,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 988b2de6b500..83b0a4c7b352 100644 --- a/drivers/platform/surface/Kconfig +++ b/drivers/platform/surface/Kconfig @@ -41,6 +41,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 + */ + +#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) + + +/* -- 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 = ""; \ + 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 "); +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 + */ + +#ifndef _LINUX_SURFACE_ACPI_NOTIFY_H +#define _LINUX_SURFACE_ACPI_NOTIFY_H + +#include +#include + +/** + * 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 */ -- cgit v1.2.3 From 251000c5e3593dfc8010c71ceae8ba190c977ea3 Mon Sep 17 00:00:00 2001 From: Henrique de Moraes Holschuh Date: Fri, 15 Jan 2021 15:47:21 -0300 Subject: MAINTAINERS: update email address for Henrique de Moraes Holschuh Update my email address in MAINTAINERS to the one I have been using for commits, Signed-off-by and Acked-by tags. Only two ancient commits had the old ibm-acpi@hmh.eng.br, so it is unlikely to justify a .mailmap entry. Note that ibm-acpi@hmh.eng.br does work as a way to contact me, but apparently it is best when MAINTAINERS entries match commit email addresses ;-) Signed-off-by: Henrique de Moraes Holschuh Link: https://lore.kernel.org/r/20210115184721.32546-1-hmh@hmh.eng.br Signed-off-by: Hans de Goede --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index cf250c1365b0..0906d5ecf837 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17643,7 +17643,7 @@ F: drivers/thermal/gov_power_allocator.c F: include/trace/events/thermal_power_allocator.h THINKPAD ACPI EXTRAS DRIVER -M: Henrique de Moraes Holschuh +M: Henrique de Moraes Holschuh L: ibm-acpi-devel@lists.sourceforge.net L: platform-driver-x86@vger.kernel.org S: Maintained -- cgit v1.2.3 From 5f7582aa2d3c2ea0a9c9be17bcb53d29c0417ae5 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 22 Nov 2019 16:57:30 +0200 Subject: gpio: intel-mid: Remove driver for deprecated platform Intel Moorestown and Medfield are quite old Intel Atom based 32-bit platforms, which were in limited use in some Android phones, tablets and consumer electronics more than eight years ago. There are no bugs or problems ever reported outside from Intel for breaking any of that platforms for years. It seems no real users exists who run more or less fresh kernel on it. The commit 05f4434bc130 ("ASoC: Intel: remove mfld_machine") also in align with this theory. Due to above and to reduce a burden of supporting outdated drivers we remove the support of outdated platforms completely. Moreover this code duplicates gpio-pxa since the IP has been derived from XScale implementation. If anybody wants to resurrect this it has to be part of gpio-pxa.c. Signed-off-by: Andy Shevchenko Acked-by: Linus Walleij --- MAINTAINERS | 1 - drivers/gpio/Kconfig | 7 - drivers/gpio/Makefile | 1 - drivers/gpio/TODO | 2 +- drivers/gpio/gpio-intel-mid.c | 414 ------------------------------------------ 5 files changed, 1 insertion(+), 424 deletions(-) delete mode 100644 drivers/gpio/gpio-intel-mid.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 546aa66428c9..92074cba9dcc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8940,7 +8940,6 @@ L: linux-gpio@vger.kernel.org S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-gpio-intel.git F: drivers/gpio/gpio-ich.c -F: drivers/gpio/gpio-intel-mid.c F: drivers/gpio/gpio-merrifield.c F: drivers/gpio/gpio-ml-ioh.c F: drivers/gpio/gpio-pch.c diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index c70f46e80a3b..6d123cb0bd9c 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1451,13 +1451,6 @@ config GPIO_BT8XX If unsure, say N. -config GPIO_INTEL_MID - bool "Intel MID GPIO support" - depends on X86_INTEL_MID - select GPIOLIB_IRQCHIP - help - Say Y here to support Intel MID GPIO. - config GPIO_MERRIFIELD tristate "Intel Merrifield GPIO support" depends on X86_INTEL_MID diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 35e3b6026665..a2106d667fb3 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -67,7 +67,6 @@ obj-$(CONFIG_GPIO_HISI) += gpio-hisi.o obj-$(CONFIG_GPIO_HLWD) += gpio-hlwd.o obj-$(CONFIG_HTC_EGPIO) += gpio-htc-egpio.o obj-$(CONFIG_GPIO_ICH) += gpio-ich.o -obj-$(CONFIG_GPIO_INTEL_MID) += gpio-intel-mid.o obj-$(CONFIG_GPIO_IOP) += gpio-iop.o obj-$(CONFIG_GPIO_IT87) += gpio-it87.o obj-$(CONFIG_GPIO_IXP4XX) += gpio-ixp4xx.o diff --git a/drivers/gpio/TODO b/drivers/gpio/TODO index 0229fa79499e..b8b1473a5b1e 100644 --- a/drivers/gpio/TODO +++ b/drivers/gpio/TODO @@ -101,7 +101,7 @@ for a few GPIOs. Those should stay where they are. At the same time it makes sense to get rid of code duplication in existing or new coming drivers. For example, gpio-ml-ioh should be incorporated into -gpio-pch. In similar way gpio-intel-mid into gpio-pxa. +gpio-pch. Generic MMIO GPIO diff --git a/drivers/gpio/gpio-intel-mid.c b/drivers/gpio/gpio-intel-mid.c deleted file mode 100644 index 86a10c808ef6..000000000000 --- a/drivers/gpio/gpio-intel-mid.c +++ /dev/null @@ -1,414 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel MID GPIO driver - * - * Copyright (c) 2008-2014,2016 Intel Corporation. - */ - -/* Supports: - * Moorestown platform Langwell chip. - * Medfield platform Penwell chip. - * Clovertrail platform Cloverview chip. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define INTEL_MID_IRQ_TYPE_EDGE (1 << 0) -#define INTEL_MID_IRQ_TYPE_LEVEL (1 << 1) - -/* - * Langwell chip has 64 pins and thus there are 2 32bit registers to control - * each feature, while Penwell chip has 96 pins for each block, and need 3 32bit - * registers to control them, so we only define the order here instead of a - * structure, to get a bit offset for a pin (use GPDR as an example): - * - * nreg = ngpio / 32; - * reg = offset / 32; - * bit = offset % 32; - * reg_addr = reg_base + GPDR * nreg * 4 + reg * 4; - * - * so the bit of reg_addr is to control pin offset's GPDR feature -*/ - -enum GPIO_REG { - GPLR = 0, /* pin level read-only */ - GPDR, /* pin direction */ - GPSR, /* pin set */ - GPCR, /* pin clear */ - GRER, /* rising edge detect */ - GFER, /* falling edge detect */ - GEDR, /* edge detect result */ - GAFR, /* alt function */ -}; - -/* intel_mid gpio driver data */ -struct intel_mid_gpio_ddata { - u16 ngpio; /* number of gpio pins */ - u32 chip_irq_type; /* chip interrupt type */ -}; - -struct intel_mid_gpio { - struct gpio_chip chip; - void __iomem *reg_base; - spinlock_t lock; - struct pci_dev *pdev; -}; - -static void __iomem *gpio_reg(struct gpio_chip *chip, unsigned offset, - enum GPIO_REG reg_type) -{ - struct intel_mid_gpio *priv = gpiochip_get_data(chip); - unsigned nreg = chip->ngpio / 32; - u8 reg = offset / 32; - - return priv->reg_base + reg_type * nreg * 4 + reg * 4; -} - -static void __iomem *gpio_reg_2bit(struct gpio_chip *chip, unsigned offset, - enum GPIO_REG reg_type) -{ - struct intel_mid_gpio *priv = gpiochip_get_data(chip); - unsigned nreg = chip->ngpio / 32; - u8 reg = offset / 16; - - return priv->reg_base + reg_type * nreg * 4 + reg * 4; -} - -static int intel_gpio_request(struct gpio_chip *chip, unsigned offset) -{ - void __iomem *gafr = gpio_reg_2bit(chip, offset, GAFR); - u32 value = readl(gafr); - int shift = (offset % 16) << 1, af = (value >> shift) & 3; - - if (af) { - value &= ~(3 << shift); - writel(value, gafr); - } - return 0; -} - -static int intel_gpio_get(struct gpio_chip *chip, unsigned offset) -{ - void __iomem *gplr = gpio_reg(chip, offset, GPLR); - - return !!(readl(gplr) & BIT(offset % 32)); -} - -static void intel_gpio_set(struct gpio_chip *chip, unsigned offset, int value) -{ - void __iomem *gpsr, *gpcr; - - if (value) { - gpsr = gpio_reg(chip, offset, GPSR); - writel(BIT(offset % 32), gpsr); - } else { - gpcr = gpio_reg(chip, offset, GPCR); - writel(BIT(offset % 32), gpcr); - } -} - -static int intel_gpio_direction_input(struct gpio_chip *chip, unsigned offset) -{ - struct intel_mid_gpio *priv = gpiochip_get_data(chip); - void __iomem *gpdr = gpio_reg(chip, offset, GPDR); - u32 value; - unsigned long flags; - - if (priv->pdev) - pm_runtime_get(&priv->pdev->dev); - - spin_lock_irqsave(&priv->lock, flags); - value = readl(gpdr); - value &= ~BIT(offset % 32); - writel(value, gpdr); - spin_unlock_irqrestore(&priv->lock, flags); - - if (priv->pdev) - pm_runtime_put(&priv->pdev->dev); - - return 0; -} - -static int intel_gpio_direction_output(struct gpio_chip *chip, - unsigned offset, int value) -{ - struct intel_mid_gpio *priv = gpiochip_get_data(chip); - void __iomem *gpdr = gpio_reg(chip, offset, GPDR); - unsigned long flags; - - intel_gpio_set(chip, offset, value); - - if (priv->pdev) - pm_runtime_get(&priv->pdev->dev); - - spin_lock_irqsave(&priv->lock, flags); - value = readl(gpdr); - value |= BIT(offset % 32); - writel(value, gpdr); - spin_unlock_irqrestore(&priv->lock, flags); - - if (priv->pdev) - pm_runtime_put(&priv->pdev->dev); - - return 0; -} - -static int intel_mid_irq_type(struct irq_data *d, unsigned type) -{ - struct gpio_chip *gc = irq_data_get_irq_chip_data(d); - struct intel_mid_gpio *priv = gpiochip_get_data(gc); - u32 gpio = irqd_to_hwirq(d); - unsigned long flags; - u32 value; - void __iomem *grer = gpio_reg(&priv->chip, gpio, GRER); - void __iomem *gfer = gpio_reg(&priv->chip, gpio, GFER); - - if (gpio >= priv->chip.ngpio) - return -EINVAL; - - if (priv->pdev) - pm_runtime_get(&priv->pdev->dev); - - spin_lock_irqsave(&priv->lock, flags); - if (type & IRQ_TYPE_EDGE_RISING) - value = readl(grer) | BIT(gpio % 32); - else - value = readl(grer) & (~BIT(gpio % 32)); - writel(value, grer); - - if (type & IRQ_TYPE_EDGE_FALLING) - value = readl(gfer) | BIT(gpio % 32); - else - value = readl(gfer) & (~BIT(gpio % 32)); - writel(value, gfer); - spin_unlock_irqrestore(&priv->lock, flags); - - if (priv->pdev) - pm_runtime_put(&priv->pdev->dev); - - return 0; -} - -static void intel_mid_irq_unmask(struct irq_data *d) -{ -} - -static void intel_mid_irq_mask(struct irq_data *d) -{ -} - -static struct irq_chip intel_mid_irqchip = { - .name = "INTEL_MID-GPIO", - .irq_mask = intel_mid_irq_mask, - .irq_unmask = intel_mid_irq_unmask, - .irq_set_type = intel_mid_irq_type, -}; - -static const struct intel_mid_gpio_ddata gpio_lincroft = { - .ngpio = 64, -}; - -static const struct intel_mid_gpio_ddata gpio_penwell_aon = { - .ngpio = 96, - .chip_irq_type = INTEL_MID_IRQ_TYPE_EDGE, -}; - -static const struct intel_mid_gpio_ddata gpio_penwell_core = { - .ngpio = 96, - .chip_irq_type = INTEL_MID_IRQ_TYPE_EDGE, -}; - -static const struct intel_mid_gpio_ddata gpio_cloverview_aon = { - .ngpio = 96, - .chip_irq_type = INTEL_MID_IRQ_TYPE_EDGE | INTEL_MID_IRQ_TYPE_LEVEL, -}; - -static const struct intel_mid_gpio_ddata gpio_cloverview_core = { - .ngpio = 96, - .chip_irq_type = INTEL_MID_IRQ_TYPE_EDGE, -}; - -static const struct pci_device_id intel_gpio_ids[] = { - { - /* Lincroft */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x080f), - .driver_data = (kernel_ulong_t)&gpio_lincroft, - }, - { - /* Penwell AON */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x081f), - .driver_data = (kernel_ulong_t)&gpio_penwell_aon, - }, - { - /* Penwell Core */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x081a), - .driver_data = (kernel_ulong_t)&gpio_penwell_core, - }, - { - /* Cloverview Aon */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x08eb), - .driver_data = (kernel_ulong_t)&gpio_cloverview_aon, - }, - { - /* Cloverview Core */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x08f7), - .driver_data = (kernel_ulong_t)&gpio_cloverview_core, - }, - { } -}; - -static void intel_mid_irq_handler(struct irq_desc *desc) -{ - struct gpio_chip *gc = irq_desc_get_handler_data(desc); - struct intel_mid_gpio *priv = gpiochip_get_data(gc); - struct irq_data *data = irq_desc_get_irq_data(desc); - struct irq_chip *chip = irq_data_get_irq_chip(data); - u32 base, gpio, mask; - unsigned long pending; - void __iomem *gedr; - - /* check GPIO controller to check which pin triggered the interrupt */ - for (base = 0; base < priv->chip.ngpio; base += 32) { - gedr = gpio_reg(&priv->chip, base, GEDR); - while ((pending = readl(gedr))) { - gpio = __ffs(pending); - mask = BIT(gpio); - /* Clear before handling so we can't lose an edge */ - writel(mask, gedr); - generic_handle_irq(irq_find_mapping(gc->irq.domain, - base + gpio)); - } - } - - chip->irq_eoi(data); -} - -static int intel_mid_irq_init_hw(struct gpio_chip *chip) -{ - struct intel_mid_gpio *priv = gpiochip_get_data(chip); - void __iomem *reg; - unsigned base; - - for (base = 0; base < priv->chip.ngpio; base += 32) { - /* Clear the rising-edge detect register */ - reg = gpio_reg(&priv->chip, base, GRER); - writel(0, reg); - /* Clear the falling-edge detect register */ - reg = gpio_reg(&priv->chip, base, GFER); - writel(0, reg); - /* Clear the edge detect status register */ - reg = gpio_reg(&priv->chip, base, GEDR); - writel(~0, reg); - } - - return 0; -} - -static int __maybe_unused intel_gpio_runtime_idle(struct device *dev) -{ - int err = pm_schedule_suspend(dev, 500); - return err ?: -EBUSY; -} - -static const struct dev_pm_ops intel_gpio_pm_ops = { - SET_RUNTIME_PM_OPS(NULL, NULL, intel_gpio_runtime_idle) -}; - -static int intel_gpio_probe(struct pci_dev *pdev, - const struct pci_device_id *id) -{ - void __iomem *base; - struct intel_mid_gpio *priv; - u32 gpio_base; - u32 irq_base; - int retval; - struct gpio_irq_chip *girq; - struct intel_mid_gpio_ddata *ddata = - (struct intel_mid_gpio_ddata *)id->driver_data; - - retval = pcim_enable_device(pdev); - if (retval) - return retval; - - retval = pcim_iomap_regions(pdev, 1 << 0 | 1 << 1, pci_name(pdev)); - if (retval) { - dev_err(&pdev->dev, "I/O memory mapping error\n"); - return retval; - } - - base = pcim_iomap_table(pdev)[1]; - - irq_base = readl(base); - gpio_base = readl(sizeof(u32) + base); - - /* release the IO mapping, since we already get the info from bar1 */ - pcim_iounmap_regions(pdev, 1 << 1); - - priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - priv->reg_base = pcim_iomap_table(pdev)[0]; - priv->chip.label = dev_name(&pdev->dev); - priv->chip.parent = &pdev->dev; - priv->chip.request = intel_gpio_request; - priv->chip.direction_input = intel_gpio_direction_input; - priv->chip.direction_output = intel_gpio_direction_output; - priv->chip.get = intel_gpio_get; - priv->chip.set = intel_gpio_set; - priv->chip.base = gpio_base; - priv->chip.ngpio = ddata->ngpio; - priv->chip.can_sleep = false; - priv->pdev = pdev; - - spin_lock_init(&priv->lock); - - girq = &priv->chip.irq; - girq->chip = &intel_mid_irqchip; - girq->init_hw = intel_mid_irq_init_hw; - girq->parent_handler = intel_mid_irq_handler; - girq->num_parents = 1; - girq->parents = devm_kcalloc(&pdev->dev, girq->num_parents, - sizeof(*girq->parents), - GFP_KERNEL); - if (!girq->parents) - return -ENOMEM; - girq->parents[0] = pdev->irq; - girq->first = irq_base; - girq->default_type = IRQ_TYPE_NONE; - girq->handler = handle_simple_irq; - - pci_set_drvdata(pdev, priv); - - retval = devm_gpiochip_add_data(&pdev->dev, &priv->chip, priv); - if (retval) { - dev_err(&pdev->dev, "gpiochip_add error %d\n", retval); - return retval; - } - - pm_runtime_put_noidle(&pdev->dev); - pm_runtime_allow(&pdev->dev); - - return 0; -} - -static struct pci_driver intel_gpio_driver = { - .name = "intel_mid_gpio", - .id_table = intel_gpio_ids, - .probe = intel_gpio_probe, - .driver = { - .pm = &intel_gpio_pm_ops, - }, -}; - -builtin_pci_driver(intel_gpio_driver); -- cgit v1.2.3 From aee25798acf00978a2d9d39ae8b2c2353757d01d Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 22 Nov 2019 16:57:30 +0200 Subject: gpio: msic: Remove driver for deprecated platform Intel Moorestown and Medfield are quite old Intel Atom based 32-bit platforms, which were in limited use in some Android phones, tablets and consumer electronics more than eight years ago. There are no bugs or problems ever reported outside from Intel for breaking any of that platforms for years. It seems no real users exists who run more or less fresh kernel on it. The commit 05f4434bc130 ("ASoC: Intel: remove mfld_machine") also in align with this theory. Due to above and to reduce a burden of supporting outdated drivers we remove the support of outdated platforms completely. Signed-off-by: Andy Shevchenko Acked-by: Linus Walleij --- MAINTAINERS | 1 - drivers/gpio/Kconfig | 7 -- drivers/gpio/gpio-msic.c | 314 ----------------------------------------------- 3 files changed, 322 deletions(-) delete mode 100644 drivers/gpio/gpio-msic.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 92074cba9dcc..a5f161c9a059 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9098,7 +9098,6 @@ M: Andy Shevchenko S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-gpio-intel.git F: drivers/gpio/gpio-*cove.c -F: drivers/gpio/gpio-msic.c INTEL PMIC MULTIFUNCTION DEVICE DRIVERS M: Andy Shevchenko diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 6d123cb0bd9c..a6987ff28d7c 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1249,13 +1249,6 @@ config GPIO_MAX77650 GPIO driver for MAX77650/77651 PMIC from Maxim Semiconductor. These chips have a single pin that can be configured as GPIO. -config GPIO_MSIC - bool "Intel MSIC mixed signal gpio support" - depends on (X86 || COMPILE_TEST) && MFD_INTEL_MSIC - help - Enable support for GPIO on intel MSIC controllers found in - intel MID devices - config GPIO_PALMAS bool "TI PALMAS series PMICs GPIO" depends on MFD_PALMAS diff --git a/drivers/gpio/gpio-msic.c b/drivers/gpio/gpio-msic.c deleted file mode 100644 index 7e3c96e4ab2c..000000000000 --- a/drivers/gpio/gpio-msic.c +++ /dev/null @@ -1,314 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel Medfield MSIC GPIO driver> - * Copyright (c) 2011, Intel Corporation. - * - * Author: Mathias Nyman - * Based on intel_pmic_gpio.c - */ - -#include -#include -#include -#include -#include -#include -#include - -/* the offset for the mapping of global gpio pin to irq */ -#define MSIC_GPIO_IRQ_OFFSET 0x100 - -#define MSIC_GPIO_DIR_IN 0 -#define MSIC_GPIO_DIR_OUT BIT(5) -#define MSIC_GPIO_TRIG_FALL BIT(1) -#define MSIC_GPIO_TRIG_RISE BIT(2) - -/* masks for msic gpio output GPIOxxxxCTLO registers */ -#define MSIC_GPIO_DIR_MASK BIT(5) -#define MSIC_GPIO_DRV_MASK BIT(4) -#define MSIC_GPIO_REN_MASK BIT(3) -#define MSIC_GPIO_RVAL_MASK (BIT(2) | BIT(1)) -#define MSIC_GPIO_DOUT_MASK BIT(0) - -/* masks for msic gpio input GPIOxxxxCTLI registers */ -#define MSIC_GPIO_GLBYP_MASK BIT(5) -#define MSIC_GPIO_DBNC_MASK (BIT(4) | BIT(3)) -#define MSIC_GPIO_INTCNT_MASK (BIT(2) | BIT(1)) -#define MSIC_GPIO_DIN_MASK BIT(0) - -#define MSIC_NUM_GPIO 24 - -struct msic_gpio { - struct platform_device *pdev; - struct mutex buslock; - struct gpio_chip chip; - int irq; - unsigned irq_base; - unsigned long trig_change_mask; - unsigned trig_type; -}; - -/* - * MSIC has 24 gpios, 16 low voltage (1.2-1.8v) and 8 high voltage (3v). - * Both the high and low voltage gpios are divided in two banks. - * GPIOs are numbered with GPIO0LV0 as gpio_base in the following order: - * GPIO0LV0..GPIO0LV7: low voltage, bank 0, gpio_base - * GPIO1LV0..GPIO1LV7: low voltage, bank 1, gpio_base + 8 - * GPIO0HV0..GPIO0HV3: high voltage, bank 0, gpio_base + 16 - * GPIO1HV0..GPIO1HV3: high voltage, bank 1, gpio_base + 20 - */ - -static int msic_gpio_to_ireg(unsigned offset) -{ - if (offset >= MSIC_NUM_GPIO) - return -EINVAL; - - if (offset < 8) - return INTEL_MSIC_GPIO0LV0CTLI - offset; - if (offset < 16) - return INTEL_MSIC_GPIO1LV0CTLI - offset + 8; - if (offset < 20) - return INTEL_MSIC_GPIO0HV0CTLI - offset + 16; - - return INTEL_MSIC_GPIO1HV0CTLI - offset + 20; -} - -static int msic_gpio_to_oreg(unsigned offset) -{ - if (offset >= MSIC_NUM_GPIO) - return -EINVAL; - - if (offset < 8) - return INTEL_MSIC_GPIO0LV0CTLO - offset; - if (offset < 16) - return INTEL_MSIC_GPIO1LV0CTLO - offset + 8; - if (offset < 20) - return INTEL_MSIC_GPIO0HV0CTLO - offset + 16; - - return INTEL_MSIC_GPIO1HV0CTLO - offset + 20; -} - -static int msic_gpio_direction_input(struct gpio_chip *chip, unsigned offset) -{ - int reg; - - reg = msic_gpio_to_oreg(offset); - if (reg < 0) - return reg; - - return intel_msic_reg_update(reg, MSIC_GPIO_DIR_IN, MSIC_GPIO_DIR_MASK); -} - -static int msic_gpio_direction_output(struct gpio_chip *chip, - unsigned offset, int value) -{ - int reg; - unsigned mask; - - value = (!!value) | MSIC_GPIO_DIR_OUT; - mask = MSIC_GPIO_DIR_MASK | MSIC_GPIO_DOUT_MASK; - - reg = msic_gpio_to_oreg(offset); - if (reg < 0) - return reg; - - return intel_msic_reg_update(reg, value, mask); -} - -static int msic_gpio_get(struct gpio_chip *chip, unsigned offset) -{ - u8 r; - int ret; - int reg; - - reg = msic_gpio_to_ireg(offset); - if (reg < 0) - return reg; - - ret = intel_msic_reg_read(reg, &r); - if (ret < 0) - return ret; - - return !!(r & MSIC_GPIO_DIN_MASK); -} - -static void msic_gpio_set(struct gpio_chip *chip, unsigned offset, int value) -{ - int reg; - - reg = msic_gpio_to_oreg(offset); - if (reg < 0) - return; - - intel_msic_reg_update(reg, !!value , MSIC_GPIO_DOUT_MASK); -} - -/* - * This is called from genirq with mg->buslock locked and - * irq_desc->lock held. We can not access the scu bus here, so we - * store the change and update in the bus_sync_unlock() function below - */ -static int msic_irq_type(struct irq_data *data, unsigned type) -{ - struct msic_gpio *mg = irq_data_get_irq_chip_data(data); - u32 gpio = data->irq - mg->irq_base; - - if (gpio >= mg->chip.ngpio) - return -EINVAL; - - /* mark for which gpio the trigger changed, protected by buslock */ - mg->trig_change_mask |= (1 << gpio); - mg->trig_type = type; - - return 0; -} - -static int msic_gpio_to_irq(struct gpio_chip *chip, unsigned offset) -{ - struct msic_gpio *mg = gpiochip_get_data(chip); - return mg->irq_base + offset; -} - -static void msic_bus_lock(struct irq_data *data) -{ - struct msic_gpio *mg = irq_data_get_irq_chip_data(data); - mutex_lock(&mg->buslock); -} - -static void msic_bus_sync_unlock(struct irq_data *data) -{ - struct msic_gpio *mg = irq_data_get_irq_chip_data(data); - int offset; - int reg; - u8 trig = 0; - - /* We can only get one change at a time as the buslock covers the - entire transaction. The irq_desc->lock is dropped before we are - called but that is fine */ - if (mg->trig_change_mask) { - offset = __ffs(mg->trig_change_mask); - - reg = msic_gpio_to_ireg(offset); - if (reg < 0) - goto out; - - if (mg->trig_type & IRQ_TYPE_EDGE_RISING) - trig |= MSIC_GPIO_TRIG_RISE; - if (mg->trig_type & IRQ_TYPE_EDGE_FALLING) - trig |= MSIC_GPIO_TRIG_FALL; - - intel_msic_reg_update(reg, trig, MSIC_GPIO_INTCNT_MASK); - mg->trig_change_mask = 0; - } -out: - mutex_unlock(&mg->buslock); -} - -/* Firmware does all the masking and unmasking for us, no masking here. */ -static void msic_irq_unmask(struct irq_data *data) { } - -static void msic_irq_mask(struct irq_data *data) { } - -static struct irq_chip msic_irqchip = { - .name = "MSIC-GPIO", - .irq_mask = msic_irq_mask, - .irq_unmask = msic_irq_unmask, - .irq_set_type = msic_irq_type, - .irq_bus_lock = msic_bus_lock, - .irq_bus_sync_unlock = msic_bus_sync_unlock, -}; - -static void msic_gpio_irq_handler(struct irq_desc *desc) -{ - struct irq_data *data = irq_desc_get_irq_data(desc); - struct msic_gpio *mg = irq_data_get_irq_handler_data(data); - struct irq_chip *chip = irq_data_get_irq_chip(data); - struct intel_msic *msic = pdev_to_intel_msic(mg->pdev); - unsigned long pending; - int i; - int bitnr; - u8 pin; - - for (i = 0; i < (mg->chip.ngpio / BITS_PER_BYTE); i++) { - intel_msic_irq_read(msic, INTEL_MSIC_GPIO0LVIRQ + i, &pin); - pending = pin; - - for_each_set_bit(bitnr, &pending, BITS_PER_BYTE) - generic_handle_irq(mg->irq_base + i * BITS_PER_BYTE + bitnr); - } - chip->irq_eoi(data); -} - -static int platform_msic_gpio_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct intel_msic_gpio_pdata *pdata = dev_get_platdata(dev); - struct msic_gpio *mg; - int irq = platform_get_irq(pdev, 0); - int retval; - int i; - - if (irq < 0) { - dev_err(dev, "no IRQ line: %d\n", irq); - return irq; - } - - if (!pdata || !pdata->gpio_base) { - dev_err(dev, "incorrect or missing platform data\n"); - return -EINVAL; - } - - mg = kzalloc(sizeof(*mg), GFP_KERNEL); - if (!mg) - return -ENOMEM; - - dev_set_drvdata(dev, mg); - - mg->pdev = pdev; - mg->irq = irq; - mg->irq_base = pdata->gpio_base + MSIC_GPIO_IRQ_OFFSET; - mg->chip.label = "msic_gpio"; - mg->chip.direction_input = msic_gpio_direction_input; - mg->chip.direction_output = msic_gpio_direction_output; - mg->chip.get = msic_gpio_get; - mg->chip.set = msic_gpio_set; - mg->chip.to_irq = msic_gpio_to_irq; - mg->chip.base = pdata->gpio_base; - mg->chip.ngpio = MSIC_NUM_GPIO; - mg->chip.can_sleep = true; - mg->chip.parent = dev; - - mutex_init(&mg->buslock); - - retval = gpiochip_add_data(&mg->chip, mg); - if (retval) { - dev_err(dev, "Adding MSIC gpio chip failed\n"); - goto err; - } - - for (i = 0; i < mg->chip.ngpio; i++) { - irq_set_chip_data(i + mg->irq_base, mg); - irq_set_chip_and_handler(i + mg->irq_base, - &msic_irqchip, - handle_simple_irq); - } - irq_set_chained_handler_and_data(mg->irq, msic_gpio_irq_handler, mg); - - return 0; -err: - kfree(mg); - return retval; -} - -static struct platform_driver platform_msic_gpio_driver = { - .driver = { - .name = "msic_gpio", - }, - .probe = platform_msic_gpio_probe, -}; - -static int __init platform_msic_gpio_init(void) -{ - return platform_driver_register(&platform_msic_gpio_driver); -} -subsys_initcall(platform_msic_gpio_init); -- cgit v1.2.3 From f1e1ea516721d1ea0b21327ff9e6cb2c2bb86e28 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 3 Feb 2021 13:58:32 -0600 Subject: platform/x86: Move all dell drivers to their own subdirectory A user without a Dell system doesn't need to pick any of these drivers. Users with a Dell system can enable this submenu and all drivers behind it will be enabled. Suggested-by: Andy Shevchenko Signed-off-by: Mario Limonciello Link: https://lore.kernel.org/r/20210203195832.2950605-1-mario.limonciello@dell.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- MAINTAINERS | 22 +- drivers/platform/x86/Kconfig | 176 +- drivers/platform/x86/Makefile | 16 +- drivers/platform/x86/alienware-wmi.c | 853 -------- drivers/platform/x86/dcdbas.c | 770 ------- drivers/platform/x86/dcdbas.h | 109 - drivers/platform/x86/dell-laptop.c | 2303 -------------------- drivers/platform/x86/dell-rbtn.c | 499 ----- drivers/platform/x86/dell-rbtn.h | 16 - drivers/platform/x86/dell-smbios-base.c | 652 ------ drivers/platform/x86/dell-smbios-smm.c | 183 -- drivers/platform/x86/dell-smbios-wmi.c | 277 --- drivers/platform/x86/dell-smbios.h | 100 - drivers/platform/x86/dell-smo8800.c | 232 -- drivers/platform/x86/dell-wmi-aio.c | 197 -- drivers/platform/x86/dell-wmi-descriptor.c | 206 -- drivers/platform/x86/dell-wmi-descriptor.h | 25 - drivers/platform/x86/dell-wmi-led.c | 186 -- drivers/platform/x86/dell-wmi-sysman/Makefile | 8 - .../x86/dell-wmi-sysman/biosattr-interface.c | 186 -- .../platform/x86/dell-wmi-sysman/dell-wmi-sysman.h | 191 -- .../platform/x86/dell-wmi-sysman/enum-attributes.c | 189 -- .../platform/x86/dell-wmi-sysman/int-attributes.c | 179 -- .../x86/dell-wmi-sysman/passobj-attributes.c | 187 -- .../x86/dell-wmi-sysman/passwordattr-interface.c | 153 -- .../x86/dell-wmi-sysman/string-attributes.c | 159 -- drivers/platform/x86/dell-wmi-sysman/sysman.c | 631 ------ drivers/platform/x86/dell-wmi.c | 764 ------- drivers/platform/x86/dell/Kconfig | 207 ++ drivers/platform/x86/dell/Makefile | 21 + drivers/platform/x86/dell/alienware-wmi.c | 853 ++++++++ drivers/platform/x86/dell/dcdbas.c | 770 +++++++ drivers/platform/x86/dell/dcdbas.h | 109 + drivers/platform/x86/dell/dell-laptop.c | 2303 ++++++++++++++++++++ drivers/platform/x86/dell/dell-rbtn.c | 499 +++++ drivers/platform/x86/dell/dell-rbtn.h | 16 + drivers/platform/x86/dell/dell-smbios-base.c | 652 ++++++ drivers/platform/x86/dell/dell-smbios-smm.c | 183 ++ drivers/platform/x86/dell/dell-smbios-wmi.c | 277 +++ drivers/platform/x86/dell/dell-smbios.h | 100 + drivers/platform/x86/dell/dell-smo8800.c | 232 ++ drivers/platform/x86/dell/dell-wmi-aio.c | 197 ++ drivers/platform/x86/dell/dell-wmi-descriptor.c | 206 ++ drivers/platform/x86/dell/dell-wmi-descriptor.h | 25 + drivers/platform/x86/dell/dell-wmi-led.c | 186 ++ drivers/platform/x86/dell/dell-wmi-sysman/Makefile | 8 + .../x86/dell/dell-wmi-sysman/biosattr-interface.c | 186 ++ .../x86/dell/dell-wmi-sysman/dell-wmi-sysman.h | 191 ++ .../x86/dell/dell-wmi-sysman/enum-attributes.c | 189 ++ .../x86/dell/dell-wmi-sysman/int-attributes.c | 179 ++ .../x86/dell/dell-wmi-sysman/passobj-attributes.c | 187 ++ .../dell/dell-wmi-sysman/passwordattr-interface.c | 153 ++ .../x86/dell/dell-wmi-sysman/string-attributes.c | 159 ++ drivers/platform/x86/dell/dell-wmi-sysman/sysman.c | 631 ++++++ drivers/platform/x86/dell/dell-wmi.c | 764 +++++++ drivers/platform/x86/dell/dell_rbu.c | 680 ++++++ drivers/platform/x86/dell_rbu.c | 680 ------ 57 files changed, 10176 insertions(+), 10136 deletions(-) delete mode 100644 drivers/platform/x86/alienware-wmi.c delete mode 100644 drivers/platform/x86/dcdbas.c delete mode 100644 drivers/platform/x86/dcdbas.h delete mode 100644 drivers/platform/x86/dell-laptop.c delete mode 100644 drivers/platform/x86/dell-rbtn.c delete mode 100644 drivers/platform/x86/dell-rbtn.h delete mode 100644 drivers/platform/x86/dell-smbios-base.c delete mode 100644 drivers/platform/x86/dell-smbios-smm.c delete mode 100644 drivers/platform/x86/dell-smbios-wmi.c delete mode 100644 drivers/platform/x86/dell-smbios.h delete mode 100644 drivers/platform/x86/dell-smo8800.c delete mode 100644 drivers/platform/x86/dell-wmi-aio.c delete mode 100644 drivers/platform/x86/dell-wmi-descriptor.c delete mode 100644 drivers/platform/x86/dell-wmi-descriptor.h delete mode 100644 drivers/platform/x86/dell-wmi-led.c delete mode 100644 drivers/platform/x86/dell-wmi-sysman/Makefile delete mode 100644 drivers/platform/x86/dell-wmi-sysman/biosattr-interface.c delete mode 100644 drivers/platform/x86/dell-wmi-sysman/dell-wmi-sysman.h delete mode 100644 drivers/platform/x86/dell-wmi-sysman/enum-attributes.c delete mode 100644 drivers/platform/x86/dell-wmi-sysman/int-attributes.c delete mode 100644 drivers/platform/x86/dell-wmi-sysman/passobj-attributes.c delete mode 100644 drivers/platform/x86/dell-wmi-sysman/passwordattr-interface.c delete mode 100644 drivers/platform/x86/dell-wmi-sysman/string-attributes.c delete mode 100644 drivers/platform/x86/dell-wmi-sysman/sysman.c delete mode 100644 drivers/platform/x86/dell-wmi.c create mode 100644 drivers/platform/x86/dell/Kconfig create mode 100644 drivers/platform/x86/dell/Makefile create mode 100644 drivers/platform/x86/dell/alienware-wmi.c create mode 100644 drivers/platform/x86/dell/dcdbas.c create mode 100644 drivers/platform/x86/dell/dcdbas.h create mode 100644 drivers/platform/x86/dell/dell-laptop.c create mode 100644 drivers/platform/x86/dell/dell-rbtn.c create mode 100644 drivers/platform/x86/dell/dell-rbtn.h create mode 100644 drivers/platform/x86/dell/dell-smbios-base.c create mode 100644 drivers/platform/x86/dell/dell-smbios-smm.c create mode 100644 drivers/platform/x86/dell/dell-smbios-wmi.c create mode 100644 drivers/platform/x86/dell/dell-smbios.h create mode 100644 drivers/platform/x86/dell/dell-smo8800.c create mode 100644 drivers/platform/x86/dell/dell-wmi-aio.c create mode 100644 drivers/platform/x86/dell/dell-wmi-descriptor.c create mode 100644 drivers/platform/x86/dell/dell-wmi-descriptor.h create mode 100644 drivers/platform/x86/dell/dell-wmi-led.c create mode 100644 drivers/platform/x86/dell/dell-wmi-sysman/Makefile create mode 100644 drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c create mode 100644 drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h create mode 100644 drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c create mode 100644 drivers/platform/x86/dell/dell-wmi-sysman/int-attributes.c create mode 100644 drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c create mode 100644 drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c create mode 100644 drivers/platform/x86/dell/dell-wmi-sysman/string-attributes.c create mode 100644 drivers/platform/x86/dell/dell-wmi-sysman/sysman.c create mode 100644 drivers/platform/x86/dell/dell-wmi.c create mode 100644 drivers/platform/x86/dell/dell_rbu.c delete mode 100644 drivers/platform/x86/dell_rbu.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 25de2d7aaa46..34bfa5c1aec5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4970,17 +4970,17 @@ M: Matthew Garrett M: Pali Rohár L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/platform/x86/dell-laptop.c +F: drivers/platform/x86/dell/dell-laptop.c DELL LAPTOP FREEFALL DRIVER M: Pali Rohár S: Maintained -F: drivers/platform/x86/dell-smo8800.c +F: drivers/platform/x86/dell/dell-smo8800.c DELL LAPTOP RBTN DRIVER M: Pali Rohár S: Maintained -F: drivers/platform/x86/dell-rbtn.* +F: drivers/platform/x86/dell/dell-rbtn.* DELL LAPTOP SMM DRIVER M: Pali Rohár @@ -4992,26 +4992,26 @@ DELL REMOTE BIOS UPDATE DRIVER M: Stuart Hayes L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/platform/x86/dell_rbu.c +F: drivers/platform/x86/dell/dell_rbu.c DELL SMBIOS DRIVER M: Pali Rohár M: Mario Limonciello L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/platform/x86/dell-smbios.* +F: drivers/platform/x86/dell/dell-smbios.* DELL SMBIOS SMM DRIVER M: Mario Limonciello L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/platform/x86/dell-smbios-smm.c +F: drivers/platform/x86/dell/dell-smbios-smm.c DELL SMBIOS WMI DRIVER M: Mario Limonciello L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/platform/x86/dell-smbios-wmi.c +F: drivers/platform/x86/dell/dell-smbios-wmi.c F: tools/wmi/dell-smbios-example.c DELL SYSTEMS MANAGEMENT BASE DRIVER (dcdbas) @@ -5019,12 +5019,12 @@ M: Stuart Hayes L: platform-driver-x86@vger.kernel.org S: Maintained F: Documentation/driver-api/dcdbas.rst -F: drivers/platform/x86/dcdbas.* +F: drivers/platform/x86/dell/dcdbas.* DELL WMI DESCRIPTOR DRIVER M: Mario Limonciello S: Maintained -F: drivers/platform/x86/dell-wmi-descriptor.c +F: drivers/platform/x86/dell/dell-wmi-descriptor.c DELL WMI SYSMAN DRIVER M: Divya Bharathi @@ -5033,13 +5033,13 @@ M: Prasanth Ksr L: platform-driver-x86@vger.kernel.org S: Maintained F: Documentation/ABI/testing/sysfs-class-firmware-attributes -F: drivers/platform/x86/dell-wmi-sysman/ +F: drivers/platform/x86/dell/dell-wmi-sysman/ DELL WMI NOTIFICATIONS DRIVER M: Matthew Garrett M: Pali Rohár S: Maintained -F: drivers/platform/x86/dell-wmi.c +F: drivers/platform/x86/dell/dell-wmi.c DELTA ST MEDIA DRIVER M: Hugues Fruchet diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 175ae4ae0028..cf76e724e8c3 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -49,18 +49,6 @@ config WMI_BMOF To compile this driver as a module, choose M here: the module will be called wmi-bmof. -config ALIENWARE_WMI - tristate "Alienware Special feature control" - depends on ACPI - depends on LEDS_CLASS - depends on NEW_LEDS - depends on ACPI_WMI - help - This is a driver for controlling Alienware BIOS driven - features. It exposes an interface for controlling the AlienFX - zones on Alienware machines that don't contain a dedicated AlienFX - USB MCU such as the X51 and X51-R2. - config HUAWEI_WMI tristate "Huawei WMI laptop extras driver" depends on ACPI_BATTERY @@ -327,169 +315,7 @@ config EEEPC_WMI If you have an ACPI-WMI compatible Eee PC laptop (>= 1000), say Y or M here. -config DCDBAS - tristate "Dell Systems Management Base Driver" - depends on X86 - help - The Dell Systems Management Base Driver provides a sysfs interface - for systems management software to perform System Management - Interrupts (SMIs) and Host Control Actions (system power cycle or - power off after OS shutdown) on certain Dell systems. - - See for more details on the driver - and the Dell systems on which Dell systems management software makes - use of this driver. - - Say Y or M here to enable the driver for use by Dell systems - management software such as Dell OpenManage. - -# -# The DELL_SMBIOS driver depends on ACPI_WMI and/or DCDBAS if those -# backends are selected. The "depends" line prevents a configuration -# where DELL_SMBIOS=y while either of those dependencies =m. -# -config DELL_SMBIOS - tristate "Dell SMBIOS driver" - depends on DCDBAS || DCDBAS=n - depends on ACPI_WMI || ACPI_WMI=n - help - This provides support for the Dell SMBIOS calling interface. - If you have a Dell computer you should enable this option. - - Be sure to select at least one backend for it to work properly. - -config DELL_SMBIOS_WMI - bool "Dell SMBIOS driver WMI backend" - default y - depends on ACPI_WMI - select DELL_WMI_DESCRIPTOR - depends on DELL_SMBIOS - help - This provides an implementation for the Dell SMBIOS calling interface - communicated over ACPI-WMI. - - If you have a Dell computer from >2007 you should say Y here. - If you aren't sure and this module doesn't work for your computer - it just won't load. - -config DELL_SMBIOS_SMM - bool "Dell SMBIOS driver SMM backend" - default y - depends on DCDBAS - depends on DELL_SMBIOS - help - This provides an implementation for the Dell SMBIOS calling interface - communicated over SMI/SMM. - - If you have a Dell computer from <=2017 you should say Y here. - If you aren't sure and this module doesn't work for your computer - it just won't load. - -config DELL_LAPTOP - tristate "Dell Laptop Extras" - depends on DMI - depends on BACKLIGHT_CLASS_DEVICE - depends on ACPI_VIDEO || ACPI_VIDEO = n - depends on RFKILL || RFKILL = n - depends on SERIO_I8042 - depends on DELL_SMBIOS - select POWER_SUPPLY - select LEDS_CLASS - select NEW_LEDS - select LEDS_TRIGGERS - select LEDS_TRIGGER_AUDIO - help - This driver adds support for rfkill and backlight control to Dell - laptops (except for some models covered by the Compal driver). - -config DELL_RBTN - tristate "Dell Airplane Mode Switch driver" - depends on ACPI - depends on INPUT - depends on RFKILL - help - Say Y here if you want to support Dell Airplane Mode Switch ACPI - device on Dell laptops. Sometimes it has names: DELLABCE or DELRBTN. - This driver register rfkill device or input hotkey device depending - on hardware type (hw switch slider or keyboard toggle button). For - rfkill devices it receive HW switch events and set correct hard - rfkill state. - - To compile this driver as a module, choose M here: the module will - be called dell-rbtn. - -config DELL_RBU - tristate "BIOS update support for DELL systems via sysfs" - depends on X86 - select FW_LOADER - select FW_LOADER_USER_HELPER - help - Say m if you want to have the option of updating the BIOS for your - DELL system. Note you need a Dell OpenManage or Dell Update package (DUP) - supporting application to communicate with the BIOS regarding the new - image for the image update to take effect. - See for more details on the driver. - -config DELL_SMO8800 - tristate "Dell Latitude freefall driver (ACPI SMO88XX)" - depends on ACPI - help - Say Y here if you want to support SMO88XX freefall devices - on Dell Latitude laptops. - - To compile this driver as a module, choose M here: the module will - be called dell-smo8800. - -config DELL_WMI - tristate "Dell WMI notifications" - depends on ACPI_WMI - depends on DMI - depends on INPUT - depends on ACPI_VIDEO || ACPI_VIDEO = n - depends on DELL_SMBIOS - select DELL_WMI_DESCRIPTOR - select INPUT_SPARSEKMAP - help - Say Y here if you want to support WMI-based hotkeys on Dell laptops. - - To compile this driver as a module, choose M here: the module will - be called dell-wmi. - -config DELL_WMI_SYSMAN - tristate "Dell WMI-based Systems management driver" - depends on ACPI_WMI - depends on DMI - select NLS - help - This driver allows changing BIOS settings on many Dell machines from - 2018 and newer without the use of any additional software. - - To compile this driver as a module, choose M here: the module will - be called dell-wmi-sysman. - -config DELL_WMI_DESCRIPTOR - tristate - depends on ACPI_WMI - -config DELL_WMI_AIO - tristate "WMI Hotkeys for Dell All-In-One series" - depends on ACPI_WMI - depends on INPUT - select INPUT_SPARSEKMAP - help - Say Y here if you want to support WMI-based hotkeys on Dell - All-In-One machines. - - To compile this driver as a module, choose M here: the module will - be called dell-wmi-aio. - -config DELL_WMI_LED - tristate "External LED on Dell Business Netbooks" - depends on LEDS_CLASS - depends on ACPI_WMI - help - This adds support for the Latitude 2100 and similar - notebooks that have an external LED. +source "drivers/platform/x86/dell/Kconfig" config AMILO_RFKILL tristate "Fujitsu-Siemens Amilo rfkill support" diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 19306450d791..60d554073749 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -9,7 +9,6 @@ obj-$(CONFIG_ACPI_WMI) += wmi.o obj-$(CONFIG_WMI_BMOF) += wmi-bmof.o # WMI drivers -obj-$(CONFIG_ALIENWARE_WMI) += alienware-wmi.o obj-$(CONFIG_HUAWEI_WMI) += huawei-wmi.o obj-$(CONFIG_INTEL_WMI_SBL_FW_UPDATE) += intel-wmi-sbl-fw-update.o obj-$(CONFIG_INTEL_WMI_THUNDERBOLT) += intel-wmi-thunderbolt.o @@ -37,20 +36,7 @@ obj-$(CONFIG_EEEPC_LAPTOP) += eeepc-laptop.o obj-$(CONFIG_EEEPC_WMI) += eeepc-wmi.o # Dell -obj-$(CONFIG_DCDBAS) += dcdbas.o -obj-$(CONFIG_DELL_SMBIOS) += dell-smbios.o -dell-smbios-objs := dell-smbios-base.o -dell-smbios-$(CONFIG_DELL_SMBIOS_WMI) += dell-smbios-wmi.o -dell-smbios-$(CONFIG_DELL_SMBIOS_SMM) += dell-smbios-smm.o -obj-$(CONFIG_DELL_LAPTOP) += dell-laptop.o -obj-$(CONFIG_DELL_RBTN) += dell-rbtn.o -obj-$(CONFIG_DELL_RBU) += dell_rbu.o -obj-$(CONFIG_DELL_SMO8800) += dell-smo8800.o -obj-$(CONFIG_DELL_WMI) += dell-wmi.o -obj-$(CONFIG_DELL_WMI_DESCRIPTOR) += dell-wmi-descriptor.o -obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o -obj-$(CONFIG_DELL_WMI_LED) += dell-wmi-led.o -obj-$(CONFIG_DELL_WMI_SYSMAN) += dell-wmi-sysman/ +obj-$(CONFIG_X86_PLATFORM_DRIVERS_DELL) += dell/ # Fujitsu obj-$(CONFIG_AMILO_RFKILL) += amilo-rfkill.o diff --git a/drivers/platform/x86/alienware-wmi.c b/drivers/platform/x86/alienware-wmi.c deleted file mode 100644 index 5bb2859c8285..000000000000 --- a/drivers/platform/x86/alienware-wmi.c +++ /dev/null @@ -1,853 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Alienware AlienFX control - * - * Copyright (C) 2014 Dell Inc - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include - -#define LEGACY_CONTROL_GUID "A90597CE-A997-11DA-B012-B622A1EF5492" -#define LEGACY_POWER_CONTROL_GUID "A80593CE-A997-11DA-B012-B622A1EF5492" -#define WMAX_CONTROL_GUID "A70591CE-A997-11DA-B012-B622A1EF5492" - -#define WMAX_METHOD_HDMI_SOURCE 0x1 -#define WMAX_METHOD_HDMI_STATUS 0x2 -#define WMAX_METHOD_BRIGHTNESS 0x3 -#define WMAX_METHOD_ZONE_CONTROL 0x4 -#define WMAX_METHOD_HDMI_CABLE 0x5 -#define WMAX_METHOD_AMPLIFIER_CABLE 0x6 -#define WMAX_METHOD_DEEP_SLEEP_CONTROL 0x0B -#define WMAX_METHOD_DEEP_SLEEP_STATUS 0x0C - -MODULE_AUTHOR("Mario Limonciello "); -MODULE_DESCRIPTION("Alienware special feature control"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("wmi:" LEGACY_CONTROL_GUID); -MODULE_ALIAS("wmi:" WMAX_CONTROL_GUID); - -enum INTERFACE_FLAGS { - LEGACY, - WMAX, -}; - -enum LEGACY_CONTROL_STATES { - LEGACY_RUNNING = 1, - LEGACY_BOOTING = 0, - LEGACY_SUSPEND = 3, -}; - -enum WMAX_CONTROL_STATES { - WMAX_RUNNING = 0xFF, - WMAX_BOOTING = 0, - WMAX_SUSPEND = 3, -}; - -struct quirk_entry { - u8 num_zones; - u8 hdmi_mux; - u8 amplifier; - u8 deepslp; -}; - -static struct quirk_entry *quirks; - - -static struct quirk_entry quirk_inspiron5675 = { - .num_zones = 2, - .hdmi_mux = 0, - .amplifier = 0, - .deepslp = 0, -}; - -static struct quirk_entry quirk_unknown = { - .num_zones = 2, - .hdmi_mux = 0, - .amplifier = 0, - .deepslp = 0, -}; - -static struct quirk_entry quirk_x51_r1_r2 = { - .num_zones = 3, - .hdmi_mux = 0, - .amplifier = 0, - .deepslp = 0, -}; - -static struct quirk_entry quirk_x51_r3 = { - .num_zones = 4, - .hdmi_mux = 0, - .amplifier = 1, - .deepslp = 0, -}; - -static struct quirk_entry quirk_asm100 = { - .num_zones = 2, - .hdmi_mux = 1, - .amplifier = 0, - .deepslp = 0, -}; - -static struct quirk_entry quirk_asm200 = { - .num_zones = 2, - .hdmi_mux = 1, - .amplifier = 0, - .deepslp = 1, -}; - -static struct quirk_entry quirk_asm201 = { - .num_zones = 2, - .hdmi_mux = 1, - .amplifier = 1, - .deepslp = 1, -}; - -static int __init dmi_matched(const struct dmi_system_id *dmi) -{ - quirks = dmi->driver_data; - return 1; -} - -static const struct dmi_system_id alienware_quirks[] __initconst = { - { - .callback = dmi_matched, - .ident = "Alienware X51 R3", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), - DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R3"), - }, - .driver_data = &quirk_x51_r3, - }, - { - .callback = dmi_matched, - .ident = "Alienware X51 R2", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), - DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R2"), - }, - .driver_data = &quirk_x51_r1_r2, - }, - { - .callback = dmi_matched, - .ident = "Alienware X51 R1", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), - DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51"), - }, - .driver_data = &quirk_x51_r1_r2, - }, - { - .callback = dmi_matched, - .ident = "Alienware ASM100", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), - DMI_MATCH(DMI_PRODUCT_NAME, "ASM100"), - }, - .driver_data = &quirk_asm100, - }, - { - .callback = dmi_matched, - .ident = "Alienware ASM200", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), - DMI_MATCH(DMI_PRODUCT_NAME, "ASM200"), - }, - .driver_data = &quirk_asm200, - }, - { - .callback = dmi_matched, - .ident = "Alienware ASM201", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), - DMI_MATCH(DMI_PRODUCT_NAME, "ASM201"), - }, - .driver_data = &quirk_asm201, - }, - { - .callback = dmi_matched, - .ident = "Dell Inc. Inspiron 5675", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5675"), - }, - .driver_data = &quirk_inspiron5675, - }, - {} -}; - -struct color_platform { - u8 blue; - u8 green; - u8 red; -} __packed; - -struct platform_zone { - u8 location; - struct device_attribute *attr; - struct color_platform colors; -}; - -struct wmax_brightness_args { - u32 led_mask; - u32 percentage; -}; - -struct wmax_basic_args { - u8 arg; -}; - -struct legacy_led_args { - struct color_platform colors; - u8 brightness; - u8 state; -} __packed; - -struct wmax_led_args { - u32 led_mask; - struct color_platform colors; - u8 state; -} __packed; - -static struct platform_device *platform_device; -static struct device_attribute *zone_dev_attrs; -static struct attribute **zone_attrs; -static struct platform_zone *zone_data; - -static struct platform_driver platform_driver = { - .driver = { - .name = "alienware-wmi", - } -}; - -static struct attribute_group zone_attribute_group = { - .name = "rgb_zones", -}; - -static u8 interface; -static u8 lighting_control_state; -static u8 global_brightness; - -/* - * Helpers used for zone control - */ -static int parse_rgb(const char *buf, struct platform_zone *zone) -{ - long unsigned int rgb; - int ret; - union color_union { - struct color_platform cp; - int package; - } repackager; - - ret = kstrtoul(buf, 16, &rgb); - if (ret) - return ret; - - /* RGB triplet notation is 24-bit hexadecimal */ - if (rgb > 0xFFFFFF) - return -EINVAL; - - repackager.package = rgb & 0x0f0f0f0f; - pr_debug("alienware-wmi: r: %d g:%d b: %d\n", - repackager.cp.red, repackager.cp.green, repackager.cp.blue); - zone->colors = repackager.cp; - return 0; -} - -static struct platform_zone *match_zone(struct device_attribute *attr) -{ - u8 zone; - - for (zone = 0; zone < quirks->num_zones; zone++) { - if ((struct device_attribute *)zone_data[zone].attr == attr) { - pr_debug("alienware-wmi: matched zone location: %d\n", - zone_data[zone].location); - return &zone_data[zone]; - } - } - return NULL; -} - -/* - * Individual RGB zone control - */ -static int alienware_update_led(struct platform_zone *zone) -{ - int method_id; - acpi_status status; - char *guid; - struct acpi_buffer input; - struct legacy_led_args legacy_args; - struct wmax_led_args wmax_basic_args; - if (interface == WMAX) { - wmax_basic_args.led_mask = 1 << zone->location; - wmax_basic_args.colors = zone->colors; - wmax_basic_args.state = lighting_control_state; - guid = WMAX_CONTROL_GUID; - method_id = WMAX_METHOD_ZONE_CONTROL; - - input.length = (acpi_size) sizeof(wmax_basic_args); - input.pointer = &wmax_basic_args; - } else { - legacy_args.colors = zone->colors; - legacy_args.brightness = global_brightness; - legacy_args.state = 0; - if (lighting_control_state == LEGACY_BOOTING || - lighting_control_state == LEGACY_SUSPEND) { - guid = LEGACY_POWER_CONTROL_GUID; - legacy_args.state = lighting_control_state; - } else - guid = LEGACY_CONTROL_GUID; - method_id = zone->location + 1; - - input.length = (acpi_size) sizeof(legacy_args); - input.pointer = &legacy_args; - } - pr_debug("alienware-wmi: guid %s method %d\n", guid, method_id); - - status = wmi_evaluate_method(guid, 0, method_id, &input, NULL); - if (ACPI_FAILURE(status)) - pr_err("alienware-wmi: zone set failure: %u\n", status); - return ACPI_FAILURE(status); -} - -static ssize_t zone_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct platform_zone *target_zone; - target_zone = match_zone(attr); - if (target_zone == NULL) - return sprintf(buf, "red: -1, green: -1, blue: -1\n"); - return sprintf(buf, "red: %d, green: %d, blue: %d\n", - target_zone->colors.red, - target_zone->colors.green, target_zone->colors.blue); - -} - -static ssize_t zone_set(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct platform_zone *target_zone; - int ret; - target_zone = match_zone(attr); - if (target_zone == NULL) { - pr_err("alienware-wmi: invalid target zone\n"); - return 1; - } - ret = parse_rgb(buf, target_zone); - if (ret) - return ret; - ret = alienware_update_led(target_zone); - return ret ? ret : count; -} - -/* - * LED Brightness (Global) - */ -static int wmax_brightness(int brightness) -{ - acpi_status status; - struct acpi_buffer input; - struct wmax_brightness_args args = { - .led_mask = 0xFF, - .percentage = brightness, - }; - input.length = (acpi_size) sizeof(args); - input.pointer = &args; - status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0, - WMAX_METHOD_BRIGHTNESS, &input, NULL); - if (ACPI_FAILURE(status)) - pr_err("alienware-wmi: brightness set failure: %u\n", status); - return ACPI_FAILURE(status); -} - -static void global_led_set(struct led_classdev *led_cdev, - enum led_brightness brightness) -{ - int ret; - global_brightness = brightness; - if (interface == WMAX) - ret = wmax_brightness(brightness); - else - ret = alienware_update_led(&zone_data[0]); - if (ret) - pr_err("LED brightness update failed\n"); -} - -static enum led_brightness global_led_get(struct led_classdev *led_cdev) -{ - return global_brightness; -} - -static struct led_classdev global_led = { - .brightness_set = global_led_set, - .brightness_get = global_led_get, - .name = "alienware::global_brightness", -}; - -/* - * Lighting control state device attribute (Global) - */ -static ssize_t show_control_state(struct device *dev, - struct device_attribute *attr, char *buf) -{ - if (lighting_control_state == LEGACY_BOOTING) - return scnprintf(buf, PAGE_SIZE, "[booting] running suspend\n"); - else if (lighting_control_state == LEGACY_SUSPEND) - return scnprintf(buf, PAGE_SIZE, "booting running [suspend]\n"); - return scnprintf(buf, PAGE_SIZE, "booting [running] suspend\n"); -} - -static ssize_t store_control_state(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - long unsigned int val; - if (strcmp(buf, "booting\n") == 0) - val = LEGACY_BOOTING; - else if (strcmp(buf, "suspend\n") == 0) - val = LEGACY_SUSPEND; - else if (interface == LEGACY) - val = LEGACY_RUNNING; - else - val = WMAX_RUNNING; - lighting_control_state = val; - pr_debug("alienware-wmi: updated control state to %d\n", - lighting_control_state); - return count; -} - -static DEVICE_ATTR(lighting_control_state, 0644, show_control_state, - store_control_state); - -static int alienware_zone_init(struct platform_device *dev) -{ - u8 zone; - char buffer[10]; - char *name; - - if (interface == WMAX) { - lighting_control_state = WMAX_RUNNING; - } else if (interface == LEGACY) { - lighting_control_state = LEGACY_RUNNING; - } - global_led.max_brightness = 0x0F; - global_brightness = global_led.max_brightness; - - /* - * - zone_dev_attrs num_zones + 1 is for individual zones and then - * null terminated - * - zone_attrs num_zones + 2 is for all attrs in zone_dev_attrs + - * the lighting control + null terminated - * - zone_data num_zones is for the distinct zones - */ - zone_dev_attrs = - kcalloc(quirks->num_zones + 1, sizeof(struct device_attribute), - GFP_KERNEL); - if (!zone_dev_attrs) - return -ENOMEM; - - zone_attrs = - kcalloc(quirks->num_zones + 2, sizeof(struct attribute *), - GFP_KERNEL); - if (!zone_attrs) - return -ENOMEM; - - zone_data = - kcalloc(quirks->num_zones, sizeof(struct platform_zone), - GFP_KERNEL); - if (!zone_data) - return -ENOMEM; - - for (zone = 0; zone < quirks->num_zones; zone++) { - sprintf(buffer, "zone%02hhX", zone); - name = kstrdup(buffer, GFP_KERNEL); - if (name == NULL) - return 1; - sysfs_attr_init(&zone_dev_attrs[zone].attr); - zone_dev_attrs[zone].attr.name = name; - zone_dev_attrs[zone].attr.mode = 0644; - zone_dev_attrs[zone].show = zone_show; - zone_dev_attrs[zone].store = zone_set; - zone_data[zone].location = zone; - zone_attrs[zone] = &zone_dev_attrs[zone].attr; - zone_data[zone].attr = &zone_dev_attrs[zone]; - } - zone_attrs[quirks->num_zones] = &dev_attr_lighting_control_state.attr; - zone_attribute_group.attrs = zone_attrs; - - led_classdev_register(&dev->dev, &global_led); - - return sysfs_create_group(&dev->dev.kobj, &zone_attribute_group); -} - -static void alienware_zone_exit(struct platform_device *dev) -{ - u8 zone; - - sysfs_remove_group(&dev->dev.kobj, &zone_attribute_group); - led_classdev_unregister(&global_led); - if (zone_dev_attrs) { - for (zone = 0; zone < quirks->num_zones; zone++) - kfree(zone_dev_attrs[zone].attr.name); - } - kfree(zone_dev_attrs); - kfree(zone_data); - kfree(zone_attrs); -} - -static acpi_status alienware_wmax_command(struct wmax_basic_args *in_args, - u32 command, int *out_data) -{ - acpi_status status; - union acpi_object *obj; - struct acpi_buffer input; - struct acpi_buffer output; - - input.length = (acpi_size) sizeof(*in_args); - input.pointer = in_args; - if (out_data) { - output.length = ACPI_ALLOCATE_BUFFER; - output.pointer = NULL; - status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0, - command, &input, &output); - if (ACPI_SUCCESS(status)) { - obj = (union acpi_object *)output.pointer; - if (obj && obj->type == ACPI_TYPE_INTEGER) - *out_data = (u32)obj->integer.value; - } - kfree(output.pointer); - } else { - status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0, - command, &input, NULL); - } - return status; -} - -/* - * The HDMI mux sysfs node indicates the status of the HDMI input mux. - * It can toggle between standard system GPU output and HDMI input. - */ -static ssize_t show_hdmi_cable(struct device *dev, - struct device_attribute *attr, char *buf) -{ - acpi_status status; - u32 out_data; - struct wmax_basic_args in_args = { - .arg = 0, - }; - status = - alienware_wmax_command(&in_args, WMAX_METHOD_HDMI_CABLE, - (u32 *) &out_data); - if (ACPI_SUCCESS(status)) { - if (out_data == 0) - return scnprintf(buf, PAGE_SIZE, - "[unconnected] connected unknown\n"); - else if (out_data == 1) - return scnprintf(buf, PAGE_SIZE, - "unconnected [connected] unknown\n"); - } - pr_err("alienware-wmi: unknown HDMI cable status: %d\n", status); - return scnprintf(buf, PAGE_SIZE, "unconnected connected [unknown]\n"); -} - -static ssize_t show_hdmi_source(struct device *dev, - struct device_attribute *attr, char *buf) -{ - acpi_status status; - u32 out_data; - struct wmax_basic_args in_args = { - .arg = 0, - }; - status = - alienware_wmax_command(&in_args, WMAX_METHOD_HDMI_STATUS, - (u32 *) &out_data); - - if (ACPI_SUCCESS(status)) { - if (out_data == 1) - return scnprintf(buf, PAGE_SIZE, - "[input] gpu unknown\n"); - else if (out_data == 2) - return scnprintf(buf, PAGE_SIZE, - "input [gpu] unknown\n"); - } - pr_err("alienware-wmi: unknown HDMI source status: %u\n", status); - return scnprintf(buf, PAGE_SIZE, "input gpu [unknown]\n"); -} - -static ssize_t toggle_hdmi_source(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - acpi_status status; - struct wmax_basic_args args; - if (strcmp(buf, "gpu\n") == 0) - args.arg = 1; - else if (strcmp(buf, "input\n") == 0) - args.arg = 2; - else - args.arg = 3; - pr_debug("alienware-wmi: setting hdmi to %d : %s", args.arg, buf); - - status = alienware_wmax_command(&args, WMAX_METHOD_HDMI_SOURCE, NULL); - - if (ACPI_FAILURE(status)) - pr_err("alienware-wmi: HDMI toggle failed: results: %u\n", - status); - return count; -} - -static DEVICE_ATTR(cable, S_IRUGO, show_hdmi_cable, NULL); -static DEVICE_ATTR(source, S_IRUGO | S_IWUSR, show_hdmi_source, - toggle_hdmi_source); - -static struct attribute *hdmi_attrs[] = { - &dev_attr_cable.attr, - &dev_attr_source.attr, - NULL, -}; - -static const struct attribute_group hdmi_attribute_group = { - .name = "hdmi", - .attrs = hdmi_attrs, -}; - -static void remove_hdmi(struct platform_device *dev) -{ - if (quirks->hdmi_mux > 0) - sysfs_remove_group(&dev->dev.kobj, &hdmi_attribute_group); -} - -static int create_hdmi(struct platform_device *dev) -{ - int ret; - - ret = sysfs_create_group(&dev->dev.kobj, &hdmi_attribute_group); - if (ret) - remove_hdmi(dev); - return ret; -} - -/* - * Alienware GFX amplifier support - * - Currently supports reading cable status - * - Leaving expansion room to possibly support dock/undock events later - */ -static ssize_t show_amplifier_status(struct device *dev, - struct device_attribute *attr, char *buf) -{ - acpi_status status; - u32 out_data; - struct wmax_basic_args in_args = { - .arg = 0, - }; - status = - alienware_wmax_command(&in_args, WMAX_METHOD_AMPLIFIER_CABLE, - (u32 *) &out_data); - if (ACPI_SUCCESS(status)) { - if (out_data == 0) - return scnprintf(buf, PAGE_SIZE, - "[unconnected] connected unknown\n"); - else if (out_data == 1) - return scnprintf(buf, PAGE_SIZE, - "unconnected [connected] unknown\n"); - } - pr_err("alienware-wmi: unknown amplifier cable status: %d\n", status); - return scnprintf(buf, PAGE_SIZE, "unconnected connected [unknown]\n"); -} - -static DEVICE_ATTR(status, S_IRUGO, show_amplifier_status, NULL); - -static struct attribute *amplifier_attrs[] = { - &dev_attr_status.attr, - NULL, -}; - -static const struct attribute_group amplifier_attribute_group = { - .name = "amplifier", - .attrs = amplifier_attrs, -}; - -static void remove_amplifier(struct platform_device *dev) -{ - if (quirks->amplifier > 0) - sysfs_remove_group(&dev->dev.kobj, &lifier_attribute_group); -} - -static int create_amplifier(struct platform_device *dev) -{ - int ret; - - ret = sysfs_create_group(&dev->dev.kobj, &lifier_attribute_group); - if (ret) - remove_amplifier(dev); - return ret; -} - -/* - * Deep Sleep Control support - * - Modifies BIOS setting for deep sleep control allowing extra wakeup events - */ -static ssize_t show_deepsleep_status(struct device *dev, - struct device_attribute *attr, char *buf) -{ - acpi_status status; - u32 out_data; - struct wmax_basic_args in_args = { - .arg = 0, - }; - status = alienware_wmax_command(&in_args, WMAX_METHOD_DEEP_SLEEP_STATUS, - (u32 *) &out_data); - if (ACPI_SUCCESS(status)) { - if (out_data == 0) - return scnprintf(buf, PAGE_SIZE, - "[disabled] s5 s5_s4\n"); - else if (out_data == 1) - return scnprintf(buf, PAGE_SIZE, - "disabled [s5] s5_s4\n"); - else if (out_data == 2) - return scnprintf(buf, PAGE_SIZE, - "disabled s5 [s5_s4]\n"); - } - pr_err("alienware-wmi: unknown deep sleep status: %d\n", status); - return scnprintf(buf, PAGE_SIZE, "disabled s5 s5_s4 [unknown]\n"); -} - -static ssize_t toggle_deepsleep(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - acpi_status status; - struct wmax_basic_args args; - - if (strcmp(buf, "disabled\n") == 0) - args.arg = 0; - else if (strcmp(buf, "s5\n") == 0) - args.arg = 1; - else - args.arg = 2; - pr_debug("alienware-wmi: setting deep sleep to %d : %s", args.arg, buf); - - status = alienware_wmax_command(&args, WMAX_METHOD_DEEP_SLEEP_CONTROL, - NULL); - - if (ACPI_FAILURE(status)) - pr_err("alienware-wmi: deep sleep control failed: results: %u\n", - status); - return count; -} - -static DEVICE_ATTR(deepsleep, S_IRUGO | S_IWUSR, show_deepsleep_status, toggle_deepsleep); - -static struct attribute *deepsleep_attrs[] = { - &dev_attr_deepsleep.attr, - NULL, -}; - -static const struct attribute_group deepsleep_attribute_group = { - .name = "deepsleep", - .attrs = deepsleep_attrs, -}; - -static void remove_deepsleep(struct platform_device *dev) -{ - if (quirks->deepslp > 0) - sysfs_remove_group(&dev->dev.kobj, &deepsleep_attribute_group); -} - -static int create_deepsleep(struct platform_device *dev) -{ - int ret; - - ret = sysfs_create_group(&dev->dev.kobj, &deepsleep_attribute_group); - if (ret) - remove_deepsleep(dev); - return ret; -} - -static int __init alienware_wmi_init(void) -{ - int ret; - - if (wmi_has_guid(LEGACY_CONTROL_GUID)) - interface = LEGACY; - else if (wmi_has_guid(WMAX_CONTROL_GUID)) - interface = WMAX; - else { - pr_warn("alienware-wmi: No known WMI GUID found\n"); - return -ENODEV; - } - - dmi_check_system(alienware_quirks); - if (quirks == NULL) - quirks = &quirk_unknown; - - ret = platform_driver_register(&platform_driver); - if (ret) - goto fail_platform_driver; - platform_device = platform_device_alloc("alienware-wmi", -1); - if (!platform_device) { - ret = -ENOMEM; - goto fail_platform_device1; - } - ret = platform_device_add(platform_device); - if (ret) - goto fail_platform_device2; - - if (quirks->hdmi_mux > 0) { - ret = create_hdmi(platform_device); - if (ret) - goto fail_prep_hdmi; - } - - if (quirks->amplifier > 0) { - ret = create_amplifier(platform_device); - if (ret) - goto fail_prep_amplifier; - } - - if (quirks->deepslp > 0) { - ret = create_deepsleep(platform_device); - if (ret) - goto fail_prep_deepsleep; - } - - ret = alienware_zone_init(platform_device); - if (ret) - goto fail_prep_zones; - - return 0; - -fail_prep_zones: - alienware_zone_exit(platform_device); -fail_prep_deepsleep: -fail_prep_amplifier: -fail_prep_hdmi: - platform_device_del(platform_device); -fail_platform_device2: - platform_device_put(platform_device); -fail_platform_device1: - platform_driver_unregister(&platform_driver); -fail_platform_driver: - return ret; -} - -module_init(alienware_wmi_init); - -static void __exit alienware_wmi_exit(void) -{ - if (platform_device) { - alienware_zone_exit(platform_device); - remove_hdmi(platform_device); - platform_device_unregister(platform_device); - platform_driver_unregister(&platform_driver); - } -} - -module_exit(alienware_wmi_exit); diff --git a/drivers/platform/x86/dcdbas.c b/drivers/platform/x86/dcdbas.c deleted file mode 100644 index d513a59a5d47..000000000000 --- a/drivers/platform/x86/dcdbas.c +++ /dev/null @@ -1,770 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * dcdbas.c: Dell Systems Management Base Driver - * - * The Dell Systems Management Base Driver provides a sysfs interface for - * systems management software to perform System Management Interrupts (SMIs) - * and Host Control Actions (power cycle or power off after OS shutdown) on - * Dell systems. - * - * See Documentation/driver-api/dcdbas.rst for more information. - * - * Copyright (C) 1995-2006 Dell Inc. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "dcdbas.h" - -#define DRIVER_NAME "dcdbas" -#define DRIVER_VERSION "5.6.0-3.4" -#define DRIVER_DESCRIPTION "Dell Systems Management Base Driver" - -static struct platform_device *dcdbas_pdev; - -static u8 *smi_data_buf; -static dma_addr_t smi_data_buf_handle; -static unsigned long smi_data_buf_size; -static unsigned long max_smi_data_buf_size = MAX_SMI_DATA_BUF_SIZE; -static u32 smi_data_buf_phys_addr; -static DEFINE_MUTEX(smi_data_lock); -static u8 *bios_buffer; - -static unsigned int host_control_action; -static unsigned int host_control_smi_type; -static unsigned int host_control_on_shutdown; - -static bool wsmt_enabled; - -/** - * smi_data_buf_free: free SMI data buffer - */ -static void smi_data_buf_free(void) -{ - if (!smi_data_buf || wsmt_enabled) - return; - - dev_dbg(&dcdbas_pdev->dev, "%s: phys: %x size: %lu\n", - __func__, smi_data_buf_phys_addr, smi_data_buf_size); - - dma_free_coherent(&dcdbas_pdev->dev, smi_data_buf_size, smi_data_buf, - smi_data_buf_handle); - smi_data_buf = NULL; - smi_data_buf_handle = 0; - smi_data_buf_phys_addr = 0; - smi_data_buf_size = 0; -} - -/** - * smi_data_buf_realloc: grow SMI data buffer if needed - */ -static int smi_data_buf_realloc(unsigned long size) -{ - void *buf; - dma_addr_t handle; - - if (smi_data_buf_size >= size) - return 0; - - if (size > max_smi_data_buf_size) - return -EINVAL; - - /* new buffer is needed */ - buf = dma_alloc_coherent(&dcdbas_pdev->dev, size, &handle, GFP_KERNEL); - if (!buf) { - dev_dbg(&dcdbas_pdev->dev, - "%s: failed to allocate memory size %lu\n", - __func__, size); - return -ENOMEM; - } - /* memory zeroed by dma_alloc_coherent */ - - if (smi_data_buf) - memcpy(buf, smi_data_buf, smi_data_buf_size); - - /* free any existing buffer */ - smi_data_buf_free(); - - /* set up new buffer for use */ - smi_data_buf = buf; - smi_data_buf_handle = handle; - smi_data_buf_phys_addr = (u32) virt_to_phys(buf); - smi_data_buf_size = size; - - dev_dbg(&dcdbas_pdev->dev, "%s: phys: %x size: %lu\n", - __func__, smi_data_buf_phys_addr, smi_data_buf_size); - - return 0; -} - -static ssize_t smi_data_buf_phys_addr_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - return sprintf(buf, "%x\n", smi_data_buf_phys_addr); -} - -static ssize_t smi_data_buf_size_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - return sprintf(buf, "%lu\n", smi_data_buf_size); -} - -static ssize_t smi_data_buf_size_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - unsigned long buf_size; - ssize_t ret; - - buf_size = simple_strtoul(buf, NULL, 10); - - /* make sure SMI data buffer is at least buf_size */ - mutex_lock(&smi_data_lock); - ret = smi_data_buf_realloc(buf_size); - mutex_unlock(&smi_data_lock); - if (ret) - return ret; - - return count; -} - -static ssize_t smi_data_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buf, loff_t pos, size_t count) -{ - ssize_t ret; - - mutex_lock(&smi_data_lock); - ret = memory_read_from_buffer(buf, count, &pos, smi_data_buf, - smi_data_buf_size); - mutex_unlock(&smi_data_lock); - return ret; -} - -static ssize_t smi_data_write(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buf, loff_t pos, size_t count) -{ - ssize_t ret; - - if ((pos + count) > max_smi_data_buf_size) - return -EINVAL; - - mutex_lock(&smi_data_lock); - - ret = smi_data_buf_realloc(pos + count); - if (ret) - goto out; - - memcpy(smi_data_buf + pos, buf, count); - ret = count; -out: - mutex_unlock(&smi_data_lock); - return ret; -} - -static ssize_t host_control_action_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - return sprintf(buf, "%u\n", host_control_action); -} - -static ssize_t host_control_action_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - ssize_t ret; - - /* make sure buffer is available for host control command */ - mutex_lock(&smi_data_lock); - ret = smi_data_buf_realloc(sizeof(struct apm_cmd)); - mutex_unlock(&smi_data_lock); - if (ret) - return ret; - - host_control_action = simple_strtoul(buf, NULL, 10); - return count; -} - -static ssize_t host_control_smi_type_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - return sprintf(buf, "%u\n", host_control_smi_type); -} - -static ssize_t host_control_smi_type_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - host_control_smi_type = simple_strtoul(buf, NULL, 10); - return count; -} - -static ssize_t host_control_on_shutdown_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - return sprintf(buf, "%u\n", host_control_on_shutdown); -} - -static ssize_t host_control_on_shutdown_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - host_control_on_shutdown = simple_strtoul(buf, NULL, 10); - return count; -} - -static int raise_smi(void *par) -{ - struct smi_cmd *smi_cmd = par; - - if (smp_processor_id() != 0) { - dev_dbg(&dcdbas_pdev->dev, "%s: failed to get CPU 0\n", - __func__); - return -EBUSY; - } - - /* generate SMI */ - /* inb to force posted write through and make SMI happen now */ - asm volatile ( - "outb %b0,%w1\n" - "inb %w1" - : /* no output args */ - : "a" (smi_cmd->command_code), - "d" (smi_cmd->command_address), - "b" (smi_cmd->ebx), - "c" (smi_cmd->ecx) - : "memory" - ); - - return 0; -} -/** - * dcdbas_smi_request: generate SMI request - * - * Called with smi_data_lock. - */ -int dcdbas_smi_request(struct smi_cmd *smi_cmd) -{ - int ret; - - if (smi_cmd->magic != SMI_CMD_MAGIC) { - dev_info(&dcdbas_pdev->dev, "%s: invalid magic value\n", - __func__); - return -EBADR; - } - - /* SMI requires CPU 0 */ - get_online_cpus(); - ret = smp_call_on_cpu(0, raise_smi, smi_cmd, true); - put_online_cpus(); - - return ret; -} - -/** - * smi_request_store: - * - * The valid values are: - * 0: zero SMI data buffer - * 1: generate calling interface SMI - * 2: generate raw SMI - * - * User application writes smi_cmd to smi_data before telling driver - * to generate SMI. - */ -static ssize_t smi_request_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct smi_cmd *smi_cmd; - unsigned long val = simple_strtoul(buf, NULL, 10); - ssize_t ret; - - mutex_lock(&smi_data_lock); - - if (smi_data_buf_size < sizeof(struct smi_cmd)) { - ret = -ENODEV; - goto out; - } - smi_cmd = (struct smi_cmd *)smi_data_buf; - - switch (val) { - case 2: - /* Raw SMI */ - ret = dcdbas_smi_request(smi_cmd); - if (!ret) - ret = count; - break; - case 1: - /* - * Calling Interface SMI - * - * Provide physical address of command buffer field within - * the struct smi_cmd to BIOS. - * - * Because the address that smi_cmd (smi_data_buf) points to - * will be from memremap() of a non-memory address if WSMT - * is present, we can't use virt_to_phys() on smi_cmd, so - * we have to use the physical address that was saved when - * the virtual address for smi_cmd was received. - */ - smi_cmd->ebx = smi_data_buf_phys_addr + - offsetof(struct smi_cmd, command_buffer); - ret = dcdbas_smi_request(smi_cmd); - if (!ret) - ret = count; - break; - case 0: - memset(smi_data_buf, 0, smi_data_buf_size); - ret = count; - break; - default: - ret = -EINVAL; - break; - } - -out: - mutex_unlock(&smi_data_lock); - return ret; -} -EXPORT_SYMBOL(dcdbas_smi_request); - -/** - * host_control_smi: generate host control SMI - * - * Caller must set up the host control command in smi_data_buf. - */ -static int host_control_smi(void) -{ - struct apm_cmd *apm_cmd; - u8 *data; - unsigned long flags; - u32 num_ticks; - s8 cmd_status; - u8 index; - - apm_cmd = (struct apm_cmd *)smi_data_buf; - apm_cmd->status = ESM_STATUS_CMD_UNSUCCESSFUL; - - switch (host_control_smi_type) { - case HC_SMITYPE_TYPE1: - spin_lock_irqsave(&rtc_lock, flags); - /* write SMI data buffer physical address */ - data = (u8 *)&smi_data_buf_phys_addr; - for (index = PE1300_CMOS_CMD_STRUCT_PTR; - index < (PE1300_CMOS_CMD_STRUCT_PTR + 4); - index++, data++) { - outb(index, - (CMOS_BASE_PORT + CMOS_PAGE2_INDEX_PORT_PIIX4)); - outb(*data, - (CMOS_BASE_PORT + CMOS_PAGE2_DATA_PORT_PIIX4)); - } - - /* first set status to -1 as called by spec */ - cmd_status = ESM_STATUS_CMD_UNSUCCESSFUL; - outb((u8) cmd_status, PCAT_APM_STATUS_PORT); - - /* generate SMM call */ - outb(ESM_APM_CMD, PCAT_APM_CONTROL_PORT); - spin_unlock_irqrestore(&rtc_lock, flags); - - /* wait a few to see if it executed */ - num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING; - while ((cmd_status = inb(PCAT_APM_STATUS_PORT)) - == ESM_STATUS_CMD_UNSUCCESSFUL) { - num_ticks--; - if (num_ticks == EXPIRED_TIMER) - return -ETIME; - } - break; - - case HC_SMITYPE_TYPE2: - case HC_SMITYPE_TYPE3: - spin_lock_irqsave(&rtc_lock, flags); - /* write SMI data buffer physical address */ - data = (u8 *)&smi_data_buf_phys_addr; - for (index = PE1400_CMOS_CMD_STRUCT_PTR; - index < (PE1400_CMOS_CMD_STRUCT_PTR + 4); - index++, data++) { - outb(index, (CMOS_BASE_PORT + CMOS_PAGE1_INDEX_PORT)); - outb(*data, (CMOS_BASE_PORT + CMOS_PAGE1_DATA_PORT)); - } - - /* generate SMM call */ - if (host_control_smi_type == HC_SMITYPE_TYPE3) - outb(ESM_APM_CMD, PCAT_APM_CONTROL_PORT); - else - outb(ESM_APM_CMD, PE1400_APM_CONTROL_PORT); - - /* restore RTC index pointer since it was written to above */ - CMOS_READ(RTC_REG_C); - spin_unlock_irqrestore(&rtc_lock, flags); - - /* read control port back to serialize write */ - cmd_status = inb(PE1400_APM_CONTROL_PORT); - - /* wait a few to see if it executed */ - num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING; - while (apm_cmd->status == ESM_STATUS_CMD_UNSUCCESSFUL) { - num_ticks--; - if (num_ticks == EXPIRED_TIMER) - return -ETIME; - } - break; - - default: - dev_dbg(&dcdbas_pdev->dev, "%s: invalid SMI type %u\n", - __func__, host_control_smi_type); - return -ENOSYS; - } - - return 0; -} - -/** - * dcdbas_host_control: initiate host control - * - * This function is called by the driver after the system has - * finished shutting down if the user application specified a - * host control action to perform on shutdown. It is safe to - * use smi_data_buf at this point because the system has finished - * shutting down and no userspace apps are running. - */ -static void dcdbas_host_control(void) -{ - struct apm_cmd *apm_cmd; - u8 action; - - if (host_control_action == HC_ACTION_NONE) - return; - - action = host_control_action; - host_control_action = HC_ACTION_NONE; - - if (!smi_data_buf) { - dev_dbg(&dcdbas_pdev->dev, "%s: no SMI buffer\n", __func__); - return; - } - - if (smi_data_buf_size < sizeof(struct apm_cmd)) { - dev_dbg(&dcdbas_pdev->dev, "%s: SMI buffer too small\n", - __func__); - return; - } - - apm_cmd = (struct apm_cmd *)smi_data_buf; - - /* power off takes precedence */ - if (action & HC_ACTION_HOST_CONTROL_POWEROFF) { - apm_cmd->command = ESM_APM_POWER_CYCLE; - apm_cmd->reserved = 0; - *((s16 *)&apm_cmd->parameters.shortreq.parm[0]) = (s16) 0; - host_control_smi(); - } else if (action & HC_ACTION_HOST_CONTROL_POWERCYCLE) { - apm_cmd->command = ESM_APM_POWER_CYCLE; - apm_cmd->reserved = 0; - *((s16 *)&apm_cmd->parameters.shortreq.parm[0]) = (s16) 20; - host_control_smi(); - } -} - -/* WSMT */ - -static u8 checksum(u8 *buffer, u8 length) -{ - u8 sum = 0; - u8 *end = buffer + length; - - while (buffer < end) - sum += *buffer++; - return sum; -} - -static inline struct smm_eps_table *check_eps_table(u8 *addr) -{ - struct smm_eps_table *eps = (struct smm_eps_table *)addr; - - if (strncmp(eps->smm_comm_buff_anchor, SMM_EPS_SIG, 4) != 0) - return NULL; - - if (checksum(addr, eps->length) != 0) - return NULL; - - return eps; -} - -static int dcdbas_check_wsmt(void) -{ - const struct dmi_device *dev = NULL; - struct acpi_table_wsmt *wsmt = NULL; - struct smm_eps_table *eps = NULL; - u64 bios_buf_paddr; - u64 remap_size; - u8 *addr; - - acpi_get_table(ACPI_SIG_WSMT, 0, (struct acpi_table_header **)&wsmt); - if (!wsmt) - return 0; - - /* Check if WSMT ACPI table shows that protection is enabled */ - if (!(wsmt->protection_flags & ACPI_WSMT_FIXED_COMM_BUFFERS) || - !(wsmt->protection_flags & ACPI_WSMT_COMM_BUFFER_NESTED_PTR_PROTECTION)) - return 0; - - /* - * BIOS could provide the address/size of the protected buffer - * in an SMBIOS string or in an EPS structure in 0xFxxxx. - */ - - /* Check SMBIOS for buffer address */ - while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, NULL, dev))) - if (sscanf(dev->name, "30[%16llx;%8llx]", &bios_buf_paddr, - &remap_size) == 2) - goto remap; - - /* Scan for EPS (entry point structure) */ - for (addr = (u8 *)__va(0xf0000); - addr < (u8 *)__va(0x100000 - sizeof(struct smm_eps_table)); - addr += 16) { - eps = check_eps_table(addr); - if (eps) - break; - } - - if (!eps) { - dev_dbg(&dcdbas_pdev->dev, "found WSMT, but no firmware buffer found\n"); - return -ENODEV; - } - bios_buf_paddr = eps->smm_comm_buff_addr; - remap_size = eps->num_of_4k_pages * PAGE_SIZE; - -remap: - /* - * Get physical address of buffer and map to virtual address. - * Table gives size in 4K pages, regardless of actual system page size. - */ - if (upper_32_bits(bios_buf_paddr + 8)) { - dev_warn(&dcdbas_pdev->dev, "found WSMT, but buffer address is above 4GB\n"); - return -EINVAL; - } - /* - * Limit remap size to MAX_SMI_DATA_BUF_SIZE + 8 (since the first 8 - * bytes are used for a semaphore, not the data buffer itself). - */ - if (remap_size > MAX_SMI_DATA_BUF_SIZE + 8) - remap_size = MAX_SMI_DATA_BUF_SIZE + 8; - - bios_buffer = memremap(bios_buf_paddr, remap_size, MEMREMAP_WB); - if (!bios_buffer) { - dev_warn(&dcdbas_pdev->dev, "found WSMT, but failed to map buffer\n"); - return -ENOMEM; - } - - /* First 8 bytes is for a semaphore, not part of the smi_data_buf */ - smi_data_buf_phys_addr = bios_buf_paddr + 8; - smi_data_buf = bios_buffer + 8; - smi_data_buf_size = remap_size - 8; - max_smi_data_buf_size = smi_data_buf_size; - wsmt_enabled = true; - dev_info(&dcdbas_pdev->dev, - "WSMT found, using firmware-provided SMI buffer.\n"); - return 1; -} - -/** - * dcdbas_reboot_notify: handle reboot notification for host control - */ -static int dcdbas_reboot_notify(struct notifier_block *nb, unsigned long code, - void *unused) -{ - switch (code) { - case SYS_DOWN: - case SYS_HALT: - case SYS_POWER_OFF: - if (host_control_on_shutdown) { - /* firmware is going to perform host control action */ - printk(KERN_WARNING "Please wait for shutdown " - "action to complete...\n"); - dcdbas_host_control(); - } - break; - } - - return NOTIFY_DONE; -} - -static struct notifier_block dcdbas_reboot_nb = { - .notifier_call = dcdbas_reboot_notify, - .next = NULL, - .priority = INT_MIN -}; - -static DCDBAS_BIN_ATTR_RW(smi_data); - -static struct bin_attribute *dcdbas_bin_attrs[] = { - &bin_attr_smi_data, - NULL -}; - -static DCDBAS_DEV_ATTR_RW(smi_data_buf_size); -static DCDBAS_DEV_ATTR_RO(smi_data_buf_phys_addr); -static DCDBAS_DEV_ATTR_WO(smi_request); -static DCDBAS_DEV_ATTR_RW(host_control_action); -static DCDBAS_DEV_ATTR_RW(host_control_smi_type); -static DCDBAS_DEV_ATTR_RW(host_control_on_shutdown); - -static struct attribute *dcdbas_dev_attrs[] = { - &dev_attr_smi_data_buf_size.attr, - &dev_attr_smi_data_buf_phys_addr.attr, - &dev_attr_smi_request.attr, - &dev_attr_host_control_action.attr, - &dev_attr_host_control_smi_type.attr, - &dev_attr_host_control_on_shutdown.attr, - NULL -}; - -static const struct attribute_group dcdbas_attr_group = { - .attrs = dcdbas_dev_attrs, - .bin_attrs = dcdbas_bin_attrs, -}; - -static int dcdbas_probe(struct platform_device *dev) -{ - int error; - - host_control_action = HC_ACTION_NONE; - host_control_smi_type = HC_SMITYPE_NONE; - - dcdbas_pdev = dev; - - /* Check if ACPI WSMT table specifies protected SMI buffer address */ - error = dcdbas_check_wsmt(); - if (error < 0) - return error; - - /* - * BIOS SMI calls require buffer addresses be in 32-bit address space. - * This is done by setting the DMA mask below. - */ - error = dma_set_coherent_mask(&dcdbas_pdev->dev, DMA_BIT_MASK(32)); - if (error) - return error; - - error = sysfs_create_group(&dev->dev.kobj, &dcdbas_attr_group); - if (error) - return error; - - register_reboot_notifier(&dcdbas_reboot_nb); - - dev_info(&dev->dev, "%s (version %s)\n", - DRIVER_DESCRIPTION, DRIVER_VERSION); - - return 0; -} - -static int dcdbas_remove(struct platform_device *dev) -{ - unregister_reboot_notifier(&dcdbas_reboot_nb); - sysfs_remove_group(&dev->dev.kobj, &dcdbas_attr_group); - - return 0; -} - -static struct platform_driver dcdbas_driver = { - .driver = { - .name = DRIVER_NAME, - }, - .probe = dcdbas_probe, - .remove = dcdbas_remove, -}; - -static const struct platform_device_info dcdbas_dev_info __initconst = { - .name = DRIVER_NAME, - .id = -1, - .dma_mask = DMA_BIT_MASK(32), -}; - -static struct platform_device *dcdbas_pdev_reg; - -/** - * dcdbas_init: initialize driver - */ -static int __init dcdbas_init(void) -{ - int error; - - error = platform_driver_register(&dcdbas_driver); - if (error) - return error; - - dcdbas_pdev_reg = platform_device_register_full(&dcdbas_dev_info); - if (IS_ERR(dcdbas_pdev_reg)) { - error = PTR_ERR(dcdbas_pdev_reg); - goto err_unregister_driver; - } - - return 0; - - err_unregister_driver: - platform_driver_unregister(&dcdbas_driver); - return error; -} - -/** - * dcdbas_exit: perform driver cleanup - */ -static void __exit dcdbas_exit(void) -{ - /* - * make sure functions that use dcdbas_pdev are called - * before platform_device_unregister - */ - unregister_reboot_notifier(&dcdbas_reboot_nb); - - /* - * We have to free the buffer here instead of dcdbas_remove - * because only in module exit function we can be sure that - * all sysfs attributes belonging to this module have been - * released. - */ - if (dcdbas_pdev) - smi_data_buf_free(); - if (bios_buffer) - memunmap(bios_buffer); - platform_device_unregister(dcdbas_pdev_reg); - platform_driver_unregister(&dcdbas_driver); -} - -subsys_initcall_sync(dcdbas_init); -module_exit(dcdbas_exit); - -MODULE_DESCRIPTION(DRIVER_DESCRIPTION " (version " DRIVER_VERSION ")"); -MODULE_VERSION(DRIVER_VERSION); -MODULE_AUTHOR("Dell Inc."); -MODULE_LICENSE("GPL"); -/* Any System or BIOS claiming to be by Dell */ -MODULE_ALIAS("dmi:*:[bs]vnD[Ee][Ll][Ll]*:*"); diff --git a/drivers/platform/x86/dcdbas.h b/drivers/platform/x86/dcdbas.h deleted file mode 100644 index c3cca5433525..000000000000 --- a/drivers/platform/x86/dcdbas.h +++ /dev/null @@ -1,109 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * dcdbas.h: Definitions for Dell Systems Management Base driver - * - * Copyright (C) 1995-2005 Dell Inc. - */ - -#ifndef _DCDBAS_H_ -#define _DCDBAS_H_ - -#include -#include -#include - -#define MAX_SMI_DATA_BUF_SIZE (256 * 1024) - -#define HC_ACTION_NONE (0) -#define HC_ACTION_HOST_CONTROL_POWEROFF BIT(1) -#define HC_ACTION_HOST_CONTROL_POWERCYCLE BIT(2) - -#define HC_SMITYPE_NONE (0) -#define HC_SMITYPE_TYPE1 (1) -#define HC_SMITYPE_TYPE2 (2) -#define HC_SMITYPE_TYPE3 (3) - -#define ESM_APM_CMD (0x0A0) -#define ESM_APM_POWER_CYCLE (0x10) -#define ESM_STATUS_CMD_UNSUCCESSFUL (-1) - -#define CMOS_BASE_PORT (0x070) -#define CMOS_PAGE1_INDEX_PORT (0) -#define CMOS_PAGE1_DATA_PORT (1) -#define CMOS_PAGE2_INDEX_PORT_PIIX4 (2) -#define CMOS_PAGE2_DATA_PORT_PIIX4 (3) -#define PE1400_APM_CONTROL_PORT (0x0B0) -#define PCAT_APM_CONTROL_PORT (0x0B2) -#define PCAT_APM_STATUS_PORT (0x0B3) -#define PE1300_CMOS_CMD_STRUCT_PTR (0x38) -#define PE1400_CMOS_CMD_STRUCT_PTR (0x70) - -#define MAX_SYSMGMT_SHORTCMD_PARMBUF_LEN (14) -#define MAX_SYSMGMT_LONGCMD_SGENTRY_NUM (16) - -#define TIMEOUT_USEC_SHORT_SEMA_BLOCKING (10000) -#define EXPIRED_TIMER (0) - -#define SMI_CMD_MAGIC (0x534D4931) -#define SMM_EPS_SIG "$SCB" - -#define DCDBAS_DEV_ATTR_RW(_name) \ - DEVICE_ATTR(_name,0600,_name##_show,_name##_store); - -#define DCDBAS_DEV_ATTR_RO(_name) \ - DEVICE_ATTR(_name,0400,_name##_show,NULL); - -#define DCDBAS_DEV_ATTR_WO(_name) \ - DEVICE_ATTR(_name,0200,NULL,_name##_store); - -#define DCDBAS_BIN_ATTR_RW(_name) \ -struct bin_attribute bin_attr_##_name = { \ - .attr = { .name = __stringify(_name), \ - .mode = 0600 }, \ - .read = _name##_read, \ - .write = _name##_write, \ -} - -struct smi_cmd { - __u32 magic; - __u32 ebx; - __u32 ecx; - __u16 command_address; - __u8 command_code; - __u8 reserved; - __u8 command_buffer[1]; -} __attribute__ ((packed)); - -struct apm_cmd { - __u8 command; - __s8 status; - __u16 reserved; - union { - struct { - __u8 parm[MAX_SYSMGMT_SHORTCMD_PARMBUF_LEN]; - } __attribute__ ((packed)) shortreq; - - struct { - __u16 num_sg_entries; - struct { - __u32 size; - __u64 addr; - } __attribute__ ((packed)) - sglist[MAX_SYSMGMT_LONGCMD_SGENTRY_NUM]; - } __attribute__ ((packed)) longreq; - } __attribute__ ((packed)) parameters; -} __attribute__ ((packed)); - -int dcdbas_smi_request(struct smi_cmd *smi_cmd); - -struct smm_eps_table { - char smm_comm_buff_anchor[4]; - u8 length; - u8 checksum; - u8 version; - u64 smm_comm_buff_addr; - u64 num_of_4k_pages; -} __packed; - -#endif /* _DCDBAS_H_ */ - diff --git a/drivers/platform/x86/dell-laptop.c b/drivers/platform/x86/dell-laptop.c deleted file mode 100644 index 70edc5bb3a14..000000000000 --- a/drivers/platform/x86/dell-laptop.c +++ /dev/null @@ -1,2303 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Driver for Dell laptop extras - * - * Copyright (c) Red Hat - * Copyright (c) 2014 Gabriele Mazzotta - * Copyright (c) 2014 Pali Rohár - * - * Based on documentation in the libsmbios package: - * Copyright (C) 2005-2014 Dell Inc. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "dell-rbtn.h" -#include "dell-smbios.h" - -struct quirk_entry { - bool touchpad_led; - bool kbd_led_not_present; - bool kbd_led_levels_off_1; - bool kbd_missing_ac_tag; - - bool needs_kbd_timeouts; - /* - * Ordered list of timeouts expressed in seconds. - * The list must end with -1 - */ - int kbd_timeouts[]; -}; - -static struct quirk_entry *quirks; - -static struct quirk_entry quirk_dell_vostro_v130 = { - .touchpad_led = true, -}; - -static int __init dmi_matched(const struct dmi_system_id *dmi) -{ - quirks = dmi->driver_data; - return 1; -} - -/* - * These values come from Windows utility provided by Dell. If any other value - * is used then BIOS silently set timeout to 0 without any error message. - */ -static struct quirk_entry quirk_dell_xps13_9333 = { - .needs_kbd_timeouts = true, - .kbd_timeouts = { 0, 5, 15, 60, 5 * 60, 15 * 60, -1 }, -}; - -static struct quirk_entry quirk_dell_xps13_9370 = { - .kbd_missing_ac_tag = true, -}; - -static struct quirk_entry quirk_dell_latitude_e6410 = { - .kbd_led_levels_off_1 = true, -}; - -static struct quirk_entry quirk_dell_inspiron_1012 = { - .kbd_led_not_present = true, -}; - -static struct platform_driver platform_driver = { - .driver = { - .name = "dell-laptop", - } -}; - -static struct platform_device *platform_device; -static struct backlight_device *dell_backlight_device; -static struct rfkill *wifi_rfkill; -static struct rfkill *bluetooth_rfkill; -static struct rfkill *wwan_rfkill; -static bool force_rfkill; - -module_param(force_rfkill, bool, 0444); -MODULE_PARM_DESC(force_rfkill, "enable rfkill on non whitelisted models"); - -static const struct dmi_system_id dell_device_table[] __initconst = { - { - .ident = "Dell laptop", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_CHASSIS_TYPE, "8"), - }, - }, - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /*Laptop*/ - }, - }, - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_CHASSIS_TYPE, "10"), /*Notebook*/ - }, - }, - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_CHASSIS_TYPE, "30"), /*Tablet*/ - }, - }, - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_CHASSIS_TYPE, "31"), /*Convertible*/ - }, - }, - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_CHASSIS_TYPE, "32"), /*Detachable*/ - }, - }, - { - .ident = "Dell Computer Corporation", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"), - DMI_MATCH(DMI_CHASSIS_TYPE, "8"), - }, - }, - { } -}; -MODULE_DEVICE_TABLE(dmi, dell_device_table); - -static const struct dmi_system_id dell_quirks[] __initconst = { - { - .callback = dmi_matched, - .ident = "Dell Vostro V130", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Vostro V130"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell Vostro V131", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Vostro V131"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell Vostro 3350", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3350"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell Vostro 3555", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3555"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell Inspiron N311z", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron N311z"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell Inspiron M5110", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron M5110"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell Vostro 3360", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3360"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell Vostro 3460", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3460"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell Vostro 3560", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3560"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell Vostro 3450", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Dell System Vostro 3450"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell Inspiron 5420", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5420"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell Inspiron 5520", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5520"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell Inspiron 5720", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5720"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell Inspiron 7420", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7420"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell Inspiron 7520", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7520"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell Inspiron 7720", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7720"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell XPS13 9333", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "XPS13 9333"), - }, - .driver_data = &quirk_dell_xps13_9333, - }, - { - .callback = dmi_matched, - .ident = "Dell XPS 13 9370", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "XPS 13 9370"), - }, - .driver_data = &quirk_dell_xps13_9370, - }, - { - .callback = dmi_matched, - .ident = "Dell Latitude E6410", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Latitude E6410"), - }, - .driver_data = &quirk_dell_latitude_e6410, - }, - { - .callback = dmi_matched, - .ident = "Dell Inspiron 1012", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1012"), - }, - .driver_data = &quirk_dell_inspiron_1012, - }, - { - .callback = dmi_matched, - .ident = "Dell Inspiron 1018", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1018"), - }, - .driver_data = &quirk_dell_inspiron_1012, - }, - { } -}; - -static void dell_fill_request(struct calling_interface_buffer *buffer, - u32 arg0, u32 arg1, u32 arg2, u32 arg3) -{ - memset(buffer, 0, sizeof(struct calling_interface_buffer)); - buffer->input[0] = arg0; - buffer->input[1] = arg1; - buffer->input[2] = arg2; - buffer->input[3] = arg3; -} - -static int dell_send_request(struct calling_interface_buffer *buffer, - u16 class, u16 select) -{ - int ret; - - buffer->cmd_class = class; - buffer->cmd_select = select; - ret = dell_smbios_call(buffer); - if (ret != 0) - return ret; - return dell_smbios_error(buffer->output[0]); -} - -/* - * Derived from information in smbios-wireless-ctl: - * - * cbSelect 17, Value 11 - * - * Return Wireless Info - * cbArg1, byte0 = 0x00 - * - * cbRes1 Standard return codes (0, -1, -2) - * cbRes2 Info bit flags: - * - * 0 Hardware switch supported (1) - * 1 WiFi locator supported (1) - * 2 WLAN supported (1) - * 3 Bluetooth (BT) supported (1) - * 4 WWAN supported (1) - * 5 Wireless KBD supported (1) - * 6 Uw b supported (1) - * 7 WiGig supported (1) - * 8 WLAN installed (1) - * 9 BT installed (1) - * 10 WWAN installed (1) - * 11 Uw b installed (1) - * 12 WiGig installed (1) - * 13-15 Reserved (0) - * 16 Hardware (HW) switch is On (1) - * 17 WLAN disabled (1) - * 18 BT disabled (1) - * 19 WWAN disabled (1) - * 20 Uw b disabled (1) - * 21 WiGig disabled (1) - * 20-31 Reserved (0) - * - * cbRes3 NVRAM size in bytes - * cbRes4, byte 0 NVRAM format version number - * - * - * Set QuickSet Radio Disable Flag - * cbArg1, byte0 = 0x01 - * cbArg1, byte1 - * Radio ID value: - * 0 Radio Status - * 1 WLAN ID - * 2 BT ID - * 3 WWAN ID - * 4 UWB ID - * 5 WIGIG ID - * cbArg1, byte2 Flag bits: - * 0 QuickSet disables radio (1) - * 1-7 Reserved (0) - * - * cbRes1 Standard return codes (0, -1, -2) - * cbRes2 QuickSet (QS) radio disable bit map: - * 0 QS disables WLAN - * 1 QS disables BT - * 2 QS disables WWAN - * 3 QS disables UWB - * 4 QS disables WIGIG - * 5-31 Reserved (0) - * - * Wireless Switch Configuration - * cbArg1, byte0 = 0x02 - * - * cbArg1, byte1 - * Subcommand: - * 0 Get config - * 1 Set config - * 2 Set WiFi locator enable/disable - * cbArg1,byte2 - * Switch settings (if byte 1==1): - * 0 WLAN sw itch control (1) - * 1 BT sw itch control (1) - * 2 WWAN sw itch control (1) - * 3 UWB sw itch control (1) - * 4 WiGig sw itch control (1) - * 5-7 Reserved (0) - * cbArg1, byte2 Enable bits (if byte 1==2): - * 0 Enable WiFi locator (1) - * - * cbRes1 Standard return codes (0, -1, -2) - * cbRes2 QuickSet radio disable bit map: - * 0 WLAN controlled by sw itch (1) - * 1 BT controlled by sw itch (1) - * 2 WWAN controlled by sw itch (1) - * 3 UWB controlled by sw itch (1) - * 4 WiGig controlled by sw itch (1) - * 5-6 Reserved (0) - * 7 Wireless sw itch config locked (1) - * 8 WiFi locator enabled (1) - * 9-14 Reserved (0) - * 15 WiFi locator setting locked (1) - * 16-31 Reserved (0) - * - * Read Local Config Data (LCD) - * cbArg1, byte0 = 0x10 - * cbArg1, byte1 NVRAM index low byte - * cbArg1, byte2 NVRAM index high byte - * cbRes1 Standard return codes (0, -1, -2) - * cbRes2 4 bytes read from LCD[index] - * cbRes3 4 bytes read from LCD[index+4] - * cbRes4 4 bytes read from LCD[index+8] - * - * Write Local Config Data (LCD) - * cbArg1, byte0 = 0x11 - * cbArg1, byte1 NVRAM index low byte - * cbArg1, byte2 NVRAM index high byte - * cbArg2 4 bytes to w rite at LCD[index] - * cbArg3 4 bytes to w rite at LCD[index+4] - * cbArg4 4 bytes to w rite at LCD[index+8] - * cbRes1 Standard return codes (0, -1, -2) - * - * Populate Local Config Data from NVRAM - * cbArg1, byte0 = 0x12 - * cbRes1 Standard return codes (0, -1, -2) - * - * Commit Local Config Data to NVRAM - * cbArg1, byte0 = 0x13 - * cbRes1 Standard return codes (0, -1, -2) - */ - -static int dell_rfkill_set(void *data, bool blocked) -{ - int disable = blocked ? 1 : 0; - unsigned long radio = (unsigned long)data; - int hwswitch_bit = (unsigned long)data - 1; - struct calling_interface_buffer buffer; - int hwswitch; - int status; - int ret; - - dell_fill_request(&buffer, 0, 0, 0, 0); - ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); - if (ret) - return ret; - status = buffer.output[1]; - - dell_fill_request(&buffer, 0x2, 0, 0, 0); - ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); - if (ret) - return ret; - hwswitch = buffer.output[1]; - - /* If the hardware switch controls this radio, and the hardware - switch is disabled, always disable the radio */ - if (ret == 0 && (hwswitch & BIT(hwswitch_bit)) && - (status & BIT(0)) && !(status & BIT(16))) - disable = 1; - - dell_fill_request(&buffer, 1 | (radio<<8) | (disable << 16), 0, 0, 0); - ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); - return ret; -} - -static void dell_rfkill_update_sw_state(struct rfkill *rfkill, int radio, - int status) -{ - if (status & BIT(0)) { - /* Has hw-switch, sync sw_state to BIOS */ - struct calling_interface_buffer buffer; - int block = rfkill_blocked(rfkill); - dell_fill_request(&buffer, - 1 | (radio << 8) | (block << 16), 0, 0, 0); - dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); - } else { - /* No hw-switch, sync BIOS state to sw_state */ - rfkill_set_sw_state(rfkill, !!(status & BIT(radio + 16))); - } -} - -static void dell_rfkill_update_hw_state(struct rfkill *rfkill, int radio, - int status, int hwswitch) -{ - if (hwswitch & (BIT(radio - 1))) - rfkill_set_hw_state(rfkill, !(status & BIT(16))); -} - -static void dell_rfkill_query(struct rfkill *rfkill, void *data) -{ - int radio = ((unsigned long)data & 0xF); - struct calling_interface_buffer buffer; - int hwswitch; - int status; - int ret; - - dell_fill_request(&buffer, 0, 0, 0, 0); - ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); - status = buffer.output[1]; - - if (ret != 0 || !(status & BIT(0))) { - return; - } - - dell_fill_request(&buffer, 0x2, 0, 0, 0); - ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); - hwswitch = buffer.output[1]; - - if (ret != 0) - return; - - dell_rfkill_update_hw_state(rfkill, radio, status, hwswitch); -} - -static const struct rfkill_ops dell_rfkill_ops = { - .set_block = dell_rfkill_set, - .query = dell_rfkill_query, -}; - -static struct dentry *dell_laptop_dir; - -static int dell_debugfs_show(struct seq_file *s, void *data) -{ - struct calling_interface_buffer buffer; - int hwswitch_state; - int hwswitch_ret; - int status; - int ret; - - dell_fill_request(&buffer, 0, 0, 0, 0); - ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); - if (ret) - return ret; - status = buffer.output[1]; - - dell_fill_request(&buffer, 0x2, 0, 0, 0); - hwswitch_ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); - if (hwswitch_ret) - return hwswitch_ret; - hwswitch_state = buffer.output[1]; - - seq_printf(s, "return:\t%d\n", ret); - seq_printf(s, "status:\t0x%X\n", status); - seq_printf(s, "Bit 0 : Hardware switch supported: %lu\n", - status & BIT(0)); - seq_printf(s, "Bit 1 : Wifi locator supported: %lu\n", - (status & BIT(1)) >> 1); - seq_printf(s, "Bit 2 : Wifi is supported: %lu\n", - (status & BIT(2)) >> 2); - seq_printf(s, "Bit 3 : Bluetooth is supported: %lu\n", - (status & BIT(3)) >> 3); - seq_printf(s, "Bit 4 : WWAN is supported: %lu\n", - (status & BIT(4)) >> 4); - seq_printf(s, "Bit 5 : Wireless keyboard supported: %lu\n", - (status & BIT(5)) >> 5); - seq_printf(s, "Bit 6 : UWB supported: %lu\n", - (status & BIT(6)) >> 6); - seq_printf(s, "Bit 7 : WiGig supported: %lu\n", - (status & BIT(7)) >> 7); - seq_printf(s, "Bit 8 : Wifi is installed: %lu\n", - (status & BIT(8)) >> 8); - seq_printf(s, "Bit 9 : Bluetooth is installed: %lu\n", - (status & BIT(9)) >> 9); - seq_printf(s, "Bit 10: WWAN is installed: %lu\n", - (status & BIT(10)) >> 10); - seq_printf(s, "Bit 11: UWB installed: %lu\n", - (status & BIT(11)) >> 11); - seq_printf(s, "Bit 12: WiGig installed: %lu\n", - (status & BIT(12)) >> 12); - - seq_printf(s, "Bit 16: Hardware switch is on: %lu\n", - (status & BIT(16)) >> 16); - seq_printf(s, "Bit 17: Wifi is blocked: %lu\n", - (status & BIT(17)) >> 17); - seq_printf(s, "Bit 18: Bluetooth is blocked: %lu\n", - (status & BIT(18)) >> 18); - seq_printf(s, "Bit 19: WWAN is blocked: %lu\n", - (status & BIT(19)) >> 19); - seq_printf(s, "Bit 20: UWB is blocked: %lu\n", - (status & BIT(20)) >> 20); - seq_printf(s, "Bit 21: WiGig is blocked: %lu\n", - (status & BIT(21)) >> 21); - - seq_printf(s, "\nhwswitch_return:\t%d\n", hwswitch_ret); - seq_printf(s, "hwswitch_state:\t0x%X\n", hwswitch_state); - seq_printf(s, "Bit 0 : Wifi controlled by switch: %lu\n", - hwswitch_state & BIT(0)); - seq_printf(s, "Bit 1 : Bluetooth controlled by switch: %lu\n", - (hwswitch_state & BIT(1)) >> 1); - seq_printf(s, "Bit 2 : WWAN controlled by switch: %lu\n", - (hwswitch_state & BIT(2)) >> 2); - seq_printf(s, "Bit 3 : UWB controlled by switch: %lu\n", - (hwswitch_state & BIT(3)) >> 3); - seq_printf(s, "Bit 4 : WiGig controlled by switch: %lu\n", - (hwswitch_state & BIT(4)) >> 4); - seq_printf(s, "Bit 7 : Wireless switch config locked: %lu\n", - (hwswitch_state & BIT(7)) >> 7); - seq_printf(s, "Bit 8 : Wifi locator enabled: %lu\n", - (hwswitch_state & BIT(8)) >> 8); - seq_printf(s, "Bit 15: Wifi locator setting locked: %lu\n", - (hwswitch_state & BIT(15)) >> 15); - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(dell_debugfs); - -static void dell_update_rfkill(struct work_struct *ignored) -{ - struct calling_interface_buffer buffer; - int hwswitch = 0; - int status; - int ret; - - dell_fill_request(&buffer, 0, 0, 0, 0); - ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); - status = buffer.output[1]; - - if (ret != 0) - return; - - dell_fill_request(&buffer, 0x2, 0, 0, 0); - ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); - - if (ret == 0 && (status & BIT(0))) - hwswitch = buffer.output[1]; - - if (wifi_rfkill) { - dell_rfkill_update_hw_state(wifi_rfkill, 1, status, hwswitch); - dell_rfkill_update_sw_state(wifi_rfkill, 1, status); - } - if (bluetooth_rfkill) { - dell_rfkill_update_hw_state(bluetooth_rfkill, 2, status, - hwswitch); - dell_rfkill_update_sw_state(bluetooth_rfkill, 2, status); - } - if (wwan_rfkill) { - dell_rfkill_update_hw_state(wwan_rfkill, 3, status, hwswitch); - dell_rfkill_update_sw_state(wwan_rfkill, 3, status); - } -} -static DECLARE_DELAYED_WORK(dell_rfkill_work, dell_update_rfkill); - -static bool dell_laptop_i8042_filter(unsigned char data, unsigned char str, - struct serio *port) -{ - static bool extended; - - if (str & I8042_STR_AUXDATA) - return false; - - if (unlikely(data == 0xe0)) { - extended = true; - return false; - } else if (unlikely(extended)) { - switch (data) { - case 0x8: - schedule_delayed_work(&dell_rfkill_work, - round_jiffies_relative(HZ / 4)); - break; - } - extended = false; - } - - return false; -} - -static int (*dell_rbtn_notifier_register_func)(struct notifier_block *); -static int (*dell_rbtn_notifier_unregister_func)(struct notifier_block *); - -static int dell_laptop_rbtn_notifier_call(struct notifier_block *nb, - unsigned long action, void *data) -{ - schedule_delayed_work(&dell_rfkill_work, 0); - return NOTIFY_OK; -} - -static struct notifier_block dell_laptop_rbtn_notifier = { - .notifier_call = dell_laptop_rbtn_notifier_call, -}; - -static int __init dell_setup_rfkill(void) -{ - struct calling_interface_buffer buffer; - int status, ret, whitelisted; - const char *product; - - /* - * rfkill support causes trouble on various models, mostly Inspirons. - * So we whitelist certain series, and don't support rfkill on others. - */ - whitelisted = 0; - product = dmi_get_system_info(DMI_PRODUCT_NAME); - if (product && (strncmp(product, "Latitude", 8) == 0 || - strncmp(product, "Precision", 9) == 0)) - whitelisted = 1; - if (!force_rfkill && !whitelisted) - return 0; - - dell_fill_request(&buffer, 0, 0, 0, 0); - ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); - status = buffer.output[1]; - - /* dell wireless info smbios call is not supported */ - if (ret != 0) - return 0; - - /* rfkill is only tested on laptops with a hwswitch */ - if (!(status & BIT(0)) && !force_rfkill) - return 0; - - if ((status & (1<<2|1<<8)) == (1<<2|1<<8)) { - wifi_rfkill = rfkill_alloc("dell-wifi", &platform_device->dev, - RFKILL_TYPE_WLAN, - &dell_rfkill_ops, (void *) 1); - if (!wifi_rfkill) { - ret = -ENOMEM; - goto err_wifi; - } - ret = rfkill_register(wifi_rfkill); - if (ret) - goto err_wifi; - } - - if ((status & (1<<3|1<<9)) == (1<<3|1<<9)) { - bluetooth_rfkill = rfkill_alloc("dell-bluetooth", - &platform_device->dev, - RFKILL_TYPE_BLUETOOTH, - &dell_rfkill_ops, (void *) 2); - if (!bluetooth_rfkill) { - ret = -ENOMEM; - goto err_bluetooth; - } - ret = rfkill_register(bluetooth_rfkill); - if (ret) - goto err_bluetooth; - } - - if ((status & (1<<4|1<<10)) == (1<<4|1<<10)) { - wwan_rfkill = rfkill_alloc("dell-wwan", - &platform_device->dev, - RFKILL_TYPE_WWAN, - &dell_rfkill_ops, (void *) 3); - if (!wwan_rfkill) { - ret = -ENOMEM; - goto err_wwan; - } - ret = rfkill_register(wwan_rfkill); - if (ret) - goto err_wwan; - } - - /* - * Dell Airplane Mode Switch driver (dell-rbtn) supports ACPI devices - * which can receive events from HW slider switch. - * - * Dell SMBIOS on whitelisted models supports controlling radio devices - * but does not support receiving HW button switch events. We can use - * i8042 filter hook function to receive keyboard data and handle - * keycode for HW button. - * - * So if it is possible we will use Dell Airplane Mode Switch ACPI - * driver for receiving HW events and Dell SMBIOS for setting rfkill - * states. If ACPI driver or device is not available we will fallback to - * i8042 filter hook function. - * - * To prevent duplicate rfkill devices which control and do same thing, - * dell-rbtn driver will automatically remove its own rfkill devices - * once function dell_rbtn_notifier_register() is called. - */ - - dell_rbtn_notifier_register_func = - symbol_request(dell_rbtn_notifier_register); - if (dell_rbtn_notifier_register_func) { - dell_rbtn_notifier_unregister_func = - symbol_request(dell_rbtn_notifier_unregister); - if (!dell_rbtn_notifier_unregister_func) { - symbol_put(dell_rbtn_notifier_register); - dell_rbtn_notifier_register_func = NULL; - } - } - - if (dell_rbtn_notifier_register_func) { - ret = dell_rbtn_notifier_register_func( - &dell_laptop_rbtn_notifier); - symbol_put(dell_rbtn_notifier_register); - dell_rbtn_notifier_register_func = NULL; - if (ret != 0) { - symbol_put(dell_rbtn_notifier_unregister); - dell_rbtn_notifier_unregister_func = NULL; - } - } else { - pr_info("Symbols from dell-rbtn acpi driver are not available\n"); - ret = -ENODEV; - } - - if (ret == 0) { - pr_info("Using dell-rbtn acpi driver for receiving events\n"); - } else if (ret != -ENODEV) { - pr_warn("Unable to register dell rbtn notifier\n"); - goto err_filter; - } else { - ret = i8042_install_filter(dell_laptop_i8042_filter); - if (ret) { - pr_warn("Unable to install key filter\n"); - goto err_filter; - } - pr_info("Using i8042 filter function for receiving events\n"); - } - - return 0; -err_filter: - if (wwan_rfkill) - rfkill_unregister(wwan_rfkill); -err_wwan: - rfkill_destroy(wwan_rfkill); - if (bluetooth_rfkill) - rfkill_unregister(bluetooth_rfkill); -err_bluetooth: - rfkill_destroy(bluetooth_rfkill); - if (wifi_rfkill) - rfkill_unregister(wifi_rfkill); -err_wifi: - rfkill_destroy(wifi_rfkill); - - return ret; -} - -static void dell_cleanup_rfkill(void) -{ - if (dell_rbtn_notifier_unregister_func) { - dell_rbtn_notifier_unregister_func(&dell_laptop_rbtn_notifier); - symbol_put(dell_rbtn_notifier_unregister); - dell_rbtn_notifier_unregister_func = NULL; - } else { - i8042_remove_filter(dell_laptop_i8042_filter); - } - cancel_delayed_work_sync(&dell_rfkill_work); - if (wifi_rfkill) { - rfkill_unregister(wifi_rfkill); - rfkill_destroy(wifi_rfkill); - } - if (bluetooth_rfkill) { - rfkill_unregister(bluetooth_rfkill); - rfkill_destroy(bluetooth_rfkill); - } - if (wwan_rfkill) { - rfkill_unregister(wwan_rfkill); - rfkill_destroy(wwan_rfkill); - } -} - -static int dell_send_intensity(struct backlight_device *bd) -{ - struct calling_interface_buffer buffer; - struct calling_interface_token *token; - int ret; - - token = dell_smbios_find_token(BRIGHTNESS_TOKEN); - if (!token) - return -ENODEV; - - dell_fill_request(&buffer, - token->location, bd->props.brightness, 0, 0); - if (power_supply_is_system_supplied() > 0) - ret = dell_send_request(&buffer, - CLASS_TOKEN_WRITE, SELECT_TOKEN_AC); - else - ret = dell_send_request(&buffer, - CLASS_TOKEN_WRITE, SELECT_TOKEN_BAT); - - return ret; -} - -static int dell_get_intensity(struct backlight_device *bd) -{ - struct calling_interface_buffer buffer; - struct calling_interface_token *token; - int ret; - - token = dell_smbios_find_token(BRIGHTNESS_TOKEN); - if (!token) - return -ENODEV; - - dell_fill_request(&buffer, token->location, 0, 0, 0); - if (power_supply_is_system_supplied() > 0) - ret = dell_send_request(&buffer, - CLASS_TOKEN_READ, SELECT_TOKEN_AC); - else - ret = dell_send_request(&buffer, - CLASS_TOKEN_READ, SELECT_TOKEN_BAT); - - if (ret == 0) - ret = buffer.output[1]; - - return ret; -} - -static const struct backlight_ops dell_ops = { - .get_brightness = dell_get_intensity, - .update_status = dell_send_intensity, -}; - -static void touchpad_led_on(void) -{ - int command = 0x97; - char data = 1; - i8042_command(&data, command | 1 << 12); -} - -static void touchpad_led_off(void) -{ - int command = 0x97; - char data = 2; - i8042_command(&data, command | 1 << 12); -} - -static void touchpad_led_set(struct led_classdev *led_cdev, - enum led_brightness value) -{ - if (value > 0) - touchpad_led_on(); - else - touchpad_led_off(); -} - -static struct led_classdev touchpad_led = { - .name = "dell-laptop::touchpad", - .brightness_set = touchpad_led_set, - .flags = LED_CORE_SUSPENDRESUME, -}; - -static int __init touchpad_led_init(struct device *dev) -{ - return led_classdev_register(dev, &touchpad_led); -} - -static void touchpad_led_exit(void) -{ - led_classdev_unregister(&touchpad_led); -} - -/* - * Derived from information in smbios-keyboard-ctl: - * - * cbClass 4 - * cbSelect 11 - * Keyboard illumination - * cbArg1 determines the function to be performed - * - * cbArg1 0x0 = Get Feature Information - * cbRES1 Standard return codes (0, -1, -2) - * cbRES2, word0 Bitmap of user-selectable modes - * bit 0 Always off (All systems) - * bit 1 Always on (Travis ATG, Siberia) - * bit 2 Auto: ALS-based On; ALS-based Off (Travis ATG) - * bit 3 Auto: ALS- and input-activity-based On; input-activity based Off - * bit 4 Auto: Input-activity-based On; input-activity based Off - * bit 5 Auto: Input-activity-based On (illumination level 25%); input-activity based Off - * bit 6 Auto: Input-activity-based On (illumination level 50%); input-activity based Off - * bit 7 Auto: Input-activity-based On (illumination level 75%); input-activity based Off - * bit 8 Auto: Input-activity-based On (illumination level 100%); input-activity based Off - * bits 9-15 Reserved for future use - * cbRES2, byte2 Reserved for future use - * cbRES2, byte3 Keyboard illumination type - * 0 Reserved - * 1 Tasklight - * 2 Backlight - * 3-255 Reserved for future use - * cbRES3, byte0 Supported auto keyboard illumination trigger bitmap. - * bit 0 Any keystroke - * bit 1 Touchpad activity - * bit 2 Pointing stick - * bit 3 Any mouse - * bits 4-7 Reserved for future use - * cbRES3, byte1 Supported timeout unit bitmap - * bit 0 Seconds - * bit 1 Minutes - * bit 2 Hours - * bit 3 Days - * bits 4-7 Reserved for future use - * cbRES3, byte2 Number of keyboard light brightness levels - * cbRES4, byte0 Maximum acceptable seconds value (0 if seconds not supported). - * cbRES4, byte1 Maximum acceptable minutes value (0 if minutes not supported). - * cbRES4, byte2 Maximum acceptable hours value (0 if hours not supported). - * cbRES4, byte3 Maximum acceptable days value (0 if days not supported) - * - * cbArg1 0x1 = Get Current State - * cbRES1 Standard return codes (0, -1, -2) - * cbRES2, word0 Bitmap of current mode state - * bit 0 Always off (All systems) - * bit 1 Always on (Travis ATG, Siberia) - * bit 2 Auto: ALS-based On; ALS-based Off (Travis ATG) - * bit 3 Auto: ALS- and input-activity-based On; input-activity based Off - * bit 4 Auto: Input-activity-based On; input-activity based Off - * bit 5 Auto: Input-activity-based On (illumination level 25%); input-activity based Off - * bit 6 Auto: Input-activity-based On (illumination level 50%); input-activity based Off - * bit 7 Auto: Input-activity-based On (illumination level 75%); input-activity based Off - * bit 8 Auto: Input-activity-based On (illumination level 100%); input-activity based Off - * bits 9-15 Reserved for future use - * Note: Only One bit can be set - * cbRES2, byte2 Currently active auto keyboard illumination triggers. - * bit 0 Any keystroke - * bit 1 Touchpad activity - * bit 2 Pointing stick - * bit 3 Any mouse - * bits 4-7 Reserved for future use - * cbRES2, byte3 Current Timeout on battery - * bits 7:6 Timeout units indicator: - * 00b Seconds - * 01b Minutes - * 10b Hours - * 11b Days - * bits 5:0 Timeout value (0-63) in sec/min/hr/day - * NOTE: A value of 0 means always on (no timeout) if any bits of RES3 byte - * are set upon return from the [Get feature information] call. - * cbRES3, byte0 Current setting of ALS value that turns the light on or off. - * cbRES3, byte1 Current ALS reading - * cbRES3, byte2 Current keyboard light level. - * cbRES3, byte3 Current timeout on AC Power - * bits 7:6 Timeout units indicator: - * 00b Seconds - * 01b Minutes - * 10b Hours - * 11b Days - * Bits 5:0 Timeout value (0-63) in sec/min/hr/day - * NOTE: A value of 0 means always on (no timeout) if any bits of RES3 byte2 - * are set upon return from the upon return from the [Get Feature information] call. - * - * cbArg1 0x2 = Set New State - * cbRES1 Standard return codes (0, -1, -2) - * cbArg2, word0 Bitmap of current mode state - * bit 0 Always off (All systems) - * bit 1 Always on (Travis ATG, Siberia) - * bit 2 Auto: ALS-based On; ALS-based Off (Travis ATG) - * bit 3 Auto: ALS- and input-activity-based On; input-activity based Off - * bit 4 Auto: Input-activity-based On; input-activity based Off - * bit 5 Auto: Input-activity-based On (illumination level 25%); input-activity based Off - * bit 6 Auto: Input-activity-based On (illumination level 50%); input-activity based Off - * bit 7 Auto: Input-activity-based On (illumination level 75%); input-activity based Off - * bit 8 Auto: Input-activity-based On (illumination level 100%); input-activity based Off - * bits 9-15 Reserved for future use - * Note: Only One bit can be set - * cbArg2, byte2 Desired auto keyboard illumination triggers. Must remain inactive to allow - * keyboard to turn off automatically. - * bit 0 Any keystroke - * bit 1 Touchpad activity - * bit 2 Pointing stick - * bit 3 Any mouse - * bits 4-7 Reserved for future use - * cbArg2, byte3 Desired Timeout on battery - * bits 7:6 Timeout units indicator: - * 00b Seconds - * 01b Minutes - * 10b Hours - * 11b Days - * bits 5:0 Timeout value (0-63) in sec/min/hr/day - * cbArg3, byte0 Desired setting of ALS value that turns the light on or off. - * cbArg3, byte2 Desired keyboard light level. - * cbArg3, byte3 Desired Timeout on AC power - * bits 7:6 Timeout units indicator: - * 00b Seconds - * 01b Minutes - * 10b Hours - * 11b Days - * bits 5:0 Timeout value (0-63) in sec/min/hr/day - */ - - -enum kbd_timeout_unit { - KBD_TIMEOUT_SECONDS = 0, - KBD_TIMEOUT_MINUTES, - KBD_TIMEOUT_HOURS, - KBD_TIMEOUT_DAYS, -}; - -enum kbd_mode_bit { - KBD_MODE_BIT_OFF = 0, - KBD_MODE_BIT_ON, - KBD_MODE_BIT_ALS, - KBD_MODE_BIT_TRIGGER_ALS, - KBD_MODE_BIT_TRIGGER, - KBD_MODE_BIT_TRIGGER_25, - KBD_MODE_BIT_TRIGGER_50, - KBD_MODE_BIT_TRIGGER_75, - KBD_MODE_BIT_TRIGGER_100, -}; - -#define kbd_is_als_mode_bit(bit) \ - ((bit) == KBD_MODE_BIT_ALS || (bit) == KBD_MODE_BIT_TRIGGER_ALS) -#define kbd_is_trigger_mode_bit(bit) \ - ((bit) >= KBD_MODE_BIT_TRIGGER_ALS && (bit) <= KBD_MODE_BIT_TRIGGER_100) -#define kbd_is_level_mode_bit(bit) \ - ((bit) >= KBD_MODE_BIT_TRIGGER_25 && (bit) <= KBD_MODE_BIT_TRIGGER_100) - -struct kbd_info { - u16 modes; - u8 type; - u8 triggers; - u8 levels; - u8 seconds; - u8 minutes; - u8 hours; - u8 days; -}; - -struct kbd_state { - u8 mode_bit; - u8 triggers; - u8 timeout_value; - u8 timeout_unit; - u8 timeout_value_ac; - u8 timeout_unit_ac; - u8 als_setting; - u8 als_value; - u8 level; -}; - -static const int kbd_tokens[] = { - KBD_LED_OFF_TOKEN, - KBD_LED_AUTO_25_TOKEN, - KBD_LED_AUTO_50_TOKEN, - KBD_LED_AUTO_75_TOKEN, - KBD_LED_AUTO_100_TOKEN, - KBD_LED_ON_TOKEN, -}; - -static u16 kbd_token_bits; - -static struct kbd_info kbd_info; -static bool kbd_als_supported; -static bool kbd_triggers_supported; -static bool kbd_timeout_ac_supported; - -static u8 kbd_mode_levels[16]; -static int kbd_mode_levels_count; - -static u8 kbd_previous_level; -static u8 kbd_previous_mode_bit; - -static bool kbd_led_present; -static DEFINE_MUTEX(kbd_led_mutex); -static enum led_brightness kbd_led_level; - -/* - * NOTE: there are three ways to set the keyboard backlight level. - * First, via kbd_state.mode_bit (assigning KBD_MODE_BIT_TRIGGER_* value). - * Second, via kbd_state.level (assigning numerical value <= kbd_info.levels). - * Third, via SMBIOS tokens (KBD_LED_* in kbd_tokens) - * - * There are laptops which support only one of these methods. If we want to - * support as many machines as possible we need to implement all three methods. - * The first two methods use the kbd_state structure. The third uses SMBIOS - * tokens. If kbd_info.levels == 0, the machine does not support setting the - * keyboard backlight level via kbd_state.level. - */ - -static int kbd_get_info(struct kbd_info *info) -{ - struct calling_interface_buffer buffer; - u8 units; - int ret; - - dell_fill_request(&buffer, 0, 0, 0, 0); - ret = dell_send_request(&buffer, - CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT); - if (ret) - return ret; - - info->modes = buffer.output[1] & 0xFFFF; - info->type = (buffer.output[1] >> 24) & 0xFF; - info->triggers = buffer.output[2] & 0xFF; - units = (buffer.output[2] >> 8) & 0xFF; - info->levels = (buffer.output[2] >> 16) & 0xFF; - - if (quirks && quirks->kbd_led_levels_off_1 && info->levels) - info->levels--; - - if (units & BIT(0)) - info->seconds = (buffer.output[3] >> 0) & 0xFF; - if (units & BIT(1)) - info->minutes = (buffer.output[3] >> 8) & 0xFF; - if (units & BIT(2)) - info->hours = (buffer.output[3] >> 16) & 0xFF; - if (units & BIT(3)) - info->days = (buffer.output[3] >> 24) & 0xFF; - - return ret; -} - -static unsigned int kbd_get_max_level(void) -{ - if (kbd_info.levels != 0) - return kbd_info.levels; - if (kbd_mode_levels_count > 0) - return kbd_mode_levels_count - 1; - return 0; -} - -static int kbd_get_level(struct kbd_state *state) -{ - int i; - - if (kbd_info.levels != 0) - return state->level; - - if (kbd_mode_levels_count > 0) { - for (i = 0; i < kbd_mode_levels_count; ++i) - if (kbd_mode_levels[i] == state->mode_bit) - return i; - return 0; - } - - return -EINVAL; -} - -static int kbd_set_level(struct kbd_state *state, u8 level) -{ - if (kbd_info.levels != 0) { - if (level != 0) - kbd_previous_level = level; - if (state->level == level) - return 0; - state->level = level; - if (level != 0 && state->mode_bit == KBD_MODE_BIT_OFF) - state->mode_bit = kbd_previous_mode_bit; - else if (level == 0 && state->mode_bit != KBD_MODE_BIT_OFF) { - kbd_previous_mode_bit = state->mode_bit; - state->mode_bit = KBD_MODE_BIT_OFF; - } - return 0; - } - - if (kbd_mode_levels_count > 0 && level < kbd_mode_levels_count) { - if (level != 0) - kbd_previous_level = level; - state->mode_bit = kbd_mode_levels[level]; - return 0; - } - - return -EINVAL; -} - -static int kbd_get_state(struct kbd_state *state) -{ - struct calling_interface_buffer buffer; - int ret; - - dell_fill_request(&buffer, 0x1, 0, 0, 0); - ret = dell_send_request(&buffer, - CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT); - if (ret) - return ret; - - state->mode_bit = ffs(buffer.output[1] & 0xFFFF); - if (state->mode_bit != 0) - state->mode_bit--; - - state->triggers = (buffer.output[1] >> 16) & 0xFF; - state->timeout_value = (buffer.output[1] >> 24) & 0x3F; - state->timeout_unit = (buffer.output[1] >> 30) & 0x3; - state->als_setting = buffer.output[2] & 0xFF; - state->als_value = (buffer.output[2] >> 8) & 0xFF; - state->level = (buffer.output[2] >> 16) & 0xFF; - state->timeout_value_ac = (buffer.output[2] >> 24) & 0x3F; - state->timeout_unit_ac = (buffer.output[2] >> 30) & 0x3; - - return ret; -} - -static int kbd_set_state(struct kbd_state *state) -{ - struct calling_interface_buffer buffer; - int ret; - u32 input1; - u32 input2; - - input1 = BIT(state->mode_bit) & 0xFFFF; - input1 |= (state->triggers & 0xFF) << 16; - input1 |= (state->timeout_value & 0x3F) << 24; - input1 |= (state->timeout_unit & 0x3) << 30; - input2 = state->als_setting & 0xFF; - input2 |= (state->level & 0xFF) << 16; - input2 |= (state->timeout_value_ac & 0x3F) << 24; - input2 |= (state->timeout_unit_ac & 0x3) << 30; - dell_fill_request(&buffer, 0x2, input1, input2, 0); - ret = dell_send_request(&buffer, - CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT); - - return ret; -} - -static int kbd_set_state_safe(struct kbd_state *state, struct kbd_state *old) -{ - int ret; - - ret = kbd_set_state(state); - if (ret == 0) - return 0; - - /* - * When setting the new state fails,try to restore the previous one. - * This is needed on some machines where BIOS sets a default state when - * setting a new state fails. This default state could be all off. - */ - - if (kbd_set_state(old)) - pr_err("Setting old previous keyboard state failed\n"); - - return ret; -} - -static int kbd_set_token_bit(u8 bit) -{ - struct calling_interface_buffer buffer; - struct calling_interface_token *token; - int ret; - - if (bit >= ARRAY_SIZE(kbd_tokens)) - return -EINVAL; - - token = dell_smbios_find_token(kbd_tokens[bit]); - if (!token) - return -EINVAL; - - dell_fill_request(&buffer, token->location, token->value, 0, 0); - ret = dell_send_request(&buffer, CLASS_TOKEN_WRITE, SELECT_TOKEN_STD); - - return ret; -} - -static int kbd_get_token_bit(u8 bit) -{ - struct calling_interface_buffer buffer; - struct calling_interface_token *token; - int ret; - int val; - - if (bit >= ARRAY_SIZE(kbd_tokens)) - return -EINVAL; - - token = dell_smbios_find_token(kbd_tokens[bit]); - if (!token) - return -EINVAL; - - dell_fill_request(&buffer, token->location, 0, 0, 0); - ret = dell_send_request(&buffer, CLASS_TOKEN_READ, SELECT_TOKEN_STD); - val = buffer.output[1]; - - if (ret) - return ret; - - return (val == token->value); -} - -static int kbd_get_first_active_token_bit(void) -{ - int i; - int ret; - - for (i = 0; i < ARRAY_SIZE(kbd_tokens); ++i) { - ret = kbd_get_token_bit(i); - if (ret == 1) - return i; - } - - return ret; -} - -static int kbd_get_valid_token_counts(void) -{ - return hweight16(kbd_token_bits); -} - -static inline int kbd_init_info(void) -{ - struct kbd_state state; - int ret; - int i; - - ret = kbd_get_info(&kbd_info); - if (ret) - return ret; - - /* NOTE: Old models without KBD_LED_AC_TOKEN token supports only one - * timeout value which is shared for both battery and AC power - * settings. So do not try to set AC values on old models. - */ - if ((quirks && quirks->kbd_missing_ac_tag) || - dell_smbios_find_token(KBD_LED_AC_TOKEN)) - kbd_timeout_ac_supported = true; - - kbd_get_state(&state); - - /* NOTE: timeout value is stored in 6 bits so max value is 63 */ - if (kbd_info.seconds > 63) - kbd_info.seconds = 63; - if (kbd_info.minutes > 63) - kbd_info.minutes = 63; - if (kbd_info.hours > 63) - kbd_info.hours = 63; - if (kbd_info.days > 63) - kbd_info.days = 63; - - /* NOTE: On tested machines ON mode did not work and caused - * problems (turned backlight off) so do not use it - */ - kbd_info.modes &= ~BIT(KBD_MODE_BIT_ON); - - kbd_previous_level = kbd_get_level(&state); - kbd_previous_mode_bit = state.mode_bit; - - if (kbd_previous_level == 0 && kbd_get_max_level() != 0) - kbd_previous_level = 1; - - if (kbd_previous_mode_bit == KBD_MODE_BIT_OFF) { - kbd_previous_mode_bit = - ffs(kbd_info.modes & ~BIT(KBD_MODE_BIT_OFF)); - if (kbd_previous_mode_bit != 0) - kbd_previous_mode_bit--; - } - - if (kbd_info.modes & (BIT(KBD_MODE_BIT_ALS) | - BIT(KBD_MODE_BIT_TRIGGER_ALS))) - kbd_als_supported = true; - - if (kbd_info.modes & ( - BIT(KBD_MODE_BIT_TRIGGER_ALS) | BIT(KBD_MODE_BIT_TRIGGER) | - BIT(KBD_MODE_BIT_TRIGGER_25) | BIT(KBD_MODE_BIT_TRIGGER_50) | - BIT(KBD_MODE_BIT_TRIGGER_75) | BIT(KBD_MODE_BIT_TRIGGER_100) - )) - kbd_triggers_supported = true; - - /* kbd_mode_levels[0] is reserved, see below */ - for (i = 0; i < 16; ++i) - if (kbd_is_level_mode_bit(i) && (BIT(i) & kbd_info.modes)) - kbd_mode_levels[1 + kbd_mode_levels_count++] = i; - - /* - * Find the first supported mode and assign to kbd_mode_levels[0]. - * This should be 0 (off), but we cannot depend on the BIOS to - * support 0. - */ - if (kbd_mode_levels_count > 0) { - for (i = 0; i < 16; ++i) { - if (BIT(i) & kbd_info.modes) { - kbd_mode_levels[0] = i; - break; - } - } - kbd_mode_levels_count++; - } - - return 0; - -} - -static inline void kbd_init_tokens(void) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(kbd_tokens); ++i) - if (dell_smbios_find_token(kbd_tokens[i])) - kbd_token_bits |= BIT(i); -} - -static void kbd_init(void) -{ - int ret; - - if (quirks && quirks->kbd_led_not_present) - return; - - ret = kbd_init_info(); - kbd_init_tokens(); - - /* - * Only supports keyboard backlight when it has at least two modes. - */ - if ((ret == 0 && (kbd_info.levels != 0 || kbd_mode_levels_count >= 2)) - || kbd_get_valid_token_counts() >= 2) - kbd_led_present = true; -} - -static ssize_t kbd_led_timeout_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct kbd_state new_state; - struct kbd_state state; - bool convert; - int value; - int ret; - char ch; - u8 unit; - int i; - - ret = sscanf(buf, "%d %c", &value, &ch); - if (ret < 1) - return -EINVAL; - else if (ret == 1) - ch = 's'; - - if (value < 0) - return -EINVAL; - - convert = false; - - switch (ch) { - case 's': - if (value > kbd_info.seconds) - convert = true; - unit = KBD_TIMEOUT_SECONDS; - break; - case 'm': - if (value > kbd_info.minutes) - convert = true; - unit = KBD_TIMEOUT_MINUTES; - break; - case 'h': - if (value > kbd_info.hours) - convert = true; - unit = KBD_TIMEOUT_HOURS; - break; - case 'd': - if (value > kbd_info.days) - convert = true; - unit = KBD_TIMEOUT_DAYS; - break; - default: - return -EINVAL; - } - - if (quirks && quirks->needs_kbd_timeouts) - convert = true; - - if (convert) { - /* Convert value from current units to seconds */ - switch (unit) { - case KBD_TIMEOUT_DAYS: - value *= 24; - fallthrough; - case KBD_TIMEOUT_HOURS: - value *= 60; - fallthrough; - case KBD_TIMEOUT_MINUTES: - value *= 60; - unit = KBD_TIMEOUT_SECONDS; - } - - if (quirks && quirks->needs_kbd_timeouts) { - for (i = 0; quirks->kbd_timeouts[i] != -1; i++) { - if (value <= quirks->kbd_timeouts[i]) { - value = quirks->kbd_timeouts[i]; - break; - } - } - } - - if (value <= kbd_info.seconds && kbd_info.seconds) { - unit = KBD_TIMEOUT_SECONDS; - } else if (value / 60 <= kbd_info.minutes && kbd_info.minutes) { - value /= 60; - unit = KBD_TIMEOUT_MINUTES; - } else if (value / (60 * 60) <= kbd_info.hours && kbd_info.hours) { - value /= (60 * 60); - unit = KBD_TIMEOUT_HOURS; - } else if (value / (60 * 60 * 24) <= kbd_info.days && kbd_info.days) { - value /= (60 * 60 * 24); - unit = KBD_TIMEOUT_DAYS; - } else { - return -EINVAL; - } - } - - mutex_lock(&kbd_led_mutex); - - ret = kbd_get_state(&state); - if (ret) - goto out; - - new_state = state; - - if (kbd_timeout_ac_supported && power_supply_is_system_supplied() > 0) { - new_state.timeout_value_ac = value; - new_state.timeout_unit_ac = unit; - } else { - new_state.timeout_value = value; - new_state.timeout_unit = unit; - } - - ret = kbd_set_state_safe(&new_state, &state); - if (ret) - goto out; - - ret = count; -out: - mutex_unlock(&kbd_led_mutex); - return ret; -} - -static ssize_t kbd_led_timeout_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct kbd_state state; - int value; - int ret; - int len; - u8 unit; - - ret = kbd_get_state(&state); - if (ret) - return ret; - - if (kbd_timeout_ac_supported && power_supply_is_system_supplied() > 0) { - value = state.timeout_value_ac; - unit = state.timeout_unit_ac; - } else { - value = state.timeout_value; - unit = state.timeout_unit; - } - - len = sprintf(buf, "%d", value); - - switch (unit) { - case KBD_TIMEOUT_SECONDS: - return len + sprintf(buf+len, "s\n"); - case KBD_TIMEOUT_MINUTES: - return len + sprintf(buf+len, "m\n"); - case KBD_TIMEOUT_HOURS: - return len + sprintf(buf+len, "h\n"); - case KBD_TIMEOUT_DAYS: - return len + sprintf(buf+len, "d\n"); - default: - return -EINVAL; - } - - return len; -} - -static DEVICE_ATTR(stop_timeout, S_IRUGO | S_IWUSR, - kbd_led_timeout_show, kbd_led_timeout_store); - -static const char * const kbd_led_triggers[] = { - "keyboard", - "touchpad", - /*"trackstick"*/ NULL, /* NOTE: trackstick is just alias for touchpad */ - "mouse", -}; - -static ssize_t kbd_led_triggers_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct kbd_state new_state; - struct kbd_state state; - bool triggers_enabled = false; - int trigger_bit = -1; - char trigger[21]; - int i, ret; - - ret = sscanf(buf, "%20s", trigger); - if (ret != 1) - return -EINVAL; - - if (trigger[0] != '+' && trigger[0] != '-') - return -EINVAL; - - mutex_lock(&kbd_led_mutex); - - ret = kbd_get_state(&state); - if (ret) - goto out; - - if (kbd_triggers_supported) - triggers_enabled = kbd_is_trigger_mode_bit(state.mode_bit); - - if (kbd_triggers_supported) { - for (i = 0; i < ARRAY_SIZE(kbd_led_triggers); ++i) { - if (!(kbd_info.triggers & BIT(i))) - continue; - if (!kbd_led_triggers[i]) - continue; - if (strcmp(trigger+1, kbd_led_triggers[i]) != 0) - continue; - if (trigger[0] == '+' && - triggers_enabled && (state.triggers & BIT(i))) { - ret = count; - goto out; - } - if (trigger[0] == '-' && - (!triggers_enabled || !(state.triggers & BIT(i)))) { - ret = count; - goto out; - } - trigger_bit = i; - break; - } - } - - if (trigger_bit == -1) { - ret = -EINVAL; - goto out; - } - - new_state = state; - if (trigger[0] == '+') - new_state.triggers |= BIT(trigger_bit); - else { - new_state.triggers &= ~BIT(trigger_bit); - /* - * NOTE: trackstick bit (2) must be disabled when - * disabling touchpad bit (1), otherwise touchpad - * bit (1) will not be disabled - */ - if (trigger_bit == 1) - new_state.triggers &= ~BIT(2); - } - if ((kbd_info.triggers & new_state.triggers) != - new_state.triggers) { - ret = -EINVAL; - goto out; - } - if (new_state.triggers && !triggers_enabled) { - new_state.mode_bit = KBD_MODE_BIT_TRIGGER; - kbd_set_level(&new_state, kbd_previous_level); - } else if (new_state.triggers == 0) { - kbd_set_level(&new_state, 0); - } - if (!(kbd_info.modes & BIT(new_state.mode_bit))) { - ret = -EINVAL; - goto out; - } - ret = kbd_set_state_safe(&new_state, &state); - if (ret) - goto out; - if (new_state.mode_bit != KBD_MODE_BIT_OFF) - kbd_previous_mode_bit = new_state.mode_bit; - ret = count; -out: - mutex_unlock(&kbd_led_mutex); - return ret; -} - -static ssize_t kbd_led_triggers_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct kbd_state state; - bool triggers_enabled; - int level, i, ret; - int len = 0; - - ret = kbd_get_state(&state); - if (ret) - return ret; - - len = 0; - - if (kbd_triggers_supported) { - triggers_enabled = kbd_is_trigger_mode_bit(state.mode_bit); - level = kbd_get_level(&state); - for (i = 0; i < ARRAY_SIZE(kbd_led_triggers); ++i) { - if (!(kbd_info.triggers & BIT(i))) - continue; - if (!kbd_led_triggers[i]) - continue; - if ((triggers_enabled || level <= 0) && - (state.triggers & BIT(i))) - buf[len++] = '+'; - else - buf[len++] = '-'; - len += sprintf(buf+len, "%s ", kbd_led_triggers[i]); - } - } - - if (len) - buf[len - 1] = '\n'; - - return len; -} - -static DEVICE_ATTR(start_triggers, S_IRUGO | S_IWUSR, - kbd_led_triggers_show, kbd_led_triggers_store); - -static ssize_t kbd_led_als_enabled_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct kbd_state new_state; - struct kbd_state state; - bool triggers_enabled = false; - int enable; - int ret; - - ret = kstrtoint(buf, 0, &enable); - if (ret) - return ret; - - mutex_lock(&kbd_led_mutex); - - ret = kbd_get_state(&state); - if (ret) - goto out; - - if (enable == kbd_is_als_mode_bit(state.mode_bit)) { - ret = count; - goto out; - } - - new_state = state; - - if (kbd_triggers_supported) - triggers_enabled = kbd_is_trigger_mode_bit(state.mode_bit); - - if (enable) { - if (triggers_enabled) - new_state.mode_bit = KBD_MODE_BIT_TRIGGER_ALS; - else - new_state.mode_bit = KBD_MODE_BIT_ALS; - } else { - if (triggers_enabled) { - new_state.mode_bit = KBD_MODE_BIT_TRIGGER; - kbd_set_level(&new_state, kbd_previous_level); - } else { - new_state.mode_bit = KBD_MODE_BIT_ON; - } - } - if (!(kbd_info.modes & BIT(new_state.mode_bit))) { - ret = -EINVAL; - goto out; - } - - ret = kbd_set_state_safe(&new_state, &state); - if (ret) - goto out; - kbd_previous_mode_bit = new_state.mode_bit; - - ret = count; -out: - mutex_unlock(&kbd_led_mutex); - return ret; -} - -static ssize_t kbd_led_als_enabled_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct kbd_state state; - bool enabled = false; - int ret; - - ret = kbd_get_state(&state); - if (ret) - return ret; - enabled = kbd_is_als_mode_bit(state.mode_bit); - - return sprintf(buf, "%d\n", enabled ? 1 : 0); -} - -static DEVICE_ATTR(als_enabled, S_IRUGO | S_IWUSR, - kbd_led_als_enabled_show, kbd_led_als_enabled_store); - -static ssize_t kbd_led_als_setting_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct kbd_state state; - struct kbd_state new_state; - u8 setting; - int ret; - - ret = kstrtou8(buf, 10, &setting); - if (ret) - return ret; - - mutex_lock(&kbd_led_mutex); - - ret = kbd_get_state(&state); - if (ret) - goto out; - - new_state = state; - new_state.als_setting = setting; - - ret = kbd_set_state_safe(&new_state, &state); - if (ret) - goto out; - - ret = count; -out: - mutex_unlock(&kbd_led_mutex); - return ret; -} - -static ssize_t kbd_led_als_setting_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct kbd_state state; - int ret; - - ret = kbd_get_state(&state); - if (ret) - return ret; - - return sprintf(buf, "%d\n", state.als_setting); -} - -static DEVICE_ATTR(als_setting, S_IRUGO | S_IWUSR, - kbd_led_als_setting_show, kbd_led_als_setting_store); - -static struct attribute *kbd_led_attrs[] = { - &dev_attr_stop_timeout.attr, - &dev_attr_start_triggers.attr, - NULL, -}; - -static const struct attribute_group kbd_led_group = { - .attrs = kbd_led_attrs, -}; - -static struct attribute *kbd_led_als_attrs[] = { - &dev_attr_als_enabled.attr, - &dev_attr_als_setting.attr, - NULL, -}; - -static const struct attribute_group kbd_led_als_group = { - .attrs = kbd_led_als_attrs, -}; - -static const struct attribute_group *kbd_led_groups[] = { - &kbd_led_group, - &kbd_led_als_group, - NULL, -}; - -static enum led_brightness kbd_led_level_get(struct led_classdev *led_cdev) -{ - int ret; - u16 num; - struct kbd_state state; - - if (kbd_get_max_level()) { - ret = kbd_get_state(&state); - if (ret) - return 0; - ret = kbd_get_level(&state); - if (ret < 0) - return 0; - return ret; - } - - if (kbd_get_valid_token_counts()) { - ret = kbd_get_first_active_token_bit(); - if (ret < 0) - return 0; - for (num = kbd_token_bits; num != 0 && ret > 0; --ret) - num &= num - 1; /* clear the first bit set */ - if (num == 0) - return 0; - return ffs(num) - 1; - } - - pr_warn("Keyboard brightness level control not supported\n"); - return 0; -} - -static int kbd_led_level_set(struct led_classdev *led_cdev, - enum led_brightness value) -{ - enum led_brightness new_value = value; - struct kbd_state state; - struct kbd_state new_state; - u16 num; - int ret; - - mutex_lock(&kbd_led_mutex); - - if (kbd_get_max_level()) { - ret = kbd_get_state(&state); - if (ret) - goto out; - new_state = state; - ret = kbd_set_level(&new_state, value); - if (ret) - goto out; - ret = kbd_set_state_safe(&new_state, &state); - } else if (kbd_get_valid_token_counts()) { - for (num = kbd_token_bits; num != 0 && value > 0; --value) - num &= num - 1; /* clear the first bit set */ - if (num == 0) - ret = 0; - else - ret = kbd_set_token_bit(ffs(num) - 1); - } else { - pr_warn("Keyboard brightness level control not supported\n"); - ret = -ENXIO; - } - -out: - if (ret == 0) - kbd_led_level = new_value; - - mutex_unlock(&kbd_led_mutex); - return ret; -} - -static struct led_classdev kbd_led = { - .name = "dell::kbd_backlight", - .flags = LED_BRIGHT_HW_CHANGED, - .brightness_set_blocking = kbd_led_level_set, - .brightness_get = kbd_led_level_get, - .groups = kbd_led_groups, -}; - -static int __init kbd_led_init(struct device *dev) -{ - int ret; - - kbd_init(); - if (!kbd_led_present) - return -ENODEV; - if (!kbd_als_supported) - kbd_led_groups[1] = NULL; - kbd_led.max_brightness = kbd_get_max_level(); - if (!kbd_led.max_brightness) { - kbd_led.max_brightness = kbd_get_valid_token_counts(); - if (kbd_led.max_brightness) - kbd_led.max_brightness--; - } - - kbd_led_level = kbd_led_level_get(NULL); - - ret = led_classdev_register(dev, &kbd_led); - if (ret) - kbd_led_present = false; - - return ret; -} - -static void brightness_set_exit(struct led_classdev *led_cdev, - enum led_brightness value) -{ - /* Don't change backlight level on exit */ -}; - -static void kbd_led_exit(void) -{ - if (!kbd_led_present) - return; - kbd_led.brightness_set = brightness_set_exit; - led_classdev_unregister(&kbd_led); -} - -static int dell_laptop_notifier_call(struct notifier_block *nb, - unsigned long action, void *data) -{ - bool changed = false; - enum led_brightness new_kbd_led_level; - - switch (action) { - case DELL_LAPTOP_KBD_BACKLIGHT_BRIGHTNESS_CHANGED: - if (!kbd_led_present) - break; - - mutex_lock(&kbd_led_mutex); - new_kbd_led_level = kbd_led_level_get(&kbd_led); - if (kbd_led_level != new_kbd_led_level) { - kbd_led_level = new_kbd_led_level; - changed = true; - } - mutex_unlock(&kbd_led_mutex); - - if (changed) - led_classdev_notify_brightness_hw_changed(&kbd_led, - kbd_led_level); - break; - } - - return NOTIFY_OK; -} - -static struct notifier_block dell_laptop_notifier = { - .notifier_call = dell_laptop_notifier_call, -}; - -static int micmute_led_set(struct led_classdev *led_cdev, - enum led_brightness brightness) -{ - struct calling_interface_buffer buffer; - struct calling_interface_token *token; - int state = brightness != LED_OFF; - - if (state == 0) - token = dell_smbios_find_token(GLOBAL_MIC_MUTE_DISABLE); - else - token = dell_smbios_find_token(GLOBAL_MIC_MUTE_ENABLE); - - if (!token) - return -ENODEV; - - dell_fill_request(&buffer, token->location, token->value, 0, 0); - dell_send_request(&buffer, CLASS_TOKEN_WRITE, SELECT_TOKEN_STD); - - return 0; -} - -static struct led_classdev micmute_led_cdev = { - .name = "platform::micmute", - .max_brightness = 1, - .brightness_set_blocking = micmute_led_set, - .default_trigger = "audio-micmute", -}; - -static int __init dell_init(void) -{ - struct calling_interface_token *token; - int max_intensity = 0; - int ret; - - if (!dmi_check_system(dell_device_table)) - return -ENODEV; - - quirks = NULL; - /* find if this machine support other functions */ - dmi_check_system(dell_quirks); - - ret = platform_driver_register(&platform_driver); - if (ret) - goto fail_platform_driver; - platform_device = platform_device_alloc("dell-laptop", -1); - if (!platform_device) { - ret = -ENOMEM; - goto fail_platform_device1; - } - ret = platform_device_add(platform_device); - if (ret) - goto fail_platform_device2; - - ret = dell_setup_rfkill(); - - if (ret) { - pr_warn("Unable to setup rfkill\n"); - goto fail_rfkill; - } - - if (quirks && quirks->touchpad_led) - touchpad_led_init(&platform_device->dev); - - kbd_led_init(&platform_device->dev); - - dell_laptop_dir = debugfs_create_dir("dell_laptop", NULL); - debugfs_create_file("rfkill", 0444, dell_laptop_dir, NULL, - &dell_debugfs_fops); - - dell_laptop_register_notifier(&dell_laptop_notifier); - - if (dell_smbios_find_token(GLOBAL_MIC_MUTE_DISABLE) && - dell_smbios_find_token(GLOBAL_MIC_MUTE_ENABLE)) { - micmute_led_cdev.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE); - ret = led_classdev_register(&platform_device->dev, &micmute_led_cdev); - if (ret < 0) - goto fail_led; - } - - if (acpi_video_get_backlight_type() != acpi_backlight_vendor) - return 0; - - token = dell_smbios_find_token(BRIGHTNESS_TOKEN); - if (token) { - struct calling_interface_buffer buffer; - - dell_fill_request(&buffer, token->location, 0, 0, 0); - ret = dell_send_request(&buffer, - CLASS_TOKEN_READ, SELECT_TOKEN_AC); - if (ret == 0) - max_intensity = buffer.output[3]; - } - - if (max_intensity) { - struct backlight_properties props; - memset(&props, 0, sizeof(struct backlight_properties)); - props.type = BACKLIGHT_PLATFORM; - props.max_brightness = max_intensity; - dell_backlight_device = backlight_device_register("dell_backlight", - &platform_device->dev, - NULL, - &dell_ops, - &props); - - if (IS_ERR(dell_backlight_device)) { - ret = PTR_ERR(dell_backlight_device); - dell_backlight_device = NULL; - goto fail_backlight; - } - - dell_backlight_device->props.brightness = - dell_get_intensity(dell_backlight_device); - if (dell_backlight_device->props.brightness < 0) { - ret = dell_backlight_device->props.brightness; - goto fail_get_brightness; - } - backlight_update_status(dell_backlight_device); - } - - return 0; - -fail_get_brightness: - backlight_device_unregister(dell_backlight_device); -fail_backlight: - led_classdev_unregister(&micmute_led_cdev); -fail_led: - dell_cleanup_rfkill(); -fail_rfkill: - platform_device_del(platform_device); -fail_platform_device2: - platform_device_put(platform_device); -fail_platform_device1: - platform_driver_unregister(&platform_driver); -fail_platform_driver: - return ret; -} - -static void __exit dell_exit(void) -{ - dell_laptop_unregister_notifier(&dell_laptop_notifier); - debugfs_remove_recursive(dell_laptop_dir); - if (quirks && quirks->touchpad_led) - touchpad_led_exit(); - kbd_led_exit(); - backlight_device_unregister(dell_backlight_device); - led_classdev_unregister(&micmute_led_cdev); - dell_cleanup_rfkill(); - if (platform_device) { - platform_device_unregister(platform_device); - platform_driver_unregister(&platform_driver); - } -} - -/* dell-rbtn.c driver export functions which will not work correctly (and could - * cause kernel crash) if they are called before dell-rbtn.c init code. This is - * not problem when dell-rbtn.c is compiled as external module. When both files - * (dell-rbtn.c and dell-laptop.c) are compiled statically into kernel, then we - * need to ensure that dell_init() will be called after initializing dell-rbtn. - * This can be achieved by late_initcall() instead module_init(). - */ -late_initcall(dell_init); -module_exit(dell_exit); - -MODULE_AUTHOR("Matthew Garrett "); -MODULE_AUTHOR("Gabriele Mazzotta "); -MODULE_AUTHOR("Pali Rohár "); -MODULE_DESCRIPTION("Dell laptop driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell-rbtn.c b/drivers/platform/x86/dell-rbtn.c deleted file mode 100644 index a89fad47ff13..000000000000 --- a/drivers/platform/x86/dell-rbtn.c +++ /dev/null @@ -1,499 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - Dell Airplane Mode Switch driver - Copyright (C) 2014-2015 Pali Rohár - -*/ - -#include -#include -#include -#include - -#include "dell-rbtn.h" - -enum rbtn_type { - RBTN_UNKNOWN, - RBTN_TOGGLE, - RBTN_SLIDER, -}; - -struct rbtn_data { - enum rbtn_type type; - struct rfkill *rfkill; - struct input_dev *input_dev; - bool suspended; -}; - - -/* - * acpi functions - */ - -static enum rbtn_type rbtn_check(struct acpi_device *device) -{ - unsigned long long output; - acpi_status status; - - status = acpi_evaluate_integer(device->handle, "CRBT", NULL, &output); - if (ACPI_FAILURE(status)) - return RBTN_UNKNOWN; - - switch (output) { - case 0: - case 1: - return RBTN_TOGGLE; - case 2: - case 3: - return RBTN_SLIDER; - default: - return RBTN_UNKNOWN; - } -} - -static int rbtn_get(struct acpi_device *device) -{ - unsigned long long output; - acpi_status status; - - status = acpi_evaluate_integer(device->handle, "GRBT", NULL, &output); - if (ACPI_FAILURE(status)) - return -EINVAL; - - return !output; -} - -static int rbtn_acquire(struct acpi_device *device, bool enable) -{ - struct acpi_object_list input; - union acpi_object param; - acpi_status status; - - param.type = ACPI_TYPE_INTEGER; - param.integer.value = enable; - input.count = 1; - input.pointer = ¶m; - - status = acpi_evaluate_object(device->handle, "ARBT", &input, NULL); - if (ACPI_FAILURE(status)) - return -EINVAL; - - return 0; -} - - -/* - * rfkill device - */ - -static void rbtn_rfkill_query(struct rfkill *rfkill, void *data) -{ - struct acpi_device *device = data; - int state; - - state = rbtn_get(device); - if (state < 0) - return; - - rfkill_set_states(rfkill, state, state); -} - -static int rbtn_rfkill_set_block(void *data, bool blocked) -{ - /* NOTE: setting soft rfkill state is not supported */ - return -EINVAL; -} - -static const struct rfkill_ops rbtn_ops = { - .query = rbtn_rfkill_query, - .set_block = rbtn_rfkill_set_block, -}; - -static int rbtn_rfkill_init(struct acpi_device *device) -{ - struct rbtn_data *rbtn_data = device->driver_data; - int ret; - - if (rbtn_data->rfkill) - return 0; - - /* - * NOTE: rbtn controls all radio devices, not only WLAN - * but rfkill interface does not support "ANY" type - * so "WLAN" type is used - */ - rbtn_data->rfkill = rfkill_alloc("dell-rbtn", &device->dev, - RFKILL_TYPE_WLAN, &rbtn_ops, device); - if (!rbtn_data->rfkill) - return -ENOMEM; - - ret = rfkill_register(rbtn_data->rfkill); - if (ret) { - rfkill_destroy(rbtn_data->rfkill); - rbtn_data->rfkill = NULL; - return ret; - } - - return 0; -} - -static void rbtn_rfkill_exit(struct acpi_device *device) -{ - struct rbtn_data *rbtn_data = device->driver_data; - - if (!rbtn_data->rfkill) - return; - - rfkill_unregister(rbtn_data->rfkill); - rfkill_destroy(rbtn_data->rfkill); - rbtn_data->rfkill = NULL; -} - -static void rbtn_rfkill_event(struct acpi_device *device) -{ - struct rbtn_data *rbtn_data = device->driver_data; - - if (rbtn_data->rfkill) - rbtn_rfkill_query(rbtn_data->rfkill, device); -} - - -/* - * input device - */ - -static int rbtn_input_init(struct rbtn_data *rbtn_data) -{ - int ret; - - rbtn_data->input_dev = input_allocate_device(); - if (!rbtn_data->input_dev) - return -ENOMEM; - - rbtn_data->input_dev->name = "DELL Wireless hotkeys"; - rbtn_data->input_dev->phys = "dellabce/input0"; - rbtn_data->input_dev->id.bustype = BUS_HOST; - rbtn_data->input_dev->evbit[0] = BIT(EV_KEY); - set_bit(KEY_RFKILL, rbtn_data->input_dev->keybit); - - ret = input_register_device(rbtn_data->input_dev); - if (ret) { - input_free_device(rbtn_data->input_dev); - rbtn_data->input_dev = NULL; - return ret; - } - - return 0; -} - -static void rbtn_input_exit(struct rbtn_data *rbtn_data) -{ - input_unregister_device(rbtn_data->input_dev); - rbtn_data->input_dev = NULL; -} - -static void rbtn_input_event(struct rbtn_data *rbtn_data) -{ - input_report_key(rbtn_data->input_dev, KEY_RFKILL, 1); - input_sync(rbtn_data->input_dev); - input_report_key(rbtn_data->input_dev, KEY_RFKILL, 0); - input_sync(rbtn_data->input_dev); -} - - -/* - * acpi driver - */ - -static int rbtn_add(struct acpi_device *device); -static int rbtn_remove(struct acpi_device *device); -static void rbtn_notify(struct acpi_device *device, u32 event); - -static const struct acpi_device_id rbtn_ids[] = { - { "DELRBTN", 0 }, - { "DELLABCE", 0 }, - - /* - * This driver can also handle the "DELLABC6" device that - * appears on the XPS 13 9350, but that device is disabled by - * the DSDT unless booted with acpi_osi="!Windows 2012" - * acpi_osi="!Windows 2013". - * - * According to Mario at Dell: - * - * DELLABC6 is a custom interface that was created solely to - * have airplane mode support for Windows 7. For Windows 10 - * the proper interface is to use that which is handled by - * intel-hid. A OEM airplane mode driver is not used. - * - * Since the kernel doesn't identify as Windows 7 it would be - * incorrect to do attempt to use that interface. - * - * Even if we override _OSI and bind to DELLABC6, we end up with - * inconsistent behavior in which userspace can get out of sync - * with the rfkill state as it conflicts with events from - * intel-hid. - * - * The upshot is that it is better to just ignore DELLABC6 - * devices. - */ - - { "", 0 }, -}; - -#ifdef CONFIG_PM_SLEEP -static void ACPI_SYSTEM_XFACE rbtn_clear_suspended_flag(void *context) -{ - struct rbtn_data *rbtn_data = context; - - rbtn_data->suspended = false; -} - -static int rbtn_suspend(struct device *dev) -{ - struct acpi_device *device = to_acpi_device(dev); - struct rbtn_data *rbtn_data = acpi_driver_data(device); - - rbtn_data->suspended = true; - - return 0; -} - -static int rbtn_resume(struct device *dev) -{ - struct acpi_device *device = to_acpi_device(dev); - struct rbtn_data *rbtn_data = acpi_driver_data(device); - acpi_status status; - - /* - * Upon resume, some BIOSes send an ACPI notification thet triggers - * an unwanted input event. In order to ignore it, we use a flag - * that we set at suspend and clear once we have received the extra - * ACPI notification. Since ACPI notifications are delivered - * asynchronously to drivers, we clear the flag from the workqueue - * used to deliver the notifications. This should be enough - * to have the flag cleared only after we received the extra - * notification, if any. - */ - status = acpi_os_execute(OSL_NOTIFY_HANDLER, - rbtn_clear_suspended_flag, rbtn_data); - if (ACPI_FAILURE(status)) - rbtn_clear_suspended_flag(rbtn_data); - - return 0; -} -#endif - -static SIMPLE_DEV_PM_OPS(rbtn_pm_ops, rbtn_suspend, rbtn_resume); - -static struct acpi_driver rbtn_driver = { - .name = "dell-rbtn", - .ids = rbtn_ids, - .drv.pm = &rbtn_pm_ops, - .ops = { - .add = rbtn_add, - .remove = rbtn_remove, - .notify = rbtn_notify, - }, - .owner = THIS_MODULE, -}; - - -/* - * notifier export functions - */ - -static bool auto_remove_rfkill = true; - -static ATOMIC_NOTIFIER_HEAD(rbtn_chain_head); - -static int rbtn_inc_count(struct device *dev, void *data) -{ - struct acpi_device *device = to_acpi_device(dev); - struct rbtn_data *rbtn_data = device->driver_data; - int *count = data; - - if (rbtn_data->type == RBTN_SLIDER) - (*count)++; - - return 0; -} - -static int rbtn_switch_dev(struct device *dev, void *data) -{ - struct acpi_device *device = to_acpi_device(dev); - struct rbtn_data *rbtn_data = device->driver_data; - bool enable = data; - - if (rbtn_data->type != RBTN_SLIDER) - return 0; - - if (enable) - rbtn_rfkill_init(device); - else - rbtn_rfkill_exit(device); - - return 0; -} - -int dell_rbtn_notifier_register(struct notifier_block *nb) -{ - bool first; - int count; - int ret; - - count = 0; - ret = driver_for_each_device(&rbtn_driver.drv, NULL, &count, - rbtn_inc_count); - if (ret || count == 0) - return -ENODEV; - - first = !rbtn_chain_head.head; - - ret = atomic_notifier_chain_register(&rbtn_chain_head, nb); - if (ret != 0) - return ret; - - if (auto_remove_rfkill && first) - ret = driver_for_each_device(&rbtn_driver.drv, NULL, - (void *)false, rbtn_switch_dev); - - return ret; -} -EXPORT_SYMBOL_GPL(dell_rbtn_notifier_register); - -int dell_rbtn_notifier_unregister(struct notifier_block *nb) -{ - int ret; - - ret = atomic_notifier_chain_unregister(&rbtn_chain_head, nb); - if (ret != 0) - return ret; - - if (auto_remove_rfkill && !rbtn_chain_head.head) - ret = driver_for_each_device(&rbtn_driver.drv, NULL, - (void *)true, rbtn_switch_dev); - - return ret; -} -EXPORT_SYMBOL_GPL(dell_rbtn_notifier_unregister); - - -/* - * acpi driver functions - */ - -static int rbtn_add(struct acpi_device *device) -{ - struct rbtn_data *rbtn_data; - enum rbtn_type type; - int ret = 0; - - type = rbtn_check(device); - if (type == RBTN_UNKNOWN) { - dev_info(&device->dev, "Unknown device type\n"); - return -EINVAL; - } - - ret = rbtn_acquire(device, true); - if (ret < 0) { - dev_err(&device->dev, "Cannot enable device\n"); - return ret; - } - - rbtn_data = devm_kzalloc(&device->dev, sizeof(*rbtn_data), GFP_KERNEL); - if (!rbtn_data) - return -ENOMEM; - - rbtn_data->type = type; - device->driver_data = rbtn_data; - - switch (rbtn_data->type) { - case RBTN_TOGGLE: - ret = rbtn_input_init(rbtn_data); - break; - case RBTN_SLIDER: - if (auto_remove_rfkill && rbtn_chain_head.head) - ret = 0; - else - ret = rbtn_rfkill_init(device); - break; - default: - ret = -EINVAL; - } - - return ret; - -} - -static int rbtn_remove(struct acpi_device *device) -{ - struct rbtn_data *rbtn_data = device->driver_data; - - switch (rbtn_data->type) { - case RBTN_TOGGLE: - rbtn_input_exit(rbtn_data); - break; - case RBTN_SLIDER: - rbtn_rfkill_exit(device); - break; - default: - break; - } - - rbtn_acquire(device, false); - device->driver_data = NULL; - - return 0; -} - -static void rbtn_notify(struct acpi_device *device, u32 event) -{ - struct rbtn_data *rbtn_data = device->driver_data; - - /* - * Some BIOSes send a notification at resume. - * Ignore it to prevent unwanted input events. - */ - if (rbtn_data->suspended) { - dev_dbg(&device->dev, "ACPI notification ignored\n"); - return; - } - - if (event != 0x80) { - dev_info(&device->dev, "Received unknown event (0x%x)\n", - event); - return; - } - - switch (rbtn_data->type) { - case RBTN_TOGGLE: - rbtn_input_event(rbtn_data); - break; - case RBTN_SLIDER: - rbtn_rfkill_event(device); - atomic_notifier_call_chain(&rbtn_chain_head, event, device); - break; - default: - break; - } -} - - -/* - * module functions - */ - -module_acpi_driver(rbtn_driver); - -module_param(auto_remove_rfkill, bool, 0444); - -MODULE_PARM_DESC(auto_remove_rfkill, "Automatically remove rfkill devices when " - "other modules start receiving events " - "from this module and re-add them when " - "the last module stops receiving events " - "(default true)"); -MODULE_DEVICE_TABLE(acpi, rbtn_ids); -MODULE_DESCRIPTION("Dell Airplane Mode Switch driver"); -MODULE_AUTHOR("Pali Rohár "); -MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell-rbtn.h b/drivers/platform/x86/dell-rbtn.h deleted file mode 100644 index 5e030f926c58..000000000000 --- a/drivers/platform/x86/dell-rbtn.h +++ /dev/null @@ -1,16 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - Dell Airplane Mode Switch driver - Copyright (C) 2014-2015 Pali Rohár - -*/ - -#ifndef _DELL_RBTN_H_ -#define _DELL_RBTN_H_ - -struct notifier_block; - -int dell_rbtn_notifier_register(struct notifier_block *nb); -int dell_rbtn_notifier_unregister(struct notifier_block *nb); - -#endif diff --git a/drivers/platform/x86/dell-smbios-base.c b/drivers/platform/x86/dell-smbios-base.c deleted file mode 100644 index 3a1dbf199441..000000000000 --- a/drivers/platform/x86/dell-smbios-base.c +++ /dev/null @@ -1,652 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Common functions for kernel modules using Dell SMBIOS - * - * Copyright (c) Red Hat - * Copyright (c) 2014 Gabriele Mazzotta - * Copyright (c) 2014 Pali Rohár - * - * Based on documentation in the libsmbios package: - * Copyright (C) 2005-2014 Dell Inc. - */ -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include "dell-smbios.h" - -static u32 da_supported_commands; -static int da_num_tokens; -static struct platform_device *platform_device; -static struct calling_interface_token *da_tokens; -static struct device_attribute *token_location_attrs; -static struct device_attribute *token_value_attrs; -static struct attribute **token_attrs; -static DEFINE_MUTEX(smbios_mutex); - -struct smbios_device { - struct list_head list; - struct device *device; - int (*call_fn)(struct calling_interface_buffer *arg); -}; - -struct smbios_call { - u32 need_capability; - int cmd_class; - int cmd_select; -}; - -/* calls that are whitelisted for given capabilities */ -static struct smbios_call call_whitelist[] = { - /* generally tokens are allowed, but may be further filtered or - * restricted by token blacklist or whitelist - */ - {CAP_SYS_ADMIN, CLASS_TOKEN_READ, SELECT_TOKEN_STD}, - {CAP_SYS_ADMIN, CLASS_TOKEN_READ, SELECT_TOKEN_AC}, - {CAP_SYS_ADMIN, CLASS_TOKEN_READ, SELECT_TOKEN_BAT}, - {CAP_SYS_ADMIN, CLASS_TOKEN_WRITE, SELECT_TOKEN_STD}, - {CAP_SYS_ADMIN, CLASS_TOKEN_WRITE, SELECT_TOKEN_AC}, - {CAP_SYS_ADMIN, CLASS_TOKEN_WRITE, SELECT_TOKEN_BAT}, - /* used by userspace: fwupdate */ - {CAP_SYS_ADMIN, CLASS_ADMIN_PROP, SELECT_ADMIN_PROP}, - /* used by userspace: fwupd */ - {CAP_SYS_ADMIN, CLASS_INFO, SELECT_DOCK}, - {CAP_SYS_ADMIN, CLASS_FLASH_INTERFACE, SELECT_FLASH_INTERFACE}, -}; - -/* calls that are explicitly blacklisted */ -static struct smbios_call call_blacklist[] = { - {0x0000, 1, 7}, /* manufacturing use */ - {0x0000, 6, 5}, /* manufacturing use */ - {0x0000, 11, 3}, /* write once */ - {0x0000, 11, 7}, /* write once */ - {0x0000, 11, 11}, /* write once */ - {0x0000, 19, -1}, /* diagnostics */ - /* handled by kernel: dell-laptop */ - {0x0000, CLASS_INFO, SELECT_RFKILL}, - {0x0000, CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT}, -}; - -struct token_range { - u32 need_capability; - u16 min; - u16 max; -}; - -/* tokens that are whitelisted for given capabilities */ -static struct token_range token_whitelist[] = { - /* used by userspace: fwupdate */ - {CAP_SYS_ADMIN, CAPSULE_EN_TOKEN, CAPSULE_DIS_TOKEN}, - /* can indicate to userspace that WMI is needed */ - {0x0000, WSMT_EN_TOKEN, WSMT_DIS_TOKEN} -}; - -/* tokens that are explicitly blacklisted */ -static struct token_range token_blacklist[] = { - {0x0000, 0x0058, 0x0059}, /* ME use */ - {0x0000, 0x00CD, 0x00D0}, /* raid shadow copy */ - {0x0000, 0x013A, 0x01FF}, /* sata shadow copy */ - {0x0000, 0x0175, 0x0176}, /* write once */ - {0x0000, 0x0195, 0x0197}, /* diagnostics */ - {0x0000, 0x01DC, 0x01DD}, /* manufacturing use */ - {0x0000, 0x027D, 0x0284}, /* diagnostics */ - {0x0000, 0x02E3, 0x02E3}, /* manufacturing use */ - {0x0000, 0x02FF, 0x02FF}, /* manufacturing use */ - {0x0000, 0x0300, 0x0302}, /* manufacturing use */ - {0x0000, 0x0325, 0x0326}, /* manufacturing use */ - {0x0000, 0x0332, 0x0335}, /* fan control */ - {0x0000, 0x0350, 0x0350}, /* manufacturing use */ - {0x0000, 0x0363, 0x0363}, /* manufacturing use */ - {0x0000, 0x0368, 0x0368}, /* manufacturing use */ - {0x0000, 0x03F6, 0x03F7}, /* manufacturing use */ - {0x0000, 0x049E, 0x049F}, /* manufacturing use */ - {0x0000, 0x04A0, 0x04A3}, /* disagnostics */ - {0x0000, 0x04E6, 0x04E7}, /* manufacturing use */ - {0x0000, 0x4000, 0x7FFF}, /* internal BIOS use */ - {0x0000, 0x9000, 0x9001}, /* internal BIOS use */ - {0x0000, 0xA000, 0xBFFF}, /* write only */ - {0x0000, 0xEFF0, 0xEFFF}, /* internal BIOS use */ - /* handled by kernel: dell-laptop */ - {0x0000, BRIGHTNESS_TOKEN, BRIGHTNESS_TOKEN}, - {0x0000, KBD_LED_OFF_TOKEN, KBD_LED_AUTO_TOKEN}, - {0x0000, KBD_LED_AC_TOKEN, KBD_LED_AC_TOKEN}, - {0x0000, KBD_LED_AUTO_25_TOKEN, KBD_LED_AUTO_75_TOKEN}, - {0x0000, KBD_LED_AUTO_100_TOKEN, KBD_LED_AUTO_100_TOKEN}, - {0x0000, GLOBAL_MIC_MUTE_ENABLE, GLOBAL_MIC_MUTE_DISABLE}, -}; - -static LIST_HEAD(smbios_device_list); - -int dell_smbios_error(int value) -{ - switch (value) { - case 0: /* Completed successfully */ - return 0; - case -1: /* Completed with error */ - return -EIO; - case -2: /* Function not supported */ - return -ENXIO; - default: /* Unknown error */ - return -EINVAL; - } -} -EXPORT_SYMBOL_GPL(dell_smbios_error); - -int dell_smbios_register_device(struct device *d, void *call_fn) -{ - struct smbios_device *priv; - - priv = devm_kzalloc(d, sizeof(struct smbios_device), GFP_KERNEL); - if (!priv) - return -ENOMEM; - get_device(d); - priv->device = d; - priv->call_fn = call_fn; - mutex_lock(&smbios_mutex); - list_add_tail(&priv->list, &smbios_device_list); - mutex_unlock(&smbios_mutex); - dev_dbg(d, "Added device: %s\n", d->driver->name); - return 0; -} -EXPORT_SYMBOL_GPL(dell_smbios_register_device); - -void dell_smbios_unregister_device(struct device *d) -{ - struct smbios_device *priv; - - mutex_lock(&smbios_mutex); - list_for_each_entry(priv, &smbios_device_list, list) { - if (priv->device == d) { - list_del(&priv->list); - put_device(d); - break; - } - } - mutex_unlock(&smbios_mutex); - dev_dbg(d, "Remove device: %s\n", d->driver->name); -} -EXPORT_SYMBOL_GPL(dell_smbios_unregister_device); - -int dell_smbios_call_filter(struct device *d, - struct calling_interface_buffer *buffer) -{ - u16 t = 0; - int i; - - /* can't make calls over 30 */ - if (buffer->cmd_class > 30) { - dev_dbg(d, "class too big: %u\n", buffer->cmd_class); - return -EINVAL; - } - - /* supported calls on the particular system */ - if (!(da_supported_commands & (1 << buffer->cmd_class))) { - dev_dbg(d, "invalid command, supported commands: 0x%8x\n", - da_supported_commands); - return -EINVAL; - } - - /* match against call blacklist */ - for (i = 0; i < ARRAY_SIZE(call_blacklist); i++) { - if (buffer->cmd_class != call_blacklist[i].cmd_class) - continue; - if (buffer->cmd_select != call_blacklist[i].cmd_select && - call_blacklist[i].cmd_select != -1) - continue; - dev_dbg(d, "blacklisted command: %u/%u\n", - buffer->cmd_class, buffer->cmd_select); - return -EINVAL; - } - - /* if a token call, find token ID */ - - if ((buffer->cmd_class == CLASS_TOKEN_READ || - buffer->cmd_class == CLASS_TOKEN_WRITE) && - buffer->cmd_select < 3) { - /* tokens enabled ? */ - if (!da_tokens) { - dev_dbg(d, "no token support on this system\n"); - return -EINVAL; - } - - /* find the matching token ID */ - for (i = 0; i < da_num_tokens; i++) { - if (da_tokens[i].location != buffer->input[0]) - continue; - t = da_tokens[i].tokenID; - break; - } - - /* token call; but token didn't exist */ - if (!t) { - dev_dbg(d, "token at location %04x doesn't exist\n", - buffer->input[0]); - return -EINVAL; - } - - /* match against token blacklist */ - for (i = 0; i < ARRAY_SIZE(token_blacklist); i++) { - if (!token_blacklist[i].min || !token_blacklist[i].max) - continue; - if (t >= token_blacklist[i].min && - t <= token_blacklist[i].max) - return -EINVAL; - } - - /* match against token whitelist */ - for (i = 0; i < ARRAY_SIZE(token_whitelist); i++) { - if (!token_whitelist[i].min || !token_whitelist[i].max) - continue; - if (t < token_whitelist[i].min || - t > token_whitelist[i].max) - continue; - if (!token_whitelist[i].need_capability || - capable(token_whitelist[i].need_capability)) { - dev_dbg(d, "whitelisted token: %x\n", t); - return 0; - } - - } - } - /* match against call whitelist */ - for (i = 0; i < ARRAY_SIZE(call_whitelist); i++) { - if (buffer->cmd_class != call_whitelist[i].cmd_class) - continue; - if (buffer->cmd_select != call_whitelist[i].cmd_select) - continue; - if (!call_whitelist[i].need_capability || - capable(call_whitelist[i].need_capability)) { - dev_dbg(d, "whitelisted capable command: %u/%u\n", - buffer->cmd_class, buffer->cmd_select); - return 0; - } - dev_dbg(d, "missing capability %d for %u/%u\n", - call_whitelist[i].need_capability, - buffer->cmd_class, buffer->cmd_select); - - } - - /* not in a whitelist, only allow processes with capabilities */ - if (capable(CAP_SYS_RAWIO)) { - dev_dbg(d, "Allowing %u/%u due to CAP_SYS_RAWIO\n", - buffer->cmd_class, buffer->cmd_select); - return 0; - } - - return -EACCES; -} -EXPORT_SYMBOL_GPL(dell_smbios_call_filter); - -int dell_smbios_call(struct calling_interface_buffer *buffer) -{ - int (*call_fn)(struct calling_interface_buffer *) = NULL; - struct device *selected_dev = NULL; - struct smbios_device *priv; - int ret; - - mutex_lock(&smbios_mutex); - list_for_each_entry(priv, &smbios_device_list, list) { - if (!selected_dev || priv->device->id >= selected_dev->id) { - dev_dbg(priv->device, "Trying device ID: %d\n", - priv->device->id); - call_fn = priv->call_fn; - selected_dev = priv->device; - } - } - - if (!selected_dev) { - ret = -ENODEV; - pr_err("No dell-smbios drivers are loaded\n"); - goto out_smbios_call; - } - - ret = call_fn(buffer); - -out_smbios_call: - mutex_unlock(&smbios_mutex); - return ret; -} -EXPORT_SYMBOL_GPL(dell_smbios_call); - -struct calling_interface_token *dell_smbios_find_token(int tokenid) -{ - int i; - - if (!da_tokens) - return NULL; - - for (i = 0; i < da_num_tokens; i++) { - if (da_tokens[i].tokenID == tokenid) - return &da_tokens[i]; - } - - return NULL; -} -EXPORT_SYMBOL_GPL(dell_smbios_find_token); - -static BLOCKING_NOTIFIER_HEAD(dell_laptop_chain_head); - -int dell_laptop_register_notifier(struct notifier_block *nb) -{ - return blocking_notifier_chain_register(&dell_laptop_chain_head, nb); -} -EXPORT_SYMBOL_GPL(dell_laptop_register_notifier); - -int dell_laptop_unregister_notifier(struct notifier_block *nb) -{ - return blocking_notifier_chain_unregister(&dell_laptop_chain_head, nb); -} -EXPORT_SYMBOL_GPL(dell_laptop_unregister_notifier); - -void dell_laptop_call_notifier(unsigned long action, void *data) -{ - blocking_notifier_call_chain(&dell_laptop_chain_head, action, data); -} -EXPORT_SYMBOL_GPL(dell_laptop_call_notifier); - -static void __init parse_da_table(const struct dmi_header *dm) -{ - /* Final token is a terminator, so we don't want to copy it */ - int tokens = (dm->length-11)/sizeof(struct calling_interface_token)-1; - struct calling_interface_token *new_da_tokens; - struct calling_interface_structure *table = - container_of(dm, struct calling_interface_structure, header); - - /* - * 4 bytes of table header, plus 7 bytes of Dell header - * plus at least 6 bytes of entry - */ - - if (dm->length < 17) - return; - - da_supported_commands = table->supportedCmds; - - new_da_tokens = krealloc(da_tokens, (da_num_tokens + tokens) * - sizeof(struct calling_interface_token), - GFP_KERNEL); - - if (!new_da_tokens) - return; - da_tokens = new_da_tokens; - - memcpy(da_tokens+da_num_tokens, table->tokens, - sizeof(struct calling_interface_token) * tokens); - - da_num_tokens += tokens; -} - -static void zero_duplicates(struct device *dev) -{ - int i, j; - - for (i = 0; i < da_num_tokens; i++) { - if (da_tokens[i].tokenID == 0) - continue; - for (j = i+1; j < da_num_tokens; j++) { - if (da_tokens[j].tokenID == 0) - continue; - if (da_tokens[i].tokenID == da_tokens[j].tokenID) { - dev_dbg(dev, "Zeroing dup token ID %x(%x/%x)\n", - da_tokens[j].tokenID, - da_tokens[j].location, - da_tokens[j].value); - da_tokens[j].tokenID = 0; - } - } - } -} - -static void __init find_tokens(const struct dmi_header *dm, void *dummy) -{ - switch (dm->type) { - case 0xd4: /* Indexed IO */ - case 0xd5: /* Protected Area Type 1 */ - case 0xd6: /* Protected Area Type 2 */ - break; - case 0xda: /* Calling interface */ - parse_da_table(dm); - break; - } -} - -static int match_attribute(struct device *dev, - struct device_attribute *attr) -{ - int i; - - for (i = 0; i < da_num_tokens * 2; i++) { - if (!token_attrs[i]) - continue; - if (strcmp(token_attrs[i]->name, attr->attr.name) == 0) - return i/2; - } - dev_dbg(dev, "couldn't match: %s\n", attr->attr.name); - return -EINVAL; -} - -static ssize_t location_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int i; - - if (!capable(CAP_SYS_ADMIN)) - return -EPERM; - - i = match_attribute(dev, attr); - if (i > 0) - return scnprintf(buf, PAGE_SIZE, "%08x", da_tokens[i].location); - return 0; -} - -static ssize_t value_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int i; - - if (!capable(CAP_SYS_ADMIN)) - return -EPERM; - - i = match_attribute(dev, attr); - if (i > 0) - return scnprintf(buf, PAGE_SIZE, "%08x", da_tokens[i].value); - return 0; -} - -static struct attribute_group smbios_attribute_group = { - .name = "tokens" -}; - -static struct platform_driver platform_driver = { - .driver = { - .name = "dell-smbios", - }, -}; - -static int build_tokens_sysfs(struct platform_device *dev) -{ - char *location_name; - char *value_name; - size_t size; - int ret; - int i, j; - - /* (number of tokens + 1 for null terminated */ - size = sizeof(struct device_attribute) * (da_num_tokens + 1); - token_location_attrs = kzalloc(size, GFP_KERNEL); - if (!token_location_attrs) - return -ENOMEM; - token_value_attrs = kzalloc(size, GFP_KERNEL); - if (!token_value_attrs) - goto out_allocate_value; - - /* need to store both location and value + terminator*/ - size = sizeof(struct attribute *) * ((2 * da_num_tokens) + 1); - token_attrs = kzalloc(size, GFP_KERNEL); - if (!token_attrs) - goto out_allocate_attrs; - - for (i = 0, j = 0; i < da_num_tokens; i++) { - /* skip empty */ - if (da_tokens[i].tokenID == 0) - continue; - /* add location */ - location_name = kasprintf(GFP_KERNEL, "%04x_location", - da_tokens[i].tokenID); - if (location_name == NULL) - goto out_unwind_strings; - sysfs_attr_init(&token_location_attrs[i].attr); - token_location_attrs[i].attr.name = location_name; - token_location_attrs[i].attr.mode = 0444; - token_location_attrs[i].show = location_show; - token_attrs[j++] = &token_location_attrs[i].attr; - - /* add value */ - value_name = kasprintf(GFP_KERNEL, "%04x_value", - da_tokens[i].tokenID); - if (value_name == NULL) - goto loop_fail_create_value; - sysfs_attr_init(&token_value_attrs[i].attr); - token_value_attrs[i].attr.name = value_name; - token_value_attrs[i].attr.mode = 0444; - token_value_attrs[i].show = value_show; - token_attrs[j++] = &token_value_attrs[i].attr; - continue; - -loop_fail_create_value: - kfree(location_name); - goto out_unwind_strings; - } - smbios_attribute_group.attrs = token_attrs; - - ret = sysfs_create_group(&dev->dev.kobj, &smbios_attribute_group); - if (ret) - goto out_unwind_strings; - return 0; - -out_unwind_strings: - while (i--) { - kfree(token_location_attrs[i].attr.name); - kfree(token_value_attrs[i].attr.name); - } - kfree(token_attrs); -out_allocate_attrs: - kfree(token_value_attrs); -out_allocate_value: - kfree(token_location_attrs); - - return -ENOMEM; -} - -static void free_group(struct platform_device *pdev) -{ - int i; - - sysfs_remove_group(&pdev->dev.kobj, - &smbios_attribute_group); - for (i = 0; i < da_num_tokens; i++) { - kfree(token_location_attrs[i].attr.name); - kfree(token_value_attrs[i].attr.name); - } - kfree(token_attrs); - kfree(token_value_attrs); - kfree(token_location_attrs); -} - -static int __init dell_smbios_init(void) -{ - int ret, wmi, smm; - - if (!dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "Dell System", NULL) && - !dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "www.dell.com", NULL)) { - pr_err("Unable to run on non-Dell system\n"); - return -ENODEV; - } - - dmi_walk(find_tokens, NULL); - - ret = platform_driver_register(&platform_driver); - if (ret) - goto fail_platform_driver; - - platform_device = platform_device_alloc("dell-smbios", 0); - if (!platform_device) { - ret = -ENOMEM; - goto fail_platform_device_alloc; - } - ret = platform_device_add(platform_device); - if (ret) - goto fail_platform_device_add; - - /* register backends */ - wmi = init_dell_smbios_wmi(); - if (wmi) - pr_debug("Failed to initialize WMI backend: %d\n", wmi); - smm = init_dell_smbios_smm(); - if (smm) - pr_debug("Failed to initialize SMM backend: %d\n", smm); - if (wmi && smm) { - pr_err("No SMBIOS backends available (wmi: %d, smm: %d)\n", - wmi, smm); - ret = -ENODEV; - goto fail_create_group; - } - - if (da_tokens) { - /* duplicate tokens will cause problems building sysfs files */ - zero_duplicates(&platform_device->dev); - - ret = build_tokens_sysfs(platform_device); - if (ret) - goto fail_sysfs; - } - - return 0; - -fail_sysfs: - free_group(platform_device); - -fail_create_group: - platform_device_del(platform_device); - -fail_platform_device_add: - platform_device_put(platform_device); - -fail_platform_device_alloc: - platform_driver_unregister(&platform_driver); - -fail_platform_driver: - kfree(da_tokens); - return ret; -} - -static void __exit dell_smbios_exit(void) -{ - exit_dell_smbios_wmi(); - exit_dell_smbios_smm(); - mutex_lock(&smbios_mutex); - if (platform_device) { - if (da_tokens) - free_group(platform_device); - platform_device_unregister(platform_device); - platform_driver_unregister(&platform_driver); - } - kfree(da_tokens); - mutex_unlock(&smbios_mutex); -} - -module_init(dell_smbios_init); -module_exit(dell_smbios_exit); - -MODULE_AUTHOR("Matthew Garrett "); -MODULE_AUTHOR("Gabriele Mazzotta "); -MODULE_AUTHOR("Pali Rohár "); -MODULE_AUTHOR("Mario Limonciello "); -MODULE_DESCRIPTION("Common functions for kernel modules using Dell SMBIOS"); -MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell-smbios-smm.c b/drivers/platform/x86/dell-smbios-smm.c deleted file mode 100644 index 97c52a839a3e..000000000000 --- a/drivers/platform/x86/dell-smbios-smm.c +++ /dev/null @@ -1,183 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * SMI methods for use with dell-smbios - * - * Copyright (c) Red Hat - * Copyright (c) 2014 Gabriele Mazzotta - * Copyright (c) 2014 Pali Rohár - * Copyright (c) 2017 Dell Inc. - */ -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include "dcdbas.h" -#include "dell-smbios.h" - -static int da_command_address; -static int da_command_code; -static struct calling_interface_buffer *buffer; -static struct platform_device *platform_device; -static DEFINE_MUTEX(smm_mutex); - -static const struct dmi_system_id dell_device_table[] __initconst = { - { - .ident = "Dell laptop", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_CHASSIS_TYPE, "8"), - }, - }, - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /*Laptop*/ - }, - }, - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_CHASSIS_TYPE, "10"), /*Notebook*/ - }, - }, - { - .ident = "Dell Computer Corporation", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"), - DMI_MATCH(DMI_CHASSIS_TYPE, "8"), - }, - }, - { } -}; -MODULE_DEVICE_TABLE(dmi, dell_device_table); - -static void parse_da_table(const struct dmi_header *dm) -{ - struct calling_interface_structure *table = - container_of(dm, struct calling_interface_structure, header); - - /* 4 bytes of table header, plus 7 bytes of Dell header, plus at least - * 6 bytes of entry - */ - if (dm->length < 17) - return; - - da_command_address = table->cmdIOAddress; - da_command_code = table->cmdIOCode; -} - -static void find_cmd_address(const struct dmi_header *dm, void *dummy) -{ - switch (dm->type) { - case 0xda: /* Calling interface */ - parse_da_table(dm); - break; - } -} - -static int dell_smbios_smm_call(struct calling_interface_buffer *input) -{ - struct smi_cmd command; - size_t size; - - size = sizeof(struct calling_interface_buffer); - command.magic = SMI_CMD_MAGIC; - command.command_address = da_command_address; - command.command_code = da_command_code; - command.ebx = virt_to_phys(buffer); - command.ecx = 0x42534931; - - mutex_lock(&smm_mutex); - memcpy(buffer, input, size); - dcdbas_smi_request(&command); - memcpy(input, buffer, size); - mutex_unlock(&smm_mutex); - return 0; -} - -/* When enabled this indicates that SMM won't work */ -static bool test_wsmt_enabled(void) -{ - struct calling_interface_token *wsmt; - - /* if token doesn't exist, SMM will work */ - wsmt = dell_smbios_find_token(WSMT_EN_TOKEN); - if (!wsmt) - return false; - - /* If token exists, try to access over SMM but set a dummy return. - * - If WSMT disabled it will be overwritten by SMM - * - If WSMT enabled then dummy value will remain - */ - buffer->cmd_class = CLASS_TOKEN_READ; - buffer->cmd_select = SELECT_TOKEN_STD; - memset(buffer, 0, sizeof(struct calling_interface_buffer)); - buffer->input[0] = wsmt->location; - buffer->output[0] = 99; - dell_smbios_smm_call(buffer); - if (buffer->output[0] == 99) - return true; - - return false; -} - -int init_dell_smbios_smm(void) -{ - int ret; - /* - * Allocate buffer below 4GB for SMI data--only 32-bit physical addr - * is passed to SMI handler. - */ - buffer = (void *)__get_free_page(GFP_KERNEL | GFP_DMA32); - if (!buffer) - return -ENOMEM; - - dmi_walk(find_cmd_address, NULL); - - if (test_wsmt_enabled()) { - pr_debug("Disabling due to WSMT enabled\n"); - ret = -ENODEV; - goto fail_wsmt; - } - - platform_device = platform_device_alloc("dell-smbios", 1); - if (!platform_device) { - ret = -ENOMEM; - goto fail_platform_device_alloc; - } - - ret = platform_device_add(platform_device); - if (ret) - goto fail_platform_device_add; - - ret = dell_smbios_register_device(&platform_device->dev, - &dell_smbios_smm_call); - if (ret) - goto fail_register; - - return 0; - -fail_register: - platform_device_del(platform_device); - -fail_platform_device_add: - platform_device_put(platform_device); - -fail_wsmt: -fail_platform_device_alloc: - free_page((unsigned long)buffer); - return ret; -} - -void exit_dell_smbios_smm(void) -{ - if (platform_device) { - dell_smbios_unregister_device(&platform_device->dev); - platform_device_unregister(platform_device); - free_page((unsigned long)buffer); - } -} diff --git a/drivers/platform/x86/dell-smbios-wmi.c b/drivers/platform/x86/dell-smbios-wmi.c deleted file mode 100644 index 27a298b7c541..000000000000 --- a/drivers/platform/x86/dell-smbios-wmi.c +++ /dev/null @@ -1,277 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * WMI methods for use with dell-smbios - * - * Copyright (c) 2017 Dell Inc. - */ -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include "dell-smbios.h" -#include "dell-wmi-descriptor.h" - -static DEFINE_MUTEX(call_mutex); -static DEFINE_MUTEX(list_mutex); -static int wmi_supported; - -struct misc_bios_flags_structure { - struct dmi_header header; - u16 flags0; -} __packed; -#define FLAG_HAS_ACPI_WMI 0x02 - -#define DELL_WMI_SMBIOS_GUID "A80593CE-A997-11DA-B012-B622A1EF5492" - -struct wmi_smbios_priv { - struct dell_wmi_smbios_buffer *buf; - struct list_head list; - struct wmi_device *wdev; - struct device *child; - u32 req_buf_size; -}; -static LIST_HEAD(wmi_list); - -static inline struct wmi_smbios_priv *get_first_smbios_priv(void) -{ - return list_first_entry_or_null(&wmi_list, - struct wmi_smbios_priv, - list); -} - -static int run_smbios_call(struct wmi_device *wdev) -{ - struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL}; - struct wmi_smbios_priv *priv; - struct acpi_buffer input; - union acpi_object *obj; - acpi_status status; - - priv = dev_get_drvdata(&wdev->dev); - input.length = priv->req_buf_size - sizeof(u64); - input.pointer = &priv->buf->std; - - dev_dbg(&wdev->dev, "evaluating: %u/%u [%x,%x,%x,%x]\n", - priv->buf->std.cmd_class, priv->buf->std.cmd_select, - priv->buf->std.input[0], priv->buf->std.input[1], - priv->buf->std.input[2], priv->buf->std.input[3]); - - status = wmidev_evaluate_method(wdev, 0, 1, &input, &output); - if (ACPI_FAILURE(status)) - return -EIO; - obj = (union acpi_object *)output.pointer; - if (obj->type != ACPI_TYPE_BUFFER) { - dev_dbg(&wdev->dev, "received type: %d\n", obj->type); - if (obj->type == ACPI_TYPE_INTEGER) - dev_dbg(&wdev->dev, "SMBIOS call failed: %llu\n", - obj->integer.value); - return -EIO; - } - memcpy(&priv->buf->std, obj->buffer.pointer, obj->buffer.length); - dev_dbg(&wdev->dev, "result: [%08x,%08x,%08x,%08x]\n", - priv->buf->std.output[0], priv->buf->std.output[1], - priv->buf->std.output[2], priv->buf->std.output[3]); - kfree(output.pointer); - - return 0; -} - -static int dell_smbios_wmi_call(struct calling_interface_buffer *buffer) -{ - struct wmi_smbios_priv *priv; - size_t difference; - size_t size; - int ret; - - mutex_lock(&call_mutex); - priv = get_first_smbios_priv(); - if (!priv) { - ret = -ENODEV; - goto out_wmi_call; - } - - size = sizeof(struct calling_interface_buffer); - difference = priv->req_buf_size - sizeof(u64) - size; - - memset(&priv->buf->ext, 0, difference); - memcpy(&priv->buf->std, buffer, size); - ret = run_smbios_call(priv->wdev); - memcpy(buffer, &priv->buf->std, size); -out_wmi_call: - mutex_unlock(&call_mutex); - - return ret; -} - -static long dell_smbios_wmi_filter(struct wmi_device *wdev, unsigned int cmd, - struct wmi_ioctl_buffer *arg) -{ - struct wmi_smbios_priv *priv; - int ret = 0; - - switch (cmd) { - case DELL_WMI_SMBIOS_CMD: - mutex_lock(&call_mutex); - priv = dev_get_drvdata(&wdev->dev); - if (!priv) { - ret = -ENODEV; - goto fail_smbios_cmd; - } - memcpy(priv->buf, arg, priv->req_buf_size); - if (dell_smbios_call_filter(&wdev->dev, &priv->buf->std)) { - dev_err(&wdev->dev, "Invalid call %d/%d:%8x\n", - priv->buf->std.cmd_class, - priv->buf->std.cmd_select, - priv->buf->std.input[0]); - ret = -EFAULT; - goto fail_smbios_cmd; - } - ret = run_smbios_call(priv->wdev); - if (ret) - goto fail_smbios_cmd; - memcpy(arg, priv->buf, priv->req_buf_size); -fail_smbios_cmd: - mutex_unlock(&call_mutex); - break; - default: - ret = -ENOIOCTLCMD; - } - return ret; -} - -static int dell_smbios_wmi_probe(struct wmi_device *wdev, const void *context) -{ - struct wmi_driver *wdriver = - container_of(wdev->dev.driver, struct wmi_driver, driver); - struct wmi_smbios_priv *priv; - u32 hotfix; - int count; - int ret; - - ret = dell_wmi_get_descriptor_valid(); - if (ret) - return ret; - - priv = devm_kzalloc(&wdev->dev, sizeof(struct wmi_smbios_priv), - GFP_KERNEL); - if (!priv) - return -ENOMEM; - - /* WMI buffer size will be either 4k or 32k depending on machine */ - if (!dell_wmi_get_size(&priv->req_buf_size)) - return -EPROBE_DEFER; - - /* some SMBIOS calls fail unless BIOS contains hotfix */ - if (!dell_wmi_get_hotfix(&hotfix)) - return -EPROBE_DEFER; - if (!hotfix) { - dev_warn(&wdev->dev, - "WMI SMBIOS userspace interface not supported(%u), try upgrading to a newer BIOS\n", - hotfix); - wdriver->filter_callback = NULL; - } - - /* add in the length object we will use internally with ioctl */ - priv->req_buf_size += sizeof(u64); - ret = set_required_buffer_size(wdev, priv->req_buf_size); - if (ret) - return ret; - - count = get_order(priv->req_buf_size); - priv->buf = (void *)__get_free_pages(GFP_KERNEL, count); - if (!priv->buf) - return -ENOMEM; - - /* ID is used by dell-smbios to set priority of drivers */ - wdev->dev.id = 1; - ret = dell_smbios_register_device(&wdev->dev, &dell_smbios_wmi_call); - if (ret) - goto fail_register; - - priv->wdev = wdev; - dev_set_drvdata(&wdev->dev, priv); - mutex_lock(&list_mutex); - list_add_tail(&priv->list, &wmi_list); - mutex_unlock(&list_mutex); - - return 0; - -fail_register: - free_pages((unsigned long)priv->buf, count); - return ret; -} - -static int dell_smbios_wmi_remove(struct wmi_device *wdev) -{ - struct wmi_smbios_priv *priv = dev_get_drvdata(&wdev->dev); - int count; - - mutex_lock(&call_mutex); - mutex_lock(&list_mutex); - list_del(&priv->list); - mutex_unlock(&list_mutex); - dell_smbios_unregister_device(&wdev->dev); - count = get_order(priv->req_buf_size); - free_pages((unsigned long)priv->buf, count); - mutex_unlock(&call_mutex); - return 0; -} - -static const struct wmi_device_id dell_smbios_wmi_id_table[] = { - { .guid_string = DELL_WMI_SMBIOS_GUID }, - { }, -}; - -static void parse_b1_table(const struct dmi_header *dm) -{ - struct misc_bios_flags_structure *flags = - container_of(dm, struct misc_bios_flags_structure, header); - - /* 4 bytes header, 8 bytes flags */ - if (dm->length < 12) - return; - if (dm->handle != 0xb100) - return; - if ((flags->flags0 & FLAG_HAS_ACPI_WMI)) - wmi_supported = 1; -} - -static void find_b1(const struct dmi_header *dm, void *dummy) -{ - switch (dm->type) { - case 0xb1: /* misc bios flags */ - parse_b1_table(dm); - break; - } -} - -static struct wmi_driver dell_smbios_wmi_driver = { - .driver = { - .name = "dell-smbios", - }, - .probe = dell_smbios_wmi_probe, - .remove = dell_smbios_wmi_remove, - .id_table = dell_smbios_wmi_id_table, - .filter_callback = dell_smbios_wmi_filter, -}; - -int init_dell_smbios_wmi(void) -{ - dmi_walk(find_b1, NULL); - - if (!wmi_supported) - return -ENODEV; - - return wmi_driver_register(&dell_smbios_wmi_driver); -} - -void exit_dell_smbios_wmi(void) -{ - wmi_driver_unregister(&dell_smbios_wmi_driver); -} - -MODULE_DEVICE_TABLE(wmi, dell_smbios_wmi_id_table); diff --git a/drivers/platform/x86/dell-smbios.h b/drivers/platform/x86/dell-smbios.h deleted file mode 100644 index 75fa8ea0476d..000000000000 --- a/drivers/platform/x86/dell-smbios.h +++ /dev/null @@ -1,100 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Common functions for kernel modules using Dell SMBIOS - * - * Copyright (c) Red Hat - * Copyright (c) 2014 Gabriele Mazzotta - * Copyright (c) 2014 Pali Rohár - * - * Based on documentation in the libsmbios package: - * Copyright (C) 2005-2014 Dell Inc. - */ - -#ifndef _DELL_SMBIOS_H_ -#define _DELL_SMBIOS_H_ - -#include -#include - -/* Classes and selects used only in kernel drivers */ -#define CLASS_KBD_BACKLIGHT 4 -#define SELECT_KBD_BACKLIGHT 11 - -/* Tokens used in kernel drivers, any of these - * should be filtered from userspace access - */ -#define BRIGHTNESS_TOKEN 0x007d -#define KBD_LED_AC_TOKEN 0x0451 -#define KBD_LED_OFF_TOKEN 0x01E1 -#define KBD_LED_ON_TOKEN 0x01E2 -#define KBD_LED_AUTO_TOKEN 0x01E3 -#define KBD_LED_AUTO_25_TOKEN 0x02EA -#define KBD_LED_AUTO_50_TOKEN 0x02EB -#define KBD_LED_AUTO_75_TOKEN 0x02EC -#define KBD_LED_AUTO_100_TOKEN 0x02F6 -#define GLOBAL_MIC_MUTE_ENABLE 0x0364 -#define GLOBAL_MIC_MUTE_DISABLE 0x0365 - -struct notifier_block; - -struct calling_interface_token { - u16 tokenID; - u16 location; - union { - u16 value; - u16 stringlength; - }; -}; - -struct calling_interface_structure { - struct dmi_header header; - u16 cmdIOAddress; - u8 cmdIOCode; - u32 supportedCmds; - struct calling_interface_token tokens[]; -} __packed; - -int dell_smbios_register_device(struct device *d, void *call_fn); -void dell_smbios_unregister_device(struct device *d); - -int dell_smbios_error(int value); -int dell_smbios_call_filter(struct device *d, - struct calling_interface_buffer *buffer); -int dell_smbios_call(struct calling_interface_buffer *buffer); - -struct calling_interface_token *dell_smbios_find_token(int tokenid); - -enum dell_laptop_notifier_actions { - DELL_LAPTOP_KBD_BACKLIGHT_BRIGHTNESS_CHANGED, -}; - -int dell_laptop_register_notifier(struct notifier_block *nb); -int dell_laptop_unregister_notifier(struct notifier_block *nb); -void dell_laptop_call_notifier(unsigned long action, void *data); - -/* for the supported backends */ -#ifdef CONFIG_DELL_SMBIOS_WMI -int init_dell_smbios_wmi(void); -void exit_dell_smbios_wmi(void); -#else /* CONFIG_DELL_SMBIOS_WMI */ -static inline int init_dell_smbios_wmi(void) -{ - return -ENODEV; -} -static inline void exit_dell_smbios_wmi(void) -{} -#endif /* CONFIG_DELL_SMBIOS_WMI */ - -#ifdef CONFIG_DELL_SMBIOS_SMM -int init_dell_smbios_smm(void); -void exit_dell_smbios_smm(void); -#else /* CONFIG_DELL_SMBIOS_SMM */ -static inline int init_dell_smbios_smm(void) -{ - return -ENODEV; -} -static inline void exit_dell_smbios_smm(void) -{} -#endif /* CONFIG_DELL_SMBIOS_SMM */ - -#endif /* _DELL_SMBIOS_H_ */ diff --git a/drivers/platform/x86/dell-smo8800.c b/drivers/platform/x86/dell-smo8800.c deleted file mode 100644 index 5d9304a7de1b..000000000000 --- a/drivers/platform/x86/dell-smo8800.c +++ /dev/null @@ -1,232 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * dell-smo8800.c - Dell Latitude ACPI SMO88XX freefall sensor driver - * - * Copyright (C) 2012 Sonal Santan - * Copyright (C) 2014 Pali Rohár - * - * This is loosely based on lis3lv02d driver. - */ - -#define DRIVER_NAME "smo8800" - -#include -#include -#include -#include -#include -#include -#include - -struct smo8800_device { - u32 irq; /* acpi device irq */ - atomic_t counter; /* count after last read */ - struct miscdevice miscdev; /* for /dev/freefall */ - unsigned long misc_opened; /* whether the device is open */ - wait_queue_head_t misc_wait; /* Wait queue for the misc dev */ - struct device *dev; /* acpi device */ -}; - -static irqreturn_t smo8800_interrupt_quick(int irq, void *data) -{ - struct smo8800_device *smo8800 = data; - - atomic_inc(&smo8800->counter); - wake_up_interruptible(&smo8800->misc_wait); - return IRQ_WAKE_THREAD; -} - -static irqreturn_t smo8800_interrupt_thread(int irq, void *data) -{ - struct smo8800_device *smo8800 = data; - - dev_info(smo8800->dev, "detected free fall\n"); - return IRQ_HANDLED; -} - -static acpi_status smo8800_get_resource(struct acpi_resource *resource, - void *context) -{ - struct acpi_resource_extended_irq *irq; - - if (resource->type != ACPI_RESOURCE_TYPE_EXTENDED_IRQ) - return AE_OK; - - irq = &resource->data.extended_irq; - if (!irq || !irq->interrupt_count) - return AE_OK; - - *((u32 *)context) = irq->interrupts[0]; - return AE_CTRL_TERMINATE; -} - -static u32 smo8800_get_irq(struct acpi_device *device) -{ - u32 irq = 0; - acpi_status status; - - status = acpi_walk_resources(device->handle, METHOD_NAME__CRS, - smo8800_get_resource, &irq); - if (ACPI_FAILURE(status)) { - dev_err(&device->dev, "acpi_walk_resources failed\n"); - return 0; - } - - return irq; -} - -static ssize_t smo8800_misc_read(struct file *file, char __user *buf, - size_t count, loff_t *pos) -{ - struct smo8800_device *smo8800 = container_of(file->private_data, - struct smo8800_device, miscdev); - - u32 data = 0; - unsigned char byte_data; - ssize_t retval = 1; - - if (count < 1) - return -EINVAL; - - atomic_set(&smo8800->counter, 0); - retval = wait_event_interruptible(smo8800->misc_wait, - (data = atomic_xchg(&smo8800->counter, 0))); - - if (retval) - return retval; - - retval = 1; - - if (data < 255) - byte_data = data; - else - byte_data = 255; - - if (put_user(byte_data, buf)) - retval = -EFAULT; - - return retval; -} - -static int smo8800_misc_open(struct inode *inode, struct file *file) -{ - struct smo8800_device *smo8800 = container_of(file->private_data, - struct smo8800_device, miscdev); - - if (test_and_set_bit(0, &smo8800->misc_opened)) - return -EBUSY; /* already open */ - - atomic_set(&smo8800->counter, 0); - return 0; -} - -static int smo8800_misc_release(struct inode *inode, struct file *file) -{ - struct smo8800_device *smo8800 = container_of(file->private_data, - struct smo8800_device, miscdev); - - clear_bit(0, &smo8800->misc_opened); /* release the device */ - return 0; -} - -static const struct file_operations smo8800_misc_fops = { - .owner = THIS_MODULE, - .read = smo8800_misc_read, - .open = smo8800_misc_open, - .release = smo8800_misc_release, -}; - -static int smo8800_add(struct acpi_device *device) -{ - int err; - struct smo8800_device *smo8800; - - smo8800 = devm_kzalloc(&device->dev, sizeof(*smo8800), GFP_KERNEL); - if (!smo8800) { - dev_err(&device->dev, "failed to allocate device data\n"); - return -ENOMEM; - } - - smo8800->dev = &device->dev; - smo8800->miscdev.minor = MISC_DYNAMIC_MINOR; - smo8800->miscdev.name = "freefall"; - smo8800->miscdev.fops = &smo8800_misc_fops; - - init_waitqueue_head(&smo8800->misc_wait); - - err = misc_register(&smo8800->miscdev); - if (err) { - dev_err(&device->dev, "failed to register misc dev: %d\n", err); - return err; - } - - device->driver_data = smo8800; - - smo8800->irq = smo8800_get_irq(device); - if (!smo8800->irq) { - dev_err(&device->dev, "failed to obtain IRQ\n"); - err = -EINVAL; - goto error; - } - - err = request_threaded_irq(smo8800->irq, smo8800_interrupt_quick, - smo8800_interrupt_thread, - IRQF_TRIGGER_RISING | IRQF_ONESHOT, - DRIVER_NAME, smo8800); - if (err) { - dev_err(&device->dev, - "failed to request thread for IRQ %d: %d\n", - smo8800->irq, err); - goto error; - } - - dev_dbg(&device->dev, "device /dev/freefall registered with IRQ %d\n", - smo8800->irq); - return 0; - -error: - misc_deregister(&smo8800->miscdev); - return err; -} - -static int smo8800_remove(struct acpi_device *device) -{ - struct smo8800_device *smo8800 = device->driver_data; - - free_irq(smo8800->irq, smo8800); - misc_deregister(&smo8800->miscdev); - dev_dbg(&device->dev, "device /dev/freefall unregistered\n"); - return 0; -} - -/* NOTE: Keep this list in sync with drivers/i2c/busses/i2c-i801.c */ -static const struct acpi_device_id smo8800_ids[] = { - { "SMO8800", 0 }, - { "SMO8801", 0 }, - { "SMO8810", 0 }, - { "SMO8811", 0 }, - { "SMO8820", 0 }, - { "SMO8821", 0 }, - { "SMO8830", 0 }, - { "SMO8831", 0 }, - { "", 0 }, -}; - -MODULE_DEVICE_TABLE(acpi, smo8800_ids); - -static struct acpi_driver smo8800_driver = { - .name = DRIVER_NAME, - .class = "Latitude", - .ids = smo8800_ids, - .ops = { - .add = smo8800_add, - .remove = smo8800_remove, - }, - .owner = THIS_MODULE, -}; - -module_acpi_driver(smo8800_driver); - -MODULE_DESCRIPTION("Dell Latitude freefall driver (ACPI SMO88XX)"); -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Sonal Santan, Pali Rohár"); diff --git a/drivers/platform/x86/dell-wmi-aio.c b/drivers/platform/x86/dell-wmi-aio.c deleted file mode 100644 index c7b7f1e403fb..000000000000 --- a/drivers/platform/x86/dell-wmi-aio.c +++ /dev/null @@ -1,197 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * WMI hotkeys support for Dell All-In-One series - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include - -MODULE_DESCRIPTION("WMI hotkeys driver for Dell All-In-One series"); -MODULE_LICENSE("GPL"); - -#define EVENT_GUID1 "284A0E6B-380E-472A-921F-E52786257FB4" -#define EVENT_GUID2 "02314822-307C-4F66-BF0E-48AEAEB26CC8" - -struct dell_wmi_event { - u16 length; - /* 0x000: A hot key pressed or an event occurred - * 0x00F: A sequence of hot keys are pressed */ - u16 type; - u16 event[]; -}; - -static const char *dell_wmi_aio_guids[] = { - EVENT_GUID1, - EVENT_GUID2, - NULL -}; - -MODULE_ALIAS("wmi:"EVENT_GUID1); -MODULE_ALIAS("wmi:"EVENT_GUID2); - -static const struct key_entry dell_wmi_aio_keymap[] = { - { KE_KEY, 0xc0, { KEY_VOLUMEUP } }, - { KE_KEY, 0xc1, { KEY_VOLUMEDOWN } }, - { KE_KEY, 0xe030, { KEY_VOLUMEUP } }, - { KE_KEY, 0xe02e, { KEY_VOLUMEDOWN } }, - { KE_KEY, 0xe020, { KEY_MUTE } }, - { KE_KEY, 0xe027, { KEY_DISPLAYTOGGLE } }, - { KE_KEY, 0xe006, { KEY_BRIGHTNESSUP } }, - { KE_KEY, 0xe005, { KEY_BRIGHTNESSDOWN } }, - { KE_KEY, 0xe00b, { KEY_SWITCHVIDEOMODE } }, - { KE_END, 0 } -}; - -static struct input_dev *dell_wmi_aio_input_dev; - -/* - * The new WMI event data format will follow the dell_wmi_event structure - * So, we will check if the buffer matches the format - */ -static bool dell_wmi_aio_event_check(u8 *buffer, int length) -{ - struct dell_wmi_event *event = (struct dell_wmi_event *)buffer; - - if (event == NULL || length < 6) - return false; - - if ((event->type == 0 || event->type == 0xf) && - event->length >= 2) - return true; - - return false; -} - -static void dell_wmi_aio_notify(u32 value, void *context) -{ - struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; - union acpi_object *obj; - struct dell_wmi_event *event; - acpi_status status; - - status = wmi_get_event_data(value, &response); - if (status != AE_OK) { - pr_info("bad event status 0x%x\n", status); - return; - } - - obj = (union acpi_object *)response.pointer; - if (obj) { - unsigned int scancode = 0; - - switch (obj->type) { - case ACPI_TYPE_INTEGER: - /* Most All-In-One correctly return integer scancode */ - scancode = obj->integer.value; - sparse_keymap_report_event(dell_wmi_aio_input_dev, - scancode, 1, true); - break; - case ACPI_TYPE_BUFFER: - if (dell_wmi_aio_event_check(obj->buffer.pointer, - obj->buffer.length)) { - event = (struct dell_wmi_event *) - obj->buffer.pointer; - scancode = event->event[0]; - } else { - /* Broken machines return the scancode in a - buffer */ - if (obj->buffer.pointer && - obj->buffer.length > 0) - scancode = obj->buffer.pointer[0]; - } - if (scancode) - sparse_keymap_report_event( - dell_wmi_aio_input_dev, - scancode, 1, true); - break; - } - } - kfree(obj); -} - -static int __init dell_wmi_aio_input_setup(void) -{ - int err; - - dell_wmi_aio_input_dev = input_allocate_device(); - - if (!dell_wmi_aio_input_dev) - return -ENOMEM; - - dell_wmi_aio_input_dev->name = "Dell AIO WMI hotkeys"; - dell_wmi_aio_input_dev->phys = "wmi/input0"; - dell_wmi_aio_input_dev->id.bustype = BUS_HOST; - - err = sparse_keymap_setup(dell_wmi_aio_input_dev, - dell_wmi_aio_keymap, NULL); - if (err) { - pr_err("Unable to setup input device keymap\n"); - goto err_free_dev; - } - err = input_register_device(dell_wmi_aio_input_dev); - if (err) { - pr_info("Unable to register input device\n"); - goto err_free_dev; - } - return 0; - -err_free_dev: - input_free_device(dell_wmi_aio_input_dev); - return err; -} - -static const char *dell_wmi_aio_find(void) -{ - int i; - - for (i = 0; dell_wmi_aio_guids[i] != NULL; i++) - if (wmi_has_guid(dell_wmi_aio_guids[i])) - return dell_wmi_aio_guids[i]; - - return NULL; -} - -static int __init dell_wmi_aio_init(void) -{ - int err; - const char *guid; - - guid = dell_wmi_aio_find(); - if (!guid) { - pr_warn("No known WMI GUID found\n"); - return -ENXIO; - } - - err = dell_wmi_aio_input_setup(); - if (err) - return err; - - err = wmi_install_notify_handler(guid, dell_wmi_aio_notify, NULL); - if (err) { - pr_err("Unable to register notify handler - %d\n", err); - input_unregister_device(dell_wmi_aio_input_dev); - return err; - } - - return 0; -} - -static void __exit dell_wmi_aio_exit(void) -{ - const char *guid; - - guid = dell_wmi_aio_find(); - wmi_remove_notify_handler(guid); - input_unregister_device(dell_wmi_aio_input_dev); -} - -module_init(dell_wmi_aio_init); -module_exit(dell_wmi_aio_exit); diff --git a/drivers/platform/x86/dell-wmi-descriptor.c b/drivers/platform/x86/dell-wmi-descriptor.c deleted file mode 100644 index a068900ae8a1..000000000000 --- a/drivers/platform/x86/dell-wmi-descriptor.c +++ /dev/null @@ -1,206 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Dell WMI descriptor driver - * - * Copyright (C) 2017 Dell Inc. All Rights Reserved. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include "dell-wmi-descriptor.h" - -#define DELL_WMI_DESCRIPTOR_GUID "8D9DDCBC-A997-11DA-B012-B622A1EF5492" - -struct descriptor_priv { - struct list_head list; - u32 interface_version; - u32 size; - u32 hotfix; -}; -static int descriptor_valid = -EPROBE_DEFER; -static LIST_HEAD(wmi_list); -static DEFINE_MUTEX(list_mutex); - -int dell_wmi_get_descriptor_valid(void) -{ - if (!wmi_has_guid(DELL_WMI_DESCRIPTOR_GUID)) - return -ENODEV; - - return descriptor_valid; -} -EXPORT_SYMBOL_GPL(dell_wmi_get_descriptor_valid); - -bool dell_wmi_get_interface_version(u32 *version) -{ - struct descriptor_priv *priv; - bool ret = false; - - mutex_lock(&list_mutex); - priv = list_first_entry_or_null(&wmi_list, - struct descriptor_priv, - list); - if (priv) { - *version = priv->interface_version; - ret = true; - } - mutex_unlock(&list_mutex); - return ret; -} -EXPORT_SYMBOL_GPL(dell_wmi_get_interface_version); - -bool dell_wmi_get_size(u32 *size) -{ - struct descriptor_priv *priv; - bool ret = false; - - mutex_lock(&list_mutex); - priv = list_first_entry_or_null(&wmi_list, - struct descriptor_priv, - list); - if (priv) { - *size = priv->size; - ret = true; - } - mutex_unlock(&list_mutex); - return ret; -} -EXPORT_SYMBOL_GPL(dell_wmi_get_size); - -bool dell_wmi_get_hotfix(u32 *hotfix) -{ - struct descriptor_priv *priv; - bool ret = false; - - mutex_lock(&list_mutex); - priv = list_first_entry_or_null(&wmi_list, - struct descriptor_priv, - list); - if (priv) { - *hotfix = priv->hotfix; - ret = true; - } - mutex_unlock(&list_mutex); - return ret; -} -EXPORT_SYMBOL_GPL(dell_wmi_get_hotfix); - -/* - * Descriptor buffer is 128 byte long and contains: - * - * Name Offset Length Value - * Vendor Signature 0 4 "DELL" - * Object Signature 4 4 " WMI" - * WMI Interface Version 8 4 - * WMI buffer length 12 4 - * WMI hotfix number 16 4 - */ -static int dell_wmi_descriptor_probe(struct wmi_device *wdev, - const void *context) -{ - union acpi_object *obj = NULL; - struct descriptor_priv *priv; - u32 *buffer; - int ret; - - obj = wmidev_block_query(wdev, 0); - if (!obj) { - dev_err(&wdev->dev, "failed to read Dell WMI descriptor\n"); - ret = -EIO; - goto out; - } - - if (obj->type != ACPI_TYPE_BUFFER) { - dev_err(&wdev->dev, "Dell descriptor has wrong type\n"); - ret = -EINVAL; - descriptor_valid = ret; - goto out; - } - - /* Although it's not technically a failure, this would lead to - * unexpected behavior - */ - if (obj->buffer.length != 128) { - dev_err(&wdev->dev, - "Dell descriptor buffer has unexpected length (%d)\n", - obj->buffer.length); - ret = -EINVAL; - descriptor_valid = ret; - goto out; - } - - buffer = (u32 *)obj->buffer.pointer; - - if (strncmp(obj->string.pointer, "DELL WMI", 8) != 0) { - dev_err(&wdev->dev, "Dell descriptor buffer has invalid signature (%8ph)\n", - buffer); - ret = -EINVAL; - descriptor_valid = ret; - goto out; - } - descriptor_valid = 0; - - if (buffer[2] != 0 && buffer[2] != 1) - dev_warn(&wdev->dev, "Dell descriptor buffer has unknown version (%lu)\n", - (unsigned long) buffer[2]); - - priv = devm_kzalloc(&wdev->dev, sizeof(struct descriptor_priv), - GFP_KERNEL); - - if (!priv) { - ret = -ENOMEM; - goto out; - } - - priv->interface_version = buffer[2]; - priv->size = buffer[3]; - priv->hotfix = buffer[4]; - ret = 0; - dev_set_drvdata(&wdev->dev, priv); - mutex_lock(&list_mutex); - list_add_tail(&priv->list, &wmi_list); - mutex_unlock(&list_mutex); - - dev_dbg(&wdev->dev, "Detected Dell WMI interface version %lu, buffer size %lu, hotfix %lu\n", - (unsigned long) priv->interface_version, - (unsigned long) priv->size, - (unsigned long) priv->hotfix); - -out: - kfree(obj); - return ret; -} - -static int dell_wmi_descriptor_remove(struct wmi_device *wdev) -{ - struct descriptor_priv *priv = dev_get_drvdata(&wdev->dev); - - mutex_lock(&list_mutex); - list_del(&priv->list); - mutex_unlock(&list_mutex); - return 0; -} - -static const struct wmi_device_id dell_wmi_descriptor_id_table[] = { - { .guid_string = DELL_WMI_DESCRIPTOR_GUID }, - { }, -}; - -static struct wmi_driver dell_wmi_descriptor_driver = { - .driver = { - .name = "dell-wmi-descriptor", - }, - .probe = dell_wmi_descriptor_probe, - .remove = dell_wmi_descriptor_remove, - .id_table = dell_wmi_descriptor_id_table, -}; - -module_wmi_driver(dell_wmi_descriptor_driver); - -MODULE_DEVICE_TABLE(wmi, dell_wmi_descriptor_id_table); -MODULE_AUTHOR("Mario Limonciello "); -MODULE_DESCRIPTION("Dell WMI descriptor driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell-wmi-descriptor.h b/drivers/platform/x86/dell-wmi-descriptor.h deleted file mode 100644 index 1f469fef1535..000000000000 --- a/drivers/platform/x86/dell-wmi-descriptor.h +++ /dev/null @@ -1,25 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Dell WMI descriptor driver - * - * Copyright (c) 2017 Dell Inc. - */ - -#ifndef _DELL_WMI_DESCRIPTOR_H_ -#define _DELL_WMI_DESCRIPTOR_H_ - -#include - -/* possible return values: - * -ENODEV: Descriptor GUID missing from WMI bus - * -EPROBE_DEFER: probing for dell-wmi-descriptor not yet run - * 0: valid descriptor, successfully probed - * < 0: invalid descriptor, don't probe dependent devices - */ -int dell_wmi_get_descriptor_valid(void); - -bool dell_wmi_get_interface_version(u32 *version); -bool dell_wmi_get_size(u32 *size); -bool dell_wmi_get_hotfix(u32 *hotfix); - -#endif diff --git a/drivers/platform/x86/dell-wmi-led.c b/drivers/platform/x86/dell-wmi-led.c deleted file mode 100644 index 5bedaf7f0633..000000000000 --- a/drivers/platform/x86/dell-wmi-led.c +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright (C) 2010 Dell Inc. - * Louis Davis - * Jim Dailey - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as - * published by the Free Software Foundation. - * - */ - -#include -#include -#include -#include - -MODULE_AUTHOR("Louis Davis/Jim Dailey"); -MODULE_DESCRIPTION("Dell LED Control Driver"); -MODULE_LICENSE("GPL"); - -#define DELL_LED_BIOS_GUID "F6E4FE6E-909D-47cb-8BAB-C9F6F2F8D396" -MODULE_ALIAS("wmi:" DELL_LED_BIOS_GUID); - -/* Error Result Codes: */ -#define INVALID_DEVICE_ID 250 -#define INVALID_PARAMETER 251 -#define INVALID_BUFFER 252 -#define INTERFACE_ERROR 253 -#define UNSUPPORTED_COMMAND 254 -#define UNSPECIFIED_ERROR 255 - -/* Device ID */ -#define DEVICE_ID_PANEL_BACK 1 - -/* LED Commands */ -#define CMD_LED_ON 16 -#define CMD_LED_OFF 17 -#define CMD_LED_BLINK 18 - -struct bios_args { - unsigned char length; - unsigned char result_code; - unsigned char device_id; - unsigned char command; - unsigned char on_time; - unsigned char off_time; -}; - -static int dell_led_perform_fn(u8 length, u8 result_code, u8 device_id, - u8 command, u8 on_time, u8 off_time) -{ - struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; - struct bios_args *bios_return; - struct acpi_buffer input; - union acpi_object *obj; - acpi_status status; - u8 return_code; - - struct bios_args args = { - .length = length, - .result_code = result_code, - .device_id = device_id, - .command = command, - .on_time = on_time, - .off_time = off_time - }; - - input.length = sizeof(struct bios_args); - input.pointer = &args; - - status = wmi_evaluate_method(DELL_LED_BIOS_GUID, 0, 1, &input, &output); - if (ACPI_FAILURE(status)) - return status; - - obj = output.pointer; - - if (!obj) - return -EINVAL; - if (obj->type != ACPI_TYPE_BUFFER) { - kfree(obj); - return -EINVAL; - } - - bios_return = ((struct bios_args *)obj->buffer.pointer); - return_code = bios_return->result_code; - - kfree(obj); - - return return_code; -} - -static int led_on(void) -{ - return dell_led_perform_fn(3, /* Length of command */ - INTERFACE_ERROR, /* Init to INTERFACE_ERROR */ - DEVICE_ID_PANEL_BACK, /* Device ID */ - CMD_LED_ON, /* Command */ - 0, /* not used */ - 0); /* not used */ -} - -static int led_off(void) -{ - return dell_led_perform_fn(3, /* Length of command */ - INTERFACE_ERROR, /* Init to INTERFACE_ERROR */ - DEVICE_ID_PANEL_BACK, /* Device ID */ - CMD_LED_OFF, /* Command */ - 0, /* not used */ - 0); /* not used */ -} - -static int led_blink(unsigned char on_eighths, unsigned char off_eighths) -{ - return dell_led_perform_fn(5, /* Length of command */ - INTERFACE_ERROR, /* Init to INTERFACE_ERROR */ - DEVICE_ID_PANEL_BACK, /* Device ID */ - CMD_LED_BLINK, /* Command */ - on_eighths, /* blink on in eigths of a second */ - off_eighths); /* blink off in eights of a second */ -} - -static void dell_led_set(struct led_classdev *led_cdev, - enum led_brightness value) -{ - if (value == LED_OFF) - led_off(); - else - led_on(); -} - -static int dell_led_blink(struct led_classdev *led_cdev, - unsigned long *delay_on, unsigned long *delay_off) -{ - unsigned long on_eighths; - unsigned long off_eighths; - - /* - * The Dell LED delay is based on 125ms intervals. - * Need to round up to next interval. - */ - - on_eighths = DIV_ROUND_UP(*delay_on, 125); - on_eighths = clamp_t(unsigned long, on_eighths, 1, 255); - *delay_on = on_eighths * 125; - - off_eighths = DIV_ROUND_UP(*delay_off, 125); - off_eighths = clamp_t(unsigned long, off_eighths, 1, 255); - *delay_off = off_eighths * 125; - - led_blink(on_eighths, off_eighths); - - return 0; -} - -static struct led_classdev dell_led = { - .name = "dell::lid", - .brightness = LED_OFF, - .max_brightness = 1, - .brightness_set = dell_led_set, - .blink_set = dell_led_blink, - .flags = LED_CORE_SUSPENDRESUME, -}; - -static int __init dell_led_init(void) -{ - int error = 0; - - if (!wmi_has_guid(DELL_LED_BIOS_GUID)) - return -ENODEV; - - error = led_off(); - if (error != 0) - return -ENODEV; - - return led_classdev_register(NULL, &dell_led); -} - -static void __exit dell_led_exit(void) -{ - led_classdev_unregister(&dell_led); - - led_off(); -} - -module_init(dell_led_init); -module_exit(dell_led_exit); diff --git a/drivers/platform/x86/dell-wmi-sysman/Makefile b/drivers/platform/x86/dell-wmi-sysman/Makefile deleted file mode 100644 index 825fb2fbeea8..000000000000 --- a/drivers/platform/x86/dell-wmi-sysman/Makefile +++ /dev/null @@ -1,8 +0,0 @@ -obj-$(CONFIG_DELL_WMI_SYSMAN) += dell-wmi-sysman.o -dell-wmi-sysman-objs := sysman.o \ - enum-attributes.o \ - int-attributes.o \ - string-attributes.o \ - passobj-attributes.o \ - biosattr-interface.o \ - passwordattr-interface.o diff --git a/drivers/platform/x86/dell-wmi-sysman/biosattr-interface.c b/drivers/platform/x86/dell-wmi-sysman/biosattr-interface.c deleted file mode 100644 index f95d8ddace5a..000000000000 --- a/drivers/platform/x86/dell-wmi-sysman/biosattr-interface.c +++ /dev/null @@ -1,186 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Functions corresponding to SET methods under BIOS attributes interface GUID for use - * with dell-wmi-sysman - * - * Copyright (c) 2020 Dell Inc. - */ - -#include -#include "dell-wmi-sysman.h" - -#define SETDEFAULTVALUES_METHOD_ID 0x02 -#define SETBIOSDEFAULTS_METHOD_ID 0x03 -#define SETATTRIBUTE_METHOD_ID 0x04 - -static int call_biosattributes_interface(struct wmi_device *wdev, char *in_args, size_t size, - int method_id) -{ - struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL}; - struct acpi_buffer input; - union acpi_object *obj; - acpi_status status; - int ret = -EIO; - - input.length = (acpi_size) size; - input.pointer = in_args; - status = wmidev_evaluate_method(wdev, 0, method_id, &input, &output); - if (ACPI_FAILURE(status)) - return -EIO; - obj = (union acpi_object *)output.pointer; - if (obj->type == ACPI_TYPE_INTEGER) - ret = obj->integer.value; - - if (wmi_priv.pending_changes == 0) { - wmi_priv.pending_changes = 1; - /* let userland know it may need to check reboot pending again */ - kobject_uevent(&wmi_priv.class_dev->kobj, KOBJ_CHANGE); - } - kfree(output.pointer); - return map_wmi_error(ret); -} - -/** - * set_attribute() - Update an attribute value - * @a_name: The attribute name - * @a_value: The attribute value - * - * Sets an attribute to new value - */ -int set_attribute(const char *a_name, const char *a_value) -{ - size_t security_area_size, buffer_size; - size_t a_name_size, a_value_size; - char *buffer = NULL, *start; - int ret; - - mutex_lock(&wmi_priv.mutex); - if (!wmi_priv.bios_attr_wdev) { - ret = -ENODEV; - goto out; - } - - /* build/calculate buffer */ - security_area_size = calculate_security_buffer(wmi_priv.current_admin_password); - a_name_size = calculate_string_buffer(a_name); - a_value_size = calculate_string_buffer(a_value); - buffer_size = security_area_size + a_name_size + a_value_size; - buffer = kzalloc(buffer_size, GFP_KERNEL); - if (!buffer) { - ret = -ENOMEM; - goto out; - } - - /* build security area */ - populate_security_buffer(buffer, wmi_priv.current_admin_password); - - /* build variables to set */ - start = buffer + security_area_size; - ret = populate_string_buffer(start, a_name_size, a_name); - if (ret < 0) - goto out; - start += ret; - ret = populate_string_buffer(start, a_value_size, a_value); - if (ret < 0) - goto out; - - print_hex_dump_bytes("set attribute data: ", DUMP_PREFIX_NONE, buffer, buffer_size); - ret = call_biosattributes_interface(wmi_priv.bios_attr_wdev, - buffer, buffer_size, - SETATTRIBUTE_METHOD_ID); - if (ret == -EOPNOTSUPP) - dev_err(&wmi_priv.bios_attr_wdev->dev, "admin password must be configured\n"); - else if (ret == -EACCES) - dev_err(&wmi_priv.bios_attr_wdev->dev, "invalid password\n"); - -out: - kfree(buffer); - mutex_unlock(&wmi_priv.mutex); - return ret; -} - -/** - * set_bios_defaults() - Resets BIOS defaults - * @deftype: the type of BIOS value reset to issue. - * - * Resets BIOS defaults - */ -int set_bios_defaults(u8 deftype) -{ - size_t security_area_size, buffer_size; - size_t integer_area_size = sizeof(u8); - char *buffer = NULL; - u8 *defaultType; - int ret; - - mutex_lock(&wmi_priv.mutex); - if (!wmi_priv.bios_attr_wdev) { - ret = -ENODEV; - goto out; - } - - security_area_size = calculate_security_buffer(wmi_priv.current_admin_password); - buffer_size = security_area_size + integer_area_size; - buffer = kzalloc(buffer_size, GFP_KERNEL); - if (!buffer) { - ret = -ENOMEM; - goto out; - } - - /* build security area */ - populate_security_buffer(buffer, wmi_priv.current_admin_password); - - defaultType = buffer + security_area_size; - *defaultType = deftype; - - ret = call_biosattributes_interface(wmi_priv.bios_attr_wdev, buffer, buffer_size, - SETBIOSDEFAULTS_METHOD_ID); - if (ret) - dev_err(&wmi_priv.bios_attr_wdev->dev, "reset BIOS defaults failed: %d\n", ret); - - kfree(buffer); -out: - mutex_unlock(&wmi_priv.mutex); - return ret; -} - -static int bios_attr_set_interface_probe(struct wmi_device *wdev, const void *context) -{ - mutex_lock(&wmi_priv.mutex); - wmi_priv.bios_attr_wdev = wdev; - mutex_unlock(&wmi_priv.mutex); - return 0; -} - -static int bios_attr_set_interface_remove(struct wmi_device *wdev) -{ - mutex_lock(&wmi_priv.mutex); - wmi_priv.bios_attr_wdev = NULL; - mutex_unlock(&wmi_priv.mutex); - return 0; -} - -static const struct wmi_device_id bios_attr_set_interface_id_table[] = { - { .guid_string = DELL_WMI_BIOS_ATTRIBUTES_INTERFACE_GUID }, - { }, -}; -static struct wmi_driver bios_attr_set_interface_driver = { - .driver = { - .name = DRIVER_NAME - }, - .probe = bios_attr_set_interface_probe, - .remove = bios_attr_set_interface_remove, - .id_table = bios_attr_set_interface_id_table, -}; - -int init_bios_attr_set_interface(void) -{ - return wmi_driver_register(&bios_attr_set_interface_driver); -} - -void exit_bios_attr_set_interface(void) -{ - wmi_driver_unregister(&bios_attr_set_interface_driver); -} - -MODULE_DEVICE_TABLE(wmi, bios_attr_set_interface_id_table); diff --git a/drivers/platform/x86/dell-wmi-sysman/dell-wmi-sysman.h b/drivers/platform/x86/dell-wmi-sysman/dell-wmi-sysman.h deleted file mode 100644 index b80f2a62ea3f..000000000000 --- a/drivers/platform/x86/dell-wmi-sysman/dell-wmi-sysman.h +++ /dev/null @@ -1,191 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 - * Definitions for kernel modules using Dell WMI System Management Driver - * - * Copyright (c) 2020 Dell Inc. - */ - -#ifndef _DELL_WMI_BIOS_ATTR_H_ -#define _DELL_WMI_BIOS_ATTR_H_ - -#include -#include -#include -#include -#include - -#define DRIVER_NAME "dell-wmi-sysman" -#define MAX_BUFF 512 - -#define DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BF5" -#define DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BFA" -#define DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BF9" -#define DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID "0894B8D6-44A6-4719-97D7-6AD24108BFD4" -#define DELL_WMI_BIOS_ATTRIBUTES_INTERFACE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BF4" -#define DELL_WMI_BIOS_PASSWORD_INTERFACE_GUID "70FE8229-D03B-4214-A1C6-1F884B1A892A" - -struct enumeration_data { - struct kobject *attr_name_kobj; - char display_name_language_code[MAX_BUFF]; - char dell_value_modifier[MAX_BUFF]; - char possible_values[MAX_BUFF]; - char attribute_name[MAX_BUFF]; - char default_value[MAX_BUFF]; - char dell_modifier[MAX_BUFF]; - char display_name[MAX_BUFF]; -}; - -struct integer_data { - struct kobject *attr_name_kobj; - char display_name_language_code[MAX_BUFF]; - char attribute_name[MAX_BUFF]; - char dell_modifier[MAX_BUFF]; - char display_name[MAX_BUFF]; - int scalar_increment; - int default_value; - int min_value; - int max_value; -}; - -struct str_data { - struct kobject *attr_name_kobj; - char display_name_language_code[MAX_BUFF]; - char attribute_name[MAX_BUFF]; - char display_name[MAX_BUFF]; - char default_value[MAX_BUFF]; - char dell_modifier[MAX_BUFF]; - int min_length; - int max_length; -}; - -struct po_data { - struct kobject *attr_name_kobj; - char attribute_name[MAX_BUFF]; - int min_password_length; - int max_password_length; -}; - -struct wmi_sysman_priv { - char current_admin_password[MAX_BUFF]; - char current_system_password[MAX_BUFF]; - struct wmi_device *password_attr_wdev; - struct wmi_device *bios_attr_wdev; - struct kset *authentication_dir_kset; - struct kset *main_dir_kset; - struct device *class_dev; - struct enumeration_data *enumeration_data; - int enumeration_instances_count; - struct integer_data *integer_data; - int integer_instances_count; - struct str_data *str_data; - int str_instances_count; - struct po_data *po_data; - int po_instances_count; - bool pending_changes; - struct mutex mutex; -}; - -/* global structure used by multiple WMI interfaces */ -extern struct wmi_sysman_priv wmi_priv; - -enum { ENUM, INT, STR, PO }; - -enum { - ATTR_NAME, - DISPL_NAME_LANG_CODE, - DISPLAY_NAME, - DEFAULT_VAL, - CURRENT_VAL, - MODIFIER -}; - -#define get_instance_id(type) \ -static int get_##type##_instance_id(struct kobject *kobj) \ -{ \ - int i; \ - for (i = 0; i <= wmi_priv.type##_instances_count; i++) { \ - if (!(strcmp(kobj->name, wmi_priv.type##_data[i].attribute_name)))\ - return i; \ - } \ - return -EIO; \ -} - -#define attribute_s_property_show(name, type) \ -static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \ - char *buf) \ -{ \ - int i = get_##type##_instance_id(kobj); \ - if (i >= 0) \ - return sprintf(buf, "%s\n", wmi_priv.type##_data[i].name); \ - return 0; \ -} - -#define attribute_n_property_show(name, type) \ -static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \ - char *buf) \ -{ \ - int i = get_##type##_instance_id(kobj); \ - if (i >= 0) \ - return sprintf(buf, "%d\n", wmi_priv.type##_data[i].name); \ - return 0; \ -} - -#define attribute_property_store(curr_val, type) \ -static ssize_t curr_val##_store(struct kobject *kobj, \ - struct kobj_attribute *attr, \ - const char *buf, size_t count) \ -{ \ - char *p, *buf_cp; \ - int i, ret = -EIO; \ - buf_cp = kstrdup(buf, GFP_KERNEL); \ - if (!buf_cp) \ - return -ENOMEM; \ - p = memchr(buf_cp, '\n', count); \ - \ - if (p != NULL) \ - *p = '\0'; \ - i = get_##type##_instance_id(kobj); \ - if (i >= 0) \ - ret = validate_##type##_input(i, buf_cp); \ - if (!ret) \ - ret = set_attribute(kobj->name, buf_cp); \ - kfree(buf_cp); \ - return ret ? ret : count; \ -} - -union acpi_object *get_wmiobj_pointer(int instance_id, const char *guid_string); -int get_instance_count(const char *guid_string); -void strlcpy_attr(char *dest, char *src); - -int populate_enum_data(union acpi_object *enumeration_obj, int instance_id, - struct kobject *attr_name_kobj); -int alloc_enum_data(void); -void exit_enum_attributes(void); - -int populate_int_data(union acpi_object *integer_obj, int instance_id, - struct kobject *attr_name_kobj); -int alloc_int_data(void); -void exit_int_attributes(void); - -int populate_str_data(union acpi_object *str_obj, int instance_id, struct kobject *attr_name_kobj); -int alloc_str_data(void); -void exit_str_attributes(void); - -int populate_po_data(union acpi_object *po_obj, int instance_id, struct kobject *attr_name_kobj); -int alloc_po_data(void); -void exit_po_attributes(void); - -int set_attribute(const char *a_name, const char *a_value); -int set_bios_defaults(u8 defType); - -void exit_bios_attr_set_interface(void); -int init_bios_attr_set_interface(void); -int map_wmi_error(int error_code); -size_t calculate_string_buffer(const char *str); -size_t calculate_security_buffer(char *authentication); -void populate_security_buffer(char *buffer, char *authentication); -ssize_t populate_string_buffer(char *buffer, size_t buffer_len, const char *str); -int set_new_password(const char *password_type, const char *new); -int init_bios_attr_pass_interface(void); -void exit_bios_attr_pass_interface(void); - -#endif diff --git a/drivers/platform/x86/dell-wmi-sysman/enum-attributes.c b/drivers/platform/x86/dell-wmi-sysman/enum-attributes.c deleted file mode 100644 index 80f4b7785c6c..000000000000 --- a/drivers/platform/x86/dell-wmi-sysman/enum-attributes.c +++ /dev/null @@ -1,189 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Functions corresponding to enumeration type attributes under - * BIOS Enumeration GUID for use with dell-wmi-sysman - * - * Copyright (c) 2020 Dell Inc. - */ - -#include "dell-wmi-sysman.h" - -get_instance_id(enumeration); - -static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) -{ - int instance_id = get_enumeration_instance_id(kobj); - union acpi_object *obj; - ssize_t ret; - - if (instance_id < 0) - return instance_id; - - /* need to use specific instance_id and guid combination to get right data */ - obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID); - if (!obj) - return -EIO; - if (obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_STRING) { - kfree(obj); - return -EINVAL; - } - ret = snprintf(buf, PAGE_SIZE, "%s\n", obj->package.elements[CURRENT_VAL].string.pointer); - kfree(obj); - return ret; -} - -/** - * validate_enumeration_input() - Validate input of current_value against possible values - * @instance_id: The instance on which input is validated - * @buf: Input value - */ -static int validate_enumeration_input(int instance_id, const char *buf) -{ - char *options, *tmp, *p; - int ret = -EINVAL; - - options = tmp = kstrdup(wmi_priv.enumeration_data[instance_id].possible_values, - GFP_KERNEL); - if (!options) - return -ENOMEM; - - while ((p = strsep(&options, ";")) != NULL) { - if (!*p) - continue; - if (!strcasecmp(p, buf)) { - ret = 0; - break; - } - } - - kfree(tmp); - return ret; -} - -attribute_s_property_show(display_name_language_code, enumeration); -static struct kobj_attribute displ_langcode = - __ATTR_RO(display_name_language_code); - -attribute_s_property_show(display_name, enumeration); -static struct kobj_attribute displ_name = - __ATTR_RO(display_name); - -attribute_s_property_show(default_value, enumeration); -static struct kobj_attribute default_val = - __ATTR_RO(default_value); - -attribute_property_store(current_value, enumeration); -static struct kobj_attribute current_val = - __ATTR_RW_MODE(current_value, 0600); - -attribute_s_property_show(dell_modifier, enumeration); -static struct kobj_attribute modifier = - __ATTR_RO(dell_modifier); - -attribute_s_property_show(dell_value_modifier, enumeration); -static struct kobj_attribute value_modfr = - __ATTR_RO(dell_value_modifier); - -attribute_s_property_show(possible_values, enumeration); -static struct kobj_attribute poss_val = - __ATTR_RO(possible_values); - -static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr, - char *buf) -{ - return sprintf(buf, "enumeration\n"); -} -static struct kobj_attribute type = - __ATTR_RO(type); - -static struct attribute *enumeration_attrs[] = { - &displ_langcode.attr, - &displ_name.attr, - &default_val.attr, - ¤t_val.attr, - &modifier.attr, - &value_modfr.attr, - &poss_val.attr, - &type.attr, - NULL, -}; - -static const struct attribute_group enumeration_attr_group = { - .attrs = enumeration_attrs, -}; - -int alloc_enum_data(void) -{ - int ret = 0; - - wmi_priv.enumeration_instances_count = - get_instance_count(DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID); - wmi_priv.enumeration_data = kcalloc(wmi_priv.enumeration_instances_count, - sizeof(struct enumeration_data), GFP_KERNEL); - if (!wmi_priv.enumeration_data) { - wmi_priv.enumeration_instances_count = 0; - ret = -ENOMEM; - } - return ret; -} - -/** - * populate_enum_data() - Populate all properties of an instance under enumeration attribute - * @enumeration_obj: ACPI object with enumeration data - * @instance_id: The instance to enumerate - * @attr_name_kobj: The parent kernel object - */ -int populate_enum_data(union acpi_object *enumeration_obj, int instance_id, - struct kobject *attr_name_kobj) -{ - int i, next_obj, value_modifier_count, possible_values_count; - - wmi_priv.enumeration_data[instance_id].attr_name_kobj = attr_name_kobj; - strlcpy_attr(wmi_priv.enumeration_data[instance_id].attribute_name, - enumeration_obj[ATTR_NAME].string.pointer); - strlcpy_attr(wmi_priv.enumeration_data[instance_id].display_name_language_code, - enumeration_obj[DISPL_NAME_LANG_CODE].string.pointer); - strlcpy_attr(wmi_priv.enumeration_data[instance_id].display_name, - enumeration_obj[DISPLAY_NAME].string.pointer); - strlcpy_attr(wmi_priv.enumeration_data[instance_id].default_value, - enumeration_obj[DEFAULT_VAL].string.pointer); - strlcpy_attr(wmi_priv.enumeration_data[instance_id].dell_modifier, - enumeration_obj[MODIFIER].string.pointer); - - next_obj = MODIFIER + 1; - - value_modifier_count = (uintptr_t)enumeration_obj[next_obj].string.pointer; - - for (i = 0; i < value_modifier_count; i++) { - strcat(wmi_priv.enumeration_data[instance_id].dell_value_modifier, - enumeration_obj[++next_obj].string.pointer); - strcat(wmi_priv.enumeration_data[instance_id].dell_value_modifier, ";"); - } - - possible_values_count = (uintptr_t) enumeration_obj[++next_obj].string.pointer; - - for (i = 0; i < possible_values_count; i++) { - strcat(wmi_priv.enumeration_data[instance_id].possible_values, - enumeration_obj[++next_obj].string.pointer); - strcat(wmi_priv.enumeration_data[instance_id].possible_values, ";"); - } - - return sysfs_create_group(attr_name_kobj, &enumeration_attr_group); -} - -/** - * exit_enum_attributes() - Clear all attribute data - * - * Clears all data allocated for this group of attributes - */ -void exit_enum_attributes(void) -{ - int instance_id; - - for (instance_id = 0; instance_id < wmi_priv.enumeration_instances_count; instance_id++) { - if (wmi_priv.enumeration_data[instance_id].attr_name_kobj) - sysfs_remove_group(wmi_priv.enumeration_data[instance_id].attr_name_kobj, - &enumeration_attr_group); - } - kfree(wmi_priv.enumeration_data); -} diff --git a/drivers/platform/x86/dell-wmi-sysman/int-attributes.c b/drivers/platform/x86/dell-wmi-sysman/int-attributes.c deleted file mode 100644 index 75aedbb733be..000000000000 --- a/drivers/platform/x86/dell-wmi-sysman/int-attributes.c +++ /dev/null @@ -1,179 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Functions corresponding to integer type attributes under BIOS Integer GUID for use with - * dell-wmi-sysman - * - * Copyright (c) 2020 Dell Inc. - */ - -#include "dell-wmi-sysman.h" - -enum int_properties {MIN_VALUE = 6, MAX_VALUE, SCALAR_INCR}; - -get_instance_id(integer); - -static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) -{ - int instance_id = get_integer_instance_id(kobj); - union acpi_object *obj; - ssize_t ret; - - if (instance_id < 0) - return instance_id; - - /* need to use specific instance_id and guid combination to get right data */ - obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID); - if (!obj) - return -EIO; - if (obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_INTEGER) { - kfree(obj); - return -EINVAL; - } - ret = snprintf(buf, PAGE_SIZE, "%lld\n", obj->package.elements[CURRENT_VAL].integer.value); - kfree(obj); - return ret; -} - -/** - * validate_integer_input() - Validate input of current_value against lower and upper bound - * @instance_id: The instance on which input is validated - * @buf: Input value - */ -static int validate_integer_input(int instance_id, char *buf) -{ - int in_val; - int ret; - - ret = kstrtoint(buf, 0, &in_val); - if (ret) - return ret; - if (in_val < wmi_priv.integer_data[instance_id].min_value || - in_val > wmi_priv.integer_data[instance_id].max_value) - return -EINVAL; - - /* workaround for BIOS error. - * validate input to avoid setting 0 when integer input passed with + sign - */ - if (*buf == '+') - memmove(buf, (buf + 1), strlen(buf + 1) + 1); - - return ret; -} - -attribute_s_property_show(display_name_language_code, integer); -static struct kobj_attribute integer_displ_langcode = - __ATTR_RO(display_name_language_code); - -attribute_s_property_show(display_name, integer); -static struct kobj_attribute integer_displ_name = - __ATTR_RO(display_name); - -attribute_n_property_show(default_value, integer); -static struct kobj_attribute integer_default_val = - __ATTR_RO(default_value); - -attribute_property_store(current_value, integer); -static struct kobj_attribute integer_current_val = - __ATTR_RW_MODE(current_value, 0600); - -attribute_s_property_show(dell_modifier, integer); -static struct kobj_attribute integer_modifier = - __ATTR_RO(dell_modifier); - -attribute_n_property_show(min_value, integer); -static struct kobj_attribute integer_lower_bound = - __ATTR_RO(min_value); - -attribute_n_property_show(max_value, integer); -static struct kobj_attribute integer_upper_bound = - __ATTR_RO(max_value); - -attribute_n_property_show(scalar_increment, integer); -static struct kobj_attribute integer_scalar_increment = - __ATTR_RO(scalar_increment); - -static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr, - char *buf) -{ - return sprintf(buf, "integer\n"); -} -static struct kobj_attribute integer_type = - __ATTR_RO(type); - -static struct attribute *integer_attrs[] = { - &integer_displ_langcode.attr, - &integer_displ_name.attr, - &integer_default_val.attr, - &integer_current_val.attr, - &integer_modifier.attr, - &integer_lower_bound.attr, - &integer_upper_bound.attr, - &integer_scalar_increment.attr, - &integer_type.attr, - NULL, -}; - -static const struct attribute_group integer_attr_group = { - .attrs = integer_attrs, -}; - -int alloc_int_data(void) -{ - int ret = 0; - - wmi_priv.integer_instances_count = get_instance_count(DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID); - wmi_priv.integer_data = kcalloc(wmi_priv.integer_instances_count, - sizeof(struct integer_data), GFP_KERNEL); - if (!wmi_priv.integer_data) { - wmi_priv.integer_instances_count = 0; - ret = -ENOMEM; - } - return ret; -} - -/** - * populate_int_data() - Populate all properties of an instance under integer attribute - * @integer_obj: ACPI object with integer data - * @instance_id: The instance to enumerate - * @attr_name_kobj: The parent kernel object - */ -int populate_int_data(union acpi_object *integer_obj, int instance_id, - struct kobject *attr_name_kobj) -{ - wmi_priv.integer_data[instance_id].attr_name_kobj = attr_name_kobj; - strlcpy_attr(wmi_priv.integer_data[instance_id].attribute_name, - integer_obj[ATTR_NAME].string.pointer); - strlcpy_attr(wmi_priv.integer_data[instance_id].display_name_language_code, - integer_obj[DISPL_NAME_LANG_CODE].string.pointer); - strlcpy_attr(wmi_priv.integer_data[instance_id].display_name, - integer_obj[DISPLAY_NAME].string.pointer); - wmi_priv.integer_data[instance_id].default_value = - (uintptr_t)integer_obj[DEFAULT_VAL].string.pointer; - strlcpy_attr(wmi_priv.integer_data[instance_id].dell_modifier, - integer_obj[MODIFIER].string.pointer); - wmi_priv.integer_data[instance_id].min_value = - (uintptr_t)integer_obj[MIN_VALUE].string.pointer; - wmi_priv.integer_data[instance_id].max_value = - (uintptr_t)integer_obj[MAX_VALUE].string.pointer; - wmi_priv.integer_data[instance_id].scalar_increment = - (uintptr_t)integer_obj[SCALAR_INCR].string.pointer; - - return sysfs_create_group(attr_name_kobj, &integer_attr_group); -} - -/** - * exit_int_attributes() - Clear all attribute data - * - * Clears all data allocated for this group of attributes - */ -void exit_int_attributes(void) -{ - int instance_id; - - for (instance_id = 0; instance_id < wmi_priv.integer_instances_count; instance_id++) { - if (wmi_priv.integer_data[instance_id].attr_name_kobj) - sysfs_remove_group(wmi_priv.integer_data[instance_id].attr_name_kobj, - &integer_attr_group); - } - kfree(wmi_priv.integer_data); -} diff --git a/drivers/platform/x86/dell-wmi-sysman/passobj-attributes.c b/drivers/platform/x86/dell-wmi-sysman/passobj-attributes.c deleted file mode 100644 index 3abcd95477c0..000000000000 --- a/drivers/platform/x86/dell-wmi-sysman/passobj-attributes.c +++ /dev/null @@ -1,187 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Functions corresponding to password object type attributes under BIOS Password Object GUID for - * use with dell-wmi-sysman - * - * Copyright (c) 2020 Dell Inc. - */ - -#include "dell-wmi-sysman.h" - -enum po_properties {IS_PASS_SET = 1, MIN_PASS_LEN, MAX_PASS_LEN}; - -get_instance_id(po); - -static ssize_t is_enabled_show(struct kobject *kobj, struct kobj_attribute *attr, - char *buf) -{ - int instance_id = get_po_instance_id(kobj); - union acpi_object *obj; - ssize_t ret; - - if (instance_id < 0) - return instance_id; - - /* need to use specific instance_id and guid combination to get right data */ - obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID); - if (!obj) - return -EIO; - if (obj->package.elements[IS_PASS_SET].type != ACPI_TYPE_INTEGER) { - kfree(obj); - return -EINVAL; - } - ret = snprintf(buf, PAGE_SIZE, "%lld\n", obj->package.elements[IS_PASS_SET].integer.value); - kfree(obj); - return ret; -} - -static struct kobj_attribute po_is_pass_set = __ATTR_RO(is_enabled); - -static ssize_t current_password_store(struct kobject *kobj, - struct kobj_attribute *attr, - const char *buf, size_t count) -{ - char *target = NULL; - int length; - - length = strlen(buf); - if (buf[length-1] == '\n') - length--; - - /* firmware does verifiation of min/max password length, - * hence only check for not exceeding MAX_BUFF here. - */ - if (length >= MAX_BUFF) - return -EINVAL; - - if (strcmp(kobj->name, "Admin") == 0) - target = wmi_priv.current_admin_password; - else if (strcmp(kobj->name, "System") == 0) - target = wmi_priv.current_system_password; - if (!target) - return -EIO; - memcpy(target, buf, length); - target[length] = '\0'; - - return count; -} - -static struct kobj_attribute po_current_password = __ATTR_WO(current_password); - -static ssize_t new_password_store(struct kobject *kobj, - struct kobj_attribute *attr, - const char *buf, size_t count) -{ - char *p, *buf_cp; - int ret; - - buf_cp = kstrdup(buf, GFP_KERNEL); - if (!buf_cp) - return -ENOMEM; - p = memchr(buf_cp, '\n', count); - - if (p != NULL) - *p = '\0'; - if (strlen(buf_cp) > MAX_BUFF) { - ret = -EINVAL; - goto out; - } - - ret = set_new_password(kobj->name, buf_cp); - -out: - kfree(buf_cp); - return ret ? ret : count; -} - -static struct kobj_attribute po_new_password = __ATTR_WO(new_password); - -attribute_n_property_show(min_password_length, po); -static struct kobj_attribute po_min_pass_length = __ATTR_RO(min_password_length); - -attribute_n_property_show(max_password_length, po); -static struct kobj_attribute po_max_pass_length = __ATTR_RO(max_password_length); - -static ssize_t mechanism_show(struct kobject *kobj, struct kobj_attribute *attr, - char *buf) -{ - return sprintf(buf, "password\n"); -} - -static struct kobj_attribute po_mechanism = __ATTR_RO(mechanism); - -static ssize_t role_show(struct kobject *kobj, struct kobj_attribute *attr, - char *buf) -{ - if (strcmp(kobj->name, "Admin") == 0) - return sprintf(buf, "bios-admin\n"); - else if (strcmp(kobj->name, "System") == 0) - return sprintf(buf, "power-on\n"); - return -EIO; -} - -static struct kobj_attribute po_role = __ATTR_RO(role); - -static struct attribute *po_attrs[] = { - &po_is_pass_set.attr, - &po_min_pass_length.attr, - &po_max_pass_length.attr, - &po_current_password.attr, - &po_new_password.attr, - &po_role.attr, - &po_mechanism.attr, - NULL, -}; - -static const struct attribute_group po_attr_group = { - .attrs = po_attrs, -}; - -int alloc_po_data(void) -{ - int ret = 0; - - wmi_priv.po_instances_count = get_instance_count(DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID); - wmi_priv.po_data = kcalloc(wmi_priv.po_instances_count, sizeof(struct po_data), GFP_KERNEL); - if (!wmi_priv.po_data) { - wmi_priv.po_instances_count = 0; - ret = -ENOMEM; - } - return ret; -} - -/** - * populate_po_data() - Populate all properties of an instance under password object attribute - * @po_obj: ACPI object with password object data - * @instance_id: The instance to enumerate - * @attr_name_kobj: The parent kernel object - */ -int populate_po_data(union acpi_object *po_obj, int instance_id, struct kobject *attr_name_kobj) -{ - wmi_priv.po_data[instance_id].attr_name_kobj = attr_name_kobj; - strlcpy_attr(wmi_priv.po_data[instance_id].attribute_name, - po_obj[ATTR_NAME].string.pointer); - wmi_priv.po_data[instance_id].min_password_length = - (uintptr_t)po_obj[MIN_PASS_LEN].string.pointer; - wmi_priv.po_data[instance_id].max_password_length = - (uintptr_t) po_obj[MAX_PASS_LEN].string.pointer; - - return sysfs_create_group(attr_name_kobj, &po_attr_group); -} - -/** - * exit_po_attributes() - Clear all attribute data - * - * Clears all data allocated for this group of attributes - */ -void exit_po_attributes(void) -{ - int instance_id; - - for (instance_id = 0; instance_id < wmi_priv.po_instances_count; instance_id++) { - if (wmi_priv.po_data[instance_id].attr_name_kobj) - sysfs_remove_group(wmi_priv.po_data[instance_id].attr_name_kobj, - &po_attr_group); - } - kfree(wmi_priv.po_data); -} diff --git a/drivers/platform/x86/dell-wmi-sysman/passwordattr-interface.c b/drivers/platform/x86/dell-wmi-sysman/passwordattr-interface.c deleted file mode 100644 index 5780b4d94759..000000000000 --- a/drivers/platform/x86/dell-wmi-sysman/passwordattr-interface.c +++ /dev/null @@ -1,153 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Functions corresponding to SET password methods under BIOS attributes interface GUID - * - * Copyright (c) 2020 Dell Inc. - */ - -#include -#include "dell-wmi-sysman.h" - -static int call_password_interface(struct wmi_device *wdev, char *in_args, size_t size) -{ - struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL}; - struct acpi_buffer input; - union acpi_object *obj; - acpi_status status; - int ret = -EIO; - - input.length = (acpi_size) size; - input.pointer = in_args; - status = wmidev_evaluate_method(wdev, 0, 1, &input, &output); - if (ACPI_FAILURE(status)) - return -EIO; - obj = (union acpi_object *)output.pointer; - if (obj->type == ACPI_TYPE_INTEGER) - ret = obj->integer.value; - - kfree(output.pointer); - /* let userland know it may need to check is_password_set again */ - kobject_uevent(&wmi_priv.class_dev->kobj, KOBJ_CHANGE); - return map_wmi_error(ret); -} - -/** - * set_new_password() - Sets a system admin password - * @password_type: The type of password to set - * @new: The new password - * - * Sets the password using plaintext interface - */ -int set_new_password(const char *password_type, const char *new) -{ - size_t password_type_size, current_password_size, new_size; - size_t security_area_size, buffer_size; - char *buffer = NULL, *start; - char *current_password; - int ret; - - mutex_lock(&wmi_priv.mutex); - if (!wmi_priv.password_attr_wdev) { - ret = -ENODEV; - goto out; - } - if (strcmp(password_type, "Admin") == 0) { - current_password = wmi_priv.current_admin_password; - } else if (strcmp(password_type, "System") == 0) { - current_password = wmi_priv.current_system_password; - } else { - ret = -EINVAL; - dev_err(&wmi_priv.password_attr_wdev->dev, "unknown password type %s\n", - password_type); - goto out; - } - - /* build/calculate buffer */ - security_area_size = calculate_security_buffer(wmi_priv.current_admin_password); - password_type_size = calculate_string_buffer(password_type); - current_password_size = calculate_string_buffer(current_password); - new_size = calculate_string_buffer(new); - buffer_size = security_area_size + password_type_size + current_password_size + new_size; - buffer = kzalloc(buffer_size, GFP_KERNEL); - if (!buffer) { - ret = -ENOMEM; - goto out; - } - - /* build security area */ - populate_security_buffer(buffer, wmi_priv.current_admin_password); - - /* build variables to set */ - start = buffer + security_area_size; - ret = populate_string_buffer(start, password_type_size, password_type); - if (ret < 0) - goto out; - - start += ret; - ret = populate_string_buffer(start, current_password_size, current_password); - if (ret < 0) - goto out; - - start += ret; - ret = populate_string_buffer(start, new_size, new); - if (ret < 0) - goto out; - - print_hex_dump_bytes("set new password data: ", DUMP_PREFIX_NONE, buffer, buffer_size); - ret = call_password_interface(wmi_priv.password_attr_wdev, buffer, buffer_size); - /* clear current_password here and use user input from wmi_priv.current_password */ - if (!ret) - memset(current_password, 0, MAX_BUFF); - /* explain to user the detailed failure reason */ - else if (ret == -EOPNOTSUPP) - dev_err(&wmi_priv.password_attr_wdev->dev, "admin password must be configured\n"); - else if (ret == -EACCES) - dev_err(&wmi_priv.password_attr_wdev->dev, "invalid password\n"); - -out: - kfree(buffer); - mutex_unlock(&wmi_priv.mutex); - - return ret; -} - -static int bios_attr_pass_interface_probe(struct wmi_device *wdev, const void *context) -{ - mutex_lock(&wmi_priv.mutex); - wmi_priv.password_attr_wdev = wdev; - mutex_unlock(&wmi_priv.mutex); - return 0; -} - -static int bios_attr_pass_interface_remove(struct wmi_device *wdev) -{ - mutex_lock(&wmi_priv.mutex); - wmi_priv.password_attr_wdev = NULL; - mutex_unlock(&wmi_priv.mutex); - return 0; -} - -static const struct wmi_device_id bios_attr_pass_interface_id_table[] = { - { .guid_string = DELL_WMI_BIOS_PASSWORD_INTERFACE_GUID }, - { }, -}; -static struct wmi_driver bios_attr_pass_interface_driver = { - .driver = { - .name = DRIVER_NAME"-password" - }, - .probe = bios_attr_pass_interface_probe, - .remove = bios_attr_pass_interface_remove, - .id_table = bios_attr_pass_interface_id_table, -}; - -int init_bios_attr_pass_interface(void) -{ - return wmi_driver_register(&bios_attr_pass_interface_driver); -} - -void exit_bios_attr_pass_interface(void) -{ - wmi_driver_unregister(&bios_attr_pass_interface_driver); -} - -MODULE_DEVICE_TABLE(wmi, bios_attr_pass_interface_id_table); diff --git a/drivers/platform/x86/dell-wmi-sysman/string-attributes.c b/drivers/platform/x86/dell-wmi-sysman/string-attributes.c deleted file mode 100644 index ac75dce88a4c..000000000000 --- a/drivers/platform/x86/dell-wmi-sysman/string-attributes.c +++ /dev/null @@ -1,159 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Functions corresponding to string type attributes under BIOS String GUID for use with - * dell-wmi-sysman - * - * Copyright (c) 2020 Dell Inc. - */ - -#include "dell-wmi-sysman.h" - -enum string_properties {MIN_LEN = 6, MAX_LEN}; - -get_instance_id(str); - -static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) -{ - int instance_id = get_str_instance_id(kobj); - union acpi_object *obj; - ssize_t ret; - - if (instance_id < 0) - return -EIO; - - /* need to use specific instance_id and guid combination to get right data */ - obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID); - if (!obj) - return -EIO; - if (obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_STRING) { - kfree(obj); - return -EINVAL; - } - ret = snprintf(buf, PAGE_SIZE, "%s\n", obj->package.elements[CURRENT_VAL].string.pointer); - kfree(obj); - return ret; -} - -/** - * validate_str_input() - Validate input of current_value against min and max lengths - * @instance_id: The instance on which input is validated - * @buf: Input value - */ -static int validate_str_input(int instance_id, const char *buf) -{ - int in_len = strlen(buf); - - if ((in_len < wmi_priv.str_data[instance_id].min_length) || - (in_len > wmi_priv.str_data[instance_id].max_length)) - return -EINVAL; - - return 0; -} - -attribute_s_property_show(display_name_language_code, str); -static struct kobj_attribute str_displ_langcode = - __ATTR_RO(display_name_language_code); - -attribute_s_property_show(display_name, str); -static struct kobj_attribute str_displ_name = - __ATTR_RO(display_name); - -attribute_s_property_show(default_value, str); -static struct kobj_attribute str_default_val = - __ATTR_RO(default_value); - -attribute_property_store(current_value, str); -static struct kobj_attribute str_current_val = - __ATTR_RW_MODE(current_value, 0600); - -attribute_s_property_show(dell_modifier, str); -static struct kobj_attribute str_modifier = - __ATTR_RO(dell_modifier); - -attribute_n_property_show(min_length, str); -static struct kobj_attribute str_min_length = - __ATTR_RO(min_length); - -attribute_n_property_show(max_length, str); -static struct kobj_attribute str_max_length = - __ATTR_RO(max_length); - -static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr, - char *buf) -{ - return sprintf(buf, "string\n"); -} -static struct kobj_attribute str_type = - __ATTR_RO(type); - -static struct attribute *str_attrs[] = { - &str_displ_langcode.attr, - &str_displ_name.attr, - &str_default_val.attr, - &str_current_val.attr, - &str_modifier.attr, - &str_min_length.attr, - &str_max_length.attr, - &str_type.attr, - NULL, -}; - -static const struct attribute_group str_attr_group = { - .attrs = str_attrs, -}; - -int alloc_str_data(void) -{ - int ret = 0; - - wmi_priv.str_instances_count = get_instance_count(DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID); - wmi_priv.str_data = kcalloc(wmi_priv.str_instances_count, - sizeof(struct str_data), GFP_KERNEL); - if (!wmi_priv.str_data) { - wmi_priv.str_instances_count = 0; - ret = -ENOMEM; - } - return ret; -} - -/** - * populate_str_data() - Populate all properties of an instance under string attribute - * @str_obj: ACPI object with integer data - * @instance_id: The instance to enumerate - * @attr_name_kobj: The parent kernel object - */ -int populate_str_data(union acpi_object *str_obj, int instance_id, struct kobject *attr_name_kobj) -{ - wmi_priv.str_data[instance_id].attr_name_kobj = attr_name_kobj; - strlcpy_attr(wmi_priv.str_data[instance_id].attribute_name, - str_obj[ATTR_NAME].string.pointer); - strlcpy_attr(wmi_priv.str_data[instance_id].display_name_language_code, - str_obj[DISPL_NAME_LANG_CODE].string.pointer); - strlcpy_attr(wmi_priv.str_data[instance_id].display_name, - str_obj[DISPLAY_NAME].string.pointer); - strlcpy_attr(wmi_priv.str_data[instance_id].default_value, - str_obj[DEFAULT_VAL].string.pointer); - strlcpy_attr(wmi_priv.str_data[instance_id].dell_modifier, - str_obj[MODIFIER].string.pointer); - wmi_priv.str_data[instance_id].min_length = (uintptr_t)str_obj[MIN_LEN].string.pointer; - wmi_priv.str_data[instance_id].max_length = (uintptr_t) str_obj[MAX_LEN].string.pointer; - - return sysfs_create_group(attr_name_kobj, &str_attr_group); -} - -/** - * exit_str_attributes() - Clear all attribute data - * - * Clears all data allocated for this group of attributes - */ -void exit_str_attributes(void) -{ - int instance_id; - - for (instance_id = 0; instance_id < wmi_priv.str_instances_count; instance_id++) { - if (wmi_priv.str_data[instance_id].attr_name_kobj) - sysfs_remove_group(wmi_priv.str_data[instance_id].attr_name_kobj, - &str_attr_group); - } - kfree(wmi_priv.str_data); -} diff --git a/drivers/platform/x86/dell-wmi-sysman/sysman.c b/drivers/platform/x86/dell-wmi-sysman/sysman.c deleted file mode 100644 index cb81010ba1a2..000000000000 --- a/drivers/platform/x86/dell-wmi-sysman/sysman.c +++ /dev/null @@ -1,631 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Common methods for use with dell-wmi-sysman - * - * Copyright (c) 2020 Dell Inc. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include "dell-wmi-sysman.h" - -#define MAX_TYPES 4 -#include - -static struct class firmware_attributes_class = { - .name = "firmware-attributes", -}; - -struct wmi_sysman_priv wmi_priv = { - .mutex = __MUTEX_INITIALIZER(wmi_priv.mutex), -}; - -/* reset bios to defaults */ -static const char * const reset_types[] = {"builtinsafe", "lastknowngood", "factory", "custom"}; -static int reset_option = -1; - - -/** - * populate_string_buffer() - populates a string buffer - * @buffer: the start of the destination buffer - * @buffer_len: length of the destination buffer - * @str: the string to insert into buffer - */ -ssize_t populate_string_buffer(char *buffer, size_t buffer_len, const char *str) -{ - u16 *length = (u16 *)buffer; - u16 *target = length + 1; - int ret; - - ret = utf8s_to_utf16s(str, strlen(str), UTF16_HOST_ENDIAN, - target, buffer_len - sizeof(u16)); - if (ret < 0) { - dev_err(wmi_priv.class_dev, "UTF16 conversion failed\n"); - return ret; - } - - if ((ret * sizeof(u16)) > U16_MAX) { - dev_err(wmi_priv.class_dev, "Error string too long\n"); - return -ERANGE; - } - - *length = ret * sizeof(u16); - return sizeof(u16) + *length; -} - -/** - * calculate_string_buffer() - determines size of string buffer for use with BIOS communication - * @str: the string to calculate based upon - * - */ -size_t calculate_string_buffer(const char *str) -{ - /* u16 length field + one UTF16 char for each input char */ - return sizeof(u16) + strlen(str) * sizeof(u16); -} - -/** - * calculate_security_buffer() - determines size of security buffer for authentication scheme - * @authentication: the authentication content - * - * Currently only supported type is Admin password - */ -size_t calculate_security_buffer(char *authentication) -{ - if (strlen(authentication) > 0) { - return (sizeof(u32) * 2) + strlen(authentication) + - strlen(authentication) % 2; - } - return sizeof(u32) * 2; -} - -/** - * populate_security_buffer() - builds a security buffer for authentication scheme - * @buffer: the buffer to populate - * @authentication: the authentication content - * - * Currently only supported type is PLAIN TEXT - */ -void populate_security_buffer(char *buffer, char *authentication) -{ - char *auth = buffer + sizeof(u32) * 2; - u32 *sectype = (u32 *) buffer; - u32 *seclen = sectype + 1; - - *sectype = strlen(authentication) > 0 ? 1 : 0; - *seclen = strlen(authentication); - - /* plain text */ - if (strlen(authentication) > 0) - memcpy(auth, authentication, *seclen); -} - -/** - * map_wmi_error() - map errors from WMI methods to kernel error codes - * @error_code: integer error code returned from Dell's firmware - */ -int map_wmi_error(int error_code) -{ - switch (error_code) { - case 0: - /* success */ - return 0; - case 1: - /* failed */ - return -EIO; - case 2: - /* invalid parameter */ - return -EINVAL; - case 3: - /* access denied */ - return -EACCES; - case 4: - /* not supported */ - return -EOPNOTSUPP; - case 5: - /* memory error */ - return -ENOMEM; - case 6: - /* protocol error */ - return -EPROTO; - } - /* unspecified error */ - return -EIO; -} - -/** - * reset_bios_show() - sysfs implementaton for read reset_bios - * @kobj: Kernel object for this attribute - * @attr: Kernel object attribute - * @buf: The buffer to display to userspace - */ -static ssize_t reset_bios_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) -{ - char *start = buf; - int i; - - for (i = 0; i < MAX_TYPES; i++) { - if (i == reset_option) - buf += sprintf(buf, "[%s] ", reset_types[i]); - else - buf += sprintf(buf, "%s ", reset_types[i]); - } - buf += sprintf(buf, "\n"); - return buf-start; -} - -/** - * reset_bios_store() - sysfs implementaton for write reset_bios - * @kobj: Kernel object for this attribute - * @attr: Kernel object attribute - * @buf: The buffer from userspace - * @count: the size of the buffer from userspace - */ -static ssize_t reset_bios_store(struct kobject *kobj, - struct kobj_attribute *attr, const char *buf, size_t count) -{ - int type = sysfs_match_string(reset_types, buf); - int ret; - - if (type < 0) - return type; - - ret = set_bios_defaults(type); - pr_debug("reset all attributes request type %d: %d\n", type, ret); - if (!ret) { - reset_option = type; - ret = count; - } - - return ret; -} - -/** - * pending_reboot_show() - sysfs implementaton for read pending_reboot - * @kobj: Kernel object for this attribute - * @attr: Kernel object attribute - * @buf: The buffer to display to userspace - * - * Stores default value as 0 - * When current_value is changed this attribute is set to 1 to notify reboot may be required - */ -static ssize_t pending_reboot_show(struct kobject *kobj, struct kobj_attribute *attr, - char *buf) -{ - return sprintf(buf, "%d\n", wmi_priv.pending_changes); -} - -static struct kobj_attribute reset_bios = __ATTR_RW(reset_bios); -static struct kobj_attribute pending_reboot = __ATTR_RO(pending_reboot); - - -/** - * create_attributes_level_sysfs_files() - Creates reset_bios and - * pending_reboot attributes - */ -static int create_attributes_level_sysfs_files(void) -{ - int ret = sysfs_create_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr); - - if (ret) { - pr_debug("could not create reset_bios file\n"); - return ret; - } - - ret = sysfs_create_file(&wmi_priv.main_dir_kset->kobj, &pending_reboot.attr); - if (ret) { - pr_debug("could not create changing_pending_reboot file\n"); - sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr); - } - return ret; -} - -static void release_reset_bios_data(void) -{ - sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr); - sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &pending_reboot.attr); -} - -static ssize_t wmi_sysman_attr_show(struct kobject *kobj, struct attribute *attr, - char *buf) -{ - struct kobj_attribute *kattr; - ssize_t ret = -EIO; - - kattr = container_of(attr, struct kobj_attribute, attr); - if (kattr->show) - ret = kattr->show(kobj, kattr, buf); - return ret; -} - -static ssize_t wmi_sysman_attr_store(struct kobject *kobj, struct attribute *attr, - const char *buf, size_t count) -{ - struct kobj_attribute *kattr; - ssize_t ret = -EIO; - - kattr = container_of(attr, struct kobj_attribute, attr); - if (kattr->store) - ret = kattr->store(kobj, kattr, buf, count); - return ret; -} - -static const struct sysfs_ops wmi_sysman_kobj_sysfs_ops = { - .show = wmi_sysman_attr_show, - .store = wmi_sysman_attr_store, -}; - -static void attr_name_release(struct kobject *kobj) -{ - kfree(kobj); -} - -static struct kobj_type attr_name_ktype = { - .release = attr_name_release, - .sysfs_ops = &wmi_sysman_kobj_sysfs_ops, -}; - -/** - * strlcpy_attr - Copy a length-limited, NULL-terminated string with bound checks - * @dest: Where to copy the string to - * @src: Where to copy the string from - */ -void strlcpy_attr(char *dest, char *src) -{ - size_t len = strlen(src) + 1; - - if (len > 1 && len <= MAX_BUFF) - strlcpy(dest, src, len); - - /*len can be zero because any property not-applicable to attribute can - * be empty so check only for too long buffers and log error - */ - if (len > MAX_BUFF) - pr_err("Source string returned from BIOS is out of bound!\n"); -} - -/** - * get_wmiobj_pointer() - Get Content of WMI block for particular instance - * @instance_id: WMI instance ID - * @guid_string: WMI GUID (in str form) - * - * Fetches the content for WMI block (instance_id) under GUID (guid_string) - * Caller must kfree the return - */ -union acpi_object *get_wmiobj_pointer(int instance_id, const char *guid_string) -{ - struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; - acpi_status status; - - status = wmi_query_block(guid_string, instance_id, &out); - - return ACPI_SUCCESS(status) ? (union acpi_object *)out.pointer : NULL; -} - -/** - * get_instance_count() - Compute total number of instances under guid_string - * @guid_string: WMI GUID (in string form) - */ -int get_instance_count(const char *guid_string) -{ - union acpi_object *wmi_obj = NULL; - int i = 0; - - do { - kfree(wmi_obj); - wmi_obj = get_wmiobj_pointer(i, guid_string); - i++; - } while (wmi_obj); - - return (i-1); -} - -/** - * alloc_attributes_data() - Allocate attributes data for a particular type - * @attr_type: Attribute type to allocate - */ -static int alloc_attributes_data(int attr_type) -{ - int retval = 0; - - switch (attr_type) { - case ENUM: - retval = alloc_enum_data(); - break; - case INT: - retval = alloc_int_data(); - break; - case STR: - retval = alloc_str_data(); - break; - case PO: - retval = alloc_po_data(); - break; - default: - break; - } - - return retval; -} - -/** - * destroy_attribute_objs() - Free a kset of kobjects - * @kset: The kset to destroy - * - * Fress kobjects created for each attribute_name under attribute type kset - */ -static void destroy_attribute_objs(struct kset *kset) -{ - struct kobject *pos, *next; - - list_for_each_entry_safe(pos, next, &kset->list, entry) { - kobject_put(pos); - } -} - -/** - * release_attributes_data() - Clean-up all sysfs directories and files created - */ -static void release_attributes_data(void) -{ - release_reset_bios_data(); - - mutex_lock(&wmi_priv.mutex); - exit_enum_attributes(); - exit_int_attributes(); - exit_str_attributes(); - exit_po_attributes(); - if (wmi_priv.authentication_dir_kset) { - destroy_attribute_objs(wmi_priv.authentication_dir_kset); - kset_unregister(wmi_priv.authentication_dir_kset); - wmi_priv.authentication_dir_kset = NULL; - } - if (wmi_priv.main_dir_kset) { - destroy_attribute_objs(wmi_priv.main_dir_kset); - kset_unregister(wmi_priv.main_dir_kset); - } - mutex_unlock(&wmi_priv.mutex); - -} - -/** - * init_bios_attributes() - Initialize all attributes for a type - * @attr_type: The attribute type to initialize - * @guid: The WMI GUID associated with this type to initialize - * - * Initialiaze all 4 types of attributes enumeration, integer, string and password object. - * Populates each attrbute typ's respective properties under sysfs files - */ -static int init_bios_attributes(int attr_type, const char *guid) -{ - struct kobject *attr_name_kobj; //individual attribute names - union acpi_object *obj = NULL; - union acpi_object *elements; - struct kset *tmp_set; - - /* instance_id needs to be reset for each type GUID - * also, instance IDs are unique within GUID but not across - */ - int instance_id = 0; - int retval = 0; - - retval = alloc_attributes_data(attr_type); - if (retval) - return retval; - /* need to use specific instance_id and guid combination to get right data */ - obj = get_wmiobj_pointer(instance_id, guid); - if (!obj || obj->type != ACPI_TYPE_PACKAGE) - return -ENODEV; - elements = obj->package.elements; - - mutex_lock(&wmi_priv.mutex); - while (elements) { - /* sanity checking */ - if (elements[ATTR_NAME].type != ACPI_TYPE_STRING) { - pr_debug("incorrect element type\n"); - goto nextobj; - } - if (strlen(elements[ATTR_NAME].string.pointer) == 0) { - pr_debug("empty attribute found\n"); - goto nextobj; - } - if (attr_type == PO) - tmp_set = wmi_priv.authentication_dir_kset; - else - tmp_set = wmi_priv.main_dir_kset; - - if (kset_find_obj(tmp_set, elements[ATTR_NAME].string.pointer)) { - pr_debug("duplicate attribute name found - %s\n", - elements[ATTR_NAME].string.pointer); - goto nextobj; - } - - /* build attribute */ - attr_name_kobj = kzalloc(sizeof(*attr_name_kobj), GFP_KERNEL); - if (!attr_name_kobj) { - retval = -ENOMEM; - goto err_attr_init; - } - - attr_name_kobj->kset = tmp_set; - - retval = kobject_init_and_add(attr_name_kobj, &attr_name_ktype, NULL, "%s", - elements[ATTR_NAME].string.pointer); - if (retval) { - kobject_put(attr_name_kobj); - goto err_attr_init; - } - - /* enumerate all of this attribute */ - switch (attr_type) { - case ENUM: - retval = populate_enum_data(elements, instance_id, attr_name_kobj); - break; - case INT: - retval = populate_int_data(elements, instance_id, attr_name_kobj); - break; - case STR: - retval = populate_str_data(elements, instance_id, attr_name_kobj); - break; - case PO: - retval = populate_po_data(elements, instance_id, attr_name_kobj); - break; - default: - break; - } - - if (retval) { - pr_debug("failed to populate %s\n", - elements[ATTR_NAME].string.pointer); - goto err_attr_init; - } - -nextobj: - kfree(obj); - instance_id++; - obj = get_wmiobj_pointer(instance_id, guid); - elements = obj ? obj->package.elements : NULL; - } - - mutex_unlock(&wmi_priv.mutex); - return 0; - -err_attr_init: - mutex_unlock(&wmi_priv.mutex); - release_attributes_data(); - kfree(obj); - return retval; -} - -static int __init sysman_init(void) -{ - int ret = 0; - - if (!dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "Dell System", NULL) && - !dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "www.dell.com", NULL)) { - pr_err("Unable to run on non-Dell system\n"); - return -ENODEV; - } - - ret = init_bios_attr_set_interface(); - if (ret || !wmi_priv.bios_attr_wdev) { - pr_debug("failed to initialize set interface\n"); - goto fail_set_interface; - } - - ret = init_bios_attr_pass_interface(); - if (ret || !wmi_priv.password_attr_wdev) { - pr_debug("failed to initialize pass interface\n"); - goto fail_pass_interface; - } - - ret = class_register(&firmware_attributes_class); - if (ret) - goto fail_class; - - wmi_priv.class_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0), - NULL, "%s", DRIVER_NAME); - if (IS_ERR(wmi_priv.class_dev)) { - ret = PTR_ERR(wmi_priv.class_dev); - goto fail_classdev; - } - - wmi_priv.main_dir_kset = kset_create_and_add("attributes", NULL, - &wmi_priv.class_dev->kobj); - if (!wmi_priv.main_dir_kset) { - ret = -ENOMEM; - goto fail_main_kset; - } - - wmi_priv.authentication_dir_kset = kset_create_and_add("authentication", NULL, - &wmi_priv.class_dev->kobj); - if (!wmi_priv.authentication_dir_kset) { - ret = -ENOMEM; - goto fail_authentication_kset; - } - - ret = create_attributes_level_sysfs_files(); - if (ret) { - pr_debug("could not create reset BIOS attribute\n"); - goto fail_reset_bios; - } - - ret = init_bios_attributes(ENUM, DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID); - if (ret) { - pr_debug("failed to populate enumeration type attributes\n"); - goto fail_create_group; - } - - ret = init_bios_attributes(INT, DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID); - if (ret) { - pr_debug("failed to populate integer type attributes\n"); - goto fail_create_group; - } - - ret = init_bios_attributes(STR, DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID); - if (ret) { - pr_debug("failed to populate string type attributes\n"); - goto fail_create_group; - } - - ret = init_bios_attributes(PO, DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID); - if (ret) { - pr_debug("failed to populate pass object type attributes\n"); - goto fail_create_group; - } - - return 0; - -fail_create_group: - release_attributes_data(); - -fail_reset_bios: - if (wmi_priv.authentication_dir_kset) { - kset_unregister(wmi_priv.authentication_dir_kset); - wmi_priv.authentication_dir_kset = NULL; - } - -fail_authentication_kset: - if (wmi_priv.main_dir_kset) { - kset_unregister(wmi_priv.main_dir_kset); - wmi_priv.main_dir_kset = NULL; - } - -fail_main_kset: - device_destroy(&firmware_attributes_class, MKDEV(0, 0)); - -fail_classdev: - class_unregister(&firmware_attributes_class); - -fail_class: - exit_bios_attr_pass_interface(); - -fail_pass_interface: - exit_bios_attr_set_interface(); - -fail_set_interface: - return ret; -} - -static void __exit sysman_exit(void) -{ - release_attributes_data(); - device_destroy(&firmware_attributes_class, MKDEV(0, 0)); - class_unregister(&firmware_attributes_class); - exit_bios_attr_set_interface(); - exit_bios_attr_pass_interface(); -} - -module_init(sysman_init); -module_exit(sysman_exit); - -MODULE_AUTHOR("Mario Limonciello "); -MODULE_AUTHOR("Prasanth Ksr "); -MODULE_AUTHOR("Divya Bharathi "); -MODULE_DESCRIPTION("Dell platform setting control interface"); -MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c deleted file mode 100644 index bbdb3e860892..000000000000 --- a/drivers/platform/x86/dell-wmi.c +++ /dev/null @@ -1,764 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Dell WMI hotkeys - * - * Copyright (C) 2008 Red Hat - * Copyright (C) 2014-2015 Pali Rohár - * - * Portions based on wistron_btns.c: - * Copyright (C) 2005 Miloslav Trmac - * Copyright (C) 2005 Bernhard Rosenkraenzer - * Copyright (C) 2005 Dmitry Torokhov - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "dell-smbios.h" -#include "dell-wmi-descriptor.h" - -MODULE_AUTHOR("Matthew Garrett "); -MODULE_AUTHOR("Pali Rohár "); -MODULE_DESCRIPTION("Dell laptop WMI hotkeys driver"); -MODULE_LICENSE("GPL"); - -#define DELL_EVENT_GUID "9DBB5994-A997-11DA-B012-B622A1EF5492" - -static bool wmi_requires_smbios_request; - -struct dell_wmi_priv { - struct input_dev *input_dev; - u32 interface_version; -}; - -static int __init dmi_matched(const struct dmi_system_id *dmi) -{ - wmi_requires_smbios_request = 1; - return 1; -} - -static const struct dmi_system_id dell_wmi_smbios_list[] __initconst = { - { - .callback = dmi_matched, - .ident = "Dell Inspiron M5110", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron M5110"), - }, - }, - { - .callback = dmi_matched, - .ident = "Dell Vostro V131", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Vostro V131"), - }, - }, - { } -}; - -/* - * Keymap for WMI events of type 0x0000 - * - * Certain keys are flagged as KE_IGNORE. All of these are either - * notifications (rather than requests for change) or are also sent - * via the keyboard controller so should not be sent again. - */ -static const struct key_entry dell_wmi_keymap_type_0000[] = { - { KE_IGNORE, 0x003a, { KEY_CAPSLOCK } }, - - /* Key code is followed by brightness level */ - { KE_KEY, 0xe005, { KEY_BRIGHTNESSDOWN } }, - { KE_KEY, 0xe006, { KEY_BRIGHTNESSUP } }, - - /* Battery health status button */ - { KE_KEY, 0xe007, { KEY_BATTERY } }, - - /* Radio devices state change, key code is followed by other values */ - { KE_IGNORE, 0xe008, { KEY_RFKILL } }, - - { KE_KEY, 0xe009, { KEY_EJECTCD } }, - - /* Key code is followed by: next, active and attached devices */ - { KE_KEY, 0xe00b, { KEY_SWITCHVIDEOMODE } }, - - /* Key code is followed by keyboard illumination level */ - { KE_IGNORE, 0xe00c, { KEY_KBDILLUMTOGGLE } }, - - /* BIOS error detected */ - { KE_IGNORE, 0xe00d, { KEY_RESERVED } }, - - /* Battery was removed or inserted */ - { KE_IGNORE, 0xe00e, { KEY_RESERVED } }, - - /* Wifi Catcher */ - { KE_KEY, 0xe011, { KEY_WLAN } }, - - /* Ambient light sensor toggle */ - { KE_IGNORE, 0xe013, { KEY_RESERVED } }, - - { KE_IGNORE, 0xe020, { KEY_MUTE } }, - - /* Unknown, defined in ACPI DSDT */ - /* { KE_IGNORE, 0xe023, { KEY_RESERVED } }, */ - - /* Untested, Dell Instant Launch key on Inspiron 7520 */ - /* { KE_IGNORE, 0xe024, { KEY_RESERVED } }, */ - - /* Dell Instant Launch key */ - { KE_KEY, 0xe025, { KEY_PROG4 } }, - - /* Audio panel key */ - { KE_IGNORE, 0xe026, { KEY_RESERVED } }, - - /* LCD Display On/Off Control key */ - { KE_KEY, 0xe027, { KEY_DISPLAYTOGGLE } }, - - /* Untested, Multimedia key on Dell Vostro 3560 */ - /* { KE_IGNORE, 0xe028, { KEY_RESERVED } }, */ - - /* Dell Instant Launch key */ - { KE_KEY, 0xe029, { KEY_PROG4 } }, - - /* Untested, Windows Mobility Center button on Inspiron 7520 */ - /* { KE_IGNORE, 0xe02a, { KEY_RESERVED } }, */ - - /* Unknown, defined in ACPI DSDT */ - /* { KE_IGNORE, 0xe02b, { KEY_RESERVED } }, */ - - /* Untested, Dell Audio With Preset Switch button on Inspiron 7520 */ - /* { KE_IGNORE, 0xe02c, { KEY_RESERVED } }, */ - - { KE_IGNORE, 0xe02e, { KEY_VOLUMEDOWN } }, - { KE_IGNORE, 0xe030, { KEY_VOLUMEUP } }, - { KE_IGNORE, 0xe033, { KEY_KBDILLUMUP } }, - { KE_IGNORE, 0xe034, { KEY_KBDILLUMDOWN } }, - { KE_IGNORE, 0xe03a, { KEY_CAPSLOCK } }, - - /* NIC Link is Up */ - { KE_IGNORE, 0xe043, { KEY_RESERVED } }, - - /* NIC Link is Down */ - { KE_IGNORE, 0xe044, { KEY_RESERVED } }, - - /* - * This entry is very suspicious! - * Originally Matthew Garrett created this dell-wmi driver specially for - * "button with a picture of a battery" which has event code 0xe045. - * Later Mario Limonciello from Dell told us that event code 0xe045 is - * reported by Num Lock and should be ignored because key is send also - * by keyboard controller. - * So for now we will ignore this event to prevent potential double - * Num Lock key press. - */ - { KE_IGNORE, 0xe045, { KEY_NUMLOCK } }, - - /* Scroll lock and also going to tablet mode on portable devices */ - { KE_IGNORE, 0xe046, { KEY_SCROLLLOCK } }, - - /* Untested, going from tablet mode on portable devices */ - /* { KE_IGNORE, 0xe047, { KEY_RESERVED } }, */ - - /* Dell Support Center key */ - { KE_IGNORE, 0xe06e, { KEY_RESERVED } }, - - { KE_IGNORE, 0xe0f7, { KEY_MUTE } }, - { KE_IGNORE, 0xe0f8, { KEY_VOLUMEDOWN } }, - { KE_IGNORE, 0xe0f9, { KEY_VOLUMEUP } }, -}; - -struct dell_bios_keymap_entry { - u16 scancode; - u16 keycode; -}; - -struct dell_bios_hotkey_table { - struct dmi_header header; - struct dell_bios_keymap_entry keymap[]; - -}; - -struct dell_dmi_results { - int err; - int keymap_size; - struct key_entry *keymap; -}; - -/* Uninitialized entries here are KEY_RESERVED == 0. */ -static const u16 bios_to_linux_keycode[256] = { - [0] = KEY_MEDIA, - [1] = KEY_NEXTSONG, - [2] = KEY_PLAYPAUSE, - [3] = KEY_PREVIOUSSONG, - [4] = KEY_STOPCD, - [5] = KEY_UNKNOWN, - [6] = KEY_UNKNOWN, - [7] = KEY_UNKNOWN, - [8] = KEY_WWW, - [9] = KEY_UNKNOWN, - [10] = KEY_VOLUMEDOWN, - [11] = KEY_MUTE, - [12] = KEY_VOLUMEUP, - [13] = KEY_UNKNOWN, - [14] = KEY_BATTERY, - [15] = KEY_EJECTCD, - [16] = KEY_UNKNOWN, - [17] = KEY_SLEEP, - [18] = KEY_PROG1, - [19] = KEY_BRIGHTNESSDOWN, - [20] = KEY_BRIGHTNESSUP, - [21] = KEY_BRIGHTNESS_AUTO, - [22] = KEY_KBDILLUMTOGGLE, - [23] = KEY_UNKNOWN, - [24] = KEY_SWITCHVIDEOMODE, - [25] = KEY_UNKNOWN, - [26] = KEY_UNKNOWN, - [27] = KEY_SWITCHVIDEOMODE, - [28] = KEY_UNKNOWN, - [29] = KEY_UNKNOWN, - [30] = KEY_PROG2, - [31] = KEY_UNKNOWN, - [32] = KEY_UNKNOWN, - [33] = KEY_UNKNOWN, - [34] = KEY_UNKNOWN, - [35] = KEY_UNKNOWN, - [36] = KEY_UNKNOWN, - [37] = KEY_UNKNOWN, - [38] = KEY_MICMUTE, - [255] = KEY_PROG3, -}; - -/* - * Keymap for WMI events of type 0x0010 - * - * These are applied if the 0xB2 DMI hotkey table is present and doesn't - * override them. - */ -static const struct key_entry dell_wmi_keymap_type_0010[] = { - /* Fn-lock switched to function keys */ - { KE_IGNORE, 0x0, { KEY_RESERVED } }, - - /* Fn-lock switched to multimedia keys */ - { KE_IGNORE, 0x1, { KEY_RESERVED } }, - - /* Keyboard backlight change notification */ - { KE_IGNORE, 0x3f, { KEY_RESERVED } }, - - /* Backlight brightness level */ - { KE_KEY, 0x57, { KEY_BRIGHTNESSDOWN } }, - { KE_KEY, 0x58, { KEY_BRIGHTNESSUP } }, - - /* Mic mute */ - { KE_KEY, 0x150, { KEY_MICMUTE } }, - - /* Fn-lock */ - { KE_IGNORE, 0x151, { KEY_RESERVED } }, - - /* Change keyboard illumination */ - { KE_IGNORE, 0x152, { KEY_KBDILLUMTOGGLE } }, - - /* - * Radio disable (notify only -- there is no model for which the - * WMI event is supposed to trigger an action). - */ - { KE_IGNORE, 0x153, { KEY_RFKILL } }, - - /* RGB keyboard backlight control */ - { KE_IGNORE, 0x154, { KEY_RESERVED } }, - - /* - * Stealth mode toggle. This will "disable all lights and sounds". - * The action is performed by the BIOS and EC; the WMI event is just - * a notification. On the XPS 13 9350, this is Fn+F7, and there's - * a BIOS setting to enable and disable the hotkey. - */ - { KE_IGNORE, 0x155, { KEY_RESERVED } }, - - /* Rugged magnetic dock attach/detach events */ - { KE_IGNORE, 0x156, { KEY_RESERVED } }, - { KE_IGNORE, 0x157, { KEY_RESERVED } }, - - /* Rugged programmable (P1/P2/P3 keys) */ - { KE_KEY, 0x850, { KEY_PROG1 } }, - { KE_KEY, 0x851, { KEY_PROG2 } }, - { KE_KEY, 0x852, { KEY_PROG3 } }, - - /* - * Radio disable (notify only -- there is no model for which the - * WMI event is supposed to trigger an action). - */ - { KE_IGNORE, 0xe008, { KEY_RFKILL } }, - - /* Fn-lock */ - { KE_IGNORE, 0xe035, { KEY_RESERVED } }, -}; - -/* - * Keymap for WMI events of type 0x0011 - */ -static const struct key_entry dell_wmi_keymap_type_0011[] = { - /* Battery unplugged */ - { KE_IGNORE, 0xfff0, { KEY_RESERVED } }, - - /* Battery inserted */ - { KE_IGNORE, 0xfff1, { KEY_RESERVED } }, - - /* - * Detachable keyboard detached / undocked - * Note SW_TABLET_MODE is already reported through the intel_vbtn - * driver for this, so we ignore it. - */ - { KE_IGNORE, 0xfff2, { KEY_RESERVED } }, - - /* Detachable keyboard attached / docked */ - { KE_IGNORE, 0xfff3, { KEY_RESERVED } }, - - /* Keyboard backlight level changed */ - { KE_IGNORE, KBD_LED_OFF_TOKEN, { KEY_RESERVED } }, - { KE_IGNORE, KBD_LED_ON_TOKEN, { KEY_RESERVED } }, - { KE_IGNORE, KBD_LED_AUTO_TOKEN, { KEY_RESERVED } }, - { KE_IGNORE, KBD_LED_AUTO_25_TOKEN, { KEY_RESERVED } }, - { KE_IGNORE, KBD_LED_AUTO_50_TOKEN, { KEY_RESERVED } }, - { KE_IGNORE, KBD_LED_AUTO_75_TOKEN, { KEY_RESERVED } }, - { KE_IGNORE, KBD_LED_AUTO_100_TOKEN, { KEY_RESERVED } }, -}; - -/* - * Keymap for WMI events of type 0x0012 - * They are events with extended data - */ -static const struct key_entry dell_wmi_keymap_type_0012[] = { - /* Fn-lock button pressed */ - { KE_IGNORE, 0xe035, { KEY_RESERVED } }, -}; - -static void dell_wmi_process_key(struct wmi_device *wdev, int type, int code) -{ - struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev); - const struct key_entry *key; - - key = sparse_keymap_entry_from_scancode(priv->input_dev, - (type << 16) | code); - if (!key) { - pr_info("Unknown key with type 0x%04x and code 0x%04x pressed\n", - type, code); - return; - } - - pr_debug("Key with type 0x%04x and code 0x%04x pressed\n", type, code); - - /* Don't report brightness notifications that will also come via ACPI */ - if ((key->keycode == KEY_BRIGHTNESSUP || - key->keycode == KEY_BRIGHTNESSDOWN) && - acpi_video_handles_brightness_key_presses()) - return; - - if (type == 0x0000 && code == 0xe025 && !wmi_requires_smbios_request) - return; - - if (key->keycode == KEY_KBDILLUMTOGGLE) - dell_laptop_call_notifier( - DELL_LAPTOP_KBD_BACKLIGHT_BRIGHTNESS_CHANGED, NULL); - - sparse_keymap_report_entry(priv->input_dev, key, 1, true); -} - -static void dell_wmi_notify(struct wmi_device *wdev, - union acpi_object *obj) -{ - struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev); - u16 *buffer_entry, *buffer_end; - acpi_size buffer_size; - int len, i; - - if (obj->type != ACPI_TYPE_BUFFER) { - pr_warn("bad response type %x\n", obj->type); - return; - } - - pr_debug("Received WMI event (%*ph)\n", - obj->buffer.length, obj->buffer.pointer); - - buffer_entry = (u16 *)obj->buffer.pointer; - buffer_size = obj->buffer.length/2; - buffer_end = buffer_entry + buffer_size; - - /* - * BIOS/ACPI on devices with WMI interface version 0 does not clear - * buffer before filling it. So next time when BIOS/ACPI send WMI event - * which is smaller as previous then it contains garbage in buffer from - * previous event. - * - * BIOS/ACPI on devices with WMI interface version 1 clears buffer and - * sometimes send more events in buffer at one call. - * - * So to prevent reading garbage from buffer we will process only first - * one event on devices with WMI interface version 0. - */ - if (priv->interface_version == 0 && buffer_entry < buffer_end) - if (buffer_end > buffer_entry + buffer_entry[0] + 1) - buffer_end = buffer_entry + buffer_entry[0] + 1; - - while (buffer_entry < buffer_end) { - - len = buffer_entry[0]; - if (len == 0) - break; - - len++; - - if (buffer_entry + len > buffer_end) { - pr_warn("Invalid length of WMI event\n"); - break; - } - - pr_debug("Process buffer (%*ph)\n", len*2, buffer_entry); - - switch (buffer_entry[1]) { - case 0x0000: /* One key pressed or event occurred */ - case 0x0012: /* Event with extended data occurred */ - if (len > 2) - dell_wmi_process_key(wdev, buffer_entry[1], - buffer_entry[2]); - /* Extended data is currently ignored */ - break; - case 0x0010: /* Sequence of keys pressed */ - case 0x0011: /* Sequence of events occurred */ - for (i = 2; i < len; ++i) - dell_wmi_process_key(wdev, buffer_entry[1], - buffer_entry[i]); - break; - default: /* Unknown event */ - pr_info("Unknown WMI event type 0x%x\n", - (int)buffer_entry[1]); - break; - } - - buffer_entry += len; - - } - -} - -static bool have_scancode(u32 scancode, const struct key_entry *keymap, int len) -{ - int i; - - for (i = 0; i < len; i++) - if (keymap[i].code == scancode) - return true; - - return false; -} - -static void handle_dmi_entry(const struct dmi_header *dm, void *opaque) -{ - struct dell_dmi_results *results = opaque; - struct dell_bios_hotkey_table *table; - int hotkey_num, i, pos = 0; - struct key_entry *keymap; - - if (results->err || results->keymap) - return; /* We already found the hotkey table. */ - - /* The Dell hotkey table is type 0xB2. Scan until we find it. */ - if (dm->type != 0xb2) - return; - - table = container_of(dm, struct dell_bios_hotkey_table, header); - - hotkey_num = (table->header.length - - sizeof(struct dell_bios_hotkey_table)) / - sizeof(struct dell_bios_keymap_entry); - if (hotkey_num < 1) { - /* - * Historically, dell-wmi would ignore a DMI entry of - * fewer than 7 bytes. Sizes between 4 and 8 bytes are - * nonsensical (both the header and all entries are 4 - * bytes), so we approximate the old behavior by - * ignoring tables with fewer than one entry. - */ - return; - } - - keymap = kcalloc(hotkey_num, sizeof(struct key_entry), GFP_KERNEL); - if (!keymap) { - results->err = -ENOMEM; - return; - } - - for (i = 0; i < hotkey_num; i++) { - const struct dell_bios_keymap_entry *bios_entry = - &table->keymap[i]; - - /* Uninitialized entries are 0 aka KEY_RESERVED. */ - u16 keycode = (bios_entry->keycode < - ARRAY_SIZE(bios_to_linux_keycode)) ? - bios_to_linux_keycode[bios_entry->keycode] : - (bios_entry->keycode == 0xffff ? KEY_UNKNOWN : KEY_RESERVED); - - /* - * Log if we find an entry in the DMI table that we don't - * understand. If this happens, we should figure out what - * the entry means and add it to bios_to_linux_keycode. - */ - if (keycode == KEY_RESERVED) { - pr_info("firmware scancode 0x%x maps to unrecognized keycode 0x%x\n", - bios_entry->scancode, bios_entry->keycode); - continue; - } - - if (keycode == KEY_KBDILLUMTOGGLE) - keymap[pos].type = KE_IGNORE; - else - keymap[pos].type = KE_KEY; - keymap[pos].code = bios_entry->scancode; - keymap[pos].keycode = keycode; - - pos++; - } - - results->keymap = keymap; - results->keymap_size = pos; -} - -static int dell_wmi_input_setup(struct wmi_device *wdev) -{ - struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev); - struct dell_dmi_results dmi_results = {}; - struct key_entry *keymap; - int err, i, pos = 0; - - priv->input_dev = input_allocate_device(); - if (!priv->input_dev) - return -ENOMEM; - - priv->input_dev->name = "Dell WMI hotkeys"; - priv->input_dev->id.bustype = BUS_HOST; - priv->input_dev->dev.parent = &wdev->dev; - - if (dmi_walk(handle_dmi_entry, &dmi_results)) { - /* - * Historically, dell-wmi ignored dmi_walk errors. A failure - * is certainly surprising, but it probably just indicates - * a very old laptop. - */ - pr_warn("no DMI; using the old-style hotkey interface\n"); - } - - if (dmi_results.err) { - err = dmi_results.err; - goto err_free_dev; - } - - keymap = kcalloc(dmi_results.keymap_size + - ARRAY_SIZE(dell_wmi_keymap_type_0000) + - ARRAY_SIZE(dell_wmi_keymap_type_0010) + - ARRAY_SIZE(dell_wmi_keymap_type_0011) + - ARRAY_SIZE(dell_wmi_keymap_type_0012) + - 1, - sizeof(struct key_entry), GFP_KERNEL); - if (!keymap) { - kfree(dmi_results.keymap); - err = -ENOMEM; - goto err_free_dev; - } - - /* Append table with events of type 0x0010 which comes from DMI */ - for (i = 0; i < dmi_results.keymap_size; i++) { - keymap[pos] = dmi_results.keymap[i]; - keymap[pos].code |= (0x0010 << 16); - pos++; - } - - kfree(dmi_results.keymap); - - /* Append table with extra events of type 0x0010 which are not in DMI */ - for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0010); i++) { - const struct key_entry *entry = &dell_wmi_keymap_type_0010[i]; - - /* - * Check if we've already found this scancode. This takes - * quadratic time, but it doesn't matter unless the list - * of extra keys gets very long. - */ - if (dmi_results.keymap_size && - have_scancode(entry->code | (0x0010 << 16), - keymap, dmi_results.keymap_size) - ) - continue; - - keymap[pos] = *entry; - keymap[pos].code |= (0x0010 << 16); - pos++; - } - - /* Append table with events of type 0x0011 */ - for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0011); i++) { - keymap[pos] = dell_wmi_keymap_type_0011[i]; - keymap[pos].code |= (0x0011 << 16); - pos++; - } - - /* Append table with events of type 0x0012 */ - for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0012); i++) { - keymap[pos] = dell_wmi_keymap_type_0012[i]; - keymap[pos].code |= (0x0012 << 16); - pos++; - } - - /* - * Now append also table with "legacy" events of type 0x0000. Some of - * them are reported also on laptops which have scancodes in DMI. - */ - for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0000); i++) { - keymap[pos] = dell_wmi_keymap_type_0000[i]; - pos++; - } - - keymap[pos].type = KE_END; - - err = sparse_keymap_setup(priv->input_dev, keymap, NULL); - /* - * Sparse keymap library makes a copy of keymap so we don't need the - * original one that was allocated. - */ - kfree(keymap); - if (err) - goto err_free_dev; - - err = input_register_device(priv->input_dev); - if (err) - goto err_free_dev; - - return 0; - - err_free_dev: - input_free_device(priv->input_dev); - return err; -} - -static void dell_wmi_input_destroy(struct wmi_device *wdev) -{ - struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev); - - input_unregister_device(priv->input_dev); -} - -/* - * According to Dell SMBIOS documentation: - * - * 17 3 Application Program Registration - * - * cbArg1 Application ID 1 = 0x00010000 - * cbArg2 Application ID 2 - * QUICKSET/DCP = 0x51534554 "QSET" - * ALS Driver = 0x416c7353 "AlsS" - * Latitude ON = 0x4c6f6e52 "LonR" - * cbArg3 Application version or revision number - * cbArg4 0 = Unregister application - * 1 = Register application - * cbRes1 Standard return codes (0, -1, -2) - */ - -static int dell_wmi_events_set_enabled(bool enable) -{ - struct calling_interface_buffer *buffer; - int ret; - - buffer = kzalloc(sizeof(struct calling_interface_buffer), GFP_KERNEL); - if (!buffer) - return -ENOMEM; - buffer->cmd_class = CLASS_INFO; - buffer->cmd_select = SELECT_APP_REGISTRATION; - buffer->input[0] = 0x10000; - buffer->input[1] = 0x51534554; - buffer->input[3] = enable; - ret = dell_smbios_call(buffer); - if (ret == 0) - ret = buffer->output[0]; - kfree(buffer); - - return dell_smbios_error(ret); -} - -static int dell_wmi_probe(struct wmi_device *wdev, const void *context) -{ - struct dell_wmi_priv *priv; - int ret; - - ret = dell_wmi_get_descriptor_valid(); - if (ret) - return ret; - - priv = devm_kzalloc( - &wdev->dev, sizeof(struct dell_wmi_priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - dev_set_drvdata(&wdev->dev, priv); - - if (!dell_wmi_get_interface_version(&priv->interface_version)) - return -EPROBE_DEFER; - - return dell_wmi_input_setup(wdev); -} - -static int dell_wmi_remove(struct wmi_device *wdev) -{ - dell_wmi_input_destroy(wdev); - return 0; -} -static const struct wmi_device_id dell_wmi_id_table[] = { - { .guid_string = DELL_EVENT_GUID }, - { }, -}; - -static struct wmi_driver dell_wmi_driver = { - .driver = { - .name = "dell-wmi", - }, - .id_table = dell_wmi_id_table, - .probe = dell_wmi_probe, - .remove = dell_wmi_remove, - .notify = dell_wmi_notify, -}; - -static int __init dell_wmi_init(void) -{ - int err; - - dmi_check_system(dell_wmi_smbios_list); - - if (wmi_requires_smbios_request) { - err = dell_wmi_events_set_enabled(true); - if (err) { - pr_err("Failed to enable WMI events\n"); - return err; - } - } - - return wmi_driver_register(&dell_wmi_driver); -} -late_initcall(dell_wmi_init); - -static void __exit dell_wmi_exit(void) -{ - if (wmi_requires_smbios_request) - dell_wmi_events_set_enabled(false); - - wmi_driver_unregister(&dell_wmi_driver); -} -module_exit(dell_wmi_exit); - -MODULE_DEVICE_TABLE(wmi, dell_wmi_id_table); diff --git a/drivers/platform/x86/dell/Kconfig b/drivers/platform/x86/dell/Kconfig new file mode 100644 index 000000000000..e0a55337f51a --- /dev/null +++ b/drivers/platform/x86/dell/Kconfig @@ -0,0 +1,207 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Dell X86 Platform Specific Drivers +# + +menuconfig X86_PLATFORM_DRIVERS_DELL + bool "Dell X86 Platform Specific Device Drivers" + default n + depends on X86_PLATFORM_DEVICES + help + Say Y here to get to see options for device drivers for various + Dell x86 platforms, including vendor-specific laptop extension drivers. + This option alone does not add any kernel code. + + If you say N, all options in this submenu will be skipped and disabled. + +if X86_PLATFORM_DRIVERS_DELL + +config ALIENWARE_WMI + tristate "Alienware Special feature control" + default m + depends on ACPI + depends on LEDS_CLASS + depends on NEW_LEDS + depends on ACPI_WMI + help + This is a driver for controlling Alienware BIOS driven + features. It exposes an interface for controlling the AlienFX + zones on Alienware machines that don't contain a dedicated AlienFX + USB MCU such as the X51 and X51-R2. + +config DCDBAS + tristate "Dell Systems Management Base Driver" + default m + depends on X86 + help + The Dell Systems Management Base Driver provides a sysfs interface + for systems management software to perform System Management + Interrupts (SMIs) and Host Control Actions (system power cycle or + power off after OS shutdown) on certain Dell systems. + + See for more details on the driver + and the Dell systems on which Dell systems management software makes + use of this driver. + + Say Y or M here to enable the driver for use by Dell systems + management software such as Dell OpenManage. + +config DELL_LAPTOP + tristate "Dell Laptop Extras" + default m + depends on DMI + depends on BACKLIGHT_CLASS_DEVICE + depends on ACPI_VIDEO || ACPI_VIDEO = n + depends on RFKILL || RFKILL = n + depends on SERIO_I8042 + depends on DELL_SMBIOS + select POWER_SUPPLY + select LEDS_CLASS + select NEW_LEDS + select LEDS_TRIGGERS + select LEDS_TRIGGER_AUDIO + help + This driver adds support for rfkill and backlight control to Dell + laptops (except for some models covered by the Compal driver). + +config DELL_RBU + tristate "BIOS update support for DELL systems via sysfs" + default m + depends on X86 + select FW_LOADER + select FW_LOADER_USER_HELPER + help + Say m if you want to have the option of updating the BIOS for your + DELL system. Note you need a Dell OpenManage or Dell Update package (DUP) + supporting application to communicate with the BIOS regarding the new + image for the image update to take effect. + See for more details on the driver. + +config DELL_RBTN + tristate "Dell Airplane Mode Switch driver" + default m + depends on ACPI + depends on INPUT + depends on RFKILL + help + Say Y here if you want to support Dell Airplane Mode Switch ACPI + device on Dell laptops. Sometimes it has names: DELLABCE or DELRBTN. + This driver register rfkill device or input hotkey device depending + on hardware type (hw switch slider or keyboard toggle button). For + rfkill devices it receive HW switch events and set correct hard + rfkill state. + + To compile this driver as a module, choose M here: the module will + be called dell-rbtn. + +# +# The DELL_SMBIOS driver depends on ACPI_WMI and/or DCDBAS if those +# backends are selected. The "depends" line prevents a configuration +# where DELL_SMBIOS=y while either of those dependencies =m. +# +config DELL_SMBIOS + tristate "Dell SMBIOS driver" + default m + depends on DCDBAS || DCDBAS=n + depends on ACPI_WMI || ACPI_WMI=n + help + This provides support for the Dell SMBIOS calling interface. + If you have a Dell computer you should enable this option. + + Be sure to select at least one backend for it to work properly. + +config DELL_SMBIOS_WMI + bool "Dell SMBIOS driver WMI backend" + default y + depends on ACPI_WMI + select DELL_WMI_DESCRIPTOR + depends on DELL_SMBIOS + help + This provides an implementation for the Dell SMBIOS calling interface + communicated over ACPI-WMI. + + If you have a Dell computer from >2007 you should say Y here. + If you aren't sure and this module doesn't work for your computer + it just won't load. + +config DELL_SMBIOS_SMM + bool "Dell SMBIOS driver SMM backend" + default y + depends on DCDBAS + depends on DELL_SMBIOS + help + This provides an implementation for the Dell SMBIOS calling interface + communicated over SMI/SMM. + + If you have a Dell computer from <=2017 you should say Y here. + If you aren't sure and this module doesn't work for your computer + it just won't load. + +config DELL_SMO8800 + tristate "Dell Latitude freefall driver (ACPI SMO88XX)" + default m + depends on ACPI + help + Say Y here if you want to support SMO88XX freefall devices + on Dell Latitude laptops. + + To compile this driver as a module, choose M here: the module will + be called dell-smo8800. + +config DELL_WMI + tristate "Dell WMI notifications" + default m + depends on ACPI_WMI + depends on DMI + depends on INPUT + depends on ACPI_VIDEO || ACPI_VIDEO = n + depends on DELL_SMBIOS + select DELL_WMI_DESCRIPTOR + select INPUT_SPARSEKMAP + help + Say Y here if you want to support WMI-based hotkeys on Dell laptops. + + To compile this driver as a module, choose M here: the module will + be called dell-wmi. + +config DELL_WMI_AIO + tristate "WMI Hotkeys for Dell All-In-One series" + default m + depends on ACPI_WMI + depends on INPUT + select INPUT_SPARSEKMAP + help + Say Y here if you want to support WMI-based hotkeys on Dell + All-In-One machines. + + To compile this driver as a module, choose M here: the module will + be called dell-wmi-aio. + +config DELL_WMI_DESCRIPTOR + tristate + default m + depends on ACPI_WMI + +config DELL_WMI_LED + tristate "External LED on Dell Business Netbooks" + default m + depends on LEDS_CLASS + depends on ACPI_WMI + help + This adds support for the Latitude 2100 and similar + notebooks that have an external LED. + +config DELL_WMI_SYSMAN + tristate "Dell WMI-based Systems management driver" + default m + depends on ACPI_WMI + depends on DMI + select NLS + help + This driver allows changing BIOS settings on many Dell machines from + 2018 and newer without the use of any additional software. + + To compile this driver as a module, choose M here: the module will + be called dell-wmi-sysman. + +endif # X86_PLATFORM_DRIVERS_DELL diff --git a/drivers/platform/x86/dell/Makefile b/drivers/platform/x86/dell/Makefile new file mode 100644 index 000000000000..d720a3e42ae3 --- /dev/null +++ b/drivers/platform/x86/dell/Makefile @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for linux/drivers/platform/x86/dell +# Dell x86 Platform-Specific Drivers +# + +obj-$(CONFIG_ALIENWARE_WMI) += alienware-wmi.o +obj-$(CONFIG_DCDBAS) += dcdbas.o +obj-$(CONFIG_DELL_LAPTOP) += dell-laptop.o +obj-$(CONFIG_DELL_RBTN) += dell-rbtn.o +obj-$(CONFIG_DELL_RBU) += dell_rbu.o +obj-$(CONFIG_DELL_SMBIOS) += dell-smbios.o +dell-smbios-objs := dell-smbios-base.o +dell-smbios-$(CONFIG_DELL_SMBIOS_WMI) += dell-smbios-wmi.o +dell-smbios-$(CONFIG_DELL_SMBIOS_SMM) += dell-smbios-smm.o +obj-$(CONFIG_DELL_SMO8800) += dell-smo8800.o +obj-$(CONFIG_DELL_WMI) += dell-wmi.o +obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o +obj-$(CONFIG_DELL_WMI_DESCRIPTOR) += dell-wmi-descriptor.o +obj-$(CONFIG_DELL_WMI_LED) += dell-wmi-led.o +obj-$(CONFIG_DELL_WMI_SYSMAN) += dell-wmi-sysman/ diff --git a/drivers/platform/x86/dell/alienware-wmi.c b/drivers/platform/x86/dell/alienware-wmi.c new file mode 100644 index 000000000000..5bb2859c8285 --- /dev/null +++ b/drivers/platform/x86/dell/alienware-wmi.c @@ -0,0 +1,853 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Alienware AlienFX control + * + * Copyright (C) 2014 Dell Inc + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include + +#define LEGACY_CONTROL_GUID "A90597CE-A997-11DA-B012-B622A1EF5492" +#define LEGACY_POWER_CONTROL_GUID "A80593CE-A997-11DA-B012-B622A1EF5492" +#define WMAX_CONTROL_GUID "A70591CE-A997-11DA-B012-B622A1EF5492" + +#define WMAX_METHOD_HDMI_SOURCE 0x1 +#define WMAX_METHOD_HDMI_STATUS 0x2 +#define WMAX_METHOD_BRIGHTNESS 0x3 +#define WMAX_METHOD_ZONE_CONTROL 0x4 +#define WMAX_METHOD_HDMI_CABLE 0x5 +#define WMAX_METHOD_AMPLIFIER_CABLE 0x6 +#define WMAX_METHOD_DEEP_SLEEP_CONTROL 0x0B +#define WMAX_METHOD_DEEP_SLEEP_STATUS 0x0C + +MODULE_AUTHOR("Mario Limonciello "); +MODULE_DESCRIPTION("Alienware special feature control"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("wmi:" LEGACY_CONTROL_GUID); +MODULE_ALIAS("wmi:" WMAX_CONTROL_GUID); + +enum INTERFACE_FLAGS { + LEGACY, + WMAX, +}; + +enum LEGACY_CONTROL_STATES { + LEGACY_RUNNING = 1, + LEGACY_BOOTING = 0, + LEGACY_SUSPEND = 3, +}; + +enum WMAX_CONTROL_STATES { + WMAX_RUNNING = 0xFF, + WMAX_BOOTING = 0, + WMAX_SUSPEND = 3, +}; + +struct quirk_entry { + u8 num_zones; + u8 hdmi_mux; + u8 amplifier; + u8 deepslp; +}; + +static struct quirk_entry *quirks; + + +static struct quirk_entry quirk_inspiron5675 = { + .num_zones = 2, + .hdmi_mux = 0, + .amplifier = 0, + .deepslp = 0, +}; + +static struct quirk_entry quirk_unknown = { + .num_zones = 2, + .hdmi_mux = 0, + .amplifier = 0, + .deepslp = 0, +}; + +static struct quirk_entry quirk_x51_r1_r2 = { + .num_zones = 3, + .hdmi_mux = 0, + .amplifier = 0, + .deepslp = 0, +}; + +static struct quirk_entry quirk_x51_r3 = { + .num_zones = 4, + .hdmi_mux = 0, + .amplifier = 1, + .deepslp = 0, +}; + +static struct quirk_entry quirk_asm100 = { + .num_zones = 2, + .hdmi_mux = 1, + .amplifier = 0, + .deepslp = 0, +}; + +static struct quirk_entry quirk_asm200 = { + .num_zones = 2, + .hdmi_mux = 1, + .amplifier = 0, + .deepslp = 1, +}; + +static struct quirk_entry quirk_asm201 = { + .num_zones = 2, + .hdmi_mux = 1, + .amplifier = 1, + .deepslp = 1, +}; + +static int __init dmi_matched(const struct dmi_system_id *dmi) +{ + quirks = dmi->driver_data; + return 1; +} + +static const struct dmi_system_id alienware_quirks[] __initconst = { + { + .callback = dmi_matched, + .ident = "Alienware X51 R3", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R3"), + }, + .driver_data = &quirk_x51_r3, + }, + { + .callback = dmi_matched, + .ident = "Alienware X51 R2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R2"), + }, + .driver_data = &quirk_x51_r1_r2, + }, + { + .callback = dmi_matched, + .ident = "Alienware X51 R1", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51"), + }, + .driver_data = &quirk_x51_r1_r2, + }, + { + .callback = dmi_matched, + .ident = "Alienware ASM100", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "ASM100"), + }, + .driver_data = &quirk_asm100, + }, + { + .callback = dmi_matched, + .ident = "Alienware ASM200", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "ASM200"), + }, + .driver_data = &quirk_asm200, + }, + { + .callback = dmi_matched, + .ident = "Alienware ASM201", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "ASM201"), + }, + .driver_data = &quirk_asm201, + }, + { + .callback = dmi_matched, + .ident = "Dell Inc. Inspiron 5675", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5675"), + }, + .driver_data = &quirk_inspiron5675, + }, + {} +}; + +struct color_platform { + u8 blue; + u8 green; + u8 red; +} __packed; + +struct platform_zone { + u8 location; + struct device_attribute *attr; + struct color_platform colors; +}; + +struct wmax_brightness_args { + u32 led_mask; + u32 percentage; +}; + +struct wmax_basic_args { + u8 arg; +}; + +struct legacy_led_args { + struct color_platform colors; + u8 brightness; + u8 state; +} __packed; + +struct wmax_led_args { + u32 led_mask; + struct color_platform colors; + u8 state; +} __packed; + +static struct platform_device *platform_device; +static struct device_attribute *zone_dev_attrs; +static struct attribute **zone_attrs; +static struct platform_zone *zone_data; + +static struct platform_driver platform_driver = { + .driver = { + .name = "alienware-wmi", + } +}; + +static struct attribute_group zone_attribute_group = { + .name = "rgb_zones", +}; + +static u8 interface; +static u8 lighting_control_state; +static u8 global_brightness; + +/* + * Helpers used for zone control + */ +static int parse_rgb(const char *buf, struct platform_zone *zone) +{ + long unsigned int rgb; + int ret; + union color_union { + struct color_platform cp; + int package; + } repackager; + + ret = kstrtoul(buf, 16, &rgb); + if (ret) + return ret; + + /* RGB triplet notation is 24-bit hexadecimal */ + if (rgb > 0xFFFFFF) + return -EINVAL; + + repackager.package = rgb & 0x0f0f0f0f; + pr_debug("alienware-wmi: r: %d g:%d b: %d\n", + repackager.cp.red, repackager.cp.green, repackager.cp.blue); + zone->colors = repackager.cp; + return 0; +} + +static struct platform_zone *match_zone(struct device_attribute *attr) +{ + u8 zone; + + for (zone = 0; zone < quirks->num_zones; zone++) { + if ((struct device_attribute *)zone_data[zone].attr == attr) { + pr_debug("alienware-wmi: matched zone location: %d\n", + zone_data[zone].location); + return &zone_data[zone]; + } + } + return NULL; +} + +/* + * Individual RGB zone control + */ +static int alienware_update_led(struct platform_zone *zone) +{ + int method_id; + acpi_status status; + char *guid; + struct acpi_buffer input; + struct legacy_led_args legacy_args; + struct wmax_led_args wmax_basic_args; + if (interface == WMAX) { + wmax_basic_args.led_mask = 1 << zone->location; + wmax_basic_args.colors = zone->colors; + wmax_basic_args.state = lighting_control_state; + guid = WMAX_CONTROL_GUID; + method_id = WMAX_METHOD_ZONE_CONTROL; + + input.length = (acpi_size) sizeof(wmax_basic_args); + input.pointer = &wmax_basic_args; + } else { + legacy_args.colors = zone->colors; + legacy_args.brightness = global_brightness; + legacy_args.state = 0; + if (lighting_control_state == LEGACY_BOOTING || + lighting_control_state == LEGACY_SUSPEND) { + guid = LEGACY_POWER_CONTROL_GUID; + legacy_args.state = lighting_control_state; + } else + guid = LEGACY_CONTROL_GUID; + method_id = zone->location + 1; + + input.length = (acpi_size) sizeof(legacy_args); + input.pointer = &legacy_args; + } + pr_debug("alienware-wmi: guid %s method %d\n", guid, method_id); + + status = wmi_evaluate_method(guid, 0, method_id, &input, NULL); + if (ACPI_FAILURE(status)) + pr_err("alienware-wmi: zone set failure: %u\n", status); + return ACPI_FAILURE(status); +} + +static ssize_t zone_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct platform_zone *target_zone; + target_zone = match_zone(attr); + if (target_zone == NULL) + return sprintf(buf, "red: -1, green: -1, blue: -1\n"); + return sprintf(buf, "red: %d, green: %d, blue: %d\n", + target_zone->colors.red, + target_zone->colors.green, target_zone->colors.blue); + +} + +static ssize_t zone_set(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_zone *target_zone; + int ret; + target_zone = match_zone(attr); + if (target_zone == NULL) { + pr_err("alienware-wmi: invalid target zone\n"); + return 1; + } + ret = parse_rgb(buf, target_zone); + if (ret) + return ret; + ret = alienware_update_led(target_zone); + return ret ? ret : count; +} + +/* + * LED Brightness (Global) + */ +static int wmax_brightness(int brightness) +{ + acpi_status status; + struct acpi_buffer input; + struct wmax_brightness_args args = { + .led_mask = 0xFF, + .percentage = brightness, + }; + input.length = (acpi_size) sizeof(args); + input.pointer = &args; + status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0, + WMAX_METHOD_BRIGHTNESS, &input, NULL); + if (ACPI_FAILURE(status)) + pr_err("alienware-wmi: brightness set failure: %u\n", status); + return ACPI_FAILURE(status); +} + +static void global_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + int ret; + global_brightness = brightness; + if (interface == WMAX) + ret = wmax_brightness(brightness); + else + ret = alienware_update_led(&zone_data[0]); + if (ret) + pr_err("LED brightness update failed\n"); +} + +static enum led_brightness global_led_get(struct led_classdev *led_cdev) +{ + return global_brightness; +} + +static struct led_classdev global_led = { + .brightness_set = global_led_set, + .brightness_get = global_led_get, + .name = "alienware::global_brightness", +}; + +/* + * Lighting control state device attribute (Global) + */ +static ssize_t show_control_state(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (lighting_control_state == LEGACY_BOOTING) + return scnprintf(buf, PAGE_SIZE, "[booting] running suspend\n"); + else if (lighting_control_state == LEGACY_SUSPEND) + return scnprintf(buf, PAGE_SIZE, "booting running [suspend]\n"); + return scnprintf(buf, PAGE_SIZE, "booting [running] suspend\n"); +} + +static ssize_t store_control_state(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + long unsigned int val; + if (strcmp(buf, "booting\n") == 0) + val = LEGACY_BOOTING; + else if (strcmp(buf, "suspend\n") == 0) + val = LEGACY_SUSPEND; + else if (interface == LEGACY) + val = LEGACY_RUNNING; + else + val = WMAX_RUNNING; + lighting_control_state = val; + pr_debug("alienware-wmi: updated control state to %d\n", + lighting_control_state); + return count; +} + +static DEVICE_ATTR(lighting_control_state, 0644, show_control_state, + store_control_state); + +static int alienware_zone_init(struct platform_device *dev) +{ + u8 zone; + char buffer[10]; + char *name; + + if (interface == WMAX) { + lighting_control_state = WMAX_RUNNING; + } else if (interface == LEGACY) { + lighting_control_state = LEGACY_RUNNING; + } + global_led.max_brightness = 0x0F; + global_brightness = global_led.max_brightness; + + /* + * - zone_dev_attrs num_zones + 1 is for individual zones and then + * null terminated + * - zone_attrs num_zones + 2 is for all attrs in zone_dev_attrs + + * the lighting control + null terminated + * - zone_data num_zones is for the distinct zones + */ + zone_dev_attrs = + kcalloc(quirks->num_zones + 1, sizeof(struct device_attribute), + GFP_KERNEL); + if (!zone_dev_attrs) + return -ENOMEM; + + zone_attrs = + kcalloc(quirks->num_zones + 2, sizeof(struct attribute *), + GFP_KERNEL); + if (!zone_attrs) + return -ENOMEM; + + zone_data = + kcalloc(quirks->num_zones, sizeof(struct platform_zone), + GFP_KERNEL); + if (!zone_data) + return -ENOMEM; + + for (zone = 0; zone < quirks->num_zones; zone++) { + sprintf(buffer, "zone%02hhX", zone); + name = kstrdup(buffer, GFP_KERNEL); + if (name == NULL) + return 1; + sysfs_attr_init(&zone_dev_attrs[zone].attr); + zone_dev_attrs[zone].attr.name = name; + zone_dev_attrs[zone].attr.mode = 0644; + zone_dev_attrs[zone].show = zone_show; + zone_dev_attrs[zone].store = zone_set; + zone_data[zone].location = zone; + zone_attrs[zone] = &zone_dev_attrs[zone].attr; + zone_data[zone].attr = &zone_dev_attrs[zone]; + } + zone_attrs[quirks->num_zones] = &dev_attr_lighting_control_state.attr; + zone_attribute_group.attrs = zone_attrs; + + led_classdev_register(&dev->dev, &global_led); + + return sysfs_create_group(&dev->dev.kobj, &zone_attribute_group); +} + +static void alienware_zone_exit(struct platform_device *dev) +{ + u8 zone; + + sysfs_remove_group(&dev->dev.kobj, &zone_attribute_group); + led_classdev_unregister(&global_led); + if (zone_dev_attrs) { + for (zone = 0; zone < quirks->num_zones; zone++) + kfree(zone_dev_attrs[zone].attr.name); + } + kfree(zone_dev_attrs); + kfree(zone_data); + kfree(zone_attrs); +} + +static acpi_status alienware_wmax_command(struct wmax_basic_args *in_args, + u32 command, int *out_data) +{ + acpi_status status; + union acpi_object *obj; + struct acpi_buffer input; + struct acpi_buffer output; + + input.length = (acpi_size) sizeof(*in_args); + input.pointer = in_args; + if (out_data) { + output.length = ACPI_ALLOCATE_BUFFER; + output.pointer = NULL; + status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0, + command, &input, &output); + if (ACPI_SUCCESS(status)) { + obj = (union acpi_object *)output.pointer; + if (obj && obj->type == ACPI_TYPE_INTEGER) + *out_data = (u32)obj->integer.value; + } + kfree(output.pointer); + } else { + status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0, + command, &input, NULL); + } + return status; +} + +/* + * The HDMI mux sysfs node indicates the status of the HDMI input mux. + * It can toggle between standard system GPU output and HDMI input. + */ +static ssize_t show_hdmi_cable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + acpi_status status; + u32 out_data; + struct wmax_basic_args in_args = { + .arg = 0, + }; + status = + alienware_wmax_command(&in_args, WMAX_METHOD_HDMI_CABLE, + (u32 *) &out_data); + if (ACPI_SUCCESS(status)) { + if (out_data == 0) + return scnprintf(buf, PAGE_SIZE, + "[unconnected] connected unknown\n"); + else if (out_data == 1) + return scnprintf(buf, PAGE_SIZE, + "unconnected [connected] unknown\n"); + } + pr_err("alienware-wmi: unknown HDMI cable status: %d\n", status); + return scnprintf(buf, PAGE_SIZE, "unconnected connected [unknown]\n"); +} + +static ssize_t show_hdmi_source(struct device *dev, + struct device_attribute *attr, char *buf) +{ + acpi_status status; + u32 out_data; + struct wmax_basic_args in_args = { + .arg = 0, + }; + status = + alienware_wmax_command(&in_args, WMAX_METHOD_HDMI_STATUS, + (u32 *) &out_data); + + if (ACPI_SUCCESS(status)) { + if (out_data == 1) + return scnprintf(buf, PAGE_SIZE, + "[input] gpu unknown\n"); + else if (out_data == 2) + return scnprintf(buf, PAGE_SIZE, + "input [gpu] unknown\n"); + } + pr_err("alienware-wmi: unknown HDMI source status: %u\n", status); + return scnprintf(buf, PAGE_SIZE, "input gpu [unknown]\n"); +} + +static ssize_t toggle_hdmi_source(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + acpi_status status; + struct wmax_basic_args args; + if (strcmp(buf, "gpu\n") == 0) + args.arg = 1; + else if (strcmp(buf, "input\n") == 0) + args.arg = 2; + else + args.arg = 3; + pr_debug("alienware-wmi: setting hdmi to %d : %s", args.arg, buf); + + status = alienware_wmax_command(&args, WMAX_METHOD_HDMI_SOURCE, NULL); + + if (ACPI_FAILURE(status)) + pr_err("alienware-wmi: HDMI toggle failed: results: %u\n", + status); + return count; +} + +static DEVICE_ATTR(cable, S_IRUGO, show_hdmi_cable, NULL); +static DEVICE_ATTR(source, S_IRUGO | S_IWUSR, show_hdmi_source, + toggle_hdmi_source); + +static struct attribute *hdmi_attrs[] = { + &dev_attr_cable.attr, + &dev_attr_source.attr, + NULL, +}; + +static const struct attribute_group hdmi_attribute_group = { + .name = "hdmi", + .attrs = hdmi_attrs, +}; + +static void remove_hdmi(struct platform_device *dev) +{ + if (quirks->hdmi_mux > 0) + sysfs_remove_group(&dev->dev.kobj, &hdmi_attribute_group); +} + +static int create_hdmi(struct platform_device *dev) +{ + int ret; + + ret = sysfs_create_group(&dev->dev.kobj, &hdmi_attribute_group); + if (ret) + remove_hdmi(dev); + return ret; +} + +/* + * Alienware GFX amplifier support + * - Currently supports reading cable status + * - Leaving expansion room to possibly support dock/undock events later + */ +static ssize_t show_amplifier_status(struct device *dev, + struct device_attribute *attr, char *buf) +{ + acpi_status status; + u32 out_data; + struct wmax_basic_args in_args = { + .arg = 0, + }; + status = + alienware_wmax_command(&in_args, WMAX_METHOD_AMPLIFIER_CABLE, + (u32 *) &out_data); + if (ACPI_SUCCESS(status)) { + if (out_data == 0) + return scnprintf(buf, PAGE_SIZE, + "[unconnected] connected unknown\n"); + else if (out_data == 1) + return scnprintf(buf, PAGE_SIZE, + "unconnected [connected] unknown\n"); + } + pr_err("alienware-wmi: unknown amplifier cable status: %d\n", status); + return scnprintf(buf, PAGE_SIZE, "unconnected connected [unknown]\n"); +} + +static DEVICE_ATTR(status, S_IRUGO, show_amplifier_status, NULL); + +static struct attribute *amplifier_attrs[] = { + &dev_attr_status.attr, + NULL, +}; + +static const struct attribute_group amplifier_attribute_group = { + .name = "amplifier", + .attrs = amplifier_attrs, +}; + +static void remove_amplifier(struct platform_device *dev) +{ + if (quirks->amplifier > 0) + sysfs_remove_group(&dev->dev.kobj, &lifier_attribute_group); +} + +static int create_amplifier(struct platform_device *dev) +{ + int ret; + + ret = sysfs_create_group(&dev->dev.kobj, &lifier_attribute_group); + if (ret) + remove_amplifier(dev); + return ret; +} + +/* + * Deep Sleep Control support + * - Modifies BIOS setting for deep sleep control allowing extra wakeup events + */ +static ssize_t show_deepsleep_status(struct device *dev, + struct device_attribute *attr, char *buf) +{ + acpi_status status; + u32 out_data; + struct wmax_basic_args in_args = { + .arg = 0, + }; + status = alienware_wmax_command(&in_args, WMAX_METHOD_DEEP_SLEEP_STATUS, + (u32 *) &out_data); + if (ACPI_SUCCESS(status)) { + if (out_data == 0) + return scnprintf(buf, PAGE_SIZE, + "[disabled] s5 s5_s4\n"); + else if (out_data == 1) + return scnprintf(buf, PAGE_SIZE, + "disabled [s5] s5_s4\n"); + else if (out_data == 2) + return scnprintf(buf, PAGE_SIZE, + "disabled s5 [s5_s4]\n"); + } + pr_err("alienware-wmi: unknown deep sleep status: %d\n", status); + return scnprintf(buf, PAGE_SIZE, "disabled s5 s5_s4 [unknown]\n"); +} + +static ssize_t toggle_deepsleep(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + acpi_status status; + struct wmax_basic_args args; + + if (strcmp(buf, "disabled\n") == 0) + args.arg = 0; + else if (strcmp(buf, "s5\n") == 0) + args.arg = 1; + else + args.arg = 2; + pr_debug("alienware-wmi: setting deep sleep to %d : %s", args.arg, buf); + + status = alienware_wmax_command(&args, WMAX_METHOD_DEEP_SLEEP_CONTROL, + NULL); + + if (ACPI_FAILURE(status)) + pr_err("alienware-wmi: deep sleep control failed: results: %u\n", + status); + return count; +} + +static DEVICE_ATTR(deepsleep, S_IRUGO | S_IWUSR, show_deepsleep_status, toggle_deepsleep); + +static struct attribute *deepsleep_attrs[] = { + &dev_attr_deepsleep.attr, + NULL, +}; + +static const struct attribute_group deepsleep_attribute_group = { + .name = "deepsleep", + .attrs = deepsleep_attrs, +}; + +static void remove_deepsleep(struct platform_device *dev) +{ + if (quirks->deepslp > 0) + sysfs_remove_group(&dev->dev.kobj, &deepsleep_attribute_group); +} + +static int create_deepsleep(struct platform_device *dev) +{ + int ret; + + ret = sysfs_create_group(&dev->dev.kobj, &deepsleep_attribute_group); + if (ret) + remove_deepsleep(dev); + return ret; +} + +static int __init alienware_wmi_init(void) +{ + int ret; + + if (wmi_has_guid(LEGACY_CONTROL_GUID)) + interface = LEGACY; + else if (wmi_has_guid(WMAX_CONTROL_GUID)) + interface = WMAX; + else { + pr_warn("alienware-wmi: No known WMI GUID found\n"); + return -ENODEV; + } + + dmi_check_system(alienware_quirks); + if (quirks == NULL) + quirks = &quirk_unknown; + + ret = platform_driver_register(&platform_driver); + if (ret) + goto fail_platform_driver; + platform_device = platform_device_alloc("alienware-wmi", -1); + if (!platform_device) { + ret = -ENOMEM; + goto fail_platform_device1; + } + ret = platform_device_add(platform_device); + if (ret) + goto fail_platform_device2; + + if (quirks->hdmi_mux > 0) { + ret = create_hdmi(platform_device); + if (ret) + goto fail_prep_hdmi; + } + + if (quirks->amplifier > 0) { + ret = create_amplifier(platform_device); + if (ret) + goto fail_prep_amplifier; + } + + if (quirks->deepslp > 0) { + ret = create_deepsleep(platform_device); + if (ret) + goto fail_prep_deepsleep; + } + + ret = alienware_zone_init(platform_device); + if (ret) + goto fail_prep_zones; + + return 0; + +fail_prep_zones: + alienware_zone_exit(platform_device); +fail_prep_deepsleep: +fail_prep_amplifier: +fail_prep_hdmi: + platform_device_del(platform_device); +fail_platform_device2: + platform_device_put(platform_device); +fail_platform_device1: + platform_driver_unregister(&platform_driver); +fail_platform_driver: + return ret; +} + +module_init(alienware_wmi_init); + +static void __exit alienware_wmi_exit(void) +{ + if (platform_device) { + alienware_zone_exit(platform_device); + remove_hdmi(platform_device); + platform_device_unregister(platform_device); + platform_driver_unregister(&platform_driver); + } +} + +module_exit(alienware_wmi_exit); diff --git a/drivers/platform/x86/dell/dcdbas.c b/drivers/platform/x86/dell/dcdbas.c new file mode 100644 index 000000000000..d513a59a5d47 --- /dev/null +++ b/drivers/platform/x86/dell/dcdbas.c @@ -0,0 +1,770 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * dcdbas.c: Dell Systems Management Base Driver + * + * The Dell Systems Management Base Driver provides a sysfs interface for + * systems management software to perform System Management Interrupts (SMIs) + * and Host Control Actions (power cycle or power off after OS shutdown) on + * Dell systems. + * + * See Documentation/driver-api/dcdbas.rst for more information. + * + * Copyright (C) 1995-2006 Dell Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dcdbas.h" + +#define DRIVER_NAME "dcdbas" +#define DRIVER_VERSION "5.6.0-3.4" +#define DRIVER_DESCRIPTION "Dell Systems Management Base Driver" + +static struct platform_device *dcdbas_pdev; + +static u8 *smi_data_buf; +static dma_addr_t smi_data_buf_handle; +static unsigned long smi_data_buf_size; +static unsigned long max_smi_data_buf_size = MAX_SMI_DATA_BUF_SIZE; +static u32 smi_data_buf_phys_addr; +static DEFINE_MUTEX(smi_data_lock); +static u8 *bios_buffer; + +static unsigned int host_control_action; +static unsigned int host_control_smi_type; +static unsigned int host_control_on_shutdown; + +static bool wsmt_enabled; + +/** + * smi_data_buf_free: free SMI data buffer + */ +static void smi_data_buf_free(void) +{ + if (!smi_data_buf || wsmt_enabled) + return; + + dev_dbg(&dcdbas_pdev->dev, "%s: phys: %x size: %lu\n", + __func__, smi_data_buf_phys_addr, smi_data_buf_size); + + dma_free_coherent(&dcdbas_pdev->dev, smi_data_buf_size, smi_data_buf, + smi_data_buf_handle); + smi_data_buf = NULL; + smi_data_buf_handle = 0; + smi_data_buf_phys_addr = 0; + smi_data_buf_size = 0; +} + +/** + * smi_data_buf_realloc: grow SMI data buffer if needed + */ +static int smi_data_buf_realloc(unsigned long size) +{ + void *buf; + dma_addr_t handle; + + if (smi_data_buf_size >= size) + return 0; + + if (size > max_smi_data_buf_size) + return -EINVAL; + + /* new buffer is needed */ + buf = dma_alloc_coherent(&dcdbas_pdev->dev, size, &handle, GFP_KERNEL); + if (!buf) { + dev_dbg(&dcdbas_pdev->dev, + "%s: failed to allocate memory size %lu\n", + __func__, size); + return -ENOMEM; + } + /* memory zeroed by dma_alloc_coherent */ + + if (smi_data_buf) + memcpy(buf, smi_data_buf, smi_data_buf_size); + + /* free any existing buffer */ + smi_data_buf_free(); + + /* set up new buffer for use */ + smi_data_buf = buf; + smi_data_buf_handle = handle; + smi_data_buf_phys_addr = (u32) virt_to_phys(buf); + smi_data_buf_size = size; + + dev_dbg(&dcdbas_pdev->dev, "%s: phys: %x size: %lu\n", + __func__, smi_data_buf_phys_addr, smi_data_buf_size); + + return 0; +} + +static ssize_t smi_data_buf_phys_addr_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%x\n", smi_data_buf_phys_addr); +} + +static ssize_t smi_data_buf_size_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%lu\n", smi_data_buf_size); +} + +static ssize_t smi_data_buf_size_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long buf_size; + ssize_t ret; + + buf_size = simple_strtoul(buf, NULL, 10); + + /* make sure SMI data buffer is at least buf_size */ + mutex_lock(&smi_data_lock); + ret = smi_data_buf_realloc(buf_size); + mutex_unlock(&smi_data_lock); + if (ret) + return ret; + + return count; +} + +static ssize_t smi_data_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t pos, size_t count) +{ + ssize_t ret; + + mutex_lock(&smi_data_lock); + ret = memory_read_from_buffer(buf, count, &pos, smi_data_buf, + smi_data_buf_size); + mutex_unlock(&smi_data_lock); + return ret; +} + +static ssize_t smi_data_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t pos, size_t count) +{ + ssize_t ret; + + if ((pos + count) > max_smi_data_buf_size) + return -EINVAL; + + mutex_lock(&smi_data_lock); + + ret = smi_data_buf_realloc(pos + count); + if (ret) + goto out; + + memcpy(smi_data_buf + pos, buf, count); + ret = count; +out: + mutex_unlock(&smi_data_lock); + return ret; +} + +static ssize_t host_control_action_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%u\n", host_control_action); +} + +static ssize_t host_control_action_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + ssize_t ret; + + /* make sure buffer is available for host control command */ + mutex_lock(&smi_data_lock); + ret = smi_data_buf_realloc(sizeof(struct apm_cmd)); + mutex_unlock(&smi_data_lock); + if (ret) + return ret; + + host_control_action = simple_strtoul(buf, NULL, 10); + return count; +} + +static ssize_t host_control_smi_type_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%u\n", host_control_smi_type); +} + +static ssize_t host_control_smi_type_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + host_control_smi_type = simple_strtoul(buf, NULL, 10); + return count; +} + +static ssize_t host_control_on_shutdown_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%u\n", host_control_on_shutdown); +} + +static ssize_t host_control_on_shutdown_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + host_control_on_shutdown = simple_strtoul(buf, NULL, 10); + return count; +} + +static int raise_smi(void *par) +{ + struct smi_cmd *smi_cmd = par; + + if (smp_processor_id() != 0) { + dev_dbg(&dcdbas_pdev->dev, "%s: failed to get CPU 0\n", + __func__); + return -EBUSY; + } + + /* generate SMI */ + /* inb to force posted write through and make SMI happen now */ + asm volatile ( + "outb %b0,%w1\n" + "inb %w1" + : /* no output args */ + : "a" (smi_cmd->command_code), + "d" (smi_cmd->command_address), + "b" (smi_cmd->ebx), + "c" (smi_cmd->ecx) + : "memory" + ); + + return 0; +} +/** + * dcdbas_smi_request: generate SMI request + * + * Called with smi_data_lock. + */ +int dcdbas_smi_request(struct smi_cmd *smi_cmd) +{ + int ret; + + if (smi_cmd->magic != SMI_CMD_MAGIC) { + dev_info(&dcdbas_pdev->dev, "%s: invalid magic value\n", + __func__); + return -EBADR; + } + + /* SMI requires CPU 0 */ + get_online_cpus(); + ret = smp_call_on_cpu(0, raise_smi, smi_cmd, true); + put_online_cpus(); + + return ret; +} + +/** + * smi_request_store: + * + * The valid values are: + * 0: zero SMI data buffer + * 1: generate calling interface SMI + * 2: generate raw SMI + * + * User application writes smi_cmd to smi_data before telling driver + * to generate SMI. + */ +static ssize_t smi_request_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct smi_cmd *smi_cmd; + unsigned long val = simple_strtoul(buf, NULL, 10); + ssize_t ret; + + mutex_lock(&smi_data_lock); + + if (smi_data_buf_size < sizeof(struct smi_cmd)) { + ret = -ENODEV; + goto out; + } + smi_cmd = (struct smi_cmd *)smi_data_buf; + + switch (val) { + case 2: + /* Raw SMI */ + ret = dcdbas_smi_request(smi_cmd); + if (!ret) + ret = count; + break; + case 1: + /* + * Calling Interface SMI + * + * Provide physical address of command buffer field within + * the struct smi_cmd to BIOS. + * + * Because the address that smi_cmd (smi_data_buf) points to + * will be from memremap() of a non-memory address if WSMT + * is present, we can't use virt_to_phys() on smi_cmd, so + * we have to use the physical address that was saved when + * the virtual address for smi_cmd was received. + */ + smi_cmd->ebx = smi_data_buf_phys_addr + + offsetof(struct smi_cmd, command_buffer); + ret = dcdbas_smi_request(smi_cmd); + if (!ret) + ret = count; + break; + case 0: + memset(smi_data_buf, 0, smi_data_buf_size); + ret = count; + break; + default: + ret = -EINVAL; + break; + } + +out: + mutex_unlock(&smi_data_lock); + return ret; +} +EXPORT_SYMBOL(dcdbas_smi_request); + +/** + * host_control_smi: generate host control SMI + * + * Caller must set up the host control command in smi_data_buf. + */ +static int host_control_smi(void) +{ + struct apm_cmd *apm_cmd; + u8 *data; + unsigned long flags; + u32 num_ticks; + s8 cmd_status; + u8 index; + + apm_cmd = (struct apm_cmd *)smi_data_buf; + apm_cmd->status = ESM_STATUS_CMD_UNSUCCESSFUL; + + switch (host_control_smi_type) { + case HC_SMITYPE_TYPE1: + spin_lock_irqsave(&rtc_lock, flags); + /* write SMI data buffer physical address */ + data = (u8 *)&smi_data_buf_phys_addr; + for (index = PE1300_CMOS_CMD_STRUCT_PTR; + index < (PE1300_CMOS_CMD_STRUCT_PTR + 4); + index++, data++) { + outb(index, + (CMOS_BASE_PORT + CMOS_PAGE2_INDEX_PORT_PIIX4)); + outb(*data, + (CMOS_BASE_PORT + CMOS_PAGE2_DATA_PORT_PIIX4)); + } + + /* first set status to -1 as called by spec */ + cmd_status = ESM_STATUS_CMD_UNSUCCESSFUL; + outb((u8) cmd_status, PCAT_APM_STATUS_PORT); + + /* generate SMM call */ + outb(ESM_APM_CMD, PCAT_APM_CONTROL_PORT); + spin_unlock_irqrestore(&rtc_lock, flags); + + /* wait a few to see if it executed */ + num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING; + while ((cmd_status = inb(PCAT_APM_STATUS_PORT)) + == ESM_STATUS_CMD_UNSUCCESSFUL) { + num_ticks--; + if (num_ticks == EXPIRED_TIMER) + return -ETIME; + } + break; + + case HC_SMITYPE_TYPE2: + case HC_SMITYPE_TYPE3: + spin_lock_irqsave(&rtc_lock, flags); + /* write SMI data buffer physical address */ + data = (u8 *)&smi_data_buf_phys_addr; + for (index = PE1400_CMOS_CMD_STRUCT_PTR; + index < (PE1400_CMOS_CMD_STRUCT_PTR + 4); + index++, data++) { + outb(index, (CMOS_BASE_PORT + CMOS_PAGE1_INDEX_PORT)); + outb(*data, (CMOS_BASE_PORT + CMOS_PAGE1_DATA_PORT)); + } + + /* generate SMM call */ + if (host_control_smi_type == HC_SMITYPE_TYPE3) + outb(ESM_APM_CMD, PCAT_APM_CONTROL_PORT); + else + outb(ESM_APM_CMD, PE1400_APM_CONTROL_PORT); + + /* restore RTC index pointer since it was written to above */ + CMOS_READ(RTC_REG_C); + spin_unlock_irqrestore(&rtc_lock, flags); + + /* read control port back to serialize write */ + cmd_status = inb(PE1400_APM_CONTROL_PORT); + + /* wait a few to see if it executed */ + num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING; + while (apm_cmd->status == ESM_STATUS_CMD_UNSUCCESSFUL) { + num_ticks--; + if (num_ticks == EXPIRED_TIMER) + return -ETIME; + } + break; + + default: + dev_dbg(&dcdbas_pdev->dev, "%s: invalid SMI type %u\n", + __func__, host_control_smi_type); + return -ENOSYS; + } + + return 0; +} + +/** + * dcdbas_host_control: initiate host control + * + * This function is called by the driver after the system has + * finished shutting down if the user application specified a + * host control action to perform on shutdown. It is safe to + * use smi_data_buf at this point because the system has finished + * shutting down and no userspace apps are running. + */ +static void dcdbas_host_control(void) +{ + struct apm_cmd *apm_cmd; + u8 action; + + if (host_control_action == HC_ACTION_NONE) + return; + + action = host_control_action; + host_control_action = HC_ACTION_NONE; + + if (!smi_data_buf) { + dev_dbg(&dcdbas_pdev->dev, "%s: no SMI buffer\n", __func__); + return; + } + + if (smi_data_buf_size < sizeof(struct apm_cmd)) { + dev_dbg(&dcdbas_pdev->dev, "%s: SMI buffer too small\n", + __func__); + return; + } + + apm_cmd = (struct apm_cmd *)smi_data_buf; + + /* power off takes precedence */ + if (action & HC_ACTION_HOST_CONTROL_POWEROFF) { + apm_cmd->command = ESM_APM_POWER_CYCLE; + apm_cmd->reserved = 0; + *((s16 *)&apm_cmd->parameters.shortreq.parm[0]) = (s16) 0; + host_control_smi(); + } else if (action & HC_ACTION_HOST_CONTROL_POWERCYCLE) { + apm_cmd->command = ESM_APM_POWER_CYCLE; + apm_cmd->reserved = 0; + *((s16 *)&apm_cmd->parameters.shortreq.parm[0]) = (s16) 20; + host_control_smi(); + } +} + +/* WSMT */ + +static u8 checksum(u8 *buffer, u8 length) +{ + u8 sum = 0; + u8 *end = buffer + length; + + while (buffer < end) + sum += *buffer++; + return sum; +} + +static inline struct smm_eps_table *check_eps_table(u8 *addr) +{ + struct smm_eps_table *eps = (struct smm_eps_table *)addr; + + if (strncmp(eps->smm_comm_buff_anchor, SMM_EPS_SIG, 4) != 0) + return NULL; + + if (checksum(addr, eps->length) != 0) + return NULL; + + return eps; +} + +static int dcdbas_check_wsmt(void) +{ + const struct dmi_device *dev = NULL; + struct acpi_table_wsmt *wsmt = NULL; + struct smm_eps_table *eps = NULL; + u64 bios_buf_paddr; + u64 remap_size; + u8 *addr; + + acpi_get_table(ACPI_SIG_WSMT, 0, (struct acpi_table_header **)&wsmt); + if (!wsmt) + return 0; + + /* Check if WSMT ACPI table shows that protection is enabled */ + if (!(wsmt->protection_flags & ACPI_WSMT_FIXED_COMM_BUFFERS) || + !(wsmt->protection_flags & ACPI_WSMT_COMM_BUFFER_NESTED_PTR_PROTECTION)) + return 0; + + /* + * BIOS could provide the address/size of the protected buffer + * in an SMBIOS string or in an EPS structure in 0xFxxxx. + */ + + /* Check SMBIOS for buffer address */ + while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, NULL, dev))) + if (sscanf(dev->name, "30[%16llx;%8llx]", &bios_buf_paddr, + &remap_size) == 2) + goto remap; + + /* Scan for EPS (entry point structure) */ + for (addr = (u8 *)__va(0xf0000); + addr < (u8 *)__va(0x100000 - sizeof(struct smm_eps_table)); + addr += 16) { + eps = check_eps_table(addr); + if (eps) + break; + } + + if (!eps) { + dev_dbg(&dcdbas_pdev->dev, "found WSMT, but no firmware buffer found\n"); + return -ENODEV; + } + bios_buf_paddr = eps->smm_comm_buff_addr; + remap_size = eps->num_of_4k_pages * PAGE_SIZE; + +remap: + /* + * Get physical address of buffer and map to virtual address. + * Table gives size in 4K pages, regardless of actual system page size. + */ + if (upper_32_bits(bios_buf_paddr + 8)) { + dev_warn(&dcdbas_pdev->dev, "found WSMT, but buffer address is above 4GB\n"); + return -EINVAL; + } + /* + * Limit remap size to MAX_SMI_DATA_BUF_SIZE + 8 (since the first 8 + * bytes are used for a semaphore, not the data buffer itself). + */ + if (remap_size > MAX_SMI_DATA_BUF_SIZE + 8) + remap_size = MAX_SMI_DATA_BUF_SIZE + 8; + + bios_buffer = memremap(bios_buf_paddr, remap_size, MEMREMAP_WB); + if (!bios_buffer) { + dev_warn(&dcdbas_pdev->dev, "found WSMT, but failed to map buffer\n"); + return -ENOMEM; + } + + /* First 8 bytes is for a semaphore, not part of the smi_data_buf */ + smi_data_buf_phys_addr = bios_buf_paddr + 8; + smi_data_buf = bios_buffer + 8; + smi_data_buf_size = remap_size - 8; + max_smi_data_buf_size = smi_data_buf_size; + wsmt_enabled = true; + dev_info(&dcdbas_pdev->dev, + "WSMT found, using firmware-provided SMI buffer.\n"); + return 1; +} + +/** + * dcdbas_reboot_notify: handle reboot notification for host control + */ +static int dcdbas_reboot_notify(struct notifier_block *nb, unsigned long code, + void *unused) +{ + switch (code) { + case SYS_DOWN: + case SYS_HALT: + case SYS_POWER_OFF: + if (host_control_on_shutdown) { + /* firmware is going to perform host control action */ + printk(KERN_WARNING "Please wait for shutdown " + "action to complete...\n"); + dcdbas_host_control(); + } + break; + } + + return NOTIFY_DONE; +} + +static struct notifier_block dcdbas_reboot_nb = { + .notifier_call = dcdbas_reboot_notify, + .next = NULL, + .priority = INT_MIN +}; + +static DCDBAS_BIN_ATTR_RW(smi_data); + +static struct bin_attribute *dcdbas_bin_attrs[] = { + &bin_attr_smi_data, + NULL +}; + +static DCDBAS_DEV_ATTR_RW(smi_data_buf_size); +static DCDBAS_DEV_ATTR_RO(smi_data_buf_phys_addr); +static DCDBAS_DEV_ATTR_WO(smi_request); +static DCDBAS_DEV_ATTR_RW(host_control_action); +static DCDBAS_DEV_ATTR_RW(host_control_smi_type); +static DCDBAS_DEV_ATTR_RW(host_control_on_shutdown); + +static struct attribute *dcdbas_dev_attrs[] = { + &dev_attr_smi_data_buf_size.attr, + &dev_attr_smi_data_buf_phys_addr.attr, + &dev_attr_smi_request.attr, + &dev_attr_host_control_action.attr, + &dev_attr_host_control_smi_type.attr, + &dev_attr_host_control_on_shutdown.attr, + NULL +}; + +static const struct attribute_group dcdbas_attr_group = { + .attrs = dcdbas_dev_attrs, + .bin_attrs = dcdbas_bin_attrs, +}; + +static int dcdbas_probe(struct platform_device *dev) +{ + int error; + + host_control_action = HC_ACTION_NONE; + host_control_smi_type = HC_SMITYPE_NONE; + + dcdbas_pdev = dev; + + /* Check if ACPI WSMT table specifies protected SMI buffer address */ + error = dcdbas_check_wsmt(); + if (error < 0) + return error; + + /* + * BIOS SMI calls require buffer addresses be in 32-bit address space. + * This is done by setting the DMA mask below. + */ + error = dma_set_coherent_mask(&dcdbas_pdev->dev, DMA_BIT_MASK(32)); + if (error) + return error; + + error = sysfs_create_group(&dev->dev.kobj, &dcdbas_attr_group); + if (error) + return error; + + register_reboot_notifier(&dcdbas_reboot_nb); + + dev_info(&dev->dev, "%s (version %s)\n", + DRIVER_DESCRIPTION, DRIVER_VERSION); + + return 0; +} + +static int dcdbas_remove(struct platform_device *dev) +{ + unregister_reboot_notifier(&dcdbas_reboot_nb); + sysfs_remove_group(&dev->dev.kobj, &dcdbas_attr_group); + + return 0; +} + +static struct platform_driver dcdbas_driver = { + .driver = { + .name = DRIVER_NAME, + }, + .probe = dcdbas_probe, + .remove = dcdbas_remove, +}; + +static const struct platform_device_info dcdbas_dev_info __initconst = { + .name = DRIVER_NAME, + .id = -1, + .dma_mask = DMA_BIT_MASK(32), +}; + +static struct platform_device *dcdbas_pdev_reg; + +/** + * dcdbas_init: initialize driver + */ +static int __init dcdbas_init(void) +{ + int error; + + error = platform_driver_register(&dcdbas_driver); + if (error) + return error; + + dcdbas_pdev_reg = platform_device_register_full(&dcdbas_dev_info); + if (IS_ERR(dcdbas_pdev_reg)) { + error = PTR_ERR(dcdbas_pdev_reg); + goto err_unregister_driver; + } + + return 0; + + err_unregister_driver: + platform_driver_unregister(&dcdbas_driver); + return error; +} + +/** + * dcdbas_exit: perform driver cleanup + */ +static void __exit dcdbas_exit(void) +{ + /* + * make sure functions that use dcdbas_pdev are called + * before platform_device_unregister + */ + unregister_reboot_notifier(&dcdbas_reboot_nb); + + /* + * We have to free the buffer here instead of dcdbas_remove + * because only in module exit function we can be sure that + * all sysfs attributes belonging to this module have been + * released. + */ + if (dcdbas_pdev) + smi_data_buf_free(); + if (bios_buffer) + memunmap(bios_buffer); + platform_device_unregister(dcdbas_pdev_reg); + platform_driver_unregister(&dcdbas_driver); +} + +subsys_initcall_sync(dcdbas_init); +module_exit(dcdbas_exit); + +MODULE_DESCRIPTION(DRIVER_DESCRIPTION " (version " DRIVER_VERSION ")"); +MODULE_VERSION(DRIVER_VERSION); +MODULE_AUTHOR("Dell Inc."); +MODULE_LICENSE("GPL"); +/* Any System or BIOS claiming to be by Dell */ +MODULE_ALIAS("dmi:*:[bs]vnD[Ee][Ll][Ll]*:*"); diff --git a/drivers/platform/x86/dell/dcdbas.h b/drivers/platform/x86/dell/dcdbas.h new file mode 100644 index 000000000000..c3cca5433525 --- /dev/null +++ b/drivers/platform/x86/dell/dcdbas.h @@ -0,0 +1,109 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * dcdbas.h: Definitions for Dell Systems Management Base driver + * + * Copyright (C) 1995-2005 Dell Inc. + */ + +#ifndef _DCDBAS_H_ +#define _DCDBAS_H_ + +#include +#include +#include + +#define MAX_SMI_DATA_BUF_SIZE (256 * 1024) + +#define HC_ACTION_NONE (0) +#define HC_ACTION_HOST_CONTROL_POWEROFF BIT(1) +#define HC_ACTION_HOST_CONTROL_POWERCYCLE BIT(2) + +#define HC_SMITYPE_NONE (0) +#define HC_SMITYPE_TYPE1 (1) +#define HC_SMITYPE_TYPE2 (2) +#define HC_SMITYPE_TYPE3 (3) + +#define ESM_APM_CMD (0x0A0) +#define ESM_APM_POWER_CYCLE (0x10) +#define ESM_STATUS_CMD_UNSUCCESSFUL (-1) + +#define CMOS_BASE_PORT (0x070) +#define CMOS_PAGE1_INDEX_PORT (0) +#define CMOS_PAGE1_DATA_PORT (1) +#define CMOS_PAGE2_INDEX_PORT_PIIX4 (2) +#define CMOS_PAGE2_DATA_PORT_PIIX4 (3) +#define PE1400_APM_CONTROL_PORT (0x0B0) +#define PCAT_APM_CONTROL_PORT (0x0B2) +#define PCAT_APM_STATUS_PORT (0x0B3) +#define PE1300_CMOS_CMD_STRUCT_PTR (0x38) +#define PE1400_CMOS_CMD_STRUCT_PTR (0x70) + +#define MAX_SYSMGMT_SHORTCMD_PARMBUF_LEN (14) +#define MAX_SYSMGMT_LONGCMD_SGENTRY_NUM (16) + +#define TIMEOUT_USEC_SHORT_SEMA_BLOCKING (10000) +#define EXPIRED_TIMER (0) + +#define SMI_CMD_MAGIC (0x534D4931) +#define SMM_EPS_SIG "$SCB" + +#define DCDBAS_DEV_ATTR_RW(_name) \ + DEVICE_ATTR(_name,0600,_name##_show,_name##_store); + +#define DCDBAS_DEV_ATTR_RO(_name) \ + DEVICE_ATTR(_name,0400,_name##_show,NULL); + +#define DCDBAS_DEV_ATTR_WO(_name) \ + DEVICE_ATTR(_name,0200,NULL,_name##_store); + +#define DCDBAS_BIN_ATTR_RW(_name) \ +struct bin_attribute bin_attr_##_name = { \ + .attr = { .name = __stringify(_name), \ + .mode = 0600 }, \ + .read = _name##_read, \ + .write = _name##_write, \ +} + +struct smi_cmd { + __u32 magic; + __u32 ebx; + __u32 ecx; + __u16 command_address; + __u8 command_code; + __u8 reserved; + __u8 command_buffer[1]; +} __attribute__ ((packed)); + +struct apm_cmd { + __u8 command; + __s8 status; + __u16 reserved; + union { + struct { + __u8 parm[MAX_SYSMGMT_SHORTCMD_PARMBUF_LEN]; + } __attribute__ ((packed)) shortreq; + + struct { + __u16 num_sg_entries; + struct { + __u32 size; + __u64 addr; + } __attribute__ ((packed)) + sglist[MAX_SYSMGMT_LONGCMD_SGENTRY_NUM]; + } __attribute__ ((packed)) longreq; + } __attribute__ ((packed)) parameters; +} __attribute__ ((packed)); + +int dcdbas_smi_request(struct smi_cmd *smi_cmd); + +struct smm_eps_table { + char smm_comm_buff_anchor[4]; + u8 length; + u8 checksum; + u8 version; + u64 smm_comm_buff_addr; + u64 num_of_4k_pages; +} __packed; + +#endif /* _DCDBAS_H_ */ + diff --git a/drivers/platform/x86/dell/dell-laptop.c b/drivers/platform/x86/dell/dell-laptop.c new file mode 100644 index 000000000000..70edc5bb3a14 --- /dev/null +++ b/drivers/platform/x86/dell/dell-laptop.c @@ -0,0 +1,2303 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for Dell laptop extras + * + * Copyright (c) Red Hat + * Copyright (c) 2014 Gabriele Mazzotta + * Copyright (c) 2014 Pali Rohár + * + * Based on documentation in the libsmbios package: + * Copyright (C) 2005-2014 Dell Inc. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dell-rbtn.h" +#include "dell-smbios.h" + +struct quirk_entry { + bool touchpad_led; + bool kbd_led_not_present; + bool kbd_led_levels_off_1; + bool kbd_missing_ac_tag; + + bool needs_kbd_timeouts; + /* + * Ordered list of timeouts expressed in seconds. + * The list must end with -1 + */ + int kbd_timeouts[]; +}; + +static struct quirk_entry *quirks; + +static struct quirk_entry quirk_dell_vostro_v130 = { + .touchpad_led = true, +}; + +static int __init dmi_matched(const struct dmi_system_id *dmi) +{ + quirks = dmi->driver_data; + return 1; +} + +/* + * These values come from Windows utility provided by Dell. If any other value + * is used then BIOS silently set timeout to 0 without any error message. + */ +static struct quirk_entry quirk_dell_xps13_9333 = { + .needs_kbd_timeouts = true, + .kbd_timeouts = { 0, 5, 15, 60, 5 * 60, 15 * 60, -1 }, +}; + +static struct quirk_entry quirk_dell_xps13_9370 = { + .kbd_missing_ac_tag = true, +}; + +static struct quirk_entry quirk_dell_latitude_e6410 = { + .kbd_led_levels_off_1 = true, +}; + +static struct quirk_entry quirk_dell_inspiron_1012 = { + .kbd_led_not_present = true, +}; + +static struct platform_driver platform_driver = { + .driver = { + .name = "dell-laptop", + } +}; + +static struct platform_device *platform_device; +static struct backlight_device *dell_backlight_device; +static struct rfkill *wifi_rfkill; +static struct rfkill *bluetooth_rfkill; +static struct rfkill *wwan_rfkill; +static bool force_rfkill; + +module_param(force_rfkill, bool, 0444); +MODULE_PARM_DESC(force_rfkill, "enable rfkill on non whitelisted models"); + +static const struct dmi_system_id dell_device_table[] __initconst = { + { + .ident = "Dell laptop", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "8"), + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /*Laptop*/ + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "10"), /*Notebook*/ + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "30"), /*Tablet*/ + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "31"), /*Convertible*/ + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "32"), /*Detachable*/ + }, + }, + { + .ident = "Dell Computer Corporation", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"), + DMI_MATCH(DMI_CHASSIS_TYPE, "8"), + }, + }, + { } +}; +MODULE_DEVICE_TABLE(dmi, dell_device_table); + +static const struct dmi_system_id dell_quirks[] __initconst = { + { + .callback = dmi_matched, + .ident = "Dell Vostro V130", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Vostro V130"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Vostro V131", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Vostro V131"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Vostro 3350", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3350"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Vostro 3555", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3555"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron N311z", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron N311z"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron M5110", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron M5110"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Vostro 3360", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3360"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Vostro 3460", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3460"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Vostro 3560", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3560"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Vostro 3450", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Dell System Vostro 3450"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron 5420", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5420"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron 5520", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5520"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron 5720", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5720"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron 7420", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7420"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron 7520", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7520"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron 7720", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7720"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell XPS13 9333", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "XPS13 9333"), + }, + .driver_data = &quirk_dell_xps13_9333, + }, + { + .callback = dmi_matched, + .ident = "Dell XPS 13 9370", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "XPS 13 9370"), + }, + .driver_data = &quirk_dell_xps13_9370, + }, + { + .callback = dmi_matched, + .ident = "Dell Latitude E6410", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Latitude E6410"), + }, + .driver_data = &quirk_dell_latitude_e6410, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron 1012", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1012"), + }, + .driver_data = &quirk_dell_inspiron_1012, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron 1018", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1018"), + }, + .driver_data = &quirk_dell_inspiron_1012, + }, + { } +}; + +static void dell_fill_request(struct calling_interface_buffer *buffer, + u32 arg0, u32 arg1, u32 arg2, u32 arg3) +{ + memset(buffer, 0, sizeof(struct calling_interface_buffer)); + buffer->input[0] = arg0; + buffer->input[1] = arg1; + buffer->input[2] = arg2; + buffer->input[3] = arg3; +} + +static int dell_send_request(struct calling_interface_buffer *buffer, + u16 class, u16 select) +{ + int ret; + + buffer->cmd_class = class; + buffer->cmd_select = select; + ret = dell_smbios_call(buffer); + if (ret != 0) + return ret; + return dell_smbios_error(buffer->output[0]); +} + +/* + * Derived from information in smbios-wireless-ctl: + * + * cbSelect 17, Value 11 + * + * Return Wireless Info + * cbArg1, byte0 = 0x00 + * + * cbRes1 Standard return codes (0, -1, -2) + * cbRes2 Info bit flags: + * + * 0 Hardware switch supported (1) + * 1 WiFi locator supported (1) + * 2 WLAN supported (1) + * 3 Bluetooth (BT) supported (1) + * 4 WWAN supported (1) + * 5 Wireless KBD supported (1) + * 6 Uw b supported (1) + * 7 WiGig supported (1) + * 8 WLAN installed (1) + * 9 BT installed (1) + * 10 WWAN installed (1) + * 11 Uw b installed (1) + * 12 WiGig installed (1) + * 13-15 Reserved (0) + * 16 Hardware (HW) switch is On (1) + * 17 WLAN disabled (1) + * 18 BT disabled (1) + * 19 WWAN disabled (1) + * 20 Uw b disabled (1) + * 21 WiGig disabled (1) + * 20-31 Reserved (0) + * + * cbRes3 NVRAM size in bytes + * cbRes4, byte 0 NVRAM format version number + * + * + * Set QuickSet Radio Disable Flag + * cbArg1, byte0 = 0x01 + * cbArg1, byte1 + * Radio ID value: + * 0 Radio Status + * 1 WLAN ID + * 2 BT ID + * 3 WWAN ID + * 4 UWB ID + * 5 WIGIG ID + * cbArg1, byte2 Flag bits: + * 0 QuickSet disables radio (1) + * 1-7 Reserved (0) + * + * cbRes1 Standard return codes (0, -1, -2) + * cbRes2 QuickSet (QS) radio disable bit map: + * 0 QS disables WLAN + * 1 QS disables BT + * 2 QS disables WWAN + * 3 QS disables UWB + * 4 QS disables WIGIG + * 5-31 Reserved (0) + * + * Wireless Switch Configuration + * cbArg1, byte0 = 0x02 + * + * cbArg1, byte1 + * Subcommand: + * 0 Get config + * 1 Set config + * 2 Set WiFi locator enable/disable + * cbArg1,byte2 + * Switch settings (if byte 1==1): + * 0 WLAN sw itch control (1) + * 1 BT sw itch control (1) + * 2 WWAN sw itch control (1) + * 3 UWB sw itch control (1) + * 4 WiGig sw itch control (1) + * 5-7 Reserved (0) + * cbArg1, byte2 Enable bits (if byte 1==2): + * 0 Enable WiFi locator (1) + * + * cbRes1 Standard return codes (0, -1, -2) + * cbRes2 QuickSet radio disable bit map: + * 0 WLAN controlled by sw itch (1) + * 1 BT controlled by sw itch (1) + * 2 WWAN controlled by sw itch (1) + * 3 UWB controlled by sw itch (1) + * 4 WiGig controlled by sw itch (1) + * 5-6 Reserved (0) + * 7 Wireless sw itch config locked (1) + * 8 WiFi locator enabled (1) + * 9-14 Reserved (0) + * 15 WiFi locator setting locked (1) + * 16-31 Reserved (0) + * + * Read Local Config Data (LCD) + * cbArg1, byte0 = 0x10 + * cbArg1, byte1 NVRAM index low byte + * cbArg1, byte2 NVRAM index high byte + * cbRes1 Standard return codes (0, -1, -2) + * cbRes2 4 bytes read from LCD[index] + * cbRes3 4 bytes read from LCD[index+4] + * cbRes4 4 bytes read from LCD[index+8] + * + * Write Local Config Data (LCD) + * cbArg1, byte0 = 0x11 + * cbArg1, byte1 NVRAM index low byte + * cbArg1, byte2 NVRAM index high byte + * cbArg2 4 bytes to w rite at LCD[index] + * cbArg3 4 bytes to w rite at LCD[index+4] + * cbArg4 4 bytes to w rite at LCD[index+8] + * cbRes1 Standard return codes (0, -1, -2) + * + * Populate Local Config Data from NVRAM + * cbArg1, byte0 = 0x12 + * cbRes1 Standard return codes (0, -1, -2) + * + * Commit Local Config Data to NVRAM + * cbArg1, byte0 = 0x13 + * cbRes1 Standard return codes (0, -1, -2) + */ + +static int dell_rfkill_set(void *data, bool blocked) +{ + int disable = blocked ? 1 : 0; + unsigned long radio = (unsigned long)data; + int hwswitch_bit = (unsigned long)data - 1; + struct calling_interface_buffer buffer; + int hwswitch; + int status; + int ret; + + dell_fill_request(&buffer, 0, 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); + if (ret) + return ret; + status = buffer.output[1]; + + dell_fill_request(&buffer, 0x2, 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); + if (ret) + return ret; + hwswitch = buffer.output[1]; + + /* If the hardware switch controls this radio, and the hardware + switch is disabled, always disable the radio */ + if (ret == 0 && (hwswitch & BIT(hwswitch_bit)) && + (status & BIT(0)) && !(status & BIT(16))) + disable = 1; + + dell_fill_request(&buffer, 1 | (radio<<8) | (disable << 16), 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); + return ret; +} + +static void dell_rfkill_update_sw_state(struct rfkill *rfkill, int radio, + int status) +{ + if (status & BIT(0)) { + /* Has hw-switch, sync sw_state to BIOS */ + struct calling_interface_buffer buffer; + int block = rfkill_blocked(rfkill); + dell_fill_request(&buffer, + 1 | (radio << 8) | (block << 16), 0, 0, 0); + dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); + } else { + /* No hw-switch, sync BIOS state to sw_state */ + rfkill_set_sw_state(rfkill, !!(status & BIT(radio + 16))); + } +} + +static void dell_rfkill_update_hw_state(struct rfkill *rfkill, int radio, + int status, int hwswitch) +{ + if (hwswitch & (BIT(radio - 1))) + rfkill_set_hw_state(rfkill, !(status & BIT(16))); +} + +static void dell_rfkill_query(struct rfkill *rfkill, void *data) +{ + int radio = ((unsigned long)data & 0xF); + struct calling_interface_buffer buffer; + int hwswitch; + int status; + int ret; + + dell_fill_request(&buffer, 0, 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); + status = buffer.output[1]; + + if (ret != 0 || !(status & BIT(0))) { + return; + } + + dell_fill_request(&buffer, 0x2, 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); + hwswitch = buffer.output[1]; + + if (ret != 0) + return; + + dell_rfkill_update_hw_state(rfkill, radio, status, hwswitch); +} + +static const struct rfkill_ops dell_rfkill_ops = { + .set_block = dell_rfkill_set, + .query = dell_rfkill_query, +}; + +static struct dentry *dell_laptop_dir; + +static int dell_debugfs_show(struct seq_file *s, void *data) +{ + struct calling_interface_buffer buffer; + int hwswitch_state; + int hwswitch_ret; + int status; + int ret; + + dell_fill_request(&buffer, 0, 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); + if (ret) + return ret; + status = buffer.output[1]; + + dell_fill_request(&buffer, 0x2, 0, 0, 0); + hwswitch_ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); + if (hwswitch_ret) + return hwswitch_ret; + hwswitch_state = buffer.output[1]; + + seq_printf(s, "return:\t%d\n", ret); + seq_printf(s, "status:\t0x%X\n", status); + seq_printf(s, "Bit 0 : Hardware switch supported: %lu\n", + status & BIT(0)); + seq_printf(s, "Bit 1 : Wifi locator supported: %lu\n", + (status & BIT(1)) >> 1); + seq_printf(s, "Bit 2 : Wifi is supported: %lu\n", + (status & BIT(2)) >> 2); + seq_printf(s, "Bit 3 : Bluetooth is supported: %lu\n", + (status & BIT(3)) >> 3); + seq_printf(s, "Bit 4 : WWAN is supported: %lu\n", + (status & BIT(4)) >> 4); + seq_printf(s, "Bit 5 : Wireless keyboard supported: %lu\n", + (status & BIT(5)) >> 5); + seq_printf(s, "Bit 6 : UWB supported: %lu\n", + (status & BIT(6)) >> 6); + seq_printf(s, "Bit 7 : WiGig supported: %lu\n", + (status & BIT(7)) >> 7); + seq_printf(s, "Bit 8 : Wifi is installed: %lu\n", + (status & BIT(8)) >> 8); + seq_printf(s, "Bit 9 : Bluetooth is installed: %lu\n", + (status & BIT(9)) >> 9); + seq_printf(s, "Bit 10: WWAN is installed: %lu\n", + (status & BIT(10)) >> 10); + seq_printf(s, "Bit 11: UWB installed: %lu\n", + (status & BIT(11)) >> 11); + seq_printf(s, "Bit 12: WiGig installed: %lu\n", + (status & BIT(12)) >> 12); + + seq_printf(s, "Bit 16: Hardware switch is on: %lu\n", + (status & BIT(16)) >> 16); + seq_printf(s, "Bit 17: Wifi is blocked: %lu\n", + (status & BIT(17)) >> 17); + seq_printf(s, "Bit 18: Bluetooth is blocked: %lu\n", + (status & BIT(18)) >> 18); + seq_printf(s, "Bit 19: WWAN is blocked: %lu\n", + (status & BIT(19)) >> 19); + seq_printf(s, "Bit 20: UWB is blocked: %lu\n", + (status & BIT(20)) >> 20); + seq_printf(s, "Bit 21: WiGig is blocked: %lu\n", + (status & BIT(21)) >> 21); + + seq_printf(s, "\nhwswitch_return:\t%d\n", hwswitch_ret); + seq_printf(s, "hwswitch_state:\t0x%X\n", hwswitch_state); + seq_printf(s, "Bit 0 : Wifi controlled by switch: %lu\n", + hwswitch_state & BIT(0)); + seq_printf(s, "Bit 1 : Bluetooth controlled by switch: %lu\n", + (hwswitch_state & BIT(1)) >> 1); + seq_printf(s, "Bit 2 : WWAN controlled by switch: %lu\n", + (hwswitch_state & BIT(2)) >> 2); + seq_printf(s, "Bit 3 : UWB controlled by switch: %lu\n", + (hwswitch_state & BIT(3)) >> 3); + seq_printf(s, "Bit 4 : WiGig controlled by switch: %lu\n", + (hwswitch_state & BIT(4)) >> 4); + seq_printf(s, "Bit 7 : Wireless switch config locked: %lu\n", + (hwswitch_state & BIT(7)) >> 7); + seq_printf(s, "Bit 8 : Wifi locator enabled: %lu\n", + (hwswitch_state & BIT(8)) >> 8); + seq_printf(s, "Bit 15: Wifi locator setting locked: %lu\n", + (hwswitch_state & BIT(15)) >> 15); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(dell_debugfs); + +static void dell_update_rfkill(struct work_struct *ignored) +{ + struct calling_interface_buffer buffer; + int hwswitch = 0; + int status; + int ret; + + dell_fill_request(&buffer, 0, 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); + status = buffer.output[1]; + + if (ret != 0) + return; + + dell_fill_request(&buffer, 0x2, 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); + + if (ret == 0 && (status & BIT(0))) + hwswitch = buffer.output[1]; + + if (wifi_rfkill) { + dell_rfkill_update_hw_state(wifi_rfkill, 1, status, hwswitch); + dell_rfkill_update_sw_state(wifi_rfkill, 1, status); + } + if (bluetooth_rfkill) { + dell_rfkill_update_hw_state(bluetooth_rfkill, 2, status, + hwswitch); + dell_rfkill_update_sw_state(bluetooth_rfkill, 2, status); + } + if (wwan_rfkill) { + dell_rfkill_update_hw_state(wwan_rfkill, 3, status, hwswitch); + dell_rfkill_update_sw_state(wwan_rfkill, 3, status); + } +} +static DECLARE_DELAYED_WORK(dell_rfkill_work, dell_update_rfkill); + +static bool dell_laptop_i8042_filter(unsigned char data, unsigned char str, + struct serio *port) +{ + static bool extended; + + if (str & I8042_STR_AUXDATA) + return false; + + if (unlikely(data == 0xe0)) { + extended = true; + return false; + } else if (unlikely(extended)) { + switch (data) { + case 0x8: + schedule_delayed_work(&dell_rfkill_work, + round_jiffies_relative(HZ / 4)); + break; + } + extended = false; + } + + return false; +} + +static int (*dell_rbtn_notifier_register_func)(struct notifier_block *); +static int (*dell_rbtn_notifier_unregister_func)(struct notifier_block *); + +static int dell_laptop_rbtn_notifier_call(struct notifier_block *nb, + unsigned long action, void *data) +{ + schedule_delayed_work(&dell_rfkill_work, 0); + return NOTIFY_OK; +} + +static struct notifier_block dell_laptop_rbtn_notifier = { + .notifier_call = dell_laptop_rbtn_notifier_call, +}; + +static int __init dell_setup_rfkill(void) +{ + struct calling_interface_buffer buffer; + int status, ret, whitelisted; + const char *product; + + /* + * rfkill support causes trouble on various models, mostly Inspirons. + * So we whitelist certain series, and don't support rfkill on others. + */ + whitelisted = 0; + product = dmi_get_system_info(DMI_PRODUCT_NAME); + if (product && (strncmp(product, "Latitude", 8) == 0 || + strncmp(product, "Precision", 9) == 0)) + whitelisted = 1; + if (!force_rfkill && !whitelisted) + return 0; + + dell_fill_request(&buffer, 0, 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); + status = buffer.output[1]; + + /* dell wireless info smbios call is not supported */ + if (ret != 0) + return 0; + + /* rfkill is only tested on laptops with a hwswitch */ + if (!(status & BIT(0)) && !force_rfkill) + return 0; + + if ((status & (1<<2|1<<8)) == (1<<2|1<<8)) { + wifi_rfkill = rfkill_alloc("dell-wifi", &platform_device->dev, + RFKILL_TYPE_WLAN, + &dell_rfkill_ops, (void *) 1); + if (!wifi_rfkill) { + ret = -ENOMEM; + goto err_wifi; + } + ret = rfkill_register(wifi_rfkill); + if (ret) + goto err_wifi; + } + + if ((status & (1<<3|1<<9)) == (1<<3|1<<9)) { + bluetooth_rfkill = rfkill_alloc("dell-bluetooth", + &platform_device->dev, + RFKILL_TYPE_BLUETOOTH, + &dell_rfkill_ops, (void *) 2); + if (!bluetooth_rfkill) { + ret = -ENOMEM; + goto err_bluetooth; + } + ret = rfkill_register(bluetooth_rfkill); + if (ret) + goto err_bluetooth; + } + + if ((status & (1<<4|1<<10)) == (1<<4|1<<10)) { + wwan_rfkill = rfkill_alloc("dell-wwan", + &platform_device->dev, + RFKILL_TYPE_WWAN, + &dell_rfkill_ops, (void *) 3); + if (!wwan_rfkill) { + ret = -ENOMEM; + goto err_wwan; + } + ret = rfkill_register(wwan_rfkill); + if (ret) + goto err_wwan; + } + + /* + * Dell Airplane Mode Switch driver (dell-rbtn) supports ACPI devices + * which can receive events from HW slider switch. + * + * Dell SMBIOS on whitelisted models supports controlling radio devices + * but does not support receiving HW button switch events. We can use + * i8042 filter hook function to receive keyboard data and handle + * keycode for HW button. + * + * So if it is possible we will use Dell Airplane Mode Switch ACPI + * driver for receiving HW events and Dell SMBIOS for setting rfkill + * states. If ACPI driver or device is not available we will fallback to + * i8042 filter hook function. + * + * To prevent duplicate rfkill devices which control and do same thing, + * dell-rbtn driver will automatically remove its own rfkill devices + * once function dell_rbtn_notifier_register() is called. + */ + + dell_rbtn_notifier_register_func = + symbol_request(dell_rbtn_notifier_register); + if (dell_rbtn_notifier_register_func) { + dell_rbtn_notifier_unregister_func = + symbol_request(dell_rbtn_notifier_unregister); + if (!dell_rbtn_notifier_unregister_func) { + symbol_put(dell_rbtn_notifier_register); + dell_rbtn_notifier_register_func = NULL; + } + } + + if (dell_rbtn_notifier_register_func) { + ret = dell_rbtn_notifier_register_func( + &dell_laptop_rbtn_notifier); + symbol_put(dell_rbtn_notifier_register); + dell_rbtn_notifier_register_func = NULL; + if (ret != 0) { + symbol_put(dell_rbtn_notifier_unregister); + dell_rbtn_notifier_unregister_func = NULL; + } + } else { + pr_info("Symbols from dell-rbtn acpi driver are not available\n"); + ret = -ENODEV; + } + + if (ret == 0) { + pr_info("Using dell-rbtn acpi driver for receiving events\n"); + } else if (ret != -ENODEV) { + pr_warn("Unable to register dell rbtn notifier\n"); + goto err_filter; + } else { + ret = i8042_install_filter(dell_laptop_i8042_filter); + if (ret) { + pr_warn("Unable to install key filter\n"); + goto err_filter; + } + pr_info("Using i8042 filter function for receiving events\n"); + } + + return 0; +err_filter: + if (wwan_rfkill) + rfkill_unregister(wwan_rfkill); +err_wwan: + rfkill_destroy(wwan_rfkill); + if (bluetooth_rfkill) + rfkill_unregister(bluetooth_rfkill); +err_bluetooth: + rfkill_destroy(bluetooth_rfkill); + if (wifi_rfkill) + rfkill_unregister(wifi_rfkill); +err_wifi: + rfkill_destroy(wifi_rfkill); + + return ret; +} + +static void dell_cleanup_rfkill(void) +{ + if (dell_rbtn_notifier_unregister_func) { + dell_rbtn_notifier_unregister_func(&dell_laptop_rbtn_notifier); + symbol_put(dell_rbtn_notifier_unregister); + dell_rbtn_notifier_unregister_func = NULL; + } else { + i8042_remove_filter(dell_laptop_i8042_filter); + } + cancel_delayed_work_sync(&dell_rfkill_work); + if (wifi_rfkill) { + rfkill_unregister(wifi_rfkill); + rfkill_destroy(wifi_rfkill); + } + if (bluetooth_rfkill) { + rfkill_unregister(bluetooth_rfkill); + rfkill_destroy(bluetooth_rfkill); + } + if (wwan_rfkill) { + rfkill_unregister(wwan_rfkill); + rfkill_destroy(wwan_rfkill); + } +} + +static int dell_send_intensity(struct backlight_device *bd) +{ + struct calling_interface_buffer buffer; + struct calling_interface_token *token; + int ret; + + token = dell_smbios_find_token(BRIGHTNESS_TOKEN); + if (!token) + return -ENODEV; + + dell_fill_request(&buffer, + token->location, bd->props.brightness, 0, 0); + if (power_supply_is_system_supplied() > 0) + ret = dell_send_request(&buffer, + CLASS_TOKEN_WRITE, SELECT_TOKEN_AC); + else + ret = dell_send_request(&buffer, + CLASS_TOKEN_WRITE, SELECT_TOKEN_BAT); + + return ret; +} + +static int dell_get_intensity(struct backlight_device *bd) +{ + struct calling_interface_buffer buffer; + struct calling_interface_token *token; + int ret; + + token = dell_smbios_find_token(BRIGHTNESS_TOKEN); + if (!token) + return -ENODEV; + + dell_fill_request(&buffer, token->location, 0, 0, 0); + if (power_supply_is_system_supplied() > 0) + ret = dell_send_request(&buffer, + CLASS_TOKEN_READ, SELECT_TOKEN_AC); + else + ret = dell_send_request(&buffer, + CLASS_TOKEN_READ, SELECT_TOKEN_BAT); + + if (ret == 0) + ret = buffer.output[1]; + + return ret; +} + +static const struct backlight_ops dell_ops = { + .get_brightness = dell_get_intensity, + .update_status = dell_send_intensity, +}; + +static void touchpad_led_on(void) +{ + int command = 0x97; + char data = 1; + i8042_command(&data, command | 1 << 12); +} + +static void touchpad_led_off(void) +{ + int command = 0x97; + char data = 2; + i8042_command(&data, command | 1 << 12); +} + +static void touchpad_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + if (value > 0) + touchpad_led_on(); + else + touchpad_led_off(); +} + +static struct led_classdev touchpad_led = { + .name = "dell-laptop::touchpad", + .brightness_set = touchpad_led_set, + .flags = LED_CORE_SUSPENDRESUME, +}; + +static int __init touchpad_led_init(struct device *dev) +{ + return led_classdev_register(dev, &touchpad_led); +} + +static void touchpad_led_exit(void) +{ + led_classdev_unregister(&touchpad_led); +} + +/* + * Derived from information in smbios-keyboard-ctl: + * + * cbClass 4 + * cbSelect 11 + * Keyboard illumination + * cbArg1 determines the function to be performed + * + * cbArg1 0x0 = Get Feature Information + * cbRES1 Standard return codes (0, -1, -2) + * cbRES2, word0 Bitmap of user-selectable modes + * bit 0 Always off (All systems) + * bit 1 Always on (Travis ATG, Siberia) + * bit 2 Auto: ALS-based On; ALS-based Off (Travis ATG) + * bit 3 Auto: ALS- and input-activity-based On; input-activity based Off + * bit 4 Auto: Input-activity-based On; input-activity based Off + * bit 5 Auto: Input-activity-based On (illumination level 25%); input-activity based Off + * bit 6 Auto: Input-activity-based On (illumination level 50%); input-activity based Off + * bit 7 Auto: Input-activity-based On (illumination level 75%); input-activity based Off + * bit 8 Auto: Input-activity-based On (illumination level 100%); input-activity based Off + * bits 9-15 Reserved for future use + * cbRES2, byte2 Reserved for future use + * cbRES2, byte3 Keyboard illumination type + * 0 Reserved + * 1 Tasklight + * 2 Backlight + * 3-255 Reserved for future use + * cbRES3, byte0 Supported auto keyboard illumination trigger bitmap. + * bit 0 Any keystroke + * bit 1 Touchpad activity + * bit 2 Pointing stick + * bit 3 Any mouse + * bits 4-7 Reserved for future use + * cbRES3, byte1 Supported timeout unit bitmap + * bit 0 Seconds + * bit 1 Minutes + * bit 2 Hours + * bit 3 Days + * bits 4-7 Reserved for future use + * cbRES3, byte2 Number of keyboard light brightness levels + * cbRES4, byte0 Maximum acceptable seconds value (0 if seconds not supported). + * cbRES4, byte1 Maximum acceptable minutes value (0 if minutes not supported). + * cbRES4, byte2 Maximum acceptable hours value (0 if hours not supported). + * cbRES4, byte3 Maximum acceptable days value (0 if days not supported) + * + * cbArg1 0x1 = Get Current State + * cbRES1 Standard return codes (0, -1, -2) + * cbRES2, word0 Bitmap of current mode state + * bit 0 Always off (All systems) + * bit 1 Always on (Travis ATG, Siberia) + * bit 2 Auto: ALS-based On; ALS-based Off (Travis ATG) + * bit 3 Auto: ALS- and input-activity-based On; input-activity based Off + * bit 4 Auto: Input-activity-based On; input-activity based Off + * bit 5 Auto: Input-activity-based On (illumination level 25%); input-activity based Off + * bit 6 Auto: Input-activity-based On (illumination level 50%); input-activity based Off + * bit 7 Auto: Input-activity-based On (illumination level 75%); input-activity based Off + * bit 8 Auto: Input-activity-based On (illumination level 100%); input-activity based Off + * bits 9-15 Reserved for future use + * Note: Only One bit can be set + * cbRES2, byte2 Currently active auto keyboard illumination triggers. + * bit 0 Any keystroke + * bit 1 Touchpad activity + * bit 2 Pointing stick + * bit 3 Any mouse + * bits 4-7 Reserved for future use + * cbRES2, byte3 Current Timeout on battery + * bits 7:6 Timeout units indicator: + * 00b Seconds + * 01b Minutes + * 10b Hours + * 11b Days + * bits 5:0 Timeout value (0-63) in sec/min/hr/day + * NOTE: A value of 0 means always on (no timeout) if any bits of RES3 byte + * are set upon return from the [Get feature information] call. + * cbRES3, byte0 Current setting of ALS value that turns the light on or off. + * cbRES3, byte1 Current ALS reading + * cbRES3, byte2 Current keyboard light level. + * cbRES3, byte3 Current timeout on AC Power + * bits 7:6 Timeout units indicator: + * 00b Seconds + * 01b Minutes + * 10b Hours + * 11b Days + * Bits 5:0 Timeout value (0-63) in sec/min/hr/day + * NOTE: A value of 0 means always on (no timeout) if any bits of RES3 byte2 + * are set upon return from the upon return from the [Get Feature information] call. + * + * cbArg1 0x2 = Set New State + * cbRES1 Standard return codes (0, -1, -2) + * cbArg2, word0 Bitmap of current mode state + * bit 0 Always off (All systems) + * bit 1 Always on (Travis ATG, Siberia) + * bit 2 Auto: ALS-based On; ALS-based Off (Travis ATG) + * bit 3 Auto: ALS- and input-activity-based On; input-activity based Off + * bit 4 Auto: Input-activity-based On; input-activity based Off + * bit 5 Auto: Input-activity-based On (illumination level 25%); input-activity based Off + * bit 6 Auto: Input-activity-based On (illumination level 50%); input-activity based Off + * bit 7 Auto: Input-activity-based On (illumination level 75%); input-activity based Off + * bit 8 Auto: Input-activity-based On (illumination level 100%); input-activity based Off + * bits 9-15 Reserved for future use + * Note: Only One bit can be set + * cbArg2, byte2 Desired auto keyboard illumination triggers. Must remain inactive to allow + * keyboard to turn off automatically. + * bit 0 Any keystroke + * bit 1 Touchpad activity + * bit 2 Pointing stick + * bit 3 Any mouse + * bits 4-7 Reserved for future use + * cbArg2, byte3 Desired Timeout on battery + * bits 7:6 Timeout units indicator: + * 00b Seconds + * 01b Minutes + * 10b Hours + * 11b Days + * bits 5:0 Timeout value (0-63) in sec/min/hr/day + * cbArg3, byte0 Desired setting of ALS value that turns the light on or off. + * cbArg3, byte2 Desired keyboard light level. + * cbArg3, byte3 Desired Timeout on AC power + * bits 7:6 Timeout units indicator: + * 00b Seconds + * 01b Minutes + * 10b Hours + * 11b Days + * bits 5:0 Timeout value (0-63) in sec/min/hr/day + */ + + +enum kbd_timeout_unit { + KBD_TIMEOUT_SECONDS = 0, + KBD_TIMEOUT_MINUTES, + KBD_TIMEOUT_HOURS, + KBD_TIMEOUT_DAYS, +}; + +enum kbd_mode_bit { + KBD_MODE_BIT_OFF = 0, + KBD_MODE_BIT_ON, + KBD_MODE_BIT_ALS, + KBD_MODE_BIT_TRIGGER_ALS, + KBD_MODE_BIT_TRIGGER, + KBD_MODE_BIT_TRIGGER_25, + KBD_MODE_BIT_TRIGGER_50, + KBD_MODE_BIT_TRIGGER_75, + KBD_MODE_BIT_TRIGGER_100, +}; + +#define kbd_is_als_mode_bit(bit) \ + ((bit) == KBD_MODE_BIT_ALS || (bit) == KBD_MODE_BIT_TRIGGER_ALS) +#define kbd_is_trigger_mode_bit(bit) \ + ((bit) >= KBD_MODE_BIT_TRIGGER_ALS && (bit) <= KBD_MODE_BIT_TRIGGER_100) +#define kbd_is_level_mode_bit(bit) \ + ((bit) >= KBD_MODE_BIT_TRIGGER_25 && (bit) <= KBD_MODE_BIT_TRIGGER_100) + +struct kbd_info { + u16 modes; + u8 type; + u8 triggers; + u8 levels; + u8 seconds; + u8 minutes; + u8 hours; + u8 days; +}; + +struct kbd_state { + u8 mode_bit; + u8 triggers; + u8 timeout_value; + u8 timeout_unit; + u8 timeout_value_ac; + u8 timeout_unit_ac; + u8 als_setting; + u8 als_value; + u8 level; +}; + +static const int kbd_tokens[] = { + KBD_LED_OFF_TOKEN, + KBD_LED_AUTO_25_TOKEN, + KBD_LED_AUTO_50_TOKEN, + KBD_LED_AUTO_75_TOKEN, + KBD_LED_AUTO_100_TOKEN, + KBD_LED_ON_TOKEN, +}; + +static u16 kbd_token_bits; + +static struct kbd_info kbd_info; +static bool kbd_als_supported; +static bool kbd_triggers_supported; +static bool kbd_timeout_ac_supported; + +static u8 kbd_mode_levels[16]; +static int kbd_mode_levels_count; + +static u8 kbd_previous_level; +static u8 kbd_previous_mode_bit; + +static bool kbd_led_present; +static DEFINE_MUTEX(kbd_led_mutex); +static enum led_brightness kbd_led_level; + +/* + * NOTE: there are three ways to set the keyboard backlight level. + * First, via kbd_state.mode_bit (assigning KBD_MODE_BIT_TRIGGER_* value). + * Second, via kbd_state.level (assigning numerical value <= kbd_info.levels). + * Third, via SMBIOS tokens (KBD_LED_* in kbd_tokens) + * + * There are laptops which support only one of these methods. If we want to + * support as many machines as possible we need to implement all three methods. + * The first two methods use the kbd_state structure. The third uses SMBIOS + * tokens. If kbd_info.levels == 0, the machine does not support setting the + * keyboard backlight level via kbd_state.level. + */ + +static int kbd_get_info(struct kbd_info *info) +{ + struct calling_interface_buffer buffer; + u8 units; + int ret; + + dell_fill_request(&buffer, 0, 0, 0, 0); + ret = dell_send_request(&buffer, + CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT); + if (ret) + return ret; + + info->modes = buffer.output[1] & 0xFFFF; + info->type = (buffer.output[1] >> 24) & 0xFF; + info->triggers = buffer.output[2] & 0xFF; + units = (buffer.output[2] >> 8) & 0xFF; + info->levels = (buffer.output[2] >> 16) & 0xFF; + + if (quirks && quirks->kbd_led_levels_off_1 && info->levels) + info->levels--; + + if (units & BIT(0)) + info->seconds = (buffer.output[3] >> 0) & 0xFF; + if (units & BIT(1)) + info->minutes = (buffer.output[3] >> 8) & 0xFF; + if (units & BIT(2)) + info->hours = (buffer.output[3] >> 16) & 0xFF; + if (units & BIT(3)) + info->days = (buffer.output[3] >> 24) & 0xFF; + + return ret; +} + +static unsigned int kbd_get_max_level(void) +{ + if (kbd_info.levels != 0) + return kbd_info.levels; + if (kbd_mode_levels_count > 0) + return kbd_mode_levels_count - 1; + return 0; +} + +static int kbd_get_level(struct kbd_state *state) +{ + int i; + + if (kbd_info.levels != 0) + return state->level; + + if (kbd_mode_levels_count > 0) { + for (i = 0; i < kbd_mode_levels_count; ++i) + if (kbd_mode_levels[i] == state->mode_bit) + return i; + return 0; + } + + return -EINVAL; +} + +static int kbd_set_level(struct kbd_state *state, u8 level) +{ + if (kbd_info.levels != 0) { + if (level != 0) + kbd_previous_level = level; + if (state->level == level) + return 0; + state->level = level; + if (level != 0 && state->mode_bit == KBD_MODE_BIT_OFF) + state->mode_bit = kbd_previous_mode_bit; + else if (level == 0 && state->mode_bit != KBD_MODE_BIT_OFF) { + kbd_previous_mode_bit = state->mode_bit; + state->mode_bit = KBD_MODE_BIT_OFF; + } + return 0; + } + + if (kbd_mode_levels_count > 0 && level < kbd_mode_levels_count) { + if (level != 0) + kbd_previous_level = level; + state->mode_bit = kbd_mode_levels[level]; + return 0; + } + + return -EINVAL; +} + +static int kbd_get_state(struct kbd_state *state) +{ + struct calling_interface_buffer buffer; + int ret; + + dell_fill_request(&buffer, 0x1, 0, 0, 0); + ret = dell_send_request(&buffer, + CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT); + if (ret) + return ret; + + state->mode_bit = ffs(buffer.output[1] & 0xFFFF); + if (state->mode_bit != 0) + state->mode_bit--; + + state->triggers = (buffer.output[1] >> 16) & 0xFF; + state->timeout_value = (buffer.output[1] >> 24) & 0x3F; + state->timeout_unit = (buffer.output[1] >> 30) & 0x3; + state->als_setting = buffer.output[2] & 0xFF; + state->als_value = (buffer.output[2] >> 8) & 0xFF; + state->level = (buffer.output[2] >> 16) & 0xFF; + state->timeout_value_ac = (buffer.output[2] >> 24) & 0x3F; + state->timeout_unit_ac = (buffer.output[2] >> 30) & 0x3; + + return ret; +} + +static int kbd_set_state(struct kbd_state *state) +{ + struct calling_interface_buffer buffer; + int ret; + u32 input1; + u32 input2; + + input1 = BIT(state->mode_bit) & 0xFFFF; + input1 |= (state->triggers & 0xFF) << 16; + input1 |= (state->timeout_value & 0x3F) << 24; + input1 |= (state->timeout_unit & 0x3) << 30; + input2 = state->als_setting & 0xFF; + input2 |= (state->level & 0xFF) << 16; + input2 |= (state->timeout_value_ac & 0x3F) << 24; + input2 |= (state->timeout_unit_ac & 0x3) << 30; + dell_fill_request(&buffer, 0x2, input1, input2, 0); + ret = dell_send_request(&buffer, + CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT); + + return ret; +} + +static int kbd_set_state_safe(struct kbd_state *state, struct kbd_state *old) +{ + int ret; + + ret = kbd_set_state(state); + if (ret == 0) + return 0; + + /* + * When setting the new state fails,try to restore the previous one. + * This is needed on some machines where BIOS sets a default state when + * setting a new state fails. This default state could be all off. + */ + + if (kbd_set_state(old)) + pr_err("Setting old previous keyboard state failed\n"); + + return ret; +} + +static int kbd_set_token_bit(u8 bit) +{ + struct calling_interface_buffer buffer; + struct calling_interface_token *token; + int ret; + + if (bit >= ARRAY_SIZE(kbd_tokens)) + return -EINVAL; + + token = dell_smbios_find_token(kbd_tokens[bit]); + if (!token) + return -EINVAL; + + dell_fill_request(&buffer, token->location, token->value, 0, 0); + ret = dell_send_request(&buffer, CLASS_TOKEN_WRITE, SELECT_TOKEN_STD); + + return ret; +} + +static int kbd_get_token_bit(u8 bit) +{ + struct calling_interface_buffer buffer; + struct calling_interface_token *token; + int ret; + int val; + + if (bit >= ARRAY_SIZE(kbd_tokens)) + return -EINVAL; + + token = dell_smbios_find_token(kbd_tokens[bit]); + if (!token) + return -EINVAL; + + dell_fill_request(&buffer, token->location, 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_TOKEN_READ, SELECT_TOKEN_STD); + val = buffer.output[1]; + + if (ret) + return ret; + + return (val == token->value); +} + +static int kbd_get_first_active_token_bit(void) +{ + int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(kbd_tokens); ++i) { + ret = kbd_get_token_bit(i); + if (ret == 1) + return i; + } + + return ret; +} + +static int kbd_get_valid_token_counts(void) +{ + return hweight16(kbd_token_bits); +} + +static inline int kbd_init_info(void) +{ + struct kbd_state state; + int ret; + int i; + + ret = kbd_get_info(&kbd_info); + if (ret) + return ret; + + /* NOTE: Old models without KBD_LED_AC_TOKEN token supports only one + * timeout value which is shared for both battery and AC power + * settings. So do not try to set AC values on old models. + */ + if ((quirks && quirks->kbd_missing_ac_tag) || + dell_smbios_find_token(KBD_LED_AC_TOKEN)) + kbd_timeout_ac_supported = true; + + kbd_get_state(&state); + + /* NOTE: timeout value is stored in 6 bits so max value is 63 */ + if (kbd_info.seconds > 63) + kbd_info.seconds = 63; + if (kbd_info.minutes > 63) + kbd_info.minutes = 63; + if (kbd_info.hours > 63) + kbd_info.hours = 63; + if (kbd_info.days > 63) + kbd_info.days = 63; + + /* NOTE: On tested machines ON mode did not work and caused + * problems (turned backlight off) so do not use it + */ + kbd_info.modes &= ~BIT(KBD_MODE_BIT_ON); + + kbd_previous_level = kbd_get_level(&state); + kbd_previous_mode_bit = state.mode_bit; + + if (kbd_previous_level == 0 && kbd_get_max_level() != 0) + kbd_previous_level = 1; + + if (kbd_previous_mode_bit == KBD_MODE_BIT_OFF) { + kbd_previous_mode_bit = + ffs(kbd_info.modes & ~BIT(KBD_MODE_BIT_OFF)); + if (kbd_previous_mode_bit != 0) + kbd_previous_mode_bit--; + } + + if (kbd_info.modes & (BIT(KBD_MODE_BIT_ALS) | + BIT(KBD_MODE_BIT_TRIGGER_ALS))) + kbd_als_supported = true; + + if (kbd_info.modes & ( + BIT(KBD_MODE_BIT_TRIGGER_ALS) | BIT(KBD_MODE_BIT_TRIGGER) | + BIT(KBD_MODE_BIT_TRIGGER_25) | BIT(KBD_MODE_BIT_TRIGGER_50) | + BIT(KBD_MODE_BIT_TRIGGER_75) | BIT(KBD_MODE_BIT_TRIGGER_100) + )) + kbd_triggers_supported = true; + + /* kbd_mode_levels[0] is reserved, see below */ + for (i = 0; i < 16; ++i) + if (kbd_is_level_mode_bit(i) && (BIT(i) & kbd_info.modes)) + kbd_mode_levels[1 + kbd_mode_levels_count++] = i; + + /* + * Find the first supported mode and assign to kbd_mode_levels[0]. + * This should be 0 (off), but we cannot depend on the BIOS to + * support 0. + */ + if (kbd_mode_levels_count > 0) { + for (i = 0; i < 16; ++i) { + if (BIT(i) & kbd_info.modes) { + kbd_mode_levels[0] = i; + break; + } + } + kbd_mode_levels_count++; + } + + return 0; + +} + +static inline void kbd_init_tokens(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(kbd_tokens); ++i) + if (dell_smbios_find_token(kbd_tokens[i])) + kbd_token_bits |= BIT(i); +} + +static void kbd_init(void) +{ + int ret; + + if (quirks && quirks->kbd_led_not_present) + return; + + ret = kbd_init_info(); + kbd_init_tokens(); + + /* + * Only supports keyboard backlight when it has at least two modes. + */ + if ((ret == 0 && (kbd_info.levels != 0 || kbd_mode_levels_count >= 2)) + || kbd_get_valid_token_counts() >= 2) + kbd_led_present = true; +} + +static ssize_t kbd_led_timeout_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct kbd_state new_state; + struct kbd_state state; + bool convert; + int value; + int ret; + char ch; + u8 unit; + int i; + + ret = sscanf(buf, "%d %c", &value, &ch); + if (ret < 1) + return -EINVAL; + else if (ret == 1) + ch = 's'; + + if (value < 0) + return -EINVAL; + + convert = false; + + switch (ch) { + case 's': + if (value > kbd_info.seconds) + convert = true; + unit = KBD_TIMEOUT_SECONDS; + break; + case 'm': + if (value > kbd_info.minutes) + convert = true; + unit = KBD_TIMEOUT_MINUTES; + break; + case 'h': + if (value > kbd_info.hours) + convert = true; + unit = KBD_TIMEOUT_HOURS; + break; + case 'd': + if (value > kbd_info.days) + convert = true; + unit = KBD_TIMEOUT_DAYS; + break; + default: + return -EINVAL; + } + + if (quirks && quirks->needs_kbd_timeouts) + convert = true; + + if (convert) { + /* Convert value from current units to seconds */ + switch (unit) { + case KBD_TIMEOUT_DAYS: + value *= 24; + fallthrough; + case KBD_TIMEOUT_HOURS: + value *= 60; + fallthrough; + case KBD_TIMEOUT_MINUTES: + value *= 60; + unit = KBD_TIMEOUT_SECONDS; + } + + if (quirks && quirks->needs_kbd_timeouts) { + for (i = 0; quirks->kbd_timeouts[i] != -1; i++) { + if (value <= quirks->kbd_timeouts[i]) { + value = quirks->kbd_timeouts[i]; + break; + } + } + } + + if (value <= kbd_info.seconds && kbd_info.seconds) { + unit = KBD_TIMEOUT_SECONDS; + } else if (value / 60 <= kbd_info.minutes && kbd_info.minutes) { + value /= 60; + unit = KBD_TIMEOUT_MINUTES; + } else if (value / (60 * 60) <= kbd_info.hours && kbd_info.hours) { + value /= (60 * 60); + unit = KBD_TIMEOUT_HOURS; + } else if (value / (60 * 60 * 24) <= kbd_info.days && kbd_info.days) { + value /= (60 * 60 * 24); + unit = KBD_TIMEOUT_DAYS; + } else { + return -EINVAL; + } + } + + mutex_lock(&kbd_led_mutex); + + ret = kbd_get_state(&state); + if (ret) + goto out; + + new_state = state; + + if (kbd_timeout_ac_supported && power_supply_is_system_supplied() > 0) { + new_state.timeout_value_ac = value; + new_state.timeout_unit_ac = unit; + } else { + new_state.timeout_value = value; + new_state.timeout_unit = unit; + } + + ret = kbd_set_state_safe(&new_state, &state); + if (ret) + goto out; + + ret = count; +out: + mutex_unlock(&kbd_led_mutex); + return ret; +} + +static ssize_t kbd_led_timeout_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct kbd_state state; + int value; + int ret; + int len; + u8 unit; + + ret = kbd_get_state(&state); + if (ret) + return ret; + + if (kbd_timeout_ac_supported && power_supply_is_system_supplied() > 0) { + value = state.timeout_value_ac; + unit = state.timeout_unit_ac; + } else { + value = state.timeout_value; + unit = state.timeout_unit; + } + + len = sprintf(buf, "%d", value); + + switch (unit) { + case KBD_TIMEOUT_SECONDS: + return len + sprintf(buf+len, "s\n"); + case KBD_TIMEOUT_MINUTES: + return len + sprintf(buf+len, "m\n"); + case KBD_TIMEOUT_HOURS: + return len + sprintf(buf+len, "h\n"); + case KBD_TIMEOUT_DAYS: + return len + sprintf(buf+len, "d\n"); + default: + return -EINVAL; + } + + return len; +} + +static DEVICE_ATTR(stop_timeout, S_IRUGO | S_IWUSR, + kbd_led_timeout_show, kbd_led_timeout_store); + +static const char * const kbd_led_triggers[] = { + "keyboard", + "touchpad", + /*"trackstick"*/ NULL, /* NOTE: trackstick is just alias for touchpad */ + "mouse", +}; + +static ssize_t kbd_led_triggers_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct kbd_state new_state; + struct kbd_state state; + bool triggers_enabled = false; + int trigger_bit = -1; + char trigger[21]; + int i, ret; + + ret = sscanf(buf, "%20s", trigger); + if (ret != 1) + return -EINVAL; + + if (trigger[0] != '+' && trigger[0] != '-') + return -EINVAL; + + mutex_lock(&kbd_led_mutex); + + ret = kbd_get_state(&state); + if (ret) + goto out; + + if (kbd_triggers_supported) + triggers_enabled = kbd_is_trigger_mode_bit(state.mode_bit); + + if (kbd_triggers_supported) { + for (i = 0; i < ARRAY_SIZE(kbd_led_triggers); ++i) { + if (!(kbd_info.triggers & BIT(i))) + continue; + if (!kbd_led_triggers[i]) + continue; + if (strcmp(trigger+1, kbd_led_triggers[i]) != 0) + continue; + if (trigger[0] == '+' && + triggers_enabled && (state.triggers & BIT(i))) { + ret = count; + goto out; + } + if (trigger[0] == '-' && + (!triggers_enabled || !(state.triggers & BIT(i)))) { + ret = count; + goto out; + } + trigger_bit = i; + break; + } + } + + if (trigger_bit == -1) { + ret = -EINVAL; + goto out; + } + + new_state = state; + if (trigger[0] == '+') + new_state.triggers |= BIT(trigger_bit); + else { + new_state.triggers &= ~BIT(trigger_bit); + /* + * NOTE: trackstick bit (2) must be disabled when + * disabling touchpad bit (1), otherwise touchpad + * bit (1) will not be disabled + */ + if (trigger_bit == 1) + new_state.triggers &= ~BIT(2); + } + if ((kbd_info.triggers & new_state.triggers) != + new_state.triggers) { + ret = -EINVAL; + goto out; + } + if (new_state.triggers && !triggers_enabled) { + new_state.mode_bit = KBD_MODE_BIT_TRIGGER; + kbd_set_level(&new_state, kbd_previous_level); + } else if (new_state.triggers == 0) { + kbd_set_level(&new_state, 0); + } + if (!(kbd_info.modes & BIT(new_state.mode_bit))) { + ret = -EINVAL; + goto out; + } + ret = kbd_set_state_safe(&new_state, &state); + if (ret) + goto out; + if (new_state.mode_bit != KBD_MODE_BIT_OFF) + kbd_previous_mode_bit = new_state.mode_bit; + ret = count; +out: + mutex_unlock(&kbd_led_mutex); + return ret; +} + +static ssize_t kbd_led_triggers_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct kbd_state state; + bool triggers_enabled; + int level, i, ret; + int len = 0; + + ret = kbd_get_state(&state); + if (ret) + return ret; + + len = 0; + + if (kbd_triggers_supported) { + triggers_enabled = kbd_is_trigger_mode_bit(state.mode_bit); + level = kbd_get_level(&state); + for (i = 0; i < ARRAY_SIZE(kbd_led_triggers); ++i) { + if (!(kbd_info.triggers & BIT(i))) + continue; + if (!kbd_led_triggers[i]) + continue; + if ((triggers_enabled || level <= 0) && + (state.triggers & BIT(i))) + buf[len++] = '+'; + else + buf[len++] = '-'; + len += sprintf(buf+len, "%s ", kbd_led_triggers[i]); + } + } + + if (len) + buf[len - 1] = '\n'; + + return len; +} + +static DEVICE_ATTR(start_triggers, S_IRUGO | S_IWUSR, + kbd_led_triggers_show, kbd_led_triggers_store); + +static ssize_t kbd_led_als_enabled_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct kbd_state new_state; + struct kbd_state state; + bool triggers_enabled = false; + int enable; + int ret; + + ret = kstrtoint(buf, 0, &enable); + if (ret) + return ret; + + mutex_lock(&kbd_led_mutex); + + ret = kbd_get_state(&state); + if (ret) + goto out; + + if (enable == kbd_is_als_mode_bit(state.mode_bit)) { + ret = count; + goto out; + } + + new_state = state; + + if (kbd_triggers_supported) + triggers_enabled = kbd_is_trigger_mode_bit(state.mode_bit); + + if (enable) { + if (triggers_enabled) + new_state.mode_bit = KBD_MODE_BIT_TRIGGER_ALS; + else + new_state.mode_bit = KBD_MODE_BIT_ALS; + } else { + if (triggers_enabled) { + new_state.mode_bit = KBD_MODE_BIT_TRIGGER; + kbd_set_level(&new_state, kbd_previous_level); + } else { + new_state.mode_bit = KBD_MODE_BIT_ON; + } + } + if (!(kbd_info.modes & BIT(new_state.mode_bit))) { + ret = -EINVAL; + goto out; + } + + ret = kbd_set_state_safe(&new_state, &state); + if (ret) + goto out; + kbd_previous_mode_bit = new_state.mode_bit; + + ret = count; +out: + mutex_unlock(&kbd_led_mutex); + return ret; +} + +static ssize_t kbd_led_als_enabled_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct kbd_state state; + bool enabled = false; + int ret; + + ret = kbd_get_state(&state); + if (ret) + return ret; + enabled = kbd_is_als_mode_bit(state.mode_bit); + + return sprintf(buf, "%d\n", enabled ? 1 : 0); +} + +static DEVICE_ATTR(als_enabled, S_IRUGO | S_IWUSR, + kbd_led_als_enabled_show, kbd_led_als_enabled_store); + +static ssize_t kbd_led_als_setting_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct kbd_state state; + struct kbd_state new_state; + u8 setting; + int ret; + + ret = kstrtou8(buf, 10, &setting); + if (ret) + return ret; + + mutex_lock(&kbd_led_mutex); + + ret = kbd_get_state(&state); + if (ret) + goto out; + + new_state = state; + new_state.als_setting = setting; + + ret = kbd_set_state_safe(&new_state, &state); + if (ret) + goto out; + + ret = count; +out: + mutex_unlock(&kbd_led_mutex); + return ret; +} + +static ssize_t kbd_led_als_setting_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct kbd_state state; + int ret; + + ret = kbd_get_state(&state); + if (ret) + return ret; + + return sprintf(buf, "%d\n", state.als_setting); +} + +static DEVICE_ATTR(als_setting, S_IRUGO | S_IWUSR, + kbd_led_als_setting_show, kbd_led_als_setting_store); + +static struct attribute *kbd_led_attrs[] = { + &dev_attr_stop_timeout.attr, + &dev_attr_start_triggers.attr, + NULL, +}; + +static const struct attribute_group kbd_led_group = { + .attrs = kbd_led_attrs, +}; + +static struct attribute *kbd_led_als_attrs[] = { + &dev_attr_als_enabled.attr, + &dev_attr_als_setting.attr, + NULL, +}; + +static const struct attribute_group kbd_led_als_group = { + .attrs = kbd_led_als_attrs, +}; + +static const struct attribute_group *kbd_led_groups[] = { + &kbd_led_group, + &kbd_led_als_group, + NULL, +}; + +static enum led_brightness kbd_led_level_get(struct led_classdev *led_cdev) +{ + int ret; + u16 num; + struct kbd_state state; + + if (kbd_get_max_level()) { + ret = kbd_get_state(&state); + if (ret) + return 0; + ret = kbd_get_level(&state); + if (ret < 0) + return 0; + return ret; + } + + if (kbd_get_valid_token_counts()) { + ret = kbd_get_first_active_token_bit(); + if (ret < 0) + return 0; + for (num = kbd_token_bits; num != 0 && ret > 0; --ret) + num &= num - 1; /* clear the first bit set */ + if (num == 0) + return 0; + return ffs(num) - 1; + } + + pr_warn("Keyboard brightness level control not supported\n"); + return 0; +} + +static int kbd_led_level_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + enum led_brightness new_value = value; + struct kbd_state state; + struct kbd_state new_state; + u16 num; + int ret; + + mutex_lock(&kbd_led_mutex); + + if (kbd_get_max_level()) { + ret = kbd_get_state(&state); + if (ret) + goto out; + new_state = state; + ret = kbd_set_level(&new_state, value); + if (ret) + goto out; + ret = kbd_set_state_safe(&new_state, &state); + } else if (kbd_get_valid_token_counts()) { + for (num = kbd_token_bits; num != 0 && value > 0; --value) + num &= num - 1; /* clear the first bit set */ + if (num == 0) + ret = 0; + else + ret = kbd_set_token_bit(ffs(num) - 1); + } else { + pr_warn("Keyboard brightness level control not supported\n"); + ret = -ENXIO; + } + +out: + if (ret == 0) + kbd_led_level = new_value; + + mutex_unlock(&kbd_led_mutex); + return ret; +} + +static struct led_classdev kbd_led = { + .name = "dell::kbd_backlight", + .flags = LED_BRIGHT_HW_CHANGED, + .brightness_set_blocking = kbd_led_level_set, + .brightness_get = kbd_led_level_get, + .groups = kbd_led_groups, +}; + +static int __init kbd_led_init(struct device *dev) +{ + int ret; + + kbd_init(); + if (!kbd_led_present) + return -ENODEV; + if (!kbd_als_supported) + kbd_led_groups[1] = NULL; + kbd_led.max_brightness = kbd_get_max_level(); + if (!kbd_led.max_brightness) { + kbd_led.max_brightness = kbd_get_valid_token_counts(); + if (kbd_led.max_brightness) + kbd_led.max_brightness--; + } + + kbd_led_level = kbd_led_level_get(NULL); + + ret = led_classdev_register(dev, &kbd_led); + if (ret) + kbd_led_present = false; + + return ret; +} + +static void brightness_set_exit(struct led_classdev *led_cdev, + enum led_brightness value) +{ + /* Don't change backlight level on exit */ +}; + +static void kbd_led_exit(void) +{ + if (!kbd_led_present) + return; + kbd_led.brightness_set = brightness_set_exit; + led_classdev_unregister(&kbd_led); +} + +static int dell_laptop_notifier_call(struct notifier_block *nb, + unsigned long action, void *data) +{ + bool changed = false; + enum led_brightness new_kbd_led_level; + + switch (action) { + case DELL_LAPTOP_KBD_BACKLIGHT_BRIGHTNESS_CHANGED: + if (!kbd_led_present) + break; + + mutex_lock(&kbd_led_mutex); + new_kbd_led_level = kbd_led_level_get(&kbd_led); + if (kbd_led_level != new_kbd_led_level) { + kbd_led_level = new_kbd_led_level; + changed = true; + } + mutex_unlock(&kbd_led_mutex); + + if (changed) + led_classdev_notify_brightness_hw_changed(&kbd_led, + kbd_led_level); + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block dell_laptop_notifier = { + .notifier_call = dell_laptop_notifier_call, +}; + +static int micmute_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct calling_interface_buffer buffer; + struct calling_interface_token *token; + int state = brightness != LED_OFF; + + if (state == 0) + token = dell_smbios_find_token(GLOBAL_MIC_MUTE_DISABLE); + else + token = dell_smbios_find_token(GLOBAL_MIC_MUTE_ENABLE); + + if (!token) + return -ENODEV; + + dell_fill_request(&buffer, token->location, token->value, 0, 0); + dell_send_request(&buffer, CLASS_TOKEN_WRITE, SELECT_TOKEN_STD); + + return 0; +} + +static struct led_classdev micmute_led_cdev = { + .name = "platform::micmute", + .max_brightness = 1, + .brightness_set_blocking = micmute_led_set, + .default_trigger = "audio-micmute", +}; + +static int __init dell_init(void) +{ + struct calling_interface_token *token; + int max_intensity = 0; + int ret; + + if (!dmi_check_system(dell_device_table)) + return -ENODEV; + + quirks = NULL; + /* find if this machine support other functions */ + dmi_check_system(dell_quirks); + + ret = platform_driver_register(&platform_driver); + if (ret) + goto fail_platform_driver; + platform_device = platform_device_alloc("dell-laptop", -1); + if (!platform_device) { + ret = -ENOMEM; + goto fail_platform_device1; + } + ret = platform_device_add(platform_device); + if (ret) + goto fail_platform_device2; + + ret = dell_setup_rfkill(); + + if (ret) { + pr_warn("Unable to setup rfkill\n"); + goto fail_rfkill; + } + + if (quirks && quirks->touchpad_led) + touchpad_led_init(&platform_device->dev); + + kbd_led_init(&platform_device->dev); + + dell_laptop_dir = debugfs_create_dir("dell_laptop", NULL); + debugfs_create_file("rfkill", 0444, dell_laptop_dir, NULL, + &dell_debugfs_fops); + + dell_laptop_register_notifier(&dell_laptop_notifier); + + if (dell_smbios_find_token(GLOBAL_MIC_MUTE_DISABLE) && + dell_smbios_find_token(GLOBAL_MIC_MUTE_ENABLE)) { + micmute_led_cdev.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE); + ret = led_classdev_register(&platform_device->dev, &micmute_led_cdev); + if (ret < 0) + goto fail_led; + } + + if (acpi_video_get_backlight_type() != acpi_backlight_vendor) + return 0; + + token = dell_smbios_find_token(BRIGHTNESS_TOKEN); + if (token) { + struct calling_interface_buffer buffer; + + dell_fill_request(&buffer, token->location, 0, 0, 0); + ret = dell_send_request(&buffer, + CLASS_TOKEN_READ, SELECT_TOKEN_AC); + if (ret == 0) + max_intensity = buffer.output[3]; + } + + if (max_intensity) { + struct backlight_properties props; + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_PLATFORM; + props.max_brightness = max_intensity; + dell_backlight_device = backlight_device_register("dell_backlight", + &platform_device->dev, + NULL, + &dell_ops, + &props); + + if (IS_ERR(dell_backlight_device)) { + ret = PTR_ERR(dell_backlight_device); + dell_backlight_device = NULL; + goto fail_backlight; + } + + dell_backlight_device->props.brightness = + dell_get_intensity(dell_backlight_device); + if (dell_backlight_device->props.brightness < 0) { + ret = dell_backlight_device->props.brightness; + goto fail_get_brightness; + } + backlight_update_status(dell_backlight_device); + } + + return 0; + +fail_get_brightness: + backlight_device_unregister(dell_backlight_device); +fail_backlight: + led_classdev_unregister(&micmute_led_cdev); +fail_led: + dell_cleanup_rfkill(); +fail_rfkill: + platform_device_del(platform_device); +fail_platform_device2: + platform_device_put(platform_device); +fail_platform_device1: + platform_driver_unregister(&platform_driver); +fail_platform_driver: + return ret; +} + +static void __exit dell_exit(void) +{ + dell_laptop_unregister_notifier(&dell_laptop_notifier); + debugfs_remove_recursive(dell_laptop_dir); + if (quirks && quirks->touchpad_led) + touchpad_led_exit(); + kbd_led_exit(); + backlight_device_unregister(dell_backlight_device); + led_classdev_unregister(&micmute_led_cdev); + dell_cleanup_rfkill(); + if (platform_device) { + platform_device_unregister(platform_device); + platform_driver_unregister(&platform_driver); + } +} + +/* dell-rbtn.c driver export functions which will not work correctly (and could + * cause kernel crash) if they are called before dell-rbtn.c init code. This is + * not problem when dell-rbtn.c is compiled as external module. When both files + * (dell-rbtn.c and dell-laptop.c) are compiled statically into kernel, then we + * need to ensure that dell_init() will be called after initializing dell-rbtn. + * This can be achieved by late_initcall() instead module_init(). + */ +late_initcall(dell_init); +module_exit(dell_exit); + +MODULE_AUTHOR("Matthew Garrett "); +MODULE_AUTHOR("Gabriele Mazzotta "); +MODULE_AUTHOR("Pali Rohár "); +MODULE_DESCRIPTION("Dell laptop driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell/dell-rbtn.c b/drivers/platform/x86/dell/dell-rbtn.c new file mode 100644 index 000000000000..a89fad47ff13 --- /dev/null +++ b/drivers/platform/x86/dell/dell-rbtn.c @@ -0,0 +1,499 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Dell Airplane Mode Switch driver + Copyright (C) 2014-2015 Pali Rohár + +*/ + +#include +#include +#include +#include + +#include "dell-rbtn.h" + +enum rbtn_type { + RBTN_UNKNOWN, + RBTN_TOGGLE, + RBTN_SLIDER, +}; + +struct rbtn_data { + enum rbtn_type type; + struct rfkill *rfkill; + struct input_dev *input_dev; + bool suspended; +}; + + +/* + * acpi functions + */ + +static enum rbtn_type rbtn_check(struct acpi_device *device) +{ + unsigned long long output; + acpi_status status; + + status = acpi_evaluate_integer(device->handle, "CRBT", NULL, &output); + if (ACPI_FAILURE(status)) + return RBTN_UNKNOWN; + + switch (output) { + case 0: + case 1: + return RBTN_TOGGLE; + case 2: + case 3: + return RBTN_SLIDER; + default: + return RBTN_UNKNOWN; + } +} + +static int rbtn_get(struct acpi_device *device) +{ + unsigned long long output; + acpi_status status; + + status = acpi_evaluate_integer(device->handle, "GRBT", NULL, &output); + if (ACPI_FAILURE(status)) + return -EINVAL; + + return !output; +} + +static int rbtn_acquire(struct acpi_device *device, bool enable) +{ + struct acpi_object_list input; + union acpi_object param; + acpi_status status; + + param.type = ACPI_TYPE_INTEGER; + param.integer.value = enable; + input.count = 1; + input.pointer = ¶m; + + status = acpi_evaluate_object(device->handle, "ARBT", &input, NULL); + if (ACPI_FAILURE(status)) + return -EINVAL; + + return 0; +} + + +/* + * rfkill device + */ + +static void rbtn_rfkill_query(struct rfkill *rfkill, void *data) +{ + struct acpi_device *device = data; + int state; + + state = rbtn_get(device); + if (state < 0) + return; + + rfkill_set_states(rfkill, state, state); +} + +static int rbtn_rfkill_set_block(void *data, bool blocked) +{ + /* NOTE: setting soft rfkill state is not supported */ + return -EINVAL; +} + +static const struct rfkill_ops rbtn_ops = { + .query = rbtn_rfkill_query, + .set_block = rbtn_rfkill_set_block, +}; + +static int rbtn_rfkill_init(struct acpi_device *device) +{ + struct rbtn_data *rbtn_data = device->driver_data; + int ret; + + if (rbtn_data->rfkill) + return 0; + + /* + * NOTE: rbtn controls all radio devices, not only WLAN + * but rfkill interface does not support "ANY" type + * so "WLAN" type is used + */ + rbtn_data->rfkill = rfkill_alloc("dell-rbtn", &device->dev, + RFKILL_TYPE_WLAN, &rbtn_ops, device); + if (!rbtn_data->rfkill) + return -ENOMEM; + + ret = rfkill_register(rbtn_data->rfkill); + if (ret) { + rfkill_destroy(rbtn_data->rfkill); + rbtn_data->rfkill = NULL; + return ret; + } + + return 0; +} + +static void rbtn_rfkill_exit(struct acpi_device *device) +{ + struct rbtn_data *rbtn_data = device->driver_data; + + if (!rbtn_data->rfkill) + return; + + rfkill_unregister(rbtn_data->rfkill); + rfkill_destroy(rbtn_data->rfkill); + rbtn_data->rfkill = NULL; +} + +static void rbtn_rfkill_event(struct acpi_device *device) +{ + struct rbtn_data *rbtn_data = device->driver_data; + + if (rbtn_data->rfkill) + rbtn_rfkill_query(rbtn_data->rfkill, device); +} + + +/* + * input device + */ + +static int rbtn_input_init(struct rbtn_data *rbtn_data) +{ + int ret; + + rbtn_data->input_dev = input_allocate_device(); + if (!rbtn_data->input_dev) + return -ENOMEM; + + rbtn_data->input_dev->name = "DELL Wireless hotkeys"; + rbtn_data->input_dev->phys = "dellabce/input0"; + rbtn_data->input_dev->id.bustype = BUS_HOST; + rbtn_data->input_dev->evbit[0] = BIT(EV_KEY); + set_bit(KEY_RFKILL, rbtn_data->input_dev->keybit); + + ret = input_register_device(rbtn_data->input_dev); + if (ret) { + input_free_device(rbtn_data->input_dev); + rbtn_data->input_dev = NULL; + return ret; + } + + return 0; +} + +static void rbtn_input_exit(struct rbtn_data *rbtn_data) +{ + input_unregister_device(rbtn_data->input_dev); + rbtn_data->input_dev = NULL; +} + +static void rbtn_input_event(struct rbtn_data *rbtn_data) +{ + input_report_key(rbtn_data->input_dev, KEY_RFKILL, 1); + input_sync(rbtn_data->input_dev); + input_report_key(rbtn_data->input_dev, KEY_RFKILL, 0); + input_sync(rbtn_data->input_dev); +} + + +/* + * acpi driver + */ + +static int rbtn_add(struct acpi_device *device); +static int rbtn_remove(struct acpi_device *device); +static void rbtn_notify(struct acpi_device *device, u32 event); + +static const struct acpi_device_id rbtn_ids[] = { + { "DELRBTN", 0 }, + { "DELLABCE", 0 }, + + /* + * This driver can also handle the "DELLABC6" device that + * appears on the XPS 13 9350, but that device is disabled by + * the DSDT unless booted with acpi_osi="!Windows 2012" + * acpi_osi="!Windows 2013". + * + * According to Mario at Dell: + * + * DELLABC6 is a custom interface that was created solely to + * have airplane mode support for Windows 7. For Windows 10 + * the proper interface is to use that which is handled by + * intel-hid. A OEM airplane mode driver is not used. + * + * Since the kernel doesn't identify as Windows 7 it would be + * incorrect to do attempt to use that interface. + * + * Even if we override _OSI and bind to DELLABC6, we end up with + * inconsistent behavior in which userspace can get out of sync + * with the rfkill state as it conflicts with events from + * intel-hid. + * + * The upshot is that it is better to just ignore DELLABC6 + * devices. + */ + + { "", 0 }, +}; + +#ifdef CONFIG_PM_SLEEP +static void ACPI_SYSTEM_XFACE rbtn_clear_suspended_flag(void *context) +{ + struct rbtn_data *rbtn_data = context; + + rbtn_data->suspended = false; +} + +static int rbtn_suspend(struct device *dev) +{ + struct acpi_device *device = to_acpi_device(dev); + struct rbtn_data *rbtn_data = acpi_driver_data(device); + + rbtn_data->suspended = true; + + return 0; +} + +static int rbtn_resume(struct device *dev) +{ + struct acpi_device *device = to_acpi_device(dev); + struct rbtn_data *rbtn_data = acpi_driver_data(device); + acpi_status status; + + /* + * Upon resume, some BIOSes send an ACPI notification thet triggers + * an unwanted input event. In order to ignore it, we use a flag + * that we set at suspend and clear once we have received the extra + * ACPI notification. Since ACPI notifications are delivered + * asynchronously to drivers, we clear the flag from the workqueue + * used to deliver the notifications. This should be enough + * to have the flag cleared only after we received the extra + * notification, if any. + */ + status = acpi_os_execute(OSL_NOTIFY_HANDLER, + rbtn_clear_suspended_flag, rbtn_data); + if (ACPI_FAILURE(status)) + rbtn_clear_suspended_flag(rbtn_data); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(rbtn_pm_ops, rbtn_suspend, rbtn_resume); + +static struct acpi_driver rbtn_driver = { + .name = "dell-rbtn", + .ids = rbtn_ids, + .drv.pm = &rbtn_pm_ops, + .ops = { + .add = rbtn_add, + .remove = rbtn_remove, + .notify = rbtn_notify, + }, + .owner = THIS_MODULE, +}; + + +/* + * notifier export functions + */ + +static bool auto_remove_rfkill = true; + +static ATOMIC_NOTIFIER_HEAD(rbtn_chain_head); + +static int rbtn_inc_count(struct device *dev, void *data) +{ + struct acpi_device *device = to_acpi_device(dev); + struct rbtn_data *rbtn_data = device->driver_data; + int *count = data; + + if (rbtn_data->type == RBTN_SLIDER) + (*count)++; + + return 0; +} + +static int rbtn_switch_dev(struct device *dev, void *data) +{ + struct acpi_device *device = to_acpi_device(dev); + struct rbtn_data *rbtn_data = device->driver_data; + bool enable = data; + + if (rbtn_data->type != RBTN_SLIDER) + return 0; + + if (enable) + rbtn_rfkill_init(device); + else + rbtn_rfkill_exit(device); + + return 0; +} + +int dell_rbtn_notifier_register(struct notifier_block *nb) +{ + bool first; + int count; + int ret; + + count = 0; + ret = driver_for_each_device(&rbtn_driver.drv, NULL, &count, + rbtn_inc_count); + if (ret || count == 0) + return -ENODEV; + + first = !rbtn_chain_head.head; + + ret = atomic_notifier_chain_register(&rbtn_chain_head, nb); + if (ret != 0) + return ret; + + if (auto_remove_rfkill && first) + ret = driver_for_each_device(&rbtn_driver.drv, NULL, + (void *)false, rbtn_switch_dev); + + return ret; +} +EXPORT_SYMBOL_GPL(dell_rbtn_notifier_register); + +int dell_rbtn_notifier_unregister(struct notifier_block *nb) +{ + int ret; + + ret = atomic_notifier_chain_unregister(&rbtn_chain_head, nb); + if (ret != 0) + return ret; + + if (auto_remove_rfkill && !rbtn_chain_head.head) + ret = driver_for_each_device(&rbtn_driver.drv, NULL, + (void *)true, rbtn_switch_dev); + + return ret; +} +EXPORT_SYMBOL_GPL(dell_rbtn_notifier_unregister); + + +/* + * acpi driver functions + */ + +static int rbtn_add(struct acpi_device *device) +{ + struct rbtn_data *rbtn_data; + enum rbtn_type type; + int ret = 0; + + type = rbtn_check(device); + if (type == RBTN_UNKNOWN) { + dev_info(&device->dev, "Unknown device type\n"); + return -EINVAL; + } + + ret = rbtn_acquire(device, true); + if (ret < 0) { + dev_err(&device->dev, "Cannot enable device\n"); + return ret; + } + + rbtn_data = devm_kzalloc(&device->dev, sizeof(*rbtn_data), GFP_KERNEL); + if (!rbtn_data) + return -ENOMEM; + + rbtn_data->type = type; + device->driver_data = rbtn_data; + + switch (rbtn_data->type) { + case RBTN_TOGGLE: + ret = rbtn_input_init(rbtn_data); + break; + case RBTN_SLIDER: + if (auto_remove_rfkill && rbtn_chain_head.head) + ret = 0; + else + ret = rbtn_rfkill_init(device); + break; + default: + ret = -EINVAL; + } + + return ret; + +} + +static int rbtn_remove(struct acpi_device *device) +{ + struct rbtn_data *rbtn_data = device->driver_data; + + switch (rbtn_data->type) { + case RBTN_TOGGLE: + rbtn_input_exit(rbtn_data); + break; + case RBTN_SLIDER: + rbtn_rfkill_exit(device); + break; + default: + break; + } + + rbtn_acquire(device, false); + device->driver_data = NULL; + + return 0; +} + +static void rbtn_notify(struct acpi_device *device, u32 event) +{ + struct rbtn_data *rbtn_data = device->driver_data; + + /* + * Some BIOSes send a notification at resume. + * Ignore it to prevent unwanted input events. + */ + if (rbtn_data->suspended) { + dev_dbg(&device->dev, "ACPI notification ignored\n"); + return; + } + + if (event != 0x80) { + dev_info(&device->dev, "Received unknown event (0x%x)\n", + event); + return; + } + + switch (rbtn_data->type) { + case RBTN_TOGGLE: + rbtn_input_event(rbtn_data); + break; + case RBTN_SLIDER: + rbtn_rfkill_event(device); + atomic_notifier_call_chain(&rbtn_chain_head, event, device); + break; + default: + break; + } +} + + +/* + * module functions + */ + +module_acpi_driver(rbtn_driver); + +module_param(auto_remove_rfkill, bool, 0444); + +MODULE_PARM_DESC(auto_remove_rfkill, "Automatically remove rfkill devices when " + "other modules start receiving events " + "from this module and re-add them when " + "the last module stops receiving events " + "(default true)"); +MODULE_DEVICE_TABLE(acpi, rbtn_ids); +MODULE_DESCRIPTION("Dell Airplane Mode Switch driver"); +MODULE_AUTHOR("Pali Rohár "); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell/dell-rbtn.h b/drivers/platform/x86/dell/dell-rbtn.h new file mode 100644 index 000000000000..5e030f926c58 --- /dev/null +++ b/drivers/platform/x86/dell/dell-rbtn.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + Dell Airplane Mode Switch driver + Copyright (C) 2014-2015 Pali Rohár + +*/ + +#ifndef _DELL_RBTN_H_ +#define _DELL_RBTN_H_ + +struct notifier_block; + +int dell_rbtn_notifier_register(struct notifier_block *nb); +int dell_rbtn_notifier_unregister(struct notifier_block *nb); + +#endif diff --git a/drivers/platform/x86/dell/dell-smbios-base.c b/drivers/platform/x86/dell/dell-smbios-base.c new file mode 100644 index 000000000000..3a1dbf199441 --- /dev/null +++ b/drivers/platform/x86/dell/dell-smbios-base.c @@ -0,0 +1,652 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Common functions for kernel modules using Dell SMBIOS + * + * Copyright (c) Red Hat + * Copyright (c) 2014 Gabriele Mazzotta + * Copyright (c) 2014 Pali Rohár + * + * Based on documentation in the libsmbios package: + * Copyright (C) 2005-2014 Dell Inc. + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include "dell-smbios.h" + +static u32 da_supported_commands; +static int da_num_tokens; +static struct platform_device *platform_device; +static struct calling_interface_token *da_tokens; +static struct device_attribute *token_location_attrs; +static struct device_attribute *token_value_attrs; +static struct attribute **token_attrs; +static DEFINE_MUTEX(smbios_mutex); + +struct smbios_device { + struct list_head list; + struct device *device; + int (*call_fn)(struct calling_interface_buffer *arg); +}; + +struct smbios_call { + u32 need_capability; + int cmd_class; + int cmd_select; +}; + +/* calls that are whitelisted for given capabilities */ +static struct smbios_call call_whitelist[] = { + /* generally tokens are allowed, but may be further filtered or + * restricted by token blacklist or whitelist + */ + {CAP_SYS_ADMIN, CLASS_TOKEN_READ, SELECT_TOKEN_STD}, + {CAP_SYS_ADMIN, CLASS_TOKEN_READ, SELECT_TOKEN_AC}, + {CAP_SYS_ADMIN, CLASS_TOKEN_READ, SELECT_TOKEN_BAT}, + {CAP_SYS_ADMIN, CLASS_TOKEN_WRITE, SELECT_TOKEN_STD}, + {CAP_SYS_ADMIN, CLASS_TOKEN_WRITE, SELECT_TOKEN_AC}, + {CAP_SYS_ADMIN, CLASS_TOKEN_WRITE, SELECT_TOKEN_BAT}, + /* used by userspace: fwupdate */ + {CAP_SYS_ADMIN, CLASS_ADMIN_PROP, SELECT_ADMIN_PROP}, + /* used by userspace: fwupd */ + {CAP_SYS_ADMIN, CLASS_INFO, SELECT_DOCK}, + {CAP_SYS_ADMIN, CLASS_FLASH_INTERFACE, SELECT_FLASH_INTERFACE}, +}; + +/* calls that are explicitly blacklisted */ +static struct smbios_call call_blacklist[] = { + {0x0000, 1, 7}, /* manufacturing use */ + {0x0000, 6, 5}, /* manufacturing use */ + {0x0000, 11, 3}, /* write once */ + {0x0000, 11, 7}, /* write once */ + {0x0000, 11, 11}, /* write once */ + {0x0000, 19, -1}, /* diagnostics */ + /* handled by kernel: dell-laptop */ + {0x0000, CLASS_INFO, SELECT_RFKILL}, + {0x0000, CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT}, +}; + +struct token_range { + u32 need_capability; + u16 min; + u16 max; +}; + +/* tokens that are whitelisted for given capabilities */ +static struct token_range token_whitelist[] = { + /* used by userspace: fwupdate */ + {CAP_SYS_ADMIN, CAPSULE_EN_TOKEN, CAPSULE_DIS_TOKEN}, + /* can indicate to userspace that WMI is needed */ + {0x0000, WSMT_EN_TOKEN, WSMT_DIS_TOKEN} +}; + +/* tokens that are explicitly blacklisted */ +static struct token_range token_blacklist[] = { + {0x0000, 0x0058, 0x0059}, /* ME use */ + {0x0000, 0x00CD, 0x00D0}, /* raid shadow copy */ + {0x0000, 0x013A, 0x01FF}, /* sata shadow copy */ + {0x0000, 0x0175, 0x0176}, /* write once */ + {0x0000, 0x0195, 0x0197}, /* diagnostics */ + {0x0000, 0x01DC, 0x01DD}, /* manufacturing use */ + {0x0000, 0x027D, 0x0284}, /* diagnostics */ + {0x0000, 0x02E3, 0x02E3}, /* manufacturing use */ + {0x0000, 0x02FF, 0x02FF}, /* manufacturing use */ + {0x0000, 0x0300, 0x0302}, /* manufacturing use */ + {0x0000, 0x0325, 0x0326}, /* manufacturing use */ + {0x0000, 0x0332, 0x0335}, /* fan control */ + {0x0000, 0x0350, 0x0350}, /* manufacturing use */ + {0x0000, 0x0363, 0x0363}, /* manufacturing use */ + {0x0000, 0x0368, 0x0368}, /* manufacturing use */ + {0x0000, 0x03F6, 0x03F7}, /* manufacturing use */ + {0x0000, 0x049E, 0x049F}, /* manufacturing use */ + {0x0000, 0x04A0, 0x04A3}, /* disagnostics */ + {0x0000, 0x04E6, 0x04E7}, /* manufacturing use */ + {0x0000, 0x4000, 0x7FFF}, /* internal BIOS use */ + {0x0000, 0x9000, 0x9001}, /* internal BIOS use */ + {0x0000, 0xA000, 0xBFFF}, /* write only */ + {0x0000, 0xEFF0, 0xEFFF}, /* internal BIOS use */ + /* handled by kernel: dell-laptop */ + {0x0000, BRIGHTNESS_TOKEN, BRIGHTNESS_TOKEN}, + {0x0000, KBD_LED_OFF_TOKEN, KBD_LED_AUTO_TOKEN}, + {0x0000, KBD_LED_AC_TOKEN, KBD_LED_AC_TOKEN}, + {0x0000, KBD_LED_AUTO_25_TOKEN, KBD_LED_AUTO_75_TOKEN}, + {0x0000, KBD_LED_AUTO_100_TOKEN, KBD_LED_AUTO_100_TOKEN}, + {0x0000, GLOBAL_MIC_MUTE_ENABLE, GLOBAL_MIC_MUTE_DISABLE}, +}; + +static LIST_HEAD(smbios_device_list); + +int dell_smbios_error(int value) +{ + switch (value) { + case 0: /* Completed successfully */ + return 0; + case -1: /* Completed with error */ + return -EIO; + case -2: /* Function not supported */ + return -ENXIO; + default: /* Unknown error */ + return -EINVAL; + } +} +EXPORT_SYMBOL_GPL(dell_smbios_error); + +int dell_smbios_register_device(struct device *d, void *call_fn) +{ + struct smbios_device *priv; + + priv = devm_kzalloc(d, sizeof(struct smbios_device), GFP_KERNEL); + if (!priv) + return -ENOMEM; + get_device(d); + priv->device = d; + priv->call_fn = call_fn; + mutex_lock(&smbios_mutex); + list_add_tail(&priv->list, &smbios_device_list); + mutex_unlock(&smbios_mutex); + dev_dbg(d, "Added device: %s\n", d->driver->name); + return 0; +} +EXPORT_SYMBOL_GPL(dell_smbios_register_device); + +void dell_smbios_unregister_device(struct device *d) +{ + struct smbios_device *priv; + + mutex_lock(&smbios_mutex); + list_for_each_entry(priv, &smbios_device_list, list) { + if (priv->device == d) { + list_del(&priv->list); + put_device(d); + break; + } + } + mutex_unlock(&smbios_mutex); + dev_dbg(d, "Remove device: %s\n", d->driver->name); +} +EXPORT_SYMBOL_GPL(dell_smbios_unregister_device); + +int dell_smbios_call_filter(struct device *d, + struct calling_interface_buffer *buffer) +{ + u16 t = 0; + int i; + + /* can't make calls over 30 */ + if (buffer->cmd_class > 30) { + dev_dbg(d, "class too big: %u\n", buffer->cmd_class); + return -EINVAL; + } + + /* supported calls on the particular system */ + if (!(da_supported_commands & (1 << buffer->cmd_class))) { + dev_dbg(d, "invalid command, supported commands: 0x%8x\n", + da_supported_commands); + return -EINVAL; + } + + /* match against call blacklist */ + for (i = 0; i < ARRAY_SIZE(call_blacklist); i++) { + if (buffer->cmd_class != call_blacklist[i].cmd_class) + continue; + if (buffer->cmd_select != call_blacklist[i].cmd_select && + call_blacklist[i].cmd_select != -1) + continue; + dev_dbg(d, "blacklisted command: %u/%u\n", + buffer->cmd_class, buffer->cmd_select); + return -EINVAL; + } + + /* if a token call, find token ID */ + + if ((buffer->cmd_class == CLASS_TOKEN_READ || + buffer->cmd_class == CLASS_TOKEN_WRITE) && + buffer->cmd_select < 3) { + /* tokens enabled ? */ + if (!da_tokens) { + dev_dbg(d, "no token support on this system\n"); + return -EINVAL; + } + + /* find the matching token ID */ + for (i = 0; i < da_num_tokens; i++) { + if (da_tokens[i].location != buffer->input[0]) + continue; + t = da_tokens[i].tokenID; + break; + } + + /* token call; but token didn't exist */ + if (!t) { + dev_dbg(d, "token at location %04x doesn't exist\n", + buffer->input[0]); + return -EINVAL; + } + + /* match against token blacklist */ + for (i = 0; i < ARRAY_SIZE(token_blacklist); i++) { + if (!token_blacklist[i].min || !token_blacklist[i].max) + continue; + if (t >= token_blacklist[i].min && + t <= token_blacklist[i].max) + return -EINVAL; + } + + /* match against token whitelist */ + for (i = 0; i < ARRAY_SIZE(token_whitelist); i++) { + if (!token_whitelist[i].min || !token_whitelist[i].max) + continue; + if (t < token_whitelist[i].min || + t > token_whitelist[i].max) + continue; + if (!token_whitelist[i].need_capability || + capable(token_whitelist[i].need_capability)) { + dev_dbg(d, "whitelisted token: %x\n", t); + return 0; + } + + } + } + /* match against call whitelist */ + for (i = 0; i < ARRAY_SIZE(call_whitelist); i++) { + if (buffer->cmd_class != call_whitelist[i].cmd_class) + continue; + if (buffer->cmd_select != call_whitelist[i].cmd_select) + continue; + if (!call_whitelist[i].need_capability || + capable(call_whitelist[i].need_capability)) { + dev_dbg(d, "whitelisted capable command: %u/%u\n", + buffer->cmd_class, buffer->cmd_select); + return 0; + } + dev_dbg(d, "missing capability %d for %u/%u\n", + call_whitelist[i].need_capability, + buffer->cmd_class, buffer->cmd_select); + + } + + /* not in a whitelist, only allow processes with capabilities */ + if (capable(CAP_SYS_RAWIO)) { + dev_dbg(d, "Allowing %u/%u due to CAP_SYS_RAWIO\n", + buffer->cmd_class, buffer->cmd_select); + return 0; + } + + return -EACCES; +} +EXPORT_SYMBOL_GPL(dell_smbios_call_filter); + +int dell_smbios_call(struct calling_interface_buffer *buffer) +{ + int (*call_fn)(struct calling_interface_buffer *) = NULL; + struct device *selected_dev = NULL; + struct smbios_device *priv; + int ret; + + mutex_lock(&smbios_mutex); + list_for_each_entry(priv, &smbios_device_list, list) { + if (!selected_dev || priv->device->id >= selected_dev->id) { + dev_dbg(priv->device, "Trying device ID: %d\n", + priv->device->id); + call_fn = priv->call_fn; + selected_dev = priv->device; + } + } + + if (!selected_dev) { + ret = -ENODEV; + pr_err("No dell-smbios drivers are loaded\n"); + goto out_smbios_call; + } + + ret = call_fn(buffer); + +out_smbios_call: + mutex_unlock(&smbios_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(dell_smbios_call); + +struct calling_interface_token *dell_smbios_find_token(int tokenid) +{ + int i; + + if (!da_tokens) + return NULL; + + for (i = 0; i < da_num_tokens; i++) { + if (da_tokens[i].tokenID == tokenid) + return &da_tokens[i]; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(dell_smbios_find_token); + +static BLOCKING_NOTIFIER_HEAD(dell_laptop_chain_head); + +int dell_laptop_register_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&dell_laptop_chain_head, nb); +} +EXPORT_SYMBOL_GPL(dell_laptop_register_notifier); + +int dell_laptop_unregister_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&dell_laptop_chain_head, nb); +} +EXPORT_SYMBOL_GPL(dell_laptop_unregister_notifier); + +void dell_laptop_call_notifier(unsigned long action, void *data) +{ + blocking_notifier_call_chain(&dell_laptop_chain_head, action, data); +} +EXPORT_SYMBOL_GPL(dell_laptop_call_notifier); + +static void __init parse_da_table(const struct dmi_header *dm) +{ + /* Final token is a terminator, so we don't want to copy it */ + int tokens = (dm->length-11)/sizeof(struct calling_interface_token)-1; + struct calling_interface_token *new_da_tokens; + struct calling_interface_structure *table = + container_of(dm, struct calling_interface_structure, header); + + /* + * 4 bytes of table header, plus 7 bytes of Dell header + * plus at least 6 bytes of entry + */ + + if (dm->length < 17) + return; + + da_supported_commands = table->supportedCmds; + + new_da_tokens = krealloc(da_tokens, (da_num_tokens + tokens) * + sizeof(struct calling_interface_token), + GFP_KERNEL); + + if (!new_da_tokens) + return; + da_tokens = new_da_tokens; + + memcpy(da_tokens+da_num_tokens, table->tokens, + sizeof(struct calling_interface_token) * tokens); + + da_num_tokens += tokens; +} + +static void zero_duplicates(struct device *dev) +{ + int i, j; + + for (i = 0; i < da_num_tokens; i++) { + if (da_tokens[i].tokenID == 0) + continue; + for (j = i+1; j < da_num_tokens; j++) { + if (da_tokens[j].tokenID == 0) + continue; + if (da_tokens[i].tokenID == da_tokens[j].tokenID) { + dev_dbg(dev, "Zeroing dup token ID %x(%x/%x)\n", + da_tokens[j].tokenID, + da_tokens[j].location, + da_tokens[j].value); + da_tokens[j].tokenID = 0; + } + } + } +} + +static void __init find_tokens(const struct dmi_header *dm, void *dummy) +{ + switch (dm->type) { + case 0xd4: /* Indexed IO */ + case 0xd5: /* Protected Area Type 1 */ + case 0xd6: /* Protected Area Type 2 */ + break; + case 0xda: /* Calling interface */ + parse_da_table(dm); + break; + } +} + +static int match_attribute(struct device *dev, + struct device_attribute *attr) +{ + int i; + + for (i = 0; i < da_num_tokens * 2; i++) { + if (!token_attrs[i]) + continue; + if (strcmp(token_attrs[i]->name, attr->attr.name) == 0) + return i/2; + } + dev_dbg(dev, "couldn't match: %s\n", attr->attr.name); + return -EINVAL; +} + +static ssize_t location_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + i = match_attribute(dev, attr); + if (i > 0) + return scnprintf(buf, PAGE_SIZE, "%08x", da_tokens[i].location); + return 0; +} + +static ssize_t value_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + i = match_attribute(dev, attr); + if (i > 0) + return scnprintf(buf, PAGE_SIZE, "%08x", da_tokens[i].value); + return 0; +} + +static struct attribute_group smbios_attribute_group = { + .name = "tokens" +}; + +static struct platform_driver platform_driver = { + .driver = { + .name = "dell-smbios", + }, +}; + +static int build_tokens_sysfs(struct platform_device *dev) +{ + char *location_name; + char *value_name; + size_t size; + int ret; + int i, j; + + /* (number of tokens + 1 for null terminated */ + size = sizeof(struct device_attribute) * (da_num_tokens + 1); + token_location_attrs = kzalloc(size, GFP_KERNEL); + if (!token_location_attrs) + return -ENOMEM; + token_value_attrs = kzalloc(size, GFP_KERNEL); + if (!token_value_attrs) + goto out_allocate_value; + + /* need to store both location and value + terminator*/ + size = sizeof(struct attribute *) * ((2 * da_num_tokens) + 1); + token_attrs = kzalloc(size, GFP_KERNEL); + if (!token_attrs) + goto out_allocate_attrs; + + for (i = 0, j = 0; i < da_num_tokens; i++) { + /* skip empty */ + if (da_tokens[i].tokenID == 0) + continue; + /* add location */ + location_name = kasprintf(GFP_KERNEL, "%04x_location", + da_tokens[i].tokenID); + if (location_name == NULL) + goto out_unwind_strings; + sysfs_attr_init(&token_location_attrs[i].attr); + token_location_attrs[i].attr.name = location_name; + token_location_attrs[i].attr.mode = 0444; + token_location_attrs[i].show = location_show; + token_attrs[j++] = &token_location_attrs[i].attr; + + /* add value */ + value_name = kasprintf(GFP_KERNEL, "%04x_value", + da_tokens[i].tokenID); + if (value_name == NULL) + goto loop_fail_create_value; + sysfs_attr_init(&token_value_attrs[i].attr); + token_value_attrs[i].attr.name = value_name; + token_value_attrs[i].attr.mode = 0444; + token_value_attrs[i].show = value_show; + token_attrs[j++] = &token_value_attrs[i].attr; + continue; + +loop_fail_create_value: + kfree(location_name); + goto out_unwind_strings; + } + smbios_attribute_group.attrs = token_attrs; + + ret = sysfs_create_group(&dev->dev.kobj, &smbios_attribute_group); + if (ret) + goto out_unwind_strings; + return 0; + +out_unwind_strings: + while (i--) { + kfree(token_location_attrs[i].attr.name); + kfree(token_value_attrs[i].attr.name); + } + kfree(token_attrs); +out_allocate_attrs: + kfree(token_value_attrs); +out_allocate_value: + kfree(token_location_attrs); + + return -ENOMEM; +} + +static void free_group(struct platform_device *pdev) +{ + int i; + + sysfs_remove_group(&pdev->dev.kobj, + &smbios_attribute_group); + for (i = 0; i < da_num_tokens; i++) { + kfree(token_location_attrs[i].attr.name); + kfree(token_value_attrs[i].attr.name); + } + kfree(token_attrs); + kfree(token_value_attrs); + kfree(token_location_attrs); +} + +static int __init dell_smbios_init(void) +{ + int ret, wmi, smm; + + if (!dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "Dell System", NULL) && + !dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "www.dell.com", NULL)) { + pr_err("Unable to run on non-Dell system\n"); + return -ENODEV; + } + + dmi_walk(find_tokens, NULL); + + ret = platform_driver_register(&platform_driver); + if (ret) + goto fail_platform_driver; + + platform_device = platform_device_alloc("dell-smbios", 0); + if (!platform_device) { + ret = -ENOMEM; + goto fail_platform_device_alloc; + } + ret = platform_device_add(platform_device); + if (ret) + goto fail_platform_device_add; + + /* register backends */ + wmi = init_dell_smbios_wmi(); + if (wmi) + pr_debug("Failed to initialize WMI backend: %d\n", wmi); + smm = init_dell_smbios_smm(); + if (smm) + pr_debug("Failed to initialize SMM backend: %d\n", smm); + if (wmi && smm) { + pr_err("No SMBIOS backends available (wmi: %d, smm: %d)\n", + wmi, smm); + ret = -ENODEV; + goto fail_create_group; + } + + if (da_tokens) { + /* duplicate tokens will cause problems building sysfs files */ + zero_duplicates(&platform_device->dev); + + ret = build_tokens_sysfs(platform_device); + if (ret) + goto fail_sysfs; + } + + return 0; + +fail_sysfs: + free_group(platform_device); + +fail_create_group: + platform_device_del(platform_device); + +fail_platform_device_add: + platform_device_put(platform_device); + +fail_platform_device_alloc: + platform_driver_unregister(&platform_driver); + +fail_platform_driver: + kfree(da_tokens); + return ret; +} + +static void __exit dell_smbios_exit(void) +{ + exit_dell_smbios_wmi(); + exit_dell_smbios_smm(); + mutex_lock(&smbios_mutex); + if (platform_device) { + if (da_tokens) + free_group(platform_device); + platform_device_unregister(platform_device); + platform_driver_unregister(&platform_driver); + } + kfree(da_tokens); + mutex_unlock(&smbios_mutex); +} + +module_init(dell_smbios_init); +module_exit(dell_smbios_exit); + +MODULE_AUTHOR("Matthew Garrett "); +MODULE_AUTHOR("Gabriele Mazzotta "); +MODULE_AUTHOR("Pali Rohár "); +MODULE_AUTHOR("Mario Limonciello "); +MODULE_DESCRIPTION("Common functions for kernel modules using Dell SMBIOS"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell/dell-smbios-smm.c b/drivers/platform/x86/dell/dell-smbios-smm.c new file mode 100644 index 000000000000..97c52a839a3e --- /dev/null +++ b/drivers/platform/x86/dell/dell-smbios-smm.c @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * SMI methods for use with dell-smbios + * + * Copyright (c) Red Hat + * Copyright (c) 2014 Gabriele Mazzotta + * Copyright (c) 2014 Pali Rohár + * Copyright (c) 2017 Dell Inc. + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include "dcdbas.h" +#include "dell-smbios.h" + +static int da_command_address; +static int da_command_code; +static struct calling_interface_buffer *buffer; +static struct platform_device *platform_device; +static DEFINE_MUTEX(smm_mutex); + +static const struct dmi_system_id dell_device_table[] __initconst = { + { + .ident = "Dell laptop", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "8"), + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /*Laptop*/ + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "10"), /*Notebook*/ + }, + }, + { + .ident = "Dell Computer Corporation", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"), + DMI_MATCH(DMI_CHASSIS_TYPE, "8"), + }, + }, + { } +}; +MODULE_DEVICE_TABLE(dmi, dell_device_table); + +static void parse_da_table(const struct dmi_header *dm) +{ + struct calling_interface_structure *table = + container_of(dm, struct calling_interface_structure, header); + + /* 4 bytes of table header, plus 7 bytes of Dell header, plus at least + * 6 bytes of entry + */ + if (dm->length < 17) + return; + + da_command_address = table->cmdIOAddress; + da_command_code = table->cmdIOCode; +} + +static void find_cmd_address(const struct dmi_header *dm, void *dummy) +{ + switch (dm->type) { + case 0xda: /* Calling interface */ + parse_da_table(dm); + break; + } +} + +static int dell_smbios_smm_call(struct calling_interface_buffer *input) +{ + struct smi_cmd command; + size_t size; + + size = sizeof(struct calling_interface_buffer); + command.magic = SMI_CMD_MAGIC; + command.command_address = da_command_address; + command.command_code = da_command_code; + command.ebx = virt_to_phys(buffer); + command.ecx = 0x42534931; + + mutex_lock(&smm_mutex); + memcpy(buffer, input, size); + dcdbas_smi_request(&command); + memcpy(input, buffer, size); + mutex_unlock(&smm_mutex); + return 0; +} + +/* When enabled this indicates that SMM won't work */ +static bool test_wsmt_enabled(void) +{ + struct calling_interface_token *wsmt; + + /* if token doesn't exist, SMM will work */ + wsmt = dell_smbios_find_token(WSMT_EN_TOKEN); + if (!wsmt) + return false; + + /* If token exists, try to access over SMM but set a dummy return. + * - If WSMT disabled it will be overwritten by SMM + * - If WSMT enabled then dummy value will remain + */ + buffer->cmd_class = CLASS_TOKEN_READ; + buffer->cmd_select = SELECT_TOKEN_STD; + memset(buffer, 0, sizeof(struct calling_interface_buffer)); + buffer->input[0] = wsmt->location; + buffer->output[0] = 99; + dell_smbios_smm_call(buffer); + if (buffer->output[0] == 99) + return true; + + return false; +} + +int init_dell_smbios_smm(void) +{ + int ret; + /* + * Allocate buffer below 4GB for SMI data--only 32-bit physical addr + * is passed to SMI handler. + */ + buffer = (void *)__get_free_page(GFP_KERNEL | GFP_DMA32); + if (!buffer) + return -ENOMEM; + + dmi_walk(find_cmd_address, NULL); + + if (test_wsmt_enabled()) { + pr_debug("Disabling due to WSMT enabled\n"); + ret = -ENODEV; + goto fail_wsmt; + } + + platform_device = platform_device_alloc("dell-smbios", 1); + if (!platform_device) { + ret = -ENOMEM; + goto fail_platform_device_alloc; + } + + ret = platform_device_add(platform_device); + if (ret) + goto fail_platform_device_add; + + ret = dell_smbios_register_device(&platform_device->dev, + &dell_smbios_smm_call); + if (ret) + goto fail_register; + + return 0; + +fail_register: + platform_device_del(platform_device); + +fail_platform_device_add: + platform_device_put(platform_device); + +fail_wsmt: +fail_platform_device_alloc: + free_page((unsigned long)buffer); + return ret; +} + +void exit_dell_smbios_smm(void) +{ + if (platform_device) { + dell_smbios_unregister_device(&platform_device->dev); + platform_device_unregister(platform_device); + free_page((unsigned long)buffer); + } +} diff --git a/drivers/platform/x86/dell/dell-smbios-wmi.c b/drivers/platform/x86/dell/dell-smbios-wmi.c new file mode 100644 index 000000000000..27a298b7c541 --- /dev/null +++ b/drivers/platform/x86/dell/dell-smbios-wmi.c @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * WMI methods for use with dell-smbios + * + * Copyright (c) 2017 Dell Inc. + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include "dell-smbios.h" +#include "dell-wmi-descriptor.h" + +static DEFINE_MUTEX(call_mutex); +static DEFINE_MUTEX(list_mutex); +static int wmi_supported; + +struct misc_bios_flags_structure { + struct dmi_header header; + u16 flags0; +} __packed; +#define FLAG_HAS_ACPI_WMI 0x02 + +#define DELL_WMI_SMBIOS_GUID "A80593CE-A997-11DA-B012-B622A1EF5492" + +struct wmi_smbios_priv { + struct dell_wmi_smbios_buffer *buf; + struct list_head list; + struct wmi_device *wdev; + struct device *child; + u32 req_buf_size; +}; +static LIST_HEAD(wmi_list); + +static inline struct wmi_smbios_priv *get_first_smbios_priv(void) +{ + return list_first_entry_or_null(&wmi_list, + struct wmi_smbios_priv, + list); +} + +static int run_smbios_call(struct wmi_device *wdev) +{ + struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL}; + struct wmi_smbios_priv *priv; + struct acpi_buffer input; + union acpi_object *obj; + acpi_status status; + + priv = dev_get_drvdata(&wdev->dev); + input.length = priv->req_buf_size - sizeof(u64); + input.pointer = &priv->buf->std; + + dev_dbg(&wdev->dev, "evaluating: %u/%u [%x,%x,%x,%x]\n", + priv->buf->std.cmd_class, priv->buf->std.cmd_select, + priv->buf->std.input[0], priv->buf->std.input[1], + priv->buf->std.input[2], priv->buf->std.input[3]); + + status = wmidev_evaluate_method(wdev, 0, 1, &input, &output); + if (ACPI_FAILURE(status)) + return -EIO; + obj = (union acpi_object *)output.pointer; + if (obj->type != ACPI_TYPE_BUFFER) { + dev_dbg(&wdev->dev, "received type: %d\n", obj->type); + if (obj->type == ACPI_TYPE_INTEGER) + dev_dbg(&wdev->dev, "SMBIOS call failed: %llu\n", + obj->integer.value); + return -EIO; + } + memcpy(&priv->buf->std, obj->buffer.pointer, obj->buffer.length); + dev_dbg(&wdev->dev, "result: [%08x,%08x,%08x,%08x]\n", + priv->buf->std.output[0], priv->buf->std.output[1], + priv->buf->std.output[2], priv->buf->std.output[3]); + kfree(output.pointer); + + return 0; +} + +static int dell_smbios_wmi_call(struct calling_interface_buffer *buffer) +{ + struct wmi_smbios_priv *priv; + size_t difference; + size_t size; + int ret; + + mutex_lock(&call_mutex); + priv = get_first_smbios_priv(); + if (!priv) { + ret = -ENODEV; + goto out_wmi_call; + } + + size = sizeof(struct calling_interface_buffer); + difference = priv->req_buf_size - sizeof(u64) - size; + + memset(&priv->buf->ext, 0, difference); + memcpy(&priv->buf->std, buffer, size); + ret = run_smbios_call(priv->wdev); + memcpy(buffer, &priv->buf->std, size); +out_wmi_call: + mutex_unlock(&call_mutex); + + return ret; +} + +static long dell_smbios_wmi_filter(struct wmi_device *wdev, unsigned int cmd, + struct wmi_ioctl_buffer *arg) +{ + struct wmi_smbios_priv *priv; + int ret = 0; + + switch (cmd) { + case DELL_WMI_SMBIOS_CMD: + mutex_lock(&call_mutex); + priv = dev_get_drvdata(&wdev->dev); + if (!priv) { + ret = -ENODEV; + goto fail_smbios_cmd; + } + memcpy(priv->buf, arg, priv->req_buf_size); + if (dell_smbios_call_filter(&wdev->dev, &priv->buf->std)) { + dev_err(&wdev->dev, "Invalid call %d/%d:%8x\n", + priv->buf->std.cmd_class, + priv->buf->std.cmd_select, + priv->buf->std.input[0]); + ret = -EFAULT; + goto fail_smbios_cmd; + } + ret = run_smbios_call(priv->wdev); + if (ret) + goto fail_smbios_cmd; + memcpy(arg, priv->buf, priv->req_buf_size); +fail_smbios_cmd: + mutex_unlock(&call_mutex); + break; + default: + ret = -ENOIOCTLCMD; + } + return ret; +} + +static int dell_smbios_wmi_probe(struct wmi_device *wdev, const void *context) +{ + struct wmi_driver *wdriver = + container_of(wdev->dev.driver, struct wmi_driver, driver); + struct wmi_smbios_priv *priv; + u32 hotfix; + int count; + int ret; + + ret = dell_wmi_get_descriptor_valid(); + if (ret) + return ret; + + priv = devm_kzalloc(&wdev->dev, sizeof(struct wmi_smbios_priv), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* WMI buffer size will be either 4k or 32k depending on machine */ + if (!dell_wmi_get_size(&priv->req_buf_size)) + return -EPROBE_DEFER; + + /* some SMBIOS calls fail unless BIOS contains hotfix */ + if (!dell_wmi_get_hotfix(&hotfix)) + return -EPROBE_DEFER; + if (!hotfix) { + dev_warn(&wdev->dev, + "WMI SMBIOS userspace interface not supported(%u), try upgrading to a newer BIOS\n", + hotfix); + wdriver->filter_callback = NULL; + } + + /* add in the length object we will use internally with ioctl */ + priv->req_buf_size += sizeof(u64); + ret = set_required_buffer_size(wdev, priv->req_buf_size); + if (ret) + return ret; + + count = get_order(priv->req_buf_size); + priv->buf = (void *)__get_free_pages(GFP_KERNEL, count); + if (!priv->buf) + return -ENOMEM; + + /* ID is used by dell-smbios to set priority of drivers */ + wdev->dev.id = 1; + ret = dell_smbios_register_device(&wdev->dev, &dell_smbios_wmi_call); + if (ret) + goto fail_register; + + priv->wdev = wdev; + dev_set_drvdata(&wdev->dev, priv); + mutex_lock(&list_mutex); + list_add_tail(&priv->list, &wmi_list); + mutex_unlock(&list_mutex); + + return 0; + +fail_register: + free_pages((unsigned long)priv->buf, count); + return ret; +} + +static int dell_smbios_wmi_remove(struct wmi_device *wdev) +{ + struct wmi_smbios_priv *priv = dev_get_drvdata(&wdev->dev); + int count; + + mutex_lock(&call_mutex); + mutex_lock(&list_mutex); + list_del(&priv->list); + mutex_unlock(&list_mutex); + dell_smbios_unregister_device(&wdev->dev); + count = get_order(priv->req_buf_size); + free_pages((unsigned long)priv->buf, count); + mutex_unlock(&call_mutex); + return 0; +} + +static const struct wmi_device_id dell_smbios_wmi_id_table[] = { + { .guid_string = DELL_WMI_SMBIOS_GUID }, + { }, +}; + +static void parse_b1_table(const struct dmi_header *dm) +{ + struct misc_bios_flags_structure *flags = + container_of(dm, struct misc_bios_flags_structure, header); + + /* 4 bytes header, 8 bytes flags */ + if (dm->length < 12) + return; + if (dm->handle != 0xb100) + return; + if ((flags->flags0 & FLAG_HAS_ACPI_WMI)) + wmi_supported = 1; +} + +static void find_b1(const struct dmi_header *dm, void *dummy) +{ + switch (dm->type) { + case 0xb1: /* misc bios flags */ + parse_b1_table(dm); + break; + } +} + +static struct wmi_driver dell_smbios_wmi_driver = { + .driver = { + .name = "dell-smbios", + }, + .probe = dell_smbios_wmi_probe, + .remove = dell_smbios_wmi_remove, + .id_table = dell_smbios_wmi_id_table, + .filter_callback = dell_smbios_wmi_filter, +}; + +int init_dell_smbios_wmi(void) +{ + dmi_walk(find_b1, NULL); + + if (!wmi_supported) + return -ENODEV; + + return wmi_driver_register(&dell_smbios_wmi_driver); +} + +void exit_dell_smbios_wmi(void) +{ + wmi_driver_unregister(&dell_smbios_wmi_driver); +} + +MODULE_DEVICE_TABLE(wmi, dell_smbios_wmi_id_table); diff --git a/drivers/platform/x86/dell/dell-smbios.h b/drivers/platform/x86/dell/dell-smbios.h new file mode 100644 index 000000000000..75fa8ea0476d --- /dev/null +++ b/drivers/platform/x86/dell/dell-smbios.h @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Common functions for kernel modules using Dell SMBIOS + * + * Copyright (c) Red Hat + * Copyright (c) 2014 Gabriele Mazzotta + * Copyright (c) 2014 Pali Rohár + * + * Based on documentation in the libsmbios package: + * Copyright (C) 2005-2014 Dell Inc. + */ + +#ifndef _DELL_SMBIOS_H_ +#define _DELL_SMBIOS_H_ + +#include +#include + +/* Classes and selects used only in kernel drivers */ +#define CLASS_KBD_BACKLIGHT 4 +#define SELECT_KBD_BACKLIGHT 11 + +/* Tokens used in kernel drivers, any of these + * should be filtered from userspace access + */ +#define BRIGHTNESS_TOKEN 0x007d +#define KBD_LED_AC_TOKEN 0x0451 +#define KBD_LED_OFF_TOKEN 0x01E1 +#define KBD_LED_ON_TOKEN 0x01E2 +#define KBD_LED_AUTO_TOKEN 0x01E3 +#define KBD_LED_AUTO_25_TOKEN 0x02EA +#define KBD_LED_AUTO_50_TOKEN 0x02EB +#define KBD_LED_AUTO_75_TOKEN 0x02EC +#define KBD_LED_AUTO_100_TOKEN 0x02F6 +#define GLOBAL_MIC_MUTE_ENABLE 0x0364 +#define GLOBAL_MIC_MUTE_DISABLE 0x0365 + +struct notifier_block; + +struct calling_interface_token { + u16 tokenID; + u16 location; + union { + u16 value; + u16 stringlength; + }; +}; + +struct calling_interface_structure { + struct dmi_header header; + u16 cmdIOAddress; + u8 cmdIOCode; + u32 supportedCmds; + struct calling_interface_token tokens[]; +} __packed; + +int dell_smbios_register_device(struct device *d, void *call_fn); +void dell_smbios_unregister_device(struct device *d); + +int dell_smbios_error(int value); +int dell_smbios_call_filter(struct device *d, + struct calling_interface_buffer *buffer); +int dell_smbios_call(struct calling_interface_buffer *buffer); + +struct calling_interface_token *dell_smbios_find_token(int tokenid); + +enum dell_laptop_notifier_actions { + DELL_LAPTOP_KBD_BACKLIGHT_BRIGHTNESS_CHANGED, +}; + +int dell_laptop_register_notifier(struct notifier_block *nb); +int dell_laptop_unregister_notifier(struct notifier_block *nb); +void dell_laptop_call_notifier(unsigned long action, void *data); + +/* for the supported backends */ +#ifdef CONFIG_DELL_SMBIOS_WMI +int init_dell_smbios_wmi(void); +void exit_dell_smbios_wmi(void); +#else /* CONFIG_DELL_SMBIOS_WMI */ +static inline int init_dell_smbios_wmi(void) +{ + return -ENODEV; +} +static inline void exit_dell_smbios_wmi(void) +{} +#endif /* CONFIG_DELL_SMBIOS_WMI */ + +#ifdef CONFIG_DELL_SMBIOS_SMM +int init_dell_smbios_smm(void); +void exit_dell_smbios_smm(void); +#else /* CONFIG_DELL_SMBIOS_SMM */ +static inline int init_dell_smbios_smm(void) +{ + return -ENODEV; +} +static inline void exit_dell_smbios_smm(void) +{} +#endif /* CONFIG_DELL_SMBIOS_SMM */ + +#endif /* _DELL_SMBIOS_H_ */ diff --git a/drivers/platform/x86/dell/dell-smo8800.c b/drivers/platform/x86/dell/dell-smo8800.c new file mode 100644 index 000000000000..5d9304a7de1b --- /dev/null +++ b/drivers/platform/x86/dell/dell-smo8800.c @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * dell-smo8800.c - Dell Latitude ACPI SMO88XX freefall sensor driver + * + * Copyright (C) 2012 Sonal Santan + * Copyright (C) 2014 Pali Rohár + * + * This is loosely based on lis3lv02d driver. + */ + +#define DRIVER_NAME "smo8800" + +#include +#include +#include +#include +#include +#include +#include + +struct smo8800_device { + u32 irq; /* acpi device irq */ + atomic_t counter; /* count after last read */ + struct miscdevice miscdev; /* for /dev/freefall */ + unsigned long misc_opened; /* whether the device is open */ + wait_queue_head_t misc_wait; /* Wait queue for the misc dev */ + struct device *dev; /* acpi device */ +}; + +static irqreturn_t smo8800_interrupt_quick(int irq, void *data) +{ + struct smo8800_device *smo8800 = data; + + atomic_inc(&smo8800->counter); + wake_up_interruptible(&smo8800->misc_wait); + return IRQ_WAKE_THREAD; +} + +static irqreturn_t smo8800_interrupt_thread(int irq, void *data) +{ + struct smo8800_device *smo8800 = data; + + dev_info(smo8800->dev, "detected free fall\n"); + return IRQ_HANDLED; +} + +static acpi_status smo8800_get_resource(struct acpi_resource *resource, + void *context) +{ + struct acpi_resource_extended_irq *irq; + + if (resource->type != ACPI_RESOURCE_TYPE_EXTENDED_IRQ) + return AE_OK; + + irq = &resource->data.extended_irq; + if (!irq || !irq->interrupt_count) + return AE_OK; + + *((u32 *)context) = irq->interrupts[0]; + return AE_CTRL_TERMINATE; +} + +static u32 smo8800_get_irq(struct acpi_device *device) +{ + u32 irq = 0; + acpi_status status; + + status = acpi_walk_resources(device->handle, METHOD_NAME__CRS, + smo8800_get_resource, &irq); + if (ACPI_FAILURE(status)) { + dev_err(&device->dev, "acpi_walk_resources failed\n"); + return 0; + } + + return irq; +} + +static ssize_t smo8800_misc_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + struct smo8800_device *smo8800 = container_of(file->private_data, + struct smo8800_device, miscdev); + + u32 data = 0; + unsigned char byte_data; + ssize_t retval = 1; + + if (count < 1) + return -EINVAL; + + atomic_set(&smo8800->counter, 0); + retval = wait_event_interruptible(smo8800->misc_wait, + (data = atomic_xchg(&smo8800->counter, 0))); + + if (retval) + return retval; + + retval = 1; + + if (data < 255) + byte_data = data; + else + byte_data = 255; + + if (put_user(byte_data, buf)) + retval = -EFAULT; + + return retval; +} + +static int smo8800_misc_open(struct inode *inode, struct file *file) +{ + struct smo8800_device *smo8800 = container_of(file->private_data, + struct smo8800_device, miscdev); + + if (test_and_set_bit(0, &smo8800->misc_opened)) + return -EBUSY; /* already open */ + + atomic_set(&smo8800->counter, 0); + return 0; +} + +static int smo8800_misc_release(struct inode *inode, struct file *file) +{ + struct smo8800_device *smo8800 = container_of(file->private_data, + struct smo8800_device, miscdev); + + clear_bit(0, &smo8800->misc_opened); /* release the device */ + return 0; +} + +static const struct file_operations smo8800_misc_fops = { + .owner = THIS_MODULE, + .read = smo8800_misc_read, + .open = smo8800_misc_open, + .release = smo8800_misc_release, +}; + +static int smo8800_add(struct acpi_device *device) +{ + int err; + struct smo8800_device *smo8800; + + smo8800 = devm_kzalloc(&device->dev, sizeof(*smo8800), GFP_KERNEL); + if (!smo8800) { + dev_err(&device->dev, "failed to allocate device data\n"); + return -ENOMEM; + } + + smo8800->dev = &device->dev; + smo8800->miscdev.minor = MISC_DYNAMIC_MINOR; + smo8800->miscdev.name = "freefall"; + smo8800->miscdev.fops = &smo8800_misc_fops; + + init_waitqueue_head(&smo8800->misc_wait); + + err = misc_register(&smo8800->miscdev); + if (err) { + dev_err(&device->dev, "failed to register misc dev: %d\n", err); + return err; + } + + device->driver_data = smo8800; + + smo8800->irq = smo8800_get_irq(device); + if (!smo8800->irq) { + dev_err(&device->dev, "failed to obtain IRQ\n"); + err = -EINVAL; + goto error; + } + + err = request_threaded_irq(smo8800->irq, smo8800_interrupt_quick, + smo8800_interrupt_thread, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + DRIVER_NAME, smo8800); + if (err) { + dev_err(&device->dev, + "failed to request thread for IRQ %d: %d\n", + smo8800->irq, err); + goto error; + } + + dev_dbg(&device->dev, "device /dev/freefall registered with IRQ %d\n", + smo8800->irq); + return 0; + +error: + misc_deregister(&smo8800->miscdev); + return err; +} + +static int smo8800_remove(struct acpi_device *device) +{ + struct smo8800_device *smo8800 = device->driver_data; + + free_irq(smo8800->irq, smo8800); + misc_deregister(&smo8800->miscdev); + dev_dbg(&device->dev, "device /dev/freefall unregistered\n"); + return 0; +} + +/* NOTE: Keep this list in sync with drivers/i2c/busses/i2c-i801.c */ +static const struct acpi_device_id smo8800_ids[] = { + { "SMO8800", 0 }, + { "SMO8801", 0 }, + { "SMO8810", 0 }, + { "SMO8811", 0 }, + { "SMO8820", 0 }, + { "SMO8821", 0 }, + { "SMO8830", 0 }, + { "SMO8831", 0 }, + { "", 0 }, +}; + +MODULE_DEVICE_TABLE(acpi, smo8800_ids); + +static struct acpi_driver smo8800_driver = { + .name = DRIVER_NAME, + .class = "Latitude", + .ids = smo8800_ids, + .ops = { + .add = smo8800_add, + .remove = smo8800_remove, + }, + .owner = THIS_MODULE, +}; + +module_acpi_driver(smo8800_driver); + +MODULE_DESCRIPTION("Dell Latitude freefall driver (ACPI SMO88XX)"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Sonal Santan, Pali Rohár"); diff --git a/drivers/platform/x86/dell/dell-wmi-aio.c b/drivers/platform/x86/dell/dell-wmi-aio.c new file mode 100644 index 000000000000..c7b7f1e403fb --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi-aio.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * WMI hotkeys support for Dell All-In-One series + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_DESCRIPTION("WMI hotkeys driver for Dell All-In-One series"); +MODULE_LICENSE("GPL"); + +#define EVENT_GUID1 "284A0E6B-380E-472A-921F-E52786257FB4" +#define EVENT_GUID2 "02314822-307C-4F66-BF0E-48AEAEB26CC8" + +struct dell_wmi_event { + u16 length; + /* 0x000: A hot key pressed or an event occurred + * 0x00F: A sequence of hot keys are pressed */ + u16 type; + u16 event[]; +}; + +static const char *dell_wmi_aio_guids[] = { + EVENT_GUID1, + EVENT_GUID2, + NULL +}; + +MODULE_ALIAS("wmi:"EVENT_GUID1); +MODULE_ALIAS("wmi:"EVENT_GUID2); + +static const struct key_entry dell_wmi_aio_keymap[] = { + { KE_KEY, 0xc0, { KEY_VOLUMEUP } }, + { KE_KEY, 0xc1, { KEY_VOLUMEDOWN } }, + { KE_KEY, 0xe030, { KEY_VOLUMEUP } }, + { KE_KEY, 0xe02e, { KEY_VOLUMEDOWN } }, + { KE_KEY, 0xe020, { KEY_MUTE } }, + { KE_KEY, 0xe027, { KEY_DISPLAYTOGGLE } }, + { KE_KEY, 0xe006, { KEY_BRIGHTNESSUP } }, + { KE_KEY, 0xe005, { KEY_BRIGHTNESSDOWN } }, + { KE_KEY, 0xe00b, { KEY_SWITCHVIDEOMODE } }, + { KE_END, 0 } +}; + +static struct input_dev *dell_wmi_aio_input_dev; + +/* + * The new WMI event data format will follow the dell_wmi_event structure + * So, we will check if the buffer matches the format + */ +static bool dell_wmi_aio_event_check(u8 *buffer, int length) +{ + struct dell_wmi_event *event = (struct dell_wmi_event *)buffer; + + if (event == NULL || length < 6) + return false; + + if ((event->type == 0 || event->type == 0xf) && + event->length >= 2) + return true; + + return false; +} + +static void dell_wmi_aio_notify(u32 value, void *context) +{ + struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + struct dell_wmi_event *event; + acpi_status status; + + status = wmi_get_event_data(value, &response); + if (status != AE_OK) { + pr_info("bad event status 0x%x\n", status); + return; + } + + obj = (union acpi_object *)response.pointer; + if (obj) { + unsigned int scancode = 0; + + switch (obj->type) { + case ACPI_TYPE_INTEGER: + /* Most All-In-One correctly return integer scancode */ + scancode = obj->integer.value; + sparse_keymap_report_event(dell_wmi_aio_input_dev, + scancode, 1, true); + break; + case ACPI_TYPE_BUFFER: + if (dell_wmi_aio_event_check(obj->buffer.pointer, + obj->buffer.length)) { + event = (struct dell_wmi_event *) + obj->buffer.pointer; + scancode = event->event[0]; + } else { + /* Broken machines return the scancode in a + buffer */ + if (obj->buffer.pointer && + obj->buffer.length > 0) + scancode = obj->buffer.pointer[0]; + } + if (scancode) + sparse_keymap_report_event( + dell_wmi_aio_input_dev, + scancode, 1, true); + break; + } + } + kfree(obj); +} + +static int __init dell_wmi_aio_input_setup(void) +{ + int err; + + dell_wmi_aio_input_dev = input_allocate_device(); + + if (!dell_wmi_aio_input_dev) + return -ENOMEM; + + dell_wmi_aio_input_dev->name = "Dell AIO WMI hotkeys"; + dell_wmi_aio_input_dev->phys = "wmi/input0"; + dell_wmi_aio_input_dev->id.bustype = BUS_HOST; + + err = sparse_keymap_setup(dell_wmi_aio_input_dev, + dell_wmi_aio_keymap, NULL); + if (err) { + pr_err("Unable to setup input device keymap\n"); + goto err_free_dev; + } + err = input_register_device(dell_wmi_aio_input_dev); + if (err) { + pr_info("Unable to register input device\n"); + goto err_free_dev; + } + return 0; + +err_free_dev: + input_free_device(dell_wmi_aio_input_dev); + return err; +} + +static const char *dell_wmi_aio_find(void) +{ + int i; + + for (i = 0; dell_wmi_aio_guids[i] != NULL; i++) + if (wmi_has_guid(dell_wmi_aio_guids[i])) + return dell_wmi_aio_guids[i]; + + return NULL; +} + +static int __init dell_wmi_aio_init(void) +{ + int err; + const char *guid; + + guid = dell_wmi_aio_find(); + if (!guid) { + pr_warn("No known WMI GUID found\n"); + return -ENXIO; + } + + err = dell_wmi_aio_input_setup(); + if (err) + return err; + + err = wmi_install_notify_handler(guid, dell_wmi_aio_notify, NULL); + if (err) { + pr_err("Unable to register notify handler - %d\n", err); + input_unregister_device(dell_wmi_aio_input_dev); + return err; + } + + return 0; +} + +static void __exit dell_wmi_aio_exit(void) +{ + const char *guid; + + guid = dell_wmi_aio_find(); + wmi_remove_notify_handler(guid); + input_unregister_device(dell_wmi_aio_input_dev); +} + +module_init(dell_wmi_aio_init); +module_exit(dell_wmi_aio_exit); diff --git a/drivers/platform/x86/dell/dell-wmi-descriptor.c b/drivers/platform/x86/dell/dell-wmi-descriptor.c new file mode 100644 index 000000000000..a068900ae8a1 --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi-descriptor.c @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Dell WMI descriptor driver + * + * Copyright (C) 2017 Dell Inc. All Rights Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include "dell-wmi-descriptor.h" + +#define DELL_WMI_DESCRIPTOR_GUID "8D9DDCBC-A997-11DA-B012-B622A1EF5492" + +struct descriptor_priv { + struct list_head list; + u32 interface_version; + u32 size; + u32 hotfix; +}; +static int descriptor_valid = -EPROBE_DEFER; +static LIST_HEAD(wmi_list); +static DEFINE_MUTEX(list_mutex); + +int dell_wmi_get_descriptor_valid(void) +{ + if (!wmi_has_guid(DELL_WMI_DESCRIPTOR_GUID)) + return -ENODEV; + + return descriptor_valid; +} +EXPORT_SYMBOL_GPL(dell_wmi_get_descriptor_valid); + +bool dell_wmi_get_interface_version(u32 *version) +{ + struct descriptor_priv *priv; + bool ret = false; + + mutex_lock(&list_mutex); + priv = list_first_entry_or_null(&wmi_list, + struct descriptor_priv, + list); + if (priv) { + *version = priv->interface_version; + ret = true; + } + mutex_unlock(&list_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(dell_wmi_get_interface_version); + +bool dell_wmi_get_size(u32 *size) +{ + struct descriptor_priv *priv; + bool ret = false; + + mutex_lock(&list_mutex); + priv = list_first_entry_or_null(&wmi_list, + struct descriptor_priv, + list); + if (priv) { + *size = priv->size; + ret = true; + } + mutex_unlock(&list_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(dell_wmi_get_size); + +bool dell_wmi_get_hotfix(u32 *hotfix) +{ + struct descriptor_priv *priv; + bool ret = false; + + mutex_lock(&list_mutex); + priv = list_first_entry_or_null(&wmi_list, + struct descriptor_priv, + list); + if (priv) { + *hotfix = priv->hotfix; + ret = true; + } + mutex_unlock(&list_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(dell_wmi_get_hotfix); + +/* + * Descriptor buffer is 128 byte long and contains: + * + * Name Offset Length Value + * Vendor Signature 0 4 "DELL" + * Object Signature 4 4 " WMI" + * WMI Interface Version 8 4 + * WMI buffer length 12 4 + * WMI hotfix number 16 4 + */ +static int dell_wmi_descriptor_probe(struct wmi_device *wdev, + const void *context) +{ + union acpi_object *obj = NULL; + struct descriptor_priv *priv; + u32 *buffer; + int ret; + + obj = wmidev_block_query(wdev, 0); + if (!obj) { + dev_err(&wdev->dev, "failed to read Dell WMI descriptor\n"); + ret = -EIO; + goto out; + } + + if (obj->type != ACPI_TYPE_BUFFER) { + dev_err(&wdev->dev, "Dell descriptor has wrong type\n"); + ret = -EINVAL; + descriptor_valid = ret; + goto out; + } + + /* Although it's not technically a failure, this would lead to + * unexpected behavior + */ + if (obj->buffer.length != 128) { + dev_err(&wdev->dev, + "Dell descriptor buffer has unexpected length (%d)\n", + obj->buffer.length); + ret = -EINVAL; + descriptor_valid = ret; + goto out; + } + + buffer = (u32 *)obj->buffer.pointer; + + if (strncmp(obj->string.pointer, "DELL WMI", 8) != 0) { + dev_err(&wdev->dev, "Dell descriptor buffer has invalid signature (%8ph)\n", + buffer); + ret = -EINVAL; + descriptor_valid = ret; + goto out; + } + descriptor_valid = 0; + + if (buffer[2] != 0 && buffer[2] != 1) + dev_warn(&wdev->dev, "Dell descriptor buffer has unknown version (%lu)\n", + (unsigned long) buffer[2]); + + priv = devm_kzalloc(&wdev->dev, sizeof(struct descriptor_priv), + GFP_KERNEL); + + if (!priv) { + ret = -ENOMEM; + goto out; + } + + priv->interface_version = buffer[2]; + priv->size = buffer[3]; + priv->hotfix = buffer[4]; + ret = 0; + dev_set_drvdata(&wdev->dev, priv); + mutex_lock(&list_mutex); + list_add_tail(&priv->list, &wmi_list); + mutex_unlock(&list_mutex); + + dev_dbg(&wdev->dev, "Detected Dell WMI interface version %lu, buffer size %lu, hotfix %lu\n", + (unsigned long) priv->interface_version, + (unsigned long) priv->size, + (unsigned long) priv->hotfix); + +out: + kfree(obj); + return ret; +} + +static int dell_wmi_descriptor_remove(struct wmi_device *wdev) +{ + struct descriptor_priv *priv = dev_get_drvdata(&wdev->dev); + + mutex_lock(&list_mutex); + list_del(&priv->list); + mutex_unlock(&list_mutex); + return 0; +} + +static const struct wmi_device_id dell_wmi_descriptor_id_table[] = { + { .guid_string = DELL_WMI_DESCRIPTOR_GUID }, + { }, +}; + +static struct wmi_driver dell_wmi_descriptor_driver = { + .driver = { + .name = "dell-wmi-descriptor", + }, + .probe = dell_wmi_descriptor_probe, + .remove = dell_wmi_descriptor_remove, + .id_table = dell_wmi_descriptor_id_table, +}; + +module_wmi_driver(dell_wmi_descriptor_driver); + +MODULE_DEVICE_TABLE(wmi, dell_wmi_descriptor_id_table); +MODULE_AUTHOR("Mario Limonciello "); +MODULE_DESCRIPTION("Dell WMI descriptor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell/dell-wmi-descriptor.h b/drivers/platform/x86/dell/dell-wmi-descriptor.h new file mode 100644 index 000000000000..1f469fef1535 --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi-descriptor.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Dell WMI descriptor driver + * + * Copyright (c) 2017 Dell Inc. + */ + +#ifndef _DELL_WMI_DESCRIPTOR_H_ +#define _DELL_WMI_DESCRIPTOR_H_ + +#include + +/* possible return values: + * -ENODEV: Descriptor GUID missing from WMI bus + * -EPROBE_DEFER: probing for dell-wmi-descriptor not yet run + * 0: valid descriptor, successfully probed + * < 0: invalid descriptor, don't probe dependent devices + */ +int dell_wmi_get_descriptor_valid(void); + +bool dell_wmi_get_interface_version(u32 *version); +bool dell_wmi_get_size(u32 *size); +bool dell_wmi_get_hotfix(u32 *hotfix); + +#endif diff --git a/drivers/platform/x86/dell/dell-wmi-led.c b/drivers/platform/x86/dell/dell-wmi-led.c new file mode 100644 index 000000000000..5bedaf7f0633 --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi-led.c @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2010 Dell Inc. + * Louis Davis + * Jim Dailey + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include + +MODULE_AUTHOR("Louis Davis/Jim Dailey"); +MODULE_DESCRIPTION("Dell LED Control Driver"); +MODULE_LICENSE("GPL"); + +#define DELL_LED_BIOS_GUID "F6E4FE6E-909D-47cb-8BAB-C9F6F2F8D396" +MODULE_ALIAS("wmi:" DELL_LED_BIOS_GUID); + +/* Error Result Codes: */ +#define INVALID_DEVICE_ID 250 +#define INVALID_PARAMETER 251 +#define INVALID_BUFFER 252 +#define INTERFACE_ERROR 253 +#define UNSUPPORTED_COMMAND 254 +#define UNSPECIFIED_ERROR 255 + +/* Device ID */ +#define DEVICE_ID_PANEL_BACK 1 + +/* LED Commands */ +#define CMD_LED_ON 16 +#define CMD_LED_OFF 17 +#define CMD_LED_BLINK 18 + +struct bios_args { + unsigned char length; + unsigned char result_code; + unsigned char device_id; + unsigned char command; + unsigned char on_time; + unsigned char off_time; +}; + +static int dell_led_perform_fn(u8 length, u8 result_code, u8 device_id, + u8 command, u8 on_time, u8 off_time) +{ + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + struct bios_args *bios_return; + struct acpi_buffer input; + union acpi_object *obj; + acpi_status status; + u8 return_code; + + struct bios_args args = { + .length = length, + .result_code = result_code, + .device_id = device_id, + .command = command, + .on_time = on_time, + .off_time = off_time + }; + + input.length = sizeof(struct bios_args); + input.pointer = &args; + + status = wmi_evaluate_method(DELL_LED_BIOS_GUID, 0, 1, &input, &output); + if (ACPI_FAILURE(status)) + return status; + + obj = output.pointer; + + if (!obj) + return -EINVAL; + if (obj->type != ACPI_TYPE_BUFFER) { + kfree(obj); + return -EINVAL; + } + + bios_return = ((struct bios_args *)obj->buffer.pointer); + return_code = bios_return->result_code; + + kfree(obj); + + return return_code; +} + +static int led_on(void) +{ + return dell_led_perform_fn(3, /* Length of command */ + INTERFACE_ERROR, /* Init to INTERFACE_ERROR */ + DEVICE_ID_PANEL_BACK, /* Device ID */ + CMD_LED_ON, /* Command */ + 0, /* not used */ + 0); /* not used */ +} + +static int led_off(void) +{ + return dell_led_perform_fn(3, /* Length of command */ + INTERFACE_ERROR, /* Init to INTERFACE_ERROR */ + DEVICE_ID_PANEL_BACK, /* Device ID */ + CMD_LED_OFF, /* Command */ + 0, /* not used */ + 0); /* not used */ +} + +static int led_blink(unsigned char on_eighths, unsigned char off_eighths) +{ + return dell_led_perform_fn(5, /* Length of command */ + INTERFACE_ERROR, /* Init to INTERFACE_ERROR */ + DEVICE_ID_PANEL_BACK, /* Device ID */ + CMD_LED_BLINK, /* Command */ + on_eighths, /* blink on in eigths of a second */ + off_eighths); /* blink off in eights of a second */ +} + +static void dell_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + if (value == LED_OFF) + led_off(); + else + led_on(); +} + +static int dell_led_blink(struct led_classdev *led_cdev, + unsigned long *delay_on, unsigned long *delay_off) +{ + unsigned long on_eighths; + unsigned long off_eighths; + + /* + * The Dell LED delay is based on 125ms intervals. + * Need to round up to next interval. + */ + + on_eighths = DIV_ROUND_UP(*delay_on, 125); + on_eighths = clamp_t(unsigned long, on_eighths, 1, 255); + *delay_on = on_eighths * 125; + + off_eighths = DIV_ROUND_UP(*delay_off, 125); + off_eighths = clamp_t(unsigned long, off_eighths, 1, 255); + *delay_off = off_eighths * 125; + + led_blink(on_eighths, off_eighths); + + return 0; +} + +static struct led_classdev dell_led = { + .name = "dell::lid", + .brightness = LED_OFF, + .max_brightness = 1, + .brightness_set = dell_led_set, + .blink_set = dell_led_blink, + .flags = LED_CORE_SUSPENDRESUME, +}; + +static int __init dell_led_init(void) +{ + int error = 0; + + if (!wmi_has_guid(DELL_LED_BIOS_GUID)) + return -ENODEV; + + error = led_off(); + if (error != 0) + return -ENODEV; + + return led_classdev_register(NULL, &dell_led); +} + +static void __exit dell_led_exit(void) +{ + led_classdev_unregister(&dell_led); + + led_off(); +} + +module_init(dell_led_init); +module_exit(dell_led_exit); diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/Makefile b/drivers/platform/x86/dell/dell-wmi-sysman/Makefile new file mode 100644 index 000000000000..825fb2fbeea8 --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi-sysman/Makefile @@ -0,0 +1,8 @@ +obj-$(CONFIG_DELL_WMI_SYSMAN) += dell-wmi-sysman.o +dell-wmi-sysman-objs := sysman.o \ + enum-attributes.o \ + int-attributes.o \ + string-attributes.o \ + passobj-attributes.o \ + biosattr-interface.o \ + passwordattr-interface.o diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c b/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c new file mode 100644 index 000000000000..f95d8ddace5a --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions corresponding to SET methods under BIOS attributes interface GUID for use + * with dell-wmi-sysman + * + * Copyright (c) 2020 Dell Inc. + */ + +#include +#include "dell-wmi-sysman.h" + +#define SETDEFAULTVALUES_METHOD_ID 0x02 +#define SETBIOSDEFAULTS_METHOD_ID 0x03 +#define SETATTRIBUTE_METHOD_ID 0x04 + +static int call_biosattributes_interface(struct wmi_device *wdev, char *in_args, size_t size, + int method_id) +{ + struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL}; + struct acpi_buffer input; + union acpi_object *obj; + acpi_status status; + int ret = -EIO; + + input.length = (acpi_size) size; + input.pointer = in_args; + status = wmidev_evaluate_method(wdev, 0, method_id, &input, &output); + if (ACPI_FAILURE(status)) + return -EIO; + obj = (union acpi_object *)output.pointer; + if (obj->type == ACPI_TYPE_INTEGER) + ret = obj->integer.value; + + if (wmi_priv.pending_changes == 0) { + wmi_priv.pending_changes = 1; + /* let userland know it may need to check reboot pending again */ + kobject_uevent(&wmi_priv.class_dev->kobj, KOBJ_CHANGE); + } + kfree(output.pointer); + return map_wmi_error(ret); +} + +/** + * set_attribute() - Update an attribute value + * @a_name: The attribute name + * @a_value: The attribute value + * + * Sets an attribute to new value + */ +int set_attribute(const char *a_name, const char *a_value) +{ + size_t security_area_size, buffer_size; + size_t a_name_size, a_value_size; + char *buffer = NULL, *start; + int ret; + + mutex_lock(&wmi_priv.mutex); + if (!wmi_priv.bios_attr_wdev) { + ret = -ENODEV; + goto out; + } + + /* build/calculate buffer */ + security_area_size = calculate_security_buffer(wmi_priv.current_admin_password); + a_name_size = calculate_string_buffer(a_name); + a_value_size = calculate_string_buffer(a_value); + buffer_size = security_area_size + a_name_size + a_value_size; + buffer = kzalloc(buffer_size, GFP_KERNEL); + if (!buffer) { + ret = -ENOMEM; + goto out; + } + + /* build security area */ + populate_security_buffer(buffer, wmi_priv.current_admin_password); + + /* build variables to set */ + start = buffer + security_area_size; + ret = populate_string_buffer(start, a_name_size, a_name); + if (ret < 0) + goto out; + start += ret; + ret = populate_string_buffer(start, a_value_size, a_value); + if (ret < 0) + goto out; + + print_hex_dump_bytes("set attribute data: ", DUMP_PREFIX_NONE, buffer, buffer_size); + ret = call_biosattributes_interface(wmi_priv.bios_attr_wdev, + buffer, buffer_size, + SETATTRIBUTE_METHOD_ID); + if (ret == -EOPNOTSUPP) + dev_err(&wmi_priv.bios_attr_wdev->dev, "admin password must be configured\n"); + else if (ret == -EACCES) + dev_err(&wmi_priv.bios_attr_wdev->dev, "invalid password\n"); + +out: + kfree(buffer); + mutex_unlock(&wmi_priv.mutex); + return ret; +} + +/** + * set_bios_defaults() - Resets BIOS defaults + * @deftype: the type of BIOS value reset to issue. + * + * Resets BIOS defaults + */ +int set_bios_defaults(u8 deftype) +{ + size_t security_area_size, buffer_size; + size_t integer_area_size = sizeof(u8); + char *buffer = NULL; + u8 *defaultType; + int ret; + + mutex_lock(&wmi_priv.mutex); + if (!wmi_priv.bios_attr_wdev) { + ret = -ENODEV; + goto out; + } + + security_area_size = calculate_security_buffer(wmi_priv.current_admin_password); + buffer_size = security_area_size + integer_area_size; + buffer = kzalloc(buffer_size, GFP_KERNEL); + if (!buffer) { + ret = -ENOMEM; + goto out; + } + + /* build security area */ + populate_security_buffer(buffer, wmi_priv.current_admin_password); + + defaultType = buffer + security_area_size; + *defaultType = deftype; + + ret = call_biosattributes_interface(wmi_priv.bios_attr_wdev, buffer, buffer_size, + SETBIOSDEFAULTS_METHOD_ID); + if (ret) + dev_err(&wmi_priv.bios_attr_wdev->dev, "reset BIOS defaults failed: %d\n", ret); + + kfree(buffer); +out: + mutex_unlock(&wmi_priv.mutex); + return ret; +} + +static int bios_attr_set_interface_probe(struct wmi_device *wdev, const void *context) +{ + mutex_lock(&wmi_priv.mutex); + wmi_priv.bios_attr_wdev = wdev; + mutex_unlock(&wmi_priv.mutex); + return 0; +} + +static int bios_attr_set_interface_remove(struct wmi_device *wdev) +{ + mutex_lock(&wmi_priv.mutex); + wmi_priv.bios_attr_wdev = NULL; + mutex_unlock(&wmi_priv.mutex); + return 0; +} + +static const struct wmi_device_id bios_attr_set_interface_id_table[] = { + { .guid_string = DELL_WMI_BIOS_ATTRIBUTES_INTERFACE_GUID }, + { }, +}; +static struct wmi_driver bios_attr_set_interface_driver = { + .driver = { + .name = DRIVER_NAME + }, + .probe = bios_attr_set_interface_probe, + .remove = bios_attr_set_interface_remove, + .id_table = bios_attr_set_interface_id_table, +}; + +int init_bios_attr_set_interface(void) +{ + return wmi_driver_register(&bios_attr_set_interface_driver); +} + +void exit_bios_attr_set_interface(void) +{ + wmi_driver_unregister(&bios_attr_set_interface_driver); +} + +MODULE_DEVICE_TABLE(wmi, bios_attr_set_interface_id_table); diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h b/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h new file mode 100644 index 000000000000..b80f2a62ea3f --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h @@ -0,0 +1,191 @@ +/* SPDX-License-Identifier: GPL-2.0 + * Definitions for kernel modules using Dell WMI System Management Driver + * + * Copyright (c) 2020 Dell Inc. + */ + +#ifndef _DELL_WMI_BIOS_ATTR_H_ +#define _DELL_WMI_BIOS_ATTR_H_ + +#include +#include +#include +#include +#include + +#define DRIVER_NAME "dell-wmi-sysman" +#define MAX_BUFF 512 + +#define DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BF5" +#define DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BFA" +#define DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BF9" +#define DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID "0894B8D6-44A6-4719-97D7-6AD24108BFD4" +#define DELL_WMI_BIOS_ATTRIBUTES_INTERFACE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BF4" +#define DELL_WMI_BIOS_PASSWORD_INTERFACE_GUID "70FE8229-D03B-4214-A1C6-1F884B1A892A" + +struct enumeration_data { + struct kobject *attr_name_kobj; + char display_name_language_code[MAX_BUFF]; + char dell_value_modifier[MAX_BUFF]; + char possible_values[MAX_BUFF]; + char attribute_name[MAX_BUFF]; + char default_value[MAX_BUFF]; + char dell_modifier[MAX_BUFF]; + char display_name[MAX_BUFF]; +}; + +struct integer_data { + struct kobject *attr_name_kobj; + char display_name_language_code[MAX_BUFF]; + char attribute_name[MAX_BUFF]; + char dell_modifier[MAX_BUFF]; + char display_name[MAX_BUFF]; + int scalar_increment; + int default_value; + int min_value; + int max_value; +}; + +struct str_data { + struct kobject *attr_name_kobj; + char display_name_language_code[MAX_BUFF]; + char attribute_name[MAX_BUFF]; + char display_name[MAX_BUFF]; + char default_value[MAX_BUFF]; + char dell_modifier[MAX_BUFF]; + int min_length; + int max_length; +}; + +struct po_data { + struct kobject *attr_name_kobj; + char attribute_name[MAX_BUFF]; + int min_password_length; + int max_password_length; +}; + +struct wmi_sysman_priv { + char current_admin_password[MAX_BUFF]; + char current_system_password[MAX_BUFF]; + struct wmi_device *password_attr_wdev; + struct wmi_device *bios_attr_wdev; + struct kset *authentication_dir_kset; + struct kset *main_dir_kset; + struct device *class_dev; + struct enumeration_data *enumeration_data; + int enumeration_instances_count; + struct integer_data *integer_data; + int integer_instances_count; + struct str_data *str_data; + int str_instances_count; + struct po_data *po_data; + int po_instances_count; + bool pending_changes; + struct mutex mutex; +}; + +/* global structure used by multiple WMI interfaces */ +extern struct wmi_sysman_priv wmi_priv; + +enum { ENUM, INT, STR, PO }; + +enum { + ATTR_NAME, + DISPL_NAME_LANG_CODE, + DISPLAY_NAME, + DEFAULT_VAL, + CURRENT_VAL, + MODIFIER +}; + +#define get_instance_id(type) \ +static int get_##type##_instance_id(struct kobject *kobj) \ +{ \ + int i; \ + for (i = 0; i <= wmi_priv.type##_instances_count; i++) { \ + if (!(strcmp(kobj->name, wmi_priv.type##_data[i].attribute_name)))\ + return i; \ + } \ + return -EIO; \ +} + +#define attribute_s_property_show(name, type) \ +static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \ + char *buf) \ +{ \ + int i = get_##type##_instance_id(kobj); \ + if (i >= 0) \ + return sprintf(buf, "%s\n", wmi_priv.type##_data[i].name); \ + return 0; \ +} + +#define attribute_n_property_show(name, type) \ +static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \ + char *buf) \ +{ \ + int i = get_##type##_instance_id(kobj); \ + if (i >= 0) \ + return sprintf(buf, "%d\n", wmi_priv.type##_data[i].name); \ + return 0; \ +} + +#define attribute_property_store(curr_val, type) \ +static ssize_t curr_val##_store(struct kobject *kobj, \ + struct kobj_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + char *p, *buf_cp; \ + int i, ret = -EIO; \ + buf_cp = kstrdup(buf, GFP_KERNEL); \ + if (!buf_cp) \ + return -ENOMEM; \ + p = memchr(buf_cp, '\n', count); \ + \ + if (p != NULL) \ + *p = '\0'; \ + i = get_##type##_instance_id(kobj); \ + if (i >= 0) \ + ret = validate_##type##_input(i, buf_cp); \ + if (!ret) \ + ret = set_attribute(kobj->name, buf_cp); \ + kfree(buf_cp); \ + return ret ? ret : count; \ +} + +union acpi_object *get_wmiobj_pointer(int instance_id, const char *guid_string); +int get_instance_count(const char *guid_string); +void strlcpy_attr(char *dest, char *src); + +int populate_enum_data(union acpi_object *enumeration_obj, int instance_id, + struct kobject *attr_name_kobj); +int alloc_enum_data(void); +void exit_enum_attributes(void); + +int populate_int_data(union acpi_object *integer_obj, int instance_id, + struct kobject *attr_name_kobj); +int alloc_int_data(void); +void exit_int_attributes(void); + +int populate_str_data(union acpi_object *str_obj, int instance_id, struct kobject *attr_name_kobj); +int alloc_str_data(void); +void exit_str_attributes(void); + +int populate_po_data(union acpi_object *po_obj, int instance_id, struct kobject *attr_name_kobj); +int alloc_po_data(void); +void exit_po_attributes(void); + +int set_attribute(const char *a_name, const char *a_value); +int set_bios_defaults(u8 defType); + +void exit_bios_attr_set_interface(void); +int init_bios_attr_set_interface(void); +int map_wmi_error(int error_code); +size_t calculate_string_buffer(const char *str); +size_t calculate_security_buffer(char *authentication); +void populate_security_buffer(char *buffer, char *authentication); +ssize_t populate_string_buffer(char *buffer, size_t buffer_len, const char *str); +int set_new_password(const char *password_type, const char *new); +int init_bios_attr_pass_interface(void); +void exit_bios_attr_pass_interface(void); + +#endif diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c b/drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c new file mode 100644 index 000000000000..80f4b7785c6c --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions corresponding to enumeration type attributes under + * BIOS Enumeration GUID for use with dell-wmi-sysman + * + * Copyright (c) 2020 Dell Inc. + */ + +#include "dell-wmi-sysman.h" + +get_instance_id(enumeration); + +static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + int instance_id = get_enumeration_instance_id(kobj); + union acpi_object *obj; + ssize_t ret; + + if (instance_id < 0) + return instance_id; + + /* need to use specific instance_id and guid combination to get right data */ + obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID); + if (!obj) + return -EIO; + if (obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_STRING) { + kfree(obj); + return -EINVAL; + } + ret = snprintf(buf, PAGE_SIZE, "%s\n", obj->package.elements[CURRENT_VAL].string.pointer); + kfree(obj); + return ret; +} + +/** + * validate_enumeration_input() - Validate input of current_value against possible values + * @instance_id: The instance on which input is validated + * @buf: Input value + */ +static int validate_enumeration_input(int instance_id, const char *buf) +{ + char *options, *tmp, *p; + int ret = -EINVAL; + + options = tmp = kstrdup(wmi_priv.enumeration_data[instance_id].possible_values, + GFP_KERNEL); + if (!options) + return -ENOMEM; + + while ((p = strsep(&options, ";")) != NULL) { + if (!*p) + continue; + if (!strcasecmp(p, buf)) { + ret = 0; + break; + } + } + + kfree(tmp); + return ret; +} + +attribute_s_property_show(display_name_language_code, enumeration); +static struct kobj_attribute displ_langcode = + __ATTR_RO(display_name_language_code); + +attribute_s_property_show(display_name, enumeration); +static struct kobj_attribute displ_name = + __ATTR_RO(display_name); + +attribute_s_property_show(default_value, enumeration); +static struct kobj_attribute default_val = + __ATTR_RO(default_value); + +attribute_property_store(current_value, enumeration); +static struct kobj_attribute current_val = + __ATTR_RW_MODE(current_value, 0600); + +attribute_s_property_show(dell_modifier, enumeration); +static struct kobj_attribute modifier = + __ATTR_RO(dell_modifier); + +attribute_s_property_show(dell_value_modifier, enumeration); +static struct kobj_attribute value_modfr = + __ATTR_RO(dell_value_modifier); + +attribute_s_property_show(possible_values, enumeration); +static struct kobj_attribute poss_val = + __ATTR_RO(possible_values); + +static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sprintf(buf, "enumeration\n"); +} +static struct kobj_attribute type = + __ATTR_RO(type); + +static struct attribute *enumeration_attrs[] = { + &displ_langcode.attr, + &displ_name.attr, + &default_val.attr, + ¤t_val.attr, + &modifier.attr, + &value_modfr.attr, + &poss_val.attr, + &type.attr, + NULL, +}; + +static const struct attribute_group enumeration_attr_group = { + .attrs = enumeration_attrs, +}; + +int alloc_enum_data(void) +{ + int ret = 0; + + wmi_priv.enumeration_instances_count = + get_instance_count(DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID); + wmi_priv.enumeration_data = kcalloc(wmi_priv.enumeration_instances_count, + sizeof(struct enumeration_data), GFP_KERNEL); + if (!wmi_priv.enumeration_data) { + wmi_priv.enumeration_instances_count = 0; + ret = -ENOMEM; + } + return ret; +} + +/** + * populate_enum_data() - Populate all properties of an instance under enumeration attribute + * @enumeration_obj: ACPI object with enumeration data + * @instance_id: The instance to enumerate + * @attr_name_kobj: The parent kernel object + */ +int populate_enum_data(union acpi_object *enumeration_obj, int instance_id, + struct kobject *attr_name_kobj) +{ + int i, next_obj, value_modifier_count, possible_values_count; + + wmi_priv.enumeration_data[instance_id].attr_name_kobj = attr_name_kobj; + strlcpy_attr(wmi_priv.enumeration_data[instance_id].attribute_name, + enumeration_obj[ATTR_NAME].string.pointer); + strlcpy_attr(wmi_priv.enumeration_data[instance_id].display_name_language_code, + enumeration_obj[DISPL_NAME_LANG_CODE].string.pointer); + strlcpy_attr(wmi_priv.enumeration_data[instance_id].display_name, + enumeration_obj[DISPLAY_NAME].string.pointer); + strlcpy_attr(wmi_priv.enumeration_data[instance_id].default_value, + enumeration_obj[DEFAULT_VAL].string.pointer); + strlcpy_attr(wmi_priv.enumeration_data[instance_id].dell_modifier, + enumeration_obj[MODIFIER].string.pointer); + + next_obj = MODIFIER + 1; + + value_modifier_count = (uintptr_t)enumeration_obj[next_obj].string.pointer; + + for (i = 0; i < value_modifier_count; i++) { + strcat(wmi_priv.enumeration_data[instance_id].dell_value_modifier, + enumeration_obj[++next_obj].string.pointer); + strcat(wmi_priv.enumeration_data[instance_id].dell_value_modifier, ";"); + } + + possible_values_count = (uintptr_t) enumeration_obj[++next_obj].string.pointer; + + for (i = 0; i < possible_values_count; i++) { + strcat(wmi_priv.enumeration_data[instance_id].possible_values, + enumeration_obj[++next_obj].string.pointer); + strcat(wmi_priv.enumeration_data[instance_id].possible_values, ";"); + } + + return sysfs_create_group(attr_name_kobj, &enumeration_attr_group); +} + +/** + * exit_enum_attributes() - Clear all attribute data + * + * Clears all data allocated for this group of attributes + */ +void exit_enum_attributes(void) +{ + int instance_id; + + for (instance_id = 0; instance_id < wmi_priv.enumeration_instances_count; instance_id++) { + if (wmi_priv.enumeration_data[instance_id].attr_name_kobj) + sysfs_remove_group(wmi_priv.enumeration_data[instance_id].attr_name_kobj, + &enumeration_attr_group); + } + kfree(wmi_priv.enumeration_data); +} diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/int-attributes.c b/drivers/platform/x86/dell/dell-wmi-sysman/int-attributes.c new file mode 100644 index 000000000000..75aedbb733be --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi-sysman/int-attributes.c @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions corresponding to integer type attributes under BIOS Integer GUID for use with + * dell-wmi-sysman + * + * Copyright (c) 2020 Dell Inc. + */ + +#include "dell-wmi-sysman.h" + +enum int_properties {MIN_VALUE = 6, MAX_VALUE, SCALAR_INCR}; + +get_instance_id(integer); + +static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + int instance_id = get_integer_instance_id(kobj); + union acpi_object *obj; + ssize_t ret; + + if (instance_id < 0) + return instance_id; + + /* need to use specific instance_id and guid combination to get right data */ + obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID); + if (!obj) + return -EIO; + if (obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_INTEGER) { + kfree(obj); + return -EINVAL; + } + ret = snprintf(buf, PAGE_SIZE, "%lld\n", obj->package.elements[CURRENT_VAL].integer.value); + kfree(obj); + return ret; +} + +/** + * validate_integer_input() - Validate input of current_value against lower and upper bound + * @instance_id: The instance on which input is validated + * @buf: Input value + */ +static int validate_integer_input(int instance_id, char *buf) +{ + int in_val; + int ret; + + ret = kstrtoint(buf, 0, &in_val); + if (ret) + return ret; + if (in_val < wmi_priv.integer_data[instance_id].min_value || + in_val > wmi_priv.integer_data[instance_id].max_value) + return -EINVAL; + + /* workaround for BIOS error. + * validate input to avoid setting 0 when integer input passed with + sign + */ + if (*buf == '+') + memmove(buf, (buf + 1), strlen(buf + 1) + 1); + + return ret; +} + +attribute_s_property_show(display_name_language_code, integer); +static struct kobj_attribute integer_displ_langcode = + __ATTR_RO(display_name_language_code); + +attribute_s_property_show(display_name, integer); +static struct kobj_attribute integer_displ_name = + __ATTR_RO(display_name); + +attribute_n_property_show(default_value, integer); +static struct kobj_attribute integer_default_val = + __ATTR_RO(default_value); + +attribute_property_store(current_value, integer); +static struct kobj_attribute integer_current_val = + __ATTR_RW_MODE(current_value, 0600); + +attribute_s_property_show(dell_modifier, integer); +static struct kobj_attribute integer_modifier = + __ATTR_RO(dell_modifier); + +attribute_n_property_show(min_value, integer); +static struct kobj_attribute integer_lower_bound = + __ATTR_RO(min_value); + +attribute_n_property_show(max_value, integer); +static struct kobj_attribute integer_upper_bound = + __ATTR_RO(max_value); + +attribute_n_property_show(scalar_increment, integer); +static struct kobj_attribute integer_scalar_increment = + __ATTR_RO(scalar_increment); + +static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sprintf(buf, "integer\n"); +} +static struct kobj_attribute integer_type = + __ATTR_RO(type); + +static struct attribute *integer_attrs[] = { + &integer_displ_langcode.attr, + &integer_displ_name.attr, + &integer_default_val.attr, + &integer_current_val.attr, + &integer_modifier.attr, + &integer_lower_bound.attr, + &integer_upper_bound.attr, + &integer_scalar_increment.attr, + &integer_type.attr, + NULL, +}; + +static const struct attribute_group integer_attr_group = { + .attrs = integer_attrs, +}; + +int alloc_int_data(void) +{ + int ret = 0; + + wmi_priv.integer_instances_count = get_instance_count(DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID); + wmi_priv.integer_data = kcalloc(wmi_priv.integer_instances_count, + sizeof(struct integer_data), GFP_KERNEL); + if (!wmi_priv.integer_data) { + wmi_priv.integer_instances_count = 0; + ret = -ENOMEM; + } + return ret; +} + +/** + * populate_int_data() - Populate all properties of an instance under integer attribute + * @integer_obj: ACPI object with integer data + * @instance_id: The instance to enumerate + * @attr_name_kobj: The parent kernel object + */ +int populate_int_data(union acpi_object *integer_obj, int instance_id, + struct kobject *attr_name_kobj) +{ + wmi_priv.integer_data[instance_id].attr_name_kobj = attr_name_kobj; + strlcpy_attr(wmi_priv.integer_data[instance_id].attribute_name, + integer_obj[ATTR_NAME].string.pointer); + strlcpy_attr(wmi_priv.integer_data[instance_id].display_name_language_code, + integer_obj[DISPL_NAME_LANG_CODE].string.pointer); + strlcpy_attr(wmi_priv.integer_data[instance_id].display_name, + integer_obj[DISPLAY_NAME].string.pointer); + wmi_priv.integer_data[instance_id].default_value = + (uintptr_t)integer_obj[DEFAULT_VAL].string.pointer; + strlcpy_attr(wmi_priv.integer_data[instance_id].dell_modifier, + integer_obj[MODIFIER].string.pointer); + wmi_priv.integer_data[instance_id].min_value = + (uintptr_t)integer_obj[MIN_VALUE].string.pointer; + wmi_priv.integer_data[instance_id].max_value = + (uintptr_t)integer_obj[MAX_VALUE].string.pointer; + wmi_priv.integer_data[instance_id].scalar_increment = + (uintptr_t)integer_obj[SCALAR_INCR].string.pointer; + + return sysfs_create_group(attr_name_kobj, &integer_attr_group); +} + +/** + * exit_int_attributes() - Clear all attribute data + * + * Clears all data allocated for this group of attributes + */ +void exit_int_attributes(void) +{ + int instance_id; + + for (instance_id = 0; instance_id < wmi_priv.integer_instances_count; instance_id++) { + if (wmi_priv.integer_data[instance_id].attr_name_kobj) + sysfs_remove_group(wmi_priv.integer_data[instance_id].attr_name_kobj, + &integer_attr_group); + } + kfree(wmi_priv.integer_data); +} diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c b/drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c new file mode 100644 index 000000000000..3abcd95477c0 --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions corresponding to password object type attributes under BIOS Password Object GUID for + * use with dell-wmi-sysman + * + * Copyright (c) 2020 Dell Inc. + */ + +#include "dell-wmi-sysman.h" + +enum po_properties {IS_PASS_SET = 1, MIN_PASS_LEN, MAX_PASS_LEN}; + +get_instance_id(po); + +static ssize_t is_enabled_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + int instance_id = get_po_instance_id(kobj); + union acpi_object *obj; + ssize_t ret; + + if (instance_id < 0) + return instance_id; + + /* need to use specific instance_id and guid combination to get right data */ + obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID); + if (!obj) + return -EIO; + if (obj->package.elements[IS_PASS_SET].type != ACPI_TYPE_INTEGER) { + kfree(obj); + return -EINVAL; + } + ret = snprintf(buf, PAGE_SIZE, "%lld\n", obj->package.elements[IS_PASS_SET].integer.value); + kfree(obj); + return ret; +} + +static struct kobj_attribute po_is_pass_set = __ATTR_RO(is_enabled); + +static ssize_t current_password_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + char *target = NULL; + int length; + + length = strlen(buf); + if (buf[length-1] == '\n') + length--; + + /* firmware does verifiation of min/max password length, + * hence only check for not exceeding MAX_BUFF here. + */ + if (length >= MAX_BUFF) + return -EINVAL; + + if (strcmp(kobj->name, "Admin") == 0) + target = wmi_priv.current_admin_password; + else if (strcmp(kobj->name, "System") == 0) + target = wmi_priv.current_system_password; + if (!target) + return -EIO; + memcpy(target, buf, length); + target[length] = '\0'; + + return count; +} + +static struct kobj_attribute po_current_password = __ATTR_WO(current_password); + +static ssize_t new_password_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + char *p, *buf_cp; + int ret; + + buf_cp = kstrdup(buf, GFP_KERNEL); + if (!buf_cp) + return -ENOMEM; + p = memchr(buf_cp, '\n', count); + + if (p != NULL) + *p = '\0'; + if (strlen(buf_cp) > MAX_BUFF) { + ret = -EINVAL; + goto out; + } + + ret = set_new_password(kobj->name, buf_cp); + +out: + kfree(buf_cp); + return ret ? ret : count; +} + +static struct kobj_attribute po_new_password = __ATTR_WO(new_password); + +attribute_n_property_show(min_password_length, po); +static struct kobj_attribute po_min_pass_length = __ATTR_RO(min_password_length); + +attribute_n_property_show(max_password_length, po); +static struct kobj_attribute po_max_pass_length = __ATTR_RO(max_password_length); + +static ssize_t mechanism_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sprintf(buf, "password\n"); +} + +static struct kobj_attribute po_mechanism = __ATTR_RO(mechanism); + +static ssize_t role_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + if (strcmp(kobj->name, "Admin") == 0) + return sprintf(buf, "bios-admin\n"); + else if (strcmp(kobj->name, "System") == 0) + return sprintf(buf, "power-on\n"); + return -EIO; +} + +static struct kobj_attribute po_role = __ATTR_RO(role); + +static struct attribute *po_attrs[] = { + &po_is_pass_set.attr, + &po_min_pass_length.attr, + &po_max_pass_length.attr, + &po_current_password.attr, + &po_new_password.attr, + &po_role.attr, + &po_mechanism.attr, + NULL, +}; + +static const struct attribute_group po_attr_group = { + .attrs = po_attrs, +}; + +int alloc_po_data(void) +{ + int ret = 0; + + wmi_priv.po_instances_count = get_instance_count(DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID); + wmi_priv.po_data = kcalloc(wmi_priv.po_instances_count, sizeof(struct po_data), GFP_KERNEL); + if (!wmi_priv.po_data) { + wmi_priv.po_instances_count = 0; + ret = -ENOMEM; + } + return ret; +} + +/** + * populate_po_data() - Populate all properties of an instance under password object attribute + * @po_obj: ACPI object with password object data + * @instance_id: The instance to enumerate + * @attr_name_kobj: The parent kernel object + */ +int populate_po_data(union acpi_object *po_obj, int instance_id, struct kobject *attr_name_kobj) +{ + wmi_priv.po_data[instance_id].attr_name_kobj = attr_name_kobj; + strlcpy_attr(wmi_priv.po_data[instance_id].attribute_name, + po_obj[ATTR_NAME].string.pointer); + wmi_priv.po_data[instance_id].min_password_length = + (uintptr_t)po_obj[MIN_PASS_LEN].string.pointer; + wmi_priv.po_data[instance_id].max_password_length = + (uintptr_t) po_obj[MAX_PASS_LEN].string.pointer; + + return sysfs_create_group(attr_name_kobj, &po_attr_group); +} + +/** + * exit_po_attributes() - Clear all attribute data + * + * Clears all data allocated for this group of attributes + */ +void exit_po_attributes(void) +{ + int instance_id; + + for (instance_id = 0; instance_id < wmi_priv.po_instances_count; instance_id++) { + if (wmi_priv.po_data[instance_id].attr_name_kobj) + sysfs_remove_group(wmi_priv.po_data[instance_id].attr_name_kobj, + &po_attr_group); + } + kfree(wmi_priv.po_data); +} diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c b/drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c new file mode 100644 index 000000000000..5780b4d94759 --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions corresponding to SET password methods under BIOS attributes interface GUID + * + * Copyright (c) 2020 Dell Inc. + */ + +#include +#include "dell-wmi-sysman.h" + +static int call_password_interface(struct wmi_device *wdev, char *in_args, size_t size) +{ + struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL}; + struct acpi_buffer input; + union acpi_object *obj; + acpi_status status; + int ret = -EIO; + + input.length = (acpi_size) size; + input.pointer = in_args; + status = wmidev_evaluate_method(wdev, 0, 1, &input, &output); + if (ACPI_FAILURE(status)) + return -EIO; + obj = (union acpi_object *)output.pointer; + if (obj->type == ACPI_TYPE_INTEGER) + ret = obj->integer.value; + + kfree(output.pointer); + /* let userland know it may need to check is_password_set again */ + kobject_uevent(&wmi_priv.class_dev->kobj, KOBJ_CHANGE); + return map_wmi_error(ret); +} + +/** + * set_new_password() - Sets a system admin password + * @password_type: The type of password to set + * @new: The new password + * + * Sets the password using plaintext interface + */ +int set_new_password(const char *password_type, const char *new) +{ + size_t password_type_size, current_password_size, new_size; + size_t security_area_size, buffer_size; + char *buffer = NULL, *start; + char *current_password; + int ret; + + mutex_lock(&wmi_priv.mutex); + if (!wmi_priv.password_attr_wdev) { + ret = -ENODEV; + goto out; + } + if (strcmp(password_type, "Admin") == 0) { + current_password = wmi_priv.current_admin_password; + } else if (strcmp(password_type, "System") == 0) { + current_password = wmi_priv.current_system_password; + } else { + ret = -EINVAL; + dev_err(&wmi_priv.password_attr_wdev->dev, "unknown password type %s\n", + password_type); + goto out; + } + + /* build/calculate buffer */ + security_area_size = calculate_security_buffer(wmi_priv.current_admin_password); + password_type_size = calculate_string_buffer(password_type); + current_password_size = calculate_string_buffer(current_password); + new_size = calculate_string_buffer(new); + buffer_size = security_area_size + password_type_size + current_password_size + new_size; + buffer = kzalloc(buffer_size, GFP_KERNEL); + if (!buffer) { + ret = -ENOMEM; + goto out; + } + + /* build security area */ + populate_security_buffer(buffer, wmi_priv.current_admin_password); + + /* build variables to set */ + start = buffer + security_area_size; + ret = populate_string_buffer(start, password_type_size, password_type); + if (ret < 0) + goto out; + + start += ret; + ret = populate_string_buffer(start, current_password_size, current_password); + if (ret < 0) + goto out; + + start += ret; + ret = populate_string_buffer(start, new_size, new); + if (ret < 0) + goto out; + + print_hex_dump_bytes("set new password data: ", DUMP_PREFIX_NONE, buffer, buffer_size); + ret = call_password_interface(wmi_priv.password_attr_wdev, buffer, buffer_size); + /* clear current_password here and use user input from wmi_priv.current_password */ + if (!ret) + memset(current_password, 0, MAX_BUFF); + /* explain to user the detailed failure reason */ + else if (ret == -EOPNOTSUPP) + dev_err(&wmi_priv.password_attr_wdev->dev, "admin password must be configured\n"); + else if (ret == -EACCES) + dev_err(&wmi_priv.password_attr_wdev->dev, "invalid password\n"); + +out: + kfree(buffer); + mutex_unlock(&wmi_priv.mutex); + + return ret; +} + +static int bios_attr_pass_interface_probe(struct wmi_device *wdev, const void *context) +{ + mutex_lock(&wmi_priv.mutex); + wmi_priv.password_attr_wdev = wdev; + mutex_unlock(&wmi_priv.mutex); + return 0; +} + +static int bios_attr_pass_interface_remove(struct wmi_device *wdev) +{ + mutex_lock(&wmi_priv.mutex); + wmi_priv.password_attr_wdev = NULL; + mutex_unlock(&wmi_priv.mutex); + return 0; +} + +static const struct wmi_device_id bios_attr_pass_interface_id_table[] = { + { .guid_string = DELL_WMI_BIOS_PASSWORD_INTERFACE_GUID }, + { }, +}; +static struct wmi_driver bios_attr_pass_interface_driver = { + .driver = { + .name = DRIVER_NAME"-password" + }, + .probe = bios_attr_pass_interface_probe, + .remove = bios_attr_pass_interface_remove, + .id_table = bios_attr_pass_interface_id_table, +}; + +int init_bios_attr_pass_interface(void) +{ + return wmi_driver_register(&bios_attr_pass_interface_driver); +} + +void exit_bios_attr_pass_interface(void) +{ + wmi_driver_unregister(&bios_attr_pass_interface_driver); +} + +MODULE_DEVICE_TABLE(wmi, bios_attr_pass_interface_id_table); diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/string-attributes.c b/drivers/platform/x86/dell/dell-wmi-sysman/string-attributes.c new file mode 100644 index 000000000000..ac75dce88a4c --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi-sysman/string-attributes.c @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions corresponding to string type attributes under BIOS String GUID for use with + * dell-wmi-sysman + * + * Copyright (c) 2020 Dell Inc. + */ + +#include "dell-wmi-sysman.h" + +enum string_properties {MIN_LEN = 6, MAX_LEN}; + +get_instance_id(str); + +static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + int instance_id = get_str_instance_id(kobj); + union acpi_object *obj; + ssize_t ret; + + if (instance_id < 0) + return -EIO; + + /* need to use specific instance_id and guid combination to get right data */ + obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID); + if (!obj) + return -EIO; + if (obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_STRING) { + kfree(obj); + return -EINVAL; + } + ret = snprintf(buf, PAGE_SIZE, "%s\n", obj->package.elements[CURRENT_VAL].string.pointer); + kfree(obj); + return ret; +} + +/** + * validate_str_input() - Validate input of current_value against min and max lengths + * @instance_id: The instance on which input is validated + * @buf: Input value + */ +static int validate_str_input(int instance_id, const char *buf) +{ + int in_len = strlen(buf); + + if ((in_len < wmi_priv.str_data[instance_id].min_length) || + (in_len > wmi_priv.str_data[instance_id].max_length)) + return -EINVAL; + + return 0; +} + +attribute_s_property_show(display_name_language_code, str); +static struct kobj_attribute str_displ_langcode = + __ATTR_RO(display_name_language_code); + +attribute_s_property_show(display_name, str); +static struct kobj_attribute str_displ_name = + __ATTR_RO(display_name); + +attribute_s_property_show(default_value, str); +static struct kobj_attribute str_default_val = + __ATTR_RO(default_value); + +attribute_property_store(current_value, str); +static struct kobj_attribute str_current_val = + __ATTR_RW_MODE(current_value, 0600); + +attribute_s_property_show(dell_modifier, str); +static struct kobj_attribute str_modifier = + __ATTR_RO(dell_modifier); + +attribute_n_property_show(min_length, str); +static struct kobj_attribute str_min_length = + __ATTR_RO(min_length); + +attribute_n_property_show(max_length, str); +static struct kobj_attribute str_max_length = + __ATTR_RO(max_length); + +static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sprintf(buf, "string\n"); +} +static struct kobj_attribute str_type = + __ATTR_RO(type); + +static struct attribute *str_attrs[] = { + &str_displ_langcode.attr, + &str_displ_name.attr, + &str_default_val.attr, + &str_current_val.attr, + &str_modifier.attr, + &str_min_length.attr, + &str_max_length.attr, + &str_type.attr, + NULL, +}; + +static const struct attribute_group str_attr_group = { + .attrs = str_attrs, +}; + +int alloc_str_data(void) +{ + int ret = 0; + + wmi_priv.str_instances_count = get_instance_count(DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID); + wmi_priv.str_data = kcalloc(wmi_priv.str_instances_count, + sizeof(struct str_data), GFP_KERNEL); + if (!wmi_priv.str_data) { + wmi_priv.str_instances_count = 0; + ret = -ENOMEM; + } + return ret; +} + +/** + * populate_str_data() - Populate all properties of an instance under string attribute + * @str_obj: ACPI object with integer data + * @instance_id: The instance to enumerate + * @attr_name_kobj: The parent kernel object + */ +int populate_str_data(union acpi_object *str_obj, int instance_id, struct kobject *attr_name_kobj) +{ + wmi_priv.str_data[instance_id].attr_name_kobj = attr_name_kobj; + strlcpy_attr(wmi_priv.str_data[instance_id].attribute_name, + str_obj[ATTR_NAME].string.pointer); + strlcpy_attr(wmi_priv.str_data[instance_id].display_name_language_code, + str_obj[DISPL_NAME_LANG_CODE].string.pointer); + strlcpy_attr(wmi_priv.str_data[instance_id].display_name, + str_obj[DISPLAY_NAME].string.pointer); + strlcpy_attr(wmi_priv.str_data[instance_id].default_value, + str_obj[DEFAULT_VAL].string.pointer); + strlcpy_attr(wmi_priv.str_data[instance_id].dell_modifier, + str_obj[MODIFIER].string.pointer); + wmi_priv.str_data[instance_id].min_length = (uintptr_t)str_obj[MIN_LEN].string.pointer; + wmi_priv.str_data[instance_id].max_length = (uintptr_t) str_obj[MAX_LEN].string.pointer; + + return sysfs_create_group(attr_name_kobj, &str_attr_group); +} + +/** + * exit_str_attributes() - Clear all attribute data + * + * Clears all data allocated for this group of attributes + */ +void exit_str_attributes(void) +{ + int instance_id; + + for (instance_id = 0; instance_id < wmi_priv.str_instances_count; instance_id++) { + if (wmi_priv.str_data[instance_id].attr_name_kobj) + sysfs_remove_group(wmi_priv.str_data[instance_id].attr_name_kobj, + &str_attr_group); + } + kfree(wmi_priv.str_data); +} diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c new file mode 100644 index 000000000000..cb81010ba1a2 --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c @@ -0,0 +1,631 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Common methods for use with dell-wmi-sysman + * + * Copyright (c) 2020 Dell Inc. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include "dell-wmi-sysman.h" + +#define MAX_TYPES 4 +#include + +static struct class firmware_attributes_class = { + .name = "firmware-attributes", +}; + +struct wmi_sysman_priv wmi_priv = { + .mutex = __MUTEX_INITIALIZER(wmi_priv.mutex), +}; + +/* reset bios to defaults */ +static const char * const reset_types[] = {"builtinsafe", "lastknowngood", "factory", "custom"}; +static int reset_option = -1; + + +/** + * populate_string_buffer() - populates a string buffer + * @buffer: the start of the destination buffer + * @buffer_len: length of the destination buffer + * @str: the string to insert into buffer + */ +ssize_t populate_string_buffer(char *buffer, size_t buffer_len, const char *str) +{ + u16 *length = (u16 *)buffer; + u16 *target = length + 1; + int ret; + + ret = utf8s_to_utf16s(str, strlen(str), UTF16_HOST_ENDIAN, + target, buffer_len - sizeof(u16)); + if (ret < 0) { + dev_err(wmi_priv.class_dev, "UTF16 conversion failed\n"); + return ret; + } + + if ((ret * sizeof(u16)) > U16_MAX) { + dev_err(wmi_priv.class_dev, "Error string too long\n"); + return -ERANGE; + } + + *length = ret * sizeof(u16); + return sizeof(u16) + *length; +} + +/** + * calculate_string_buffer() - determines size of string buffer for use with BIOS communication + * @str: the string to calculate based upon + * + */ +size_t calculate_string_buffer(const char *str) +{ + /* u16 length field + one UTF16 char for each input char */ + return sizeof(u16) + strlen(str) * sizeof(u16); +} + +/** + * calculate_security_buffer() - determines size of security buffer for authentication scheme + * @authentication: the authentication content + * + * Currently only supported type is Admin password + */ +size_t calculate_security_buffer(char *authentication) +{ + if (strlen(authentication) > 0) { + return (sizeof(u32) * 2) + strlen(authentication) + + strlen(authentication) % 2; + } + return sizeof(u32) * 2; +} + +/** + * populate_security_buffer() - builds a security buffer for authentication scheme + * @buffer: the buffer to populate + * @authentication: the authentication content + * + * Currently only supported type is PLAIN TEXT + */ +void populate_security_buffer(char *buffer, char *authentication) +{ + char *auth = buffer + sizeof(u32) * 2; + u32 *sectype = (u32 *) buffer; + u32 *seclen = sectype + 1; + + *sectype = strlen(authentication) > 0 ? 1 : 0; + *seclen = strlen(authentication); + + /* plain text */ + if (strlen(authentication) > 0) + memcpy(auth, authentication, *seclen); +} + +/** + * map_wmi_error() - map errors from WMI methods to kernel error codes + * @error_code: integer error code returned from Dell's firmware + */ +int map_wmi_error(int error_code) +{ + switch (error_code) { + case 0: + /* success */ + return 0; + case 1: + /* failed */ + return -EIO; + case 2: + /* invalid parameter */ + return -EINVAL; + case 3: + /* access denied */ + return -EACCES; + case 4: + /* not supported */ + return -EOPNOTSUPP; + case 5: + /* memory error */ + return -ENOMEM; + case 6: + /* protocol error */ + return -EPROTO; + } + /* unspecified error */ + return -EIO; +} + +/** + * reset_bios_show() - sysfs implementaton for read reset_bios + * @kobj: Kernel object for this attribute + * @attr: Kernel object attribute + * @buf: The buffer to display to userspace + */ +static ssize_t reset_bios_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + char *start = buf; + int i; + + for (i = 0; i < MAX_TYPES; i++) { + if (i == reset_option) + buf += sprintf(buf, "[%s] ", reset_types[i]); + else + buf += sprintf(buf, "%s ", reset_types[i]); + } + buf += sprintf(buf, "\n"); + return buf-start; +} + +/** + * reset_bios_store() - sysfs implementaton for write reset_bios + * @kobj: Kernel object for this attribute + * @attr: Kernel object attribute + * @buf: The buffer from userspace + * @count: the size of the buffer from userspace + */ +static ssize_t reset_bios_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int type = sysfs_match_string(reset_types, buf); + int ret; + + if (type < 0) + return type; + + ret = set_bios_defaults(type); + pr_debug("reset all attributes request type %d: %d\n", type, ret); + if (!ret) { + reset_option = type; + ret = count; + } + + return ret; +} + +/** + * pending_reboot_show() - sysfs implementaton for read pending_reboot + * @kobj: Kernel object for this attribute + * @attr: Kernel object attribute + * @buf: The buffer to display to userspace + * + * Stores default value as 0 + * When current_value is changed this attribute is set to 1 to notify reboot may be required + */ +static ssize_t pending_reboot_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", wmi_priv.pending_changes); +} + +static struct kobj_attribute reset_bios = __ATTR_RW(reset_bios); +static struct kobj_attribute pending_reboot = __ATTR_RO(pending_reboot); + + +/** + * create_attributes_level_sysfs_files() - Creates reset_bios and + * pending_reboot attributes + */ +static int create_attributes_level_sysfs_files(void) +{ + int ret = sysfs_create_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr); + + if (ret) { + pr_debug("could not create reset_bios file\n"); + return ret; + } + + ret = sysfs_create_file(&wmi_priv.main_dir_kset->kobj, &pending_reboot.attr); + if (ret) { + pr_debug("could not create changing_pending_reboot file\n"); + sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr); + } + return ret; +} + +static void release_reset_bios_data(void) +{ + sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr); + sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &pending_reboot.attr); +} + +static ssize_t wmi_sysman_attr_show(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + struct kobj_attribute *kattr; + ssize_t ret = -EIO; + + kattr = container_of(attr, struct kobj_attribute, attr); + if (kattr->show) + ret = kattr->show(kobj, kattr, buf); + return ret; +} + +static ssize_t wmi_sysman_attr_store(struct kobject *kobj, struct attribute *attr, + const char *buf, size_t count) +{ + struct kobj_attribute *kattr; + ssize_t ret = -EIO; + + kattr = container_of(attr, struct kobj_attribute, attr); + if (kattr->store) + ret = kattr->store(kobj, kattr, buf, count); + return ret; +} + +static const struct sysfs_ops wmi_sysman_kobj_sysfs_ops = { + .show = wmi_sysman_attr_show, + .store = wmi_sysman_attr_store, +}; + +static void attr_name_release(struct kobject *kobj) +{ + kfree(kobj); +} + +static struct kobj_type attr_name_ktype = { + .release = attr_name_release, + .sysfs_ops = &wmi_sysman_kobj_sysfs_ops, +}; + +/** + * strlcpy_attr - Copy a length-limited, NULL-terminated string with bound checks + * @dest: Where to copy the string to + * @src: Where to copy the string from + */ +void strlcpy_attr(char *dest, char *src) +{ + size_t len = strlen(src) + 1; + + if (len > 1 && len <= MAX_BUFF) + strlcpy(dest, src, len); + + /*len can be zero because any property not-applicable to attribute can + * be empty so check only for too long buffers and log error + */ + if (len > MAX_BUFF) + pr_err("Source string returned from BIOS is out of bound!\n"); +} + +/** + * get_wmiobj_pointer() - Get Content of WMI block for particular instance + * @instance_id: WMI instance ID + * @guid_string: WMI GUID (in str form) + * + * Fetches the content for WMI block (instance_id) under GUID (guid_string) + * Caller must kfree the return + */ +union acpi_object *get_wmiobj_pointer(int instance_id, const char *guid_string) +{ + struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; + acpi_status status; + + status = wmi_query_block(guid_string, instance_id, &out); + + return ACPI_SUCCESS(status) ? (union acpi_object *)out.pointer : NULL; +} + +/** + * get_instance_count() - Compute total number of instances under guid_string + * @guid_string: WMI GUID (in string form) + */ +int get_instance_count(const char *guid_string) +{ + union acpi_object *wmi_obj = NULL; + int i = 0; + + do { + kfree(wmi_obj); + wmi_obj = get_wmiobj_pointer(i, guid_string); + i++; + } while (wmi_obj); + + return (i-1); +} + +/** + * alloc_attributes_data() - Allocate attributes data for a particular type + * @attr_type: Attribute type to allocate + */ +static int alloc_attributes_data(int attr_type) +{ + int retval = 0; + + switch (attr_type) { + case ENUM: + retval = alloc_enum_data(); + break; + case INT: + retval = alloc_int_data(); + break; + case STR: + retval = alloc_str_data(); + break; + case PO: + retval = alloc_po_data(); + break; + default: + break; + } + + return retval; +} + +/** + * destroy_attribute_objs() - Free a kset of kobjects + * @kset: The kset to destroy + * + * Fress kobjects created for each attribute_name under attribute type kset + */ +static void destroy_attribute_objs(struct kset *kset) +{ + struct kobject *pos, *next; + + list_for_each_entry_safe(pos, next, &kset->list, entry) { + kobject_put(pos); + } +} + +/** + * release_attributes_data() - Clean-up all sysfs directories and files created + */ +static void release_attributes_data(void) +{ + release_reset_bios_data(); + + mutex_lock(&wmi_priv.mutex); + exit_enum_attributes(); + exit_int_attributes(); + exit_str_attributes(); + exit_po_attributes(); + if (wmi_priv.authentication_dir_kset) { + destroy_attribute_objs(wmi_priv.authentication_dir_kset); + kset_unregister(wmi_priv.authentication_dir_kset); + wmi_priv.authentication_dir_kset = NULL; + } + if (wmi_priv.main_dir_kset) { + destroy_attribute_objs(wmi_priv.main_dir_kset); + kset_unregister(wmi_priv.main_dir_kset); + } + mutex_unlock(&wmi_priv.mutex); + +} + +/** + * init_bios_attributes() - Initialize all attributes for a type + * @attr_type: The attribute type to initialize + * @guid: The WMI GUID associated with this type to initialize + * + * Initialiaze all 4 types of attributes enumeration, integer, string and password object. + * Populates each attrbute typ's respective properties under sysfs files + */ +static int init_bios_attributes(int attr_type, const char *guid) +{ + struct kobject *attr_name_kobj; //individual attribute names + union acpi_object *obj = NULL; + union acpi_object *elements; + struct kset *tmp_set; + + /* instance_id needs to be reset for each type GUID + * also, instance IDs are unique within GUID but not across + */ + int instance_id = 0; + int retval = 0; + + retval = alloc_attributes_data(attr_type); + if (retval) + return retval; + /* need to use specific instance_id and guid combination to get right data */ + obj = get_wmiobj_pointer(instance_id, guid); + if (!obj || obj->type != ACPI_TYPE_PACKAGE) + return -ENODEV; + elements = obj->package.elements; + + mutex_lock(&wmi_priv.mutex); + while (elements) { + /* sanity checking */ + if (elements[ATTR_NAME].type != ACPI_TYPE_STRING) { + pr_debug("incorrect element type\n"); + goto nextobj; + } + if (strlen(elements[ATTR_NAME].string.pointer) == 0) { + pr_debug("empty attribute found\n"); + goto nextobj; + } + if (attr_type == PO) + tmp_set = wmi_priv.authentication_dir_kset; + else + tmp_set = wmi_priv.main_dir_kset; + + if (kset_find_obj(tmp_set, elements[ATTR_NAME].string.pointer)) { + pr_debug("duplicate attribute name found - %s\n", + elements[ATTR_NAME].string.pointer); + goto nextobj; + } + + /* build attribute */ + attr_name_kobj = kzalloc(sizeof(*attr_name_kobj), GFP_KERNEL); + if (!attr_name_kobj) { + retval = -ENOMEM; + goto err_attr_init; + } + + attr_name_kobj->kset = tmp_set; + + retval = kobject_init_and_add(attr_name_kobj, &attr_name_ktype, NULL, "%s", + elements[ATTR_NAME].string.pointer); + if (retval) { + kobject_put(attr_name_kobj); + goto err_attr_init; + } + + /* enumerate all of this attribute */ + switch (attr_type) { + case ENUM: + retval = populate_enum_data(elements, instance_id, attr_name_kobj); + break; + case INT: + retval = populate_int_data(elements, instance_id, attr_name_kobj); + break; + case STR: + retval = populate_str_data(elements, instance_id, attr_name_kobj); + break; + case PO: + retval = populate_po_data(elements, instance_id, attr_name_kobj); + break; + default: + break; + } + + if (retval) { + pr_debug("failed to populate %s\n", + elements[ATTR_NAME].string.pointer); + goto err_attr_init; + } + +nextobj: + kfree(obj); + instance_id++; + obj = get_wmiobj_pointer(instance_id, guid); + elements = obj ? obj->package.elements : NULL; + } + + mutex_unlock(&wmi_priv.mutex); + return 0; + +err_attr_init: + mutex_unlock(&wmi_priv.mutex); + release_attributes_data(); + kfree(obj); + return retval; +} + +static int __init sysman_init(void) +{ + int ret = 0; + + if (!dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "Dell System", NULL) && + !dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "www.dell.com", NULL)) { + pr_err("Unable to run on non-Dell system\n"); + return -ENODEV; + } + + ret = init_bios_attr_set_interface(); + if (ret || !wmi_priv.bios_attr_wdev) { + pr_debug("failed to initialize set interface\n"); + goto fail_set_interface; + } + + ret = init_bios_attr_pass_interface(); + if (ret || !wmi_priv.password_attr_wdev) { + pr_debug("failed to initialize pass interface\n"); + goto fail_pass_interface; + } + + ret = class_register(&firmware_attributes_class); + if (ret) + goto fail_class; + + wmi_priv.class_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0), + NULL, "%s", DRIVER_NAME); + if (IS_ERR(wmi_priv.class_dev)) { + ret = PTR_ERR(wmi_priv.class_dev); + goto fail_classdev; + } + + wmi_priv.main_dir_kset = kset_create_and_add("attributes", NULL, + &wmi_priv.class_dev->kobj); + if (!wmi_priv.main_dir_kset) { + ret = -ENOMEM; + goto fail_main_kset; + } + + wmi_priv.authentication_dir_kset = kset_create_and_add("authentication", NULL, + &wmi_priv.class_dev->kobj); + if (!wmi_priv.authentication_dir_kset) { + ret = -ENOMEM; + goto fail_authentication_kset; + } + + ret = create_attributes_level_sysfs_files(); + if (ret) { + pr_debug("could not create reset BIOS attribute\n"); + goto fail_reset_bios; + } + + ret = init_bios_attributes(ENUM, DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID); + if (ret) { + pr_debug("failed to populate enumeration type attributes\n"); + goto fail_create_group; + } + + ret = init_bios_attributes(INT, DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID); + if (ret) { + pr_debug("failed to populate integer type attributes\n"); + goto fail_create_group; + } + + ret = init_bios_attributes(STR, DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID); + if (ret) { + pr_debug("failed to populate string type attributes\n"); + goto fail_create_group; + } + + ret = init_bios_attributes(PO, DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID); + if (ret) { + pr_debug("failed to populate pass object type attributes\n"); + goto fail_create_group; + } + + return 0; + +fail_create_group: + release_attributes_data(); + +fail_reset_bios: + if (wmi_priv.authentication_dir_kset) { + kset_unregister(wmi_priv.authentication_dir_kset); + wmi_priv.authentication_dir_kset = NULL; + } + +fail_authentication_kset: + if (wmi_priv.main_dir_kset) { + kset_unregister(wmi_priv.main_dir_kset); + wmi_priv.main_dir_kset = NULL; + } + +fail_main_kset: + device_destroy(&firmware_attributes_class, MKDEV(0, 0)); + +fail_classdev: + class_unregister(&firmware_attributes_class); + +fail_class: + exit_bios_attr_pass_interface(); + +fail_pass_interface: + exit_bios_attr_set_interface(); + +fail_set_interface: + return ret; +} + +static void __exit sysman_exit(void) +{ + release_attributes_data(); + device_destroy(&firmware_attributes_class, MKDEV(0, 0)); + class_unregister(&firmware_attributes_class); + exit_bios_attr_set_interface(); + exit_bios_attr_pass_interface(); +} + +module_init(sysman_init); +module_exit(sysman_exit); + +MODULE_AUTHOR("Mario Limonciello "); +MODULE_AUTHOR("Prasanth Ksr "); +MODULE_AUTHOR("Divya Bharathi "); +MODULE_DESCRIPTION("Dell platform setting control interface"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell/dell-wmi.c b/drivers/platform/x86/dell/dell-wmi.c new file mode 100644 index 000000000000..bbdb3e860892 --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi.c @@ -0,0 +1,764 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Dell WMI hotkeys + * + * Copyright (C) 2008 Red Hat + * Copyright (C) 2014-2015 Pali Rohár + * + * Portions based on wistron_btns.c: + * Copyright (C) 2005 Miloslav Trmac + * Copyright (C) 2005 Bernhard Rosenkraenzer + * Copyright (C) 2005 Dmitry Torokhov + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dell-smbios.h" +#include "dell-wmi-descriptor.h" + +MODULE_AUTHOR("Matthew Garrett "); +MODULE_AUTHOR("Pali Rohár "); +MODULE_DESCRIPTION("Dell laptop WMI hotkeys driver"); +MODULE_LICENSE("GPL"); + +#define DELL_EVENT_GUID "9DBB5994-A997-11DA-B012-B622A1EF5492" + +static bool wmi_requires_smbios_request; + +struct dell_wmi_priv { + struct input_dev *input_dev; + u32 interface_version; +}; + +static int __init dmi_matched(const struct dmi_system_id *dmi) +{ + wmi_requires_smbios_request = 1; + return 1; +} + +static const struct dmi_system_id dell_wmi_smbios_list[] __initconst = { + { + .callback = dmi_matched, + .ident = "Dell Inspiron M5110", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron M5110"), + }, + }, + { + .callback = dmi_matched, + .ident = "Dell Vostro V131", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Vostro V131"), + }, + }, + { } +}; + +/* + * Keymap for WMI events of type 0x0000 + * + * Certain keys are flagged as KE_IGNORE. All of these are either + * notifications (rather than requests for change) or are also sent + * via the keyboard controller so should not be sent again. + */ +static const struct key_entry dell_wmi_keymap_type_0000[] = { + { KE_IGNORE, 0x003a, { KEY_CAPSLOCK } }, + + /* Key code is followed by brightness level */ + { KE_KEY, 0xe005, { KEY_BRIGHTNESSDOWN } }, + { KE_KEY, 0xe006, { KEY_BRIGHTNESSUP } }, + + /* Battery health status button */ + { KE_KEY, 0xe007, { KEY_BATTERY } }, + + /* Radio devices state change, key code is followed by other values */ + { KE_IGNORE, 0xe008, { KEY_RFKILL } }, + + { KE_KEY, 0xe009, { KEY_EJECTCD } }, + + /* Key code is followed by: next, active and attached devices */ + { KE_KEY, 0xe00b, { KEY_SWITCHVIDEOMODE } }, + + /* Key code is followed by keyboard illumination level */ + { KE_IGNORE, 0xe00c, { KEY_KBDILLUMTOGGLE } }, + + /* BIOS error detected */ + { KE_IGNORE, 0xe00d, { KEY_RESERVED } }, + + /* Battery was removed or inserted */ + { KE_IGNORE, 0xe00e, { KEY_RESERVED } }, + + /* Wifi Catcher */ + { KE_KEY, 0xe011, { KEY_WLAN } }, + + /* Ambient light sensor toggle */ + { KE_IGNORE, 0xe013, { KEY_RESERVED } }, + + { KE_IGNORE, 0xe020, { KEY_MUTE } }, + + /* Unknown, defined in ACPI DSDT */ + /* { KE_IGNORE, 0xe023, { KEY_RESERVED } }, */ + + /* Untested, Dell Instant Launch key on Inspiron 7520 */ + /* { KE_IGNORE, 0xe024, { KEY_RESERVED } }, */ + + /* Dell Instant Launch key */ + { KE_KEY, 0xe025, { KEY_PROG4 } }, + + /* Audio panel key */ + { KE_IGNORE, 0xe026, { KEY_RESERVED } }, + + /* LCD Display On/Off Control key */ + { KE_KEY, 0xe027, { KEY_DISPLAYTOGGLE } }, + + /* Untested, Multimedia key on Dell Vostro 3560 */ + /* { KE_IGNORE, 0xe028, { KEY_RESERVED } }, */ + + /* Dell Instant Launch key */ + { KE_KEY, 0xe029, { KEY_PROG4 } }, + + /* Untested, Windows Mobility Center button on Inspiron 7520 */ + /* { KE_IGNORE, 0xe02a, { KEY_RESERVED } }, */ + + /* Unknown, defined in ACPI DSDT */ + /* { KE_IGNORE, 0xe02b, { KEY_RESERVED } }, */ + + /* Untested, Dell Audio With Preset Switch button on Inspiron 7520 */ + /* { KE_IGNORE, 0xe02c, { KEY_RESERVED } }, */ + + { KE_IGNORE, 0xe02e, { KEY_VOLUMEDOWN } }, + { KE_IGNORE, 0xe030, { KEY_VOLUMEUP } }, + { KE_IGNORE, 0xe033, { KEY_KBDILLUMUP } }, + { KE_IGNORE, 0xe034, { KEY_KBDILLUMDOWN } }, + { KE_IGNORE, 0xe03a, { KEY_CAPSLOCK } }, + + /* NIC Link is Up */ + { KE_IGNORE, 0xe043, { KEY_RESERVED } }, + + /* NIC Link is Down */ + { KE_IGNORE, 0xe044, { KEY_RESERVED } }, + + /* + * This entry is very suspicious! + * Originally Matthew Garrett created this dell-wmi driver specially for + * "button with a picture of a battery" which has event code 0xe045. + * Later Mario Limonciello from Dell told us that event code 0xe045 is + * reported by Num Lock and should be ignored because key is send also + * by keyboard controller. + * So for now we will ignore this event to prevent potential double + * Num Lock key press. + */ + { KE_IGNORE, 0xe045, { KEY_NUMLOCK } }, + + /* Scroll lock and also going to tablet mode on portable devices */ + { KE_IGNORE, 0xe046, { KEY_SCROLLLOCK } }, + + /* Untested, going from tablet mode on portable devices */ + /* { KE_IGNORE, 0xe047, { KEY_RESERVED } }, */ + + /* Dell Support Center key */ + { KE_IGNORE, 0xe06e, { KEY_RESERVED } }, + + { KE_IGNORE, 0xe0f7, { KEY_MUTE } }, + { KE_IGNORE, 0xe0f8, { KEY_VOLUMEDOWN } }, + { KE_IGNORE, 0xe0f9, { KEY_VOLUMEUP } }, +}; + +struct dell_bios_keymap_entry { + u16 scancode; + u16 keycode; +}; + +struct dell_bios_hotkey_table { + struct dmi_header header; + struct dell_bios_keymap_entry keymap[]; + +}; + +struct dell_dmi_results { + int err; + int keymap_size; + struct key_entry *keymap; +}; + +/* Uninitialized entries here are KEY_RESERVED == 0. */ +static const u16 bios_to_linux_keycode[256] = { + [0] = KEY_MEDIA, + [1] = KEY_NEXTSONG, + [2] = KEY_PLAYPAUSE, + [3] = KEY_PREVIOUSSONG, + [4] = KEY_STOPCD, + [5] = KEY_UNKNOWN, + [6] = KEY_UNKNOWN, + [7] = KEY_UNKNOWN, + [8] = KEY_WWW, + [9] = KEY_UNKNOWN, + [10] = KEY_VOLUMEDOWN, + [11] = KEY_MUTE, + [12] = KEY_VOLUMEUP, + [13] = KEY_UNKNOWN, + [14] = KEY_BATTERY, + [15] = KEY_EJECTCD, + [16] = KEY_UNKNOWN, + [17] = KEY_SLEEP, + [18] = KEY_PROG1, + [19] = KEY_BRIGHTNESSDOWN, + [20] = KEY_BRIGHTNESSUP, + [21] = KEY_BRIGHTNESS_AUTO, + [22] = KEY_KBDILLUMTOGGLE, + [23] = KEY_UNKNOWN, + [24] = KEY_SWITCHVIDEOMODE, + [25] = KEY_UNKNOWN, + [26] = KEY_UNKNOWN, + [27] = KEY_SWITCHVIDEOMODE, + [28] = KEY_UNKNOWN, + [29] = KEY_UNKNOWN, + [30] = KEY_PROG2, + [31] = KEY_UNKNOWN, + [32] = KEY_UNKNOWN, + [33] = KEY_UNKNOWN, + [34] = KEY_UNKNOWN, + [35] = KEY_UNKNOWN, + [36] = KEY_UNKNOWN, + [37] = KEY_UNKNOWN, + [38] = KEY_MICMUTE, + [255] = KEY_PROG3, +}; + +/* + * Keymap for WMI events of type 0x0010 + * + * These are applied if the 0xB2 DMI hotkey table is present and doesn't + * override them. + */ +static const struct key_entry dell_wmi_keymap_type_0010[] = { + /* Fn-lock switched to function keys */ + { KE_IGNORE, 0x0, { KEY_RESERVED } }, + + /* Fn-lock switched to multimedia keys */ + { KE_IGNORE, 0x1, { KEY_RESERVED } }, + + /* Keyboard backlight change notification */ + { KE_IGNORE, 0x3f, { KEY_RESERVED } }, + + /* Backlight brightness level */ + { KE_KEY, 0x57, { KEY_BRIGHTNESSDOWN } }, + { KE_KEY, 0x58, { KEY_BRIGHTNESSUP } }, + + /* Mic mute */ + { KE_KEY, 0x150, { KEY_MICMUTE } }, + + /* Fn-lock */ + { KE_IGNORE, 0x151, { KEY_RESERVED } }, + + /* Change keyboard illumination */ + { KE_IGNORE, 0x152, { KEY_KBDILLUMTOGGLE } }, + + /* + * Radio disable (notify only -- there is no model for which the + * WMI event is supposed to trigger an action). + */ + { KE_IGNORE, 0x153, { KEY_RFKILL } }, + + /* RGB keyboard backlight control */ + { KE_IGNORE, 0x154, { KEY_RESERVED } }, + + /* + * Stealth mode toggle. This will "disable all lights and sounds". + * The action is performed by the BIOS and EC; the WMI event is just + * a notification. On the XPS 13 9350, this is Fn+F7, and there's + * a BIOS setting to enable and disable the hotkey. + */ + { KE_IGNORE, 0x155, { KEY_RESERVED } }, + + /* Rugged magnetic dock attach/detach events */ + { KE_IGNORE, 0x156, { KEY_RESERVED } }, + { KE_IGNORE, 0x157, { KEY_RESERVED } }, + + /* Rugged programmable (P1/P2/P3 keys) */ + { KE_KEY, 0x850, { KEY_PROG1 } }, + { KE_KEY, 0x851, { KEY_PROG2 } }, + { KE_KEY, 0x852, { KEY_PROG3 } }, + + /* + * Radio disable (notify only -- there is no model for which the + * WMI event is supposed to trigger an action). + */ + { KE_IGNORE, 0xe008, { KEY_RFKILL } }, + + /* Fn-lock */ + { KE_IGNORE, 0xe035, { KEY_RESERVED } }, +}; + +/* + * Keymap for WMI events of type 0x0011 + */ +static const struct key_entry dell_wmi_keymap_type_0011[] = { + /* Battery unplugged */ + { KE_IGNORE, 0xfff0, { KEY_RESERVED } }, + + /* Battery inserted */ + { KE_IGNORE, 0xfff1, { KEY_RESERVED } }, + + /* + * Detachable keyboard detached / undocked + * Note SW_TABLET_MODE is already reported through the intel_vbtn + * driver for this, so we ignore it. + */ + { KE_IGNORE, 0xfff2, { KEY_RESERVED } }, + + /* Detachable keyboard attached / docked */ + { KE_IGNORE, 0xfff3, { KEY_RESERVED } }, + + /* Keyboard backlight level changed */ + { KE_IGNORE, KBD_LED_OFF_TOKEN, { KEY_RESERVED } }, + { KE_IGNORE, KBD_LED_ON_TOKEN, { KEY_RESERVED } }, + { KE_IGNORE, KBD_LED_AUTO_TOKEN, { KEY_RESERVED } }, + { KE_IGNORE, KBD_LED_AUTO_25_TOKEN, { KEY_RESERVED } }, + { KE_IGNORE, KBD_LED_AUTO_50_TOKEN, { KEY_RESERVED } }, + { KE_IGNORE, KBD_LED_AUTO_75_TOKEN, { KEY_RESERVED } }, + { KE_IGNORE, KBD_LED_AUTO_100_TOKEN, { KEY_RESERVED } }, +}; + +/* + * Keymap for WMI events of type 0x0012 + * They are events with extended data + */ +static const struct key_entry dell_wmi_keymap_type_0012[] = { + /* Fn-lock button pressed */ + { KE_IGNORE, 0xe035, { KEY_RESERVED } }, +}; + +static void dell_wmi_process_key(struct wmi_device *wdev, int type, int code) +{ + struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev); + const struct key_entry *key; + + key = sparse_keymap_entry_from_scancode(priv->input_dev, + (type << 16) | code); + if (!key) { + pr_info("Unknown key with type 0x%04x and code 0x%04x pressed\n", + type, code); + return; + } + + pr_debug("Key with type 0x%04x and code 0x%04x pressed\n", type, code); + + /* Don't report brightness notifications that will also come via ACPI */ + if ((key->keycode == KEY_BRIGHTNESSUP || + key->keycode == KEY_BRIGHTNESSDOWN) && + acpi_video_handles_brightness_key_presses()) + return; + + if (type == 0x0000 && code == 0xe025 && !wmi_requires_smbios_request) + return; + + if (key->keycode == KEY_KBDILLUMTOGGLE) + dell_laptop_call_notifier( + DELL_LAPTOP_KBD_BACKLIGHT_BRIGHTNESS_CHANGED, NULL); + + sparse_keymap_report_entry(priv->input_dev, key, 1, true); +} + +static void dell_wmi_notify(struct wmi_device *wdev, + union acpi_object *obj) +{ + struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev); + u16 *buffer_entry, *buffer_end; + acpi_size buffer_size; + int len, i; + + if (obj->type != ACPI_TYPE_BUFFER) { + pr_warn("bad response type %x\n", obj->type); + return; + } + + pr_debug("Received WMI event (%*ph)\n", + obj->buffer.length, obj->buffer.pointer); + + buffer_entry = (u16 *)obj->buffer.pointer; + buffer_size = obj->buffer.length/2; + buffer_end = buffer_entry + buffer_size; + + /* + * BIOS/ACPI on devices with WMI interface version 0 does not clear + * buffer before filling it. So next time when BIOS/ACPI send WMI event + * which is smaller as previous then it contains garbage in buffer from + * previous event. + * + * BIOS/ACPI on devices with WMI interface version 1 clears buffer and + * sometimes send more events in buffer at one call. + * + * So to prevent reading garbage from buffer we will process only first + * one event on devices with WMI interface version 0. + */ + if (priv->interface_version == 0 && buffer_entry < buffer_end) + if (buffer_end > buffer_entry + buffer_entry[0] + 1) + buffer_end = buffer_entry + buffer_entry[0] + 1; + + while (buffer_entry < buffer_end) { + + len = buffer_entry[0]; + if (len == 0) + break; + + len++; + + if (buffer_entry + len > buffer_end) { + pr_warn("Invalid length of WMI event\n"); + break; + } + + pr_debug("Process buffer (%*ph)\n", len*2, buffer_entry); + + switch (buffer_entry[1]) { + case 0x0000: /* One key pressed or event occurred */ + case 0x0012: /* Event with extended data occurred */ + if (len > 2) + dell_wmi_process_key(wdev, buffer_entry[1], + buffer_entry[2]); + /* Extended data is currently ignored */ + break; + case 0x0010: /* Sequence of keys pressed */ + case 0x0011: /* Sequence of events occurred */ + for (i = 2; i < len; ++i) + dell_wmi_process_key(wdev, buffer_entry[1], + buffer_entry[i]); + break; + default: /* Unknown event */ + pr_info("Unknown WMI event type 0x%x\n", + (int)buffer_entry[1]); + break; + } + + buffer_entry += len; + + } + +} + +static bool have_scancode(u32 scancode, const struct key_entry *keymap, int len) +{ + int i; + + for (i = 0; i < len; i++) + if (keymap[i].code == scancode) + return true; + + return false; +} + +static void handle_dmi_entry(const struct dmi_header *dm, void *opaque) +{ + struct dell_dmi_results *results = opaque; + struct dell_bios_hotkey_table *table; + int hotkey_num, i, pos = 0; + struct key_entry *keymap; + + if (results->err || results->keymap) + return; /* We already found the hotkey table. */ + + /* The Dell hotkey table is type 0xB2. Scan until we find it. */ + if (dm->type != 0xb2) + return; + + table = container_of(dm, struct dell_bios_hotkey_table, header); + + hotkey_num = (table->header.length - + sizeof(struct dell_bios_hotkey_table)) / + sizeof(struct dell_bios_keymap_entry); + if (hotkey_num < 1) { + /* + * Historically, dell-wmi would ignore a DMI entry of + * fewer than 7 bytes. Sizes between 4 and 8 bytes are + * nonsensical (both the header and all entries are 4 + * bytes), so we approximate the old behavior by + * ignoring tables with fewer than one entry. + */ + return; + } + + keymap = kcalloc(hotkey_num, sizeof(struct key_entry), GFP_KERNEL); + if (!keymap) { + results->err = -ENOMEM; + return; + } + + for (i = 0; i < hotkey_num; i++) { + const struct dell_bios_keymap_entry *bios_entry = + &table->keymap[i]; + + /* Uninitialized entries are 0 aka KEY_RESERVED. */ + u16 keycode = (bios_entry->keycode < + ARRAY_SIZE(bios_to_linux_keycode)) ? + bios_to_linux_keycode[bios_entry->keycode] : + (bios_entry->keycode == 0xffff ? KEY_UNKNOWN : KEY_RESERVED); + + /* + * Log if we find an entry in the DMI table that we don't + * understand. If this happens, we should figure out what + * the entry means and add it to bios_to_linux_keycode. + */ + if (keycode == KEY_RESERVED) { + pr_info("firmware scancode 0x%x maps to unrecognized keycode 0x%x\n", + bios_entry->scancode, bios_entry->keycode); + continue; + } + + if (keycode == KEY_KBDILLUMTOGGLE) + keymap[pos].type = KE_IGNORE; + else + keymap[pos].type = KE_KEY; + keymap[pos].code = bios_entry->scancode; + keymap[pos].keycode = keycode; + + pos++; + } + + results->keymap = keymap; + results->keymap_size = pos; +} + +static int dell_wmi_input_setup(struct wmi_device *wdev) +{ + struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev); + struct dell_dmi_results dmi_results = {}; + struct key_entry *keymap; + int err, i, pos = 0; + + priv->input_dev = input_allocate_device(); + if (!priv->input_dev) + return -ENOMEM; + + priv->input_dev->name = "Dell WMI hotkeys"; + priv->input_dev->id.bustype = BUS_HOST; + priv->input_dev->dev.parent = &wdev->dev; + + if (dmi_walk(handle_dmi_entry, &dmi_results)) { + /* + * Historically, dell-wmi ignored dmi_walk errors. A failure + * is certainly surprising, but it probably just indicates + * a very old laptop. + */ + pr_warn("no DMI; using the old-style hotkey interface\n"); + } + + if (dmi_results.err) { + err = dmi_results.err; + goto err_free_dev; + } + + keymap = kcalloc(dmi_results.keymap_size + + ARRAY_SIZE(dell_wmi_keymap_type_0000) + + ARRAY_SIZE(dell_wmi_keymap_type_0010) + + ARRAY_SIZE(dell_wmi_keymap_type_0011) + + ARRAY_SIZE(dell_wmi_keymap_type_0012) + + 1, + sizeof(struct key_entry), GFP_KERNEL); + if (!keymap) { + kfree(dmi_results.keymap); + err = -ENOMEM; + goto err_free_dev; + } + + /* Append table with events of type 0x0010 which comes from DMI */ + for (i = 0; i < dmi_results.keymap_size; i++) { + keymap[pos] = dmi_results.keymap[i]; + keymap[pos].code |= (0x0010 << 16); + pos++; + } + + kfree(dmi_results.keymap); + + /* Append table with extra events of type 0x0010 which are not in DMI */ + for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0010); i++) { + const struct key_entry *entry = &dell_wmi_keymap_type_0010[i]; + + /* + * Check if we've already found this scancode. This takes + * quadratic time, but it doesn't matter unless the list + * of extra keys gets very long. + */ + if (dmi_results.keymap_size && + have_scancode(entry->code | (0x0010 << 16), + keymap, dmi_results.keymap_size) + ) + continue; + + keymap[pos] = *entry; + keymap[pos].code |= (0x0010 << 16); + pos++; + } + + /* Append table with events of type 0x0011 */ + for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0011); i++) { + keymap[pos] = dell_wmi_keymap_type_0011[i]; + keymap[pos].code |= (0x0011 << 16); + pos++; + } + + /* Append table with events of type 0x0012 */ + for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0012); i++) { + keymap[pos] = dell_wmi_keymap_type_0012[i]; + keymap[pos].code |= (0x0012 << 16); + pos++; + } + + /* + * Now append also table with "legacy" events of type 0x0000. Some of + * them are reported also on laptops which have scancodes in DMI. + */ + for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0000); i++) { + keymap[pos] = dell_wmi_keymap_type_0000[i]; + pos++; + } + + keymap[pos].type = KE_END; + + err = sparse_keymap_setup(priv->input_dev, keymap, NULL); + /* + * Sparse keymap library makes a copy of keymap so we don't need the + * original one that was allocated. + */ + kfree(keymap); + if (err) + goto err_free_dev; + + err = input_register_device(priv->input_dev); + if (err) + goto err_free_dev; + + return 0; + + err_free_dev: + input_free_device(priv->input_dev); + return err; +} + +static void dell_wmi_input_destroy(struct wmi_device *wdev) +{ + struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev); + + input_unregister_device(priv->input_dev); +} + +/* + * According to Dell SMBIOS documentation: + * + * 17 3 Application Program Registration + * + * cbArg1 Application ID 1 = 0x00010000 + * cbArg2 Application ID 2 + * QUICKSET/DCP = 0x51534554 "QSET" + * ALS Driver = 0x416c7353 "AlsS" + * Latitude ON = 0x4c6f6e52 "LonR" + * cbArg3 Application version or revision number + * cbArg4 0 = Unregister application + * 1 = Register application + * cbRes1 Standard return codes (0, -1, -2) + */ + +static int dell_wmi_events_set_enabled(bool enable) +{ + struct calling_interface_buffer *buffer; + int ret; + + buffer = kzalloc(sizeof(struct calling_interface_buffer), GFP_KERNEL); + if (!buffer) + return -ENOMEM; + buffer->cmd_class = CLASS_INFO; + buffer->cmd_select = SELECT_APP_REGISTRATION; + buffer->input[0] = 0x10000; + buffer->input[1] = 0x51534554; + buffer->input[3] = enable; + ret = dell_smbios_call(buffer); + if (ret == 0) + ret = buffer->output[0]; + kfree(buffer); + + return dell_smbios_error(ret); +} + +static int dell_wmi_probe(struct wmi_device *wdev, const void *context) +{ + struct dell_wmi_priv *priv; + int ret; + + ret = dell_wmi_get_descriptor_valid(); + if (ret) + return ret; + + priv = devm_kzalloc( + &wdev->dev, sizeof(struct dell_wmi_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + dev_set_drvdata(&wdev->dev, priv); + + if (!dell_wmi_get_interface_version(&priv->interface_version)) + return -EPROBE_DEFER; + + return dell_wmi_input_setup(wdev); +} + +static int dell_wmi_remove(struct wmi_device *wdev) +{ + dell_wmi_input_destroy(wdev); + return 0; +} +static const struct wmi_device_id dell_wmi_id_table[] = { + { .guid_string = DELL_EVENT_GUID }, + { }, +}; + +static struct wmi_driver dell_wmi_driver = { + .driver = { + .name = "dell-wmi", + }, + .id_table = dell_wmi_id_table, + .probe = dell_wmi_probe, + .remove = dell_wmi_remove, + .notify = dell_wmi_notify, +}; + +static int __init dell_wmi_init(void) +{ + int err; + + dmi_check_system(dell_wmi_smbios_list); + + if (wmi_requires_smbios_request) { + err = dell_wmi_events_set_enabled(true); + if (err) { + pr_err("Failed to enable WMI events\n"); + return err; + } + } + + return wmi_driver_register(&dell_wmi_driver); +} +late_initcall(dell_wmi_init); + +static void __exit dell_wmi_exit(void) +{ + if (wmi_requires_smbios_request) + dell_wmi_events_set_enabled(false); + + wmi_driver_unregister(&dell_wmi_driver); +} +module_exit(dell_wmi_exit); + +MODULE_DEVICE_TABLE(wmi, dell_wmi_id_table); diff --git a/drivers/platform/x86/dell/dell_rbu.c b/drivers/platform/x86/dell/dell_rbu.c new file mode 100644 index 000000000000..03c3ff34bcf5 --- /dev/null +++ b/drivers/platform/x86/dell/dell_rbu.c @@ -0,0 +1,680 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * dell_rbu.c + * Bios Update driver for Dell systems + * Author: Dell Inc + * Abhay Salunke + * + * Copyright (C) 2005 Dell Inc. + * + * Remote BIOS Update (rbu) driver is used for updating DELL BIOS by + * creating entries in the /sys file systems on Linux 2.6 and higher + * kernels. The driver supports two mechanism to update the BIOS namely + * contiguous and packetized. Both these methods still require having some + * application to set the CMOS bit indicating the BIOS to update itself + * after a reboot. + * + * Contiguous method: + * This driver writes the incoming data in a monolithic image by allocating + * contiguous physical pages large enough to accommodate the incoming BIOS + * image size. + * + * Packetized method: + * The driver writes the incoming packet image by allocating a new packet + * on every time the packet data is written. This driver requires an + * application to break the BIOS image in to fixed sized packet chunks. + * + * See Documentation/admin-guide/dell_rbu.rst for more info. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Abhay Salunke "); +MODULE_DESCRIPTION("Driver for updating BIOS image on DELL systems"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("3.2"); + +#define BIOS_SCAN_LIMIT 0xffffffff +#define MAX_IMAGE_LENGTH 16 +static struct _rbu_data { + void *image_update_buffer; + unsigned long image_update_buffer_size; + unsigned long bios_image_size; + int image_update_ordernum; + spinlock_t lock; + unsigned long packet_read_count; + unsigned long num_packets; + unsigned long packetsize; + unsigned long imagesize; + int entry_created; +} rbu_data; + +static char image_type[MAX_IMAGE_LENGTH + 1] = "mono"; +module_param_string(image_type, image_type, sizeof (image_type), 0); +MODULE_PARM_DESC(image_type, "BIOS image type. choose- mono or packet or init"); + +static unsigned long allocation_floor = 0x100000; +module_param(allocation_floor, ulong, 0644); +MODULE_PARM_DESC(allocation_floor, "Minimum address for allocations when using Packet mode"); + +struct packet_data { + struct list_head list; + size_t length; + void *data; + int ordernum; +}; + +static struct packet_data packet_data_head; + +static struct platform_device *rbu_device; +static int context; + +static void init_packet_head(void) +{ + INIT_LIST_HEAD(&packet_data_head.list); + rbu_data.packet_read_count = 0; + rbu_data.num_packets = 0; + rbu_data.packetsize = 0; + rbu_data.imagesize = 0; +} + +static int create_packet(void *data, size_t length) +{ + struct packet_data *newpacket; + int ordernum = 0; + int retval = 0; + unsigned int packet_array_size = 0; + void **invalid_addr_packet_array = NULL; + void *packet_data_temp_buf = NULL; + unsigned int idx = 0; + + pr_debug("entry\n"); + + if (!rbu_data.packetsize) { + pr_debug("packetsize not specified\n"); + retval = -EINVAL; + goto out_noalloc; + } + + spin_unlock(&rbu_data.lock); + + newpacket = kzalloc(sizeof (struct packet_data), GFP_KERNEL); + + if (!newpacket) { + pr_warn("failed to allocate new packet\n"); + retval = -ENOMEM; + spin_lock(&rbu_data.lock); + goto out_noalloc; + } + + ordernum = get_order(length); + + /* + * BIOS errata mean we cannot allocate packets below 1MB or they will + * be overwritten by BIOS. + * + * array to temporarily hold packets + * that are below the allocation floor + * + * NOTE: very simplistic because we only need the floor to be at 1MB + * due to BIOS errata. This shouldn't be used for higher floors + * or you will run out of mem trying to allocate the array. + */ + packet_array_size = max_t(unsigned int, allocation_floor / rbu_data.packetsize, 1); + invalid_addr_packet_array = kcalloc(packet_array_size, sizeof(void *), + GFP_KERNEL); + + if (!invalid_addr_packet_array) { + pr_warn("failed to allocate invalid_addr_packet_array\n"); + retval = -ENOMEM; + spin_lock(&rbu_data.lock); + goto out_alloc_packet; + } + + while (!packet_data_temp_buf) { + packet_data_temp_buf = (unsigned char *) + __get_free_pages(GFP_KERNEL, ordernum); + if (!packet_data_temp_buf) { + pr_warn("failed to allocate new packet\n"); + retval = -ENOMEM; + spin_lock(&rbu_data.lock); + goto out_alloc_packet_array; + } + + if ((unsigned long)virt_to_phys(packet_data_temp_buf) + < allocation_floor) { + pr_debug("packet 0x%lx below floor at 0x%lx\n", + (unsigned long)virt_to_phys( + packet_data_temp_buf), + allocation_floor); + invalid_addr_packet_array[idx++] = packet_data_temp_buf; + packet_data_temp_buf = NULL; + } + } + /* + * set to uncachable or it may never get written back before reboot + */ + set_memory_uc((unsigned long)packet_data_temp_buf, 1 << ordernum); + + spin_lock(&rbu_data.lock); + + newpacket->data = packet_data_temp_buf; + + pr_debug("newpacket at physical addr %lx\n", + (unsigned long)virt_to_phys(newpacket->data)); + + /* packets may not have fixed size */ + newpacket->length = length; + newpacket->ordernum = ordernum; + ++rbu_data.num_packets; + + /* initialize the newly created packet headers */ + INIT_LIST_HEAD(&newpacket->list); + list_add_tail(&newpacket->list, &packet_data_head.list); + + memcpy(newpacket->data, data, length); + + pr_debug("exit\n"); + +out_alloc_packet_array: + /* always free packet array */ + while (idx--) { + pr_debug("freeing unused packet below floor 0x%lx\n", + (unsigned long)virt_to_phys(invalid_addr_packet_array[idx])); + free_pages((unsigned long)invalid_addr_packet_array[idx], ordernum); + } + kfree(invalid_addr_packet_array); + +out_alloc_packet: + /* if error, free data */ + if (retval) + kfree(newpacket); + +out_noalloc: + return retval; +} + +static int packetize_data(const u8 *data, size_t length) +{ + int rc = 0; + int done = 0; + int packet_length; + u8 *temp; + u8 *end = (u8 *) data + length; + pr_debug("data length %zd\n", length); + if (!rbu_data.packetsize) { + pr_warn("packetsize not specified\n"); + return -EIO; + } + + temp = (u8 *) data; + + /* packetize the hunk */ + while (!done) { + if ((temp + rbu_data.packetsize) < end) + packet_length = rbu_data.packetsize; + else { + /* this is the last packet */ + packet_length = end - temp; + done = 1; + } + + if ((rc = create_packet(temp, packet_length))) + return rc; + + pr_debug("%p:%td\n", temp, (end - temp)); + temp += packet_length; + } + + rbu_data.imagesize = length; + + return rc; +} + +static int do_packet_read(char *data, struct packet_data *newpacket, + int length, int bytes_read, int *list_read_count) +{ + void *ptemp_buf; + int bytes_copied = 0; + int j = 0; + + *list_read_count += newpacket->length; + + if (*list_read_count > bytes_read) { + /* point to the start of unread data */ + j = newpacket->length - (*list_read_count - bytes_read); + /* point to the offset in the packet buffer */ + ptemp_buf = (u8 *) newpacket->data + j; + /* + * check if there is enough room in + * * the incoming buffer + */ + if (length > (*list_read_count - bytes_read)) + /* + * copy what ever is there in this + * packet and move on + */ + bytes_copied = (*list_read_count - bytes_read); + else + /* copy the remaining */ + bytes_copied = length; + memcpy(data, ptemp_buf, bytes_copied); + } + return bytes_copied; +} + +static int packet_read_list(char *data, size_t * pread_length) +{ + struct packet_data *newpacket; + int temp_count = 0; + int bytes_copied = 0; + int bytes_read = 0; + int remaining_bytes = 0; + char *pdest = data; + + /* check if we have any packets */ + if (0 == rbu_data.num_packets) + return -ENOMEM; + + remaining_bytes = *pread_length; + bytes_read = rbu_data.packet_read_count; + + list_for_each_entry(newpacket, (&packet_data_head.list)->next, list) { + bytes_copied = do_packet_read(pdest, newpacket, + remaining_bytes, bytes_read, &temp_count); + remaining_bytes -= bytes_copied; + bytes_read += bytes_copied; + pdest += bytes_copied; + /* + * check if we reached end of buffer before reaching the + * last packet + */ + if (remaining_bytes == 0) + break; + } + /*finally set the bytes read */ + *pread_length = bytes_read - rbu_data.packet_read_count; + rbu_data.packet_read_count = bytes_read; + return 0; +} + +static void packet_empty_list(void) +{ + struct packet_data *newpacket, *tmp; + + list_for_each_entry_safe(newpacket, tmp, (&packet_data_head.list)->next, list) { + list_del(&newpacket->list); + + /* + * zero out the RBU packet memory before freeing + * to make sure there are no stale RBU packets left in memory + */ + memset(newpacket->data, 0, rbu_data.packetsize); + set_memory_wb((unsigned long)newpacket->data, + 1 << newpacket->ordernum); + free_pages((unsigned long) newpacket->data, + newpacket->ordernum); + kfree(newpacket); + } + rbu_data.packet_read_count = 0; + rbu_data.num_packets = 0; + rbu_data.imagesize = 0; +} + +/* + * img_update_free: Frees the buffer allocated for storing BIOS image + * Always called with lock held and returned with lock held + */ +static void img_update_free(void) +{ + if (!rbu_data.image_update_buffer) + return; + /* + * zero out this buffer before freeing it to get rid of any stale + * BIOS image copied in memory. + */ + memset(rbu_data.image_update_buffer, 0, + rbu_data.image_update_buffer_size); + free_pages((unsigned long) rbu_data.image_update_buffer, + rbu_data.image_update_ordernum); + + /* + * Re-initialize the rbu_data variables after a free + */ + rbu_data.image_update_ordernum = -1; + rbu_data.image_update_buffer = NULL; + rbu_data.image_update_buffer_size = 0; + rbu_data.bios_image_size = 0; +} + +/* + * img_update_realloc: This function allocates the contiguous pages to + * accommodate the requested size of data. The memory address and size + * values are stored globally and on every call to this function the new + * size is checked to see if more data is required than the existing size. + * If true the previous memory is freed and new allocation is done to + * accommodate the new size. If the incoming size is less then than the + * already allocated size, then that memory is reused. This function is + * called with lock held and returns with lock held. + */ +static int img_update_realloc(unsigned long size) +{ + unsigned char *image_update_buffer = NULL; + unsigned long img_buf_phys_addr; + int ordernum; + + /* + * check if the buffer of sufficient size has been + * already allocated + */ + if (rbu_data.image_update_buffer_size >= size) { + /* + * check for corruption + */ + if ((size != 0) && (rbu_data.image_update_buffer == NULL)) { + pr_err("corruption check failed\n"); + return -EINVAL; + } + /* + * we have a valid pre-allocated buffer with + * sufficient size + */ + return 0; + } + + /* + * free any previously allocated buffer + */ + img_update_free(); + + spin_unlock(&rbu_data.lock); + + ordernum = get_order(size); + image_update_buffer = + (unsigned char *)__get_free_pages(GFP_DMA32, ordernum); + spin_lock(&rbu_data.lock); + if (!image_update_buffer) { + pr_debug("Not enough memory for image update: size = %ld\n", size); + return -ENOMEM; + } + + img_buf_phys_addr = (unsigned long)virt_to_phys(image_update_buffer); + if (WARN_ON_ONCE(img_buf_phys_addr > BIOS_SCAN_LIMIT)) + return -EINVAL; /* can't happen per definition */ + + rbu_data.image_update_buffer = image_update_buffer; + rbu_data.image_update_buffer_size = size; + rbu_data.bios_image_size = rbu_data.image_update_buffer_size; + rbu_data.image_update_ordernum = ordernum; + return 0; +} + +static ssize_t read_packet_data(char *buffer, loff_t pos, size_t count) +{ + int retval; + size_t bytes_left; + size_t data_length; + char *ptempBuf = buffer; + + /* check to see if we have something to return */ + if (rbu_data.num_packets == 0) { + pr_debug("no packets written\n"); + retval = -ENOMEM; + goto read_rbu_data_exit; + } + + if (pos > rbu_data.imagesize) { + retval = 0; + pr_warn("data underrun\n"); + goto read_rbu_data_exit; + } + + bytes_left = rbu_data.imagesize - pos; + data_length = min(bytes_left, count); + + if ((retval = packet_read_list(ptempBuf, &data_length)) < 0) + goto read_rbu_data_exit; + + if ((pos + count) > rbu_data.imagesize) { + rbu_data.packet_read_count = 0; + /* this was the last copy */ + retval = bytes_left; + } else + retval = count; + + read_rbu_data_exit: + return retval; +} + +static ssize_t read_rbu_mono_data(char *buffer, loff_t pos, size_t count) +{ + /* check to see if we have something to return */ + if ((rbu_data.image_update_buffer == NULL) || + (rbu_data.bios_image_size == 0)) { + pr_debug("image_update_buffer %p, bios_image_size %lu\n", + rbu_data.image_update_buffer, + rbu_data.bios_image_size); + return -ENOMEM; + } + + return memory_read_from_buffer(buffer, count, &pos, + rbu_data.image_update_buffer, rbu_data.bios_image_size); +} + +static ssize_t data_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buffer, loff_t pos, size_t count) +{ + ssize_t ret_count = 0; + + spin_lock(&rbu_data.lock); + + if (!strcmp(image_type, "mono")) + ret_count = read_rbu_mono_data(buffer, pos, count); + else if (!strcmp(image_type, "packet")) + ret_count = read_packet_data(buffer, pos, count); + else + pr_debug("invalid image type specified\n"); + + spin_unlock(&rbu_data.lock); + return ret_count; +} +static BIN_ATTR_RO(data, 0); + +static void callbackfn_rbu(const struct firmware *fw, void *context) +{ + rbu_data.entry_created = 0; + + if (!fw) + return; + + if (!fw->size) + goto out; + + spin_lock(&rbu_data.lock); + if (!strcmp(image_type, "mono")) { + if (!img_update_realloc(fw->size)) + memcpy(rbu_data.image_update_buffer, + fw->data, fw->size); + } else if (!strcmp(image_type, "packet")) { + /* + * we need to free previous packets if a + * new hunk of packets needs to be downloaded + */ + packet_empty_list(); + if (packetize_data(fw->data, fw->size)) + /* Incase something goes wrong when we are + * in middle of packetizing the data, we + * need to free up whatever packets might + * have been created before we quit. + */ + packet_empty_list(); + } else + pr_debug("invalid image type specified\n"); + spin_unlock(&rbu_data.lock); + out: + release_firmware(fw); +} + +static ssize_t image_type_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buffer, loff_t pos, size_t count) +{ + int size = 0; + if (!pos) + size = scnprintf(buffer, count, "%s\n", image_type); + return size; +} + +static ssize_t image_type_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buffer, loff_t pos, size_t count) +{ + int rc = count; + int req_firm_rc = 0; + int i; + spin_lock(&rbu_data.lock); + /* + * Find the first newline or space + */ + for (i = 0; i < count; ++i) + if (buffer[i] == '\n' || buffer[i] == ' ') { + buffer[i] = '\0'; + break; + } + if (i == count) + buffer[count] = '\0'; + + if (strstr(buffer, "mono")) + strcpy(image_type, "mono"); + else if (strstr(buffer, "packet")) + strcpy(image_type, "packet"); + else if (strstr(buffer, "init")) { + /* + * If due to the user error the driver gets in a bad + * state where even though it is loaded , the + * /sys/class/firmware/dell_rbu entries are missing. + * to cover this situation the user can recreate entries + * by writing init to image_type. + */ + if (!rbu_data.entry_created) { + spin_unlock(&rbu_data.lock); + req_firm_rc = request_firmware_nowait(THIS_MODULE, + FW_ACTION_NOHOTPLUG, "dell_rbu", + &rbu_device->dev, GFP_KERNEL, &context, + callbackfn_rbu); + if (req_firm_rc) { + pr_err("request_firmware_nowait failed %d\n", rc); + rc = -EIO; + } else + rbu_data.entry_created = 1; + + spin_lock(&rbu_data.lock); + } + } else { + pr_warn("image_type is invalid\n"); + spin_unlock(&rbu_data.lock); + return -EINVAL; + } + + /* we must free all previous allocations */ + packet_empty_list(); + img_update_free(); + spin_unlock(&rbu_data.lock); + + return rc; +} +static BIN_ATTR_RW(image_type, 0); + +static ssize_t packet_size_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buffer, loff_t pos, size_t count) +{ + int size = 0; + if (!pos) { + spin_lock(&rbu_data.lock); + size = scnprintf(buffer, count, "%lu\n", rbu_data.packetsize); + spin_unlock(&rbu_data.lock); + } + return size; +} + +static ssize_t packet_size_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buffer, loff_t pos, size_t count) +{ + unsigned long temp; + spin_lock(&rbu_data.lock); + packet_empty_list(); + sscanf(buffer, "%lu", &temp); + if (temp < 0xffffffff) + rbu_data.packetsize = temp; + + spin_unlock(&rbu_data.lock); + return count; +} +static BIN_ATTR_RW(packet_size, 0); + +static struct bin_attribute *rbu_bin_attrs[] = { + &bin_attr_data, + &bin_attr_image_type, + &bin_attr_packet_size, + NULL +}; + +static const struct attribute_group rbu_group = { + .bin_attrs = rbu_bin_attrs, +}; + +static int __init dcdrbu_init(void) +{ + int rc; + spin_lock_init(&rbu_data.lock); + + init_packet_head(); + rbu_device = platform_device_register_simple("dell_rbu", -1, NULL, 0); + if (IS_ERR(rbu_device)) { + pr_err("platform_device_register_simple failed\n"); + return PTR_ERR(rbu_device); + } + + rc = sysfs_create_group(&rbu_device->dev.kobj, &rbu_group); + if (rc) + goto out_devreg; + + rbu_data.entry_created = 0; + return 0; + +out_devreg: + platform_device_unregister(rbu_device); + return rc; +} + +static __exit void dcdrbu_exit(void) +{ + spin_lock(&rbu_data.lock); + packet_empty_list(); + img_update_free(); + spin_unlock(&rbu_data.lock); + sysfs_remove_group(&rbu_device->dev.kobj, &rbu_group); + platform_device_unregister(rbu_device); +} + +module_exit(dcdrbu_exit); +module_init(dcdrbu_init); + +/* vim:noet:ts=8:sw=8 +*/ diff --git a/drivers/platform/x86/dell_rbu.c b/drivers/platform/x86/dell_rbu.c deleted file mode 100644 index 03c3ff34bcf5..000000000000 --- a/drivers/platform/x86/dell_rbu.c +++ /dev/null @@ -1,680 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * dell_rbu.c - * Bios Update driver for Dell systems - * Author: Dell Inc - * Abhay Salunke - * - * Copyright (C) 2005 Dell Inc. - * - * Remote BIOS Update (rbu) driver is used for updating DELL BIOS by - * creating entries in the /sys file systems on Linux 2.6 and higher - * kernels. The driver supports two mechanism to update the BIOS namely - * contiguous and packetized. Both these methods still require having some - * application to set the CMOS bit indicating the BIOS to update itself - * after a reboot. - * - * Contiguous method: - * This driver writes the incoming data in a monolithic image by allocating - * contiguous physical pages large enough to accommodate the incoming BIOS - * image size. - * - * Packetized method: - * The driver writes the incoming packet image by allocating a new packet - * on every time the packet data is written. This driver requires an - * application to break the BIOS image in to fixed sized packet chunks. - * - * See Documentation/admin-guide/dell_rbu.rst for more info. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -MODULE_AUTHOR("Abhay Salunke "); -MODULE_DESCRIPTION("Driver for updating BIOS image on DELL systems"); -MODULE_LICENSE("GPL"); -MODULE_VERSION("3.2"); - -#define BIOS_SCAN_LIMIT 0xffffffff -#define MAX_IMAGE_LENGTH 16 -static struct _rbu_data { - void *image_update_buffer; - unsigned long image_update_buffer_size; - unsigned long bios_image_size; - int image_update_ordernum; - spinlock_t lock; - unsigned long packet_read_count; - unsigned long num_packets; - unsigned long packetsize; - unsigned long imagesize; - int entry_created; -} rbu_data; - -static char image_type[MAX_IMAGE_LENGTH + 1] = "mono"; -module_param_string(image_type, image_type, sizeof (image_type), 0); -MODULE_PARM_DESC(image_type, "BIOS image type. choose- mono or packet or init"); - -static unsigned long allocation_floor = 0x100000; -module_param(allocation_floor, ulong, 0644); -MODULE_PARM_DESC(allocation_floor, "Minimum address for allocations when using Packet mode"); - -struct packet_data { - struct list_head list; - size_t length; - void *data; - int ordernum; -}; - -static struct packet_data packet_data_head; - -static struct platform_device *rbu_device; -static int context; - -static void init_packet_head(void) -{ - INIT_LIST_HEAD(&packet_data_head.list); - rbu_data.packet_read_count = 0; - rbu_data.num_packets = 0; - rbu_data.packetsize = 0; - rbu_data.imagesize = 0; -} - -static int create_packet(void *data, size_t length) -{ - struct packet_data *newpacket; - int ordernum = 0; - int retval = 0; - unsigned int packet_array_size = 0; - void **invalid_addr_packet_array = NULL; - void *packet_data_temp_buf = NULL; - unsigned int idx = 0; - - pr_debug("entry\n"); - - if (!rbu_data.packetsize) { - pr_debug("packetsize not specified\n"); - retval = -EINVAL; - goto out_noalloc; - } - - spin_unlock(&rbu_data.lock); - - newpacket = kzalloc(sizeof (struct packet_data), GFP_KERNEL); - - if (!newpacket) { - pr_warn("failed to allocate new packet\n"); - retval = -ENOMEM; - spin_lock(&rbu_data.lock); - goto out_noalloc; - } - - ordernum = get_order(length); - - /* - * BIOS errata mean we cannot allocate packets below 1MB or they will - * be overwritten by BIOS. - * - * array to temporarily hold packets - * that are below the allocation floor - * - * NOTE: very simplistic because we only need the floor to be at 1MB - * due to BIOS errata. This shouldn't be used for higher floors - * or you will run out of mem trying to allocate the array. - */ - packet_array_size = max_t(unsigned int, allocation_floor / rbu_data.packetsize, 1); - invalid_addr_packet_array = kcalloc(packet_array_size, sizeof(void *), - GFP_KERNEL); - - if (!invalid_addr_packet_array) { - pr_warn("failed to allocate invalid_addr_packet_array\n"); - retval = -ENOMEM; - spin_lock(&rbu_data.lock); - goto out_alloc_packet; - } - - while (!packet_data_temp_buf) { - packet_data_temp_buf = (unsigned char *) - __get_free_pages(GFP_KERNEL, ordernum); - if (!packet_data_temp_buf) { - pr_warn("failed to allocate new packet\n"); - retval = -ENOMEM; - spin_lock(&rbu_data.lock); - goto out_alloc_packet_array; - } - - if ((unsigned long)virt_to_phys(packet_data_temp_buf) - < allocation_floor) { - pr_debug("packet 0x%lx below floor at 0x%lx\n", - (unsigned long)virt_to_phys( - packet_data_temp_buf), - allocation_floor); - invalid_addr_packet_array[idx++] = packet_data_temp_buf; - packet_data_temp_buf = NULL; - } - } - /* - * set to uncachable or it may never get written back before reboot - */ - set_memory_uc((unsigned long)packet_data_temp_buf, 1 << ordernum); - - spin_lock(&rbu_data.lock); - - newpacket->data = packet_data_temp_buf; - - pr_debug("newpacket at physical addr %lx\n", - (unsigned long)virt_to_phys(newpacket->data)); - - /* packets may not have fixed size */ - newpacket->length = length; - newpacket->ordernum = ordernum; - ++rbu_data.num_packets; - - /* initialize the newly created packet headers */ - INIT_LIST_HEAD(&newpacket->list); - list_add_tail(&newpacket->list, &packet_data_head.list); - - memcpy(newpacket->data, data, length); - - pr_debug("exit\n"); - -out_alloc_packet_array: - /* always free packet array */ - while (idx--) { - pr_debug("freeing unused packet below floor 0x%lx\n", - (unsigned long)virt_to_phys(invalid_addr_packet_array[idx])); - free_pages((unsigned long)invalid_addr_packet_array[idx], ordernum); - } - kfree(invalid_addr_packet_array); - -out_alloc_packet: - /* if error, free data */ - if (retval) - kfree(newpacket); - -out_noalloc: - return retval; -} - -static int packetize_data(const u8 *data, size_t length) -{ - int rc = 0; - int done = 0; - int packet_length; - u8 *temp; - u8 *end = (u8 *) data + length; - pr_debug("data length %zd\n", length); - if (!rbu_data.packetsize) { - pr_warn("packetsize not specified\n"); - return -EIO; - } - - temp = (u8 *) data; - - /* packetize the hunk */ - while (!done) { - if ((temp + rbu_data.packetsize) < end) - packet_length = rbu_data.packetsize; - else { - /* this is the last packet */ - packet_length = end - temp; - done = 1; - } - - if ((rc = create_packet(temp, packet_length))) - return rc; - - pr_debug("%p:%td\n", temp, (end - temp)); - temp += packet_length; - } - - rbu_data.imagesize = length; - - return rc; -} - -static int do_packet_read(char *data, struct packet_data *newpacket, - int length, int bytes_read, int *list_read_count) -{ - void *ptemp_buf; - int bytes_copied = 0; - int j = 0; - - *list_read_count += newpacket->length; - - if (*list_read_count > bytes_read) { - /* point to the start of unread data */ - j = newpacket->length - (*list_read_count - bytes_read); - /* point to the offset in the packet buffer */ - ptemp_buf = (u8 *) newpacket->data + j; - /* - * check if there is enough room in - * * the incoming buffer - */ - if (length > (*list_read_count - bytes_read)) - /* - * copy what ever is there in this - * packet and move on - */ - bytes_copied = (*list_read_count - bytes_read); - else - /* copy the remaining */ - bytes_copied = length; - memcpy(data, ptemp_buf, bytes_copied); - } - return bytes_copied; -} - -static int packet_read_list(char *data, size_t * pread_length) -{ - struct packet_data *newpacket; - int temp_count = 0; - int bytes_copied = 0; - int bytes_read = 0; - int remaining_bytes = 0; - char *pdest = data; - - /* check if we have any packets */ - if (0 == rbu_data.num_packets) - return -ENOMEM; - - remaining_bytes = *pread_length; - bytes_read = rbu_data.packet_read_count; - - list_for_each_entry(newpacket, (&packet_data_head.list)->next, list) { - bytes_copied = do_packet_read(pdest, newpacket, - remaining_bytes, bytes_read, &temp_count); - remaining_bytes -= bytes_copied; - bytes_read += bytes_copied; - pdest += bytes_copied; - /* - * check if we reached end of buffer before reaching the - * last packet - */ - if (remaining_bytes == 0) - break; - } - /*finally set the bytes read */ - *pread_length = bytes_read - rbu_data.packet_read_count; - rbu_data.packet_read_count = bytes_read; - return 0; -} - -static void packet_empty_list(void) -{ - struct packet_data *newpacket, *tmp; - - list_for_each_entry_safe(newpacket, tmp, (&packet_data_head.list)->next, list) { - list_del(&newpacket->list); - - /* - * zero out the RBU packet memory before freeing - * to make sure there are no stale RBU packets left in memory - */ - memset(newpacket->data, 0, rbu_data.packetsize); - set_memory_wb((unsigned long)newpacket->data, - 1 << newpacket->ordernum); - free_pages((unsigned long) newpacket->data, - newpacket->ordernum); - kfree(newpacket); - } - rbu_data.packet_read_count = 0; - rbu_data.num_packets = 0; - rbu_data.imagesize = 0; -} - -/* - * img_update_free: Frees the buffer allocated for storing BIOS image - * Always called with lock held and returned with lock held - */ -static void img_update_free(void) -{ - if (!rbu_data.image_update_buffer) - return; - /* - * zero out this buffer before freeing it to get rid of any stale - * BIOS image copied in memory. - */ - memset(rbu_data.image_update_buffer, 0, - rbu_data.image_update_buffer_size); - free_pages((unsigned long) rbu_data.image_update_buffer, - rbu_data.image_update_ordernum); - - /* - * Re-initialize the rbu_data variables after a free - */ - rbu_data.image_update_ordernum = -1; - rbu_data.image_update_buffer = NULL; - rbu_data.image_update_buffer_size = 0; - rbu_data.bios_image_size = 0; -} - -/* - * img_update_realloc: This function allocates the contiguous pages to - * accommodate the requested size of data. The memory address and size - * values are stored globally and on every call to this function the new - * size is checked to see if more data is required than the existing size. - * If true the previous memory is freed and new allocation is done to - * accommodate the new size. If the incoming size is less then than the - * already allocated size, then that memory is reused. This function is - * called with lock held and returns with lock held. - */ -static int img_update_realloc(unsigned long size) -{ - unsigned char *image_update_buffer = NULL; - unsigned long img_buf_phys_addr; - int ordernum; - - /* - * check if the buffer of sufficient size has been - * already allocated - */ - if (rbu_data.image_update_buffer_size >= size) { - /* - * check for corruption - */ - if ((size != 0) && (rbu_data.image_update_buffer == NULL)) { - pr_err("corruption check failed\n"); - return -EINVAL; - } - /* - * we have a valid pre-allocated buffer with - * sufficient size - */ - return 0; - } - - /* - * free any previously allocated buffer - */ - img_update_free(); - - spin_unlock(&rbu_data.lock); - - ordernum = get_order(size); - image_update_buffer = - (unsigned char *)__get_free_pages(GFP_DMA32, ordernum); - spin_lock(&rbu_data.lock); - if (!image_update_buffer) { - pr_debug("Not enough memory for image update: size = %ld\n", size); - return -ENOMEM; - } - - img_buf_phys_addr = (unsigned long)virt_to_phys(image_update_buffer); - if (WARN_ON_ONCE(img_buf_phys_addr > BIOS_SCAN_LIMIT)) - return -EINVAL; /* can't happen per definition */ - - rbu_data.image_update_buffer = image_update_buffer; - rbu_data.image_update_buffer_size = size; - rbu_data.bios_image_size = rbu_data.image_update_buffer_size; - rbu_data.image_update_ordernum = ordernum; - return 0; -} - -static ssize_t read_packet_data(char *buffer, loff_t pos, size_t count) -{ - int retval; - size_t bytes_left; - size_t data_length; - char *ptempBuf = buffer; - - /* check to see if we have something to return */ - if (rbu_data.num_packets == 0) { - pr_debug("no packets written\n"); - retval = -ENOMEM; - goto read_rbu_data_exit; - } - - if (pos > rbu_data.imagesize) { - retval = 0; - pr_warn("data underrun\n"); - goto read_rbu_data_exit; - } - - bytes_left = rbu_data.imagesize - pos; - data_length = min(bytes_left, count); - - if ((retval = packet_read_list(ptempBuf, &data_length)) < 0) - goto read_rbu_data_exit; - - if ((pos + count) > rbu_data.imagesize) { - rbu_data.packet_read_count = 0; - /* this was the last copy */ - retval = bytes_left; - } else - retval = count; - - read_rbu_data_exit: - return retval; -} - -static ssize_t read_rbu_mono_data(char *buffer, loff_t pos, size_t count) -{ - /* check to see if we have something to return */ - if ((rbu_data.image_update_buffer == NULL) || - (rbu_data.bios_image_size == 0)) { - pr_debug("image_update_buffer %p, bios_image_size %lu\n", - rbu_data.image_update_buffer, - rbu_data.bios_image_size); - return -ENOMEM; - } - - return memory_read_from_buffer(buffer, count, &pos, - rbu_data.image_update_buffer, rbu_data.bios_image_size); -} - -static ssize_t data_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buffer, loff_t pos, size_t count) -{ - ssize_t ret_count = 0; - - spin_lock(&rbu_data.lock); - - if (!strcmp(image_type, "mono")) - ret_count = read_rbu_mono_data(buffer, pos, count); - else if (!strcmp(image_type, "packet")) - ret_count = read_packet_data(buffer, pos, count); - else - pr_debug("invalid image type specified\n"); - - spin_unlock(&rbu_data.lock); - return ret_count; -} -static BIN_ATTR_RO(data, 0); - -static void callbackfn_rbu(const struct firmware *fw, void *context) -{ - rbu_data.entry_created = 0; - - if (!fw) - return; - - if (!fw->size) - goto out; - - spin_lock(&rbu_data.lock); - if (!strcmp(image_type, "mono")) { - if (!img_update_realloc(fw->size)) - memcpy(rbu_data.image_update_buffer, - fw->data, fw->size); - } else if (!strcmp(image_type, "packet")) { - /* - * we need to free previous packets if a - * new hunk of packets needs to be downloaded - */ - packet_empty_list(); - if (packetize_data(fw->data, fw->size)) - /* Incase something goes wrong when we are - * in middle of packetizing the data, we - * need to free up whatever packets might - * have been created before we quit. - */ - packet_empty_list(); - } else - pr_debug("invalid image type specified\n"); - spin_unlock(&rbu_data.lock); - out: - release_firmware(fw); -} - -static ssize_t image_type_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buffer, loff_t pos, size_t count) -{ - int size = 0; - if (!pos) - size = scnprintf(buffer, count, "%s\n", image_type); - return size; -} - -static ssize_t image_type_write(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buffer, loff_t pos, size_t count) -{ - int rc = count; - int req_firm_rc = 0; - int i; - spin_lock(&rbu_data.lock); - /* - * Find the first newline or space - */ - for (i = 0; i < count; ++i) - if (buffer[i] == '\n' || buffer[i] == ' ') { - buffer[i] = '\0'; - break; - } - if (i == count) - buffer[count] = '\0'; - - if (strstr(buffer, "mono")) - strcpy(image_type, "mono"); - else if (strstr(buffer, "packet")) - strcpy(image_type, "packet"); - else if (strstr(buffer, "init")) { - /* - * If due to the user error the driver gets in a bad - * state where even though it is loaded , the - * /sys/class/firmware/dell_rbu entries are missing. - * to cover this situation the user can recreate entries - * by writing init to image_type. - */ - if (!rbu_data.entry_created) { - spin_unlock(&rbu_data.lock); - req_firm_rc = request_firmware_nowait(THIS_MODULE, - FW_ACTION_NOHOTPLUG, "dell_rbu", - &rbu_device->dev, GFP_KERNEL, &context, - callbackfn_rbu); - if (req_firm_rc) { - pr_err("request_firmware_nowait failed %d\n", rc); - rc = -EIO; - } else - rbu_data.entry_created = 1; - - spin_lock(&rbu_data.lock); - } - } else { - pr_warn("image_type is invalid\n"); - spin_unlock(&rbu_data.lock); - return -EINVAL; - } - - /* we must free all previous allocations */ - packet_empty_list(); - img_update_free(); - spin_unlock(&rbu_data.lock); - - return rc; -} -static BIN_ATTR_RW(image_type, 0); - -static ssize_t packet_size_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buffer, loff_t pos, size_t count) -{ - int size = 0; - if (!pos) { - spin_lock(&rbu_data.lock); - size = scnprintf(buffer, count, "%lu\n", rbu_data.packetsize); - spin_unlock(&rbu_data.lock); - } - return size; -} - -static ssize_t packet_size_write(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buffer, loff_t pos, size_t count) -{ - unsigned long temp; - spin_lock(&rbu_data.lock); - packet_empty_list(); - sscanf(buffer, "%lu", &temp); - if (temp < 0xffffffff) - rbu_data.packetsize = temp; - - spin_unlock(&rbu_data.lock); - return count; -} -static BIN_ATTR_RW(packet_size, 0); - -static struct bin_attribute *rbu_bin_attrs[] = { - &bin_attr_data, - &bin_attr_image_type, - &bin_attr_packet_size, - NULL -}; - -static const struct attribute_group rbu_group = { - .bin_attrs = rbu_bin_attrs, -}; - -static int __init dcdrbu_init(void) -{ - int rc; - spin_lock_init(&rbu_data.lock); - - init_packet_head(); - rbu_device = platform_device_register_simple("dell_rbu", -1, NULL, 0); - if (IS_ERR(rbu_device)) { - pr_err("platform_device_register_simple failed\n"); - return PTR_ERR(rbu_device); - } - - rc = sysfs_create_group(&rbu_device->dev.kobj, &rbu_group); - if (rc) - goto out_devreg; - - rbu_data.entry_created = 0; - return 0; - -out_devreg: - platform_device_unregister(rbu_device); - return rc; -} - -static __exit void dcdrbu_exit(void) -{ - spin_lock(&rbu_data.lock); - packet_empty_list(); - img_update_free(); - spin_unlock(&rbu_data.lock); - sysfs_remove_group(&rbu_device->dev.kobj, &rbu_group); - platform_device_unregister(rbu_device); -} - -module_exit(dcdrbu_exit); -module_init(dcdrbu_init); - -/* vim:noet:ts=8:sw=8 -*/ -- cgit v1.2.3 From bd69bcce4aa089435e2891222236b1cb20395bec Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Fri, 5 Feb 2021 02:26:57 +0100 Subject: platform/surface: Add Surface Hot-Plug driver Some Surface Book 2 and 3 models have a discrete GPU (dGPU) that is hot-pluggable. On those devices, the dGPU is contained in the base, which can be separated from the tablet part (containing CPU and touchscreen) while the device is running. It (in general) is presented as/behaves like a standard PCIe hot-plug capable device, however, this device can also be put into D3cold. In D3cold, the device itself is turned off and can thus not submit any standard PCIe hot-plug events. To properly detect hot-(un)plugging while the dGPU is in D3cold, out-of-band signaling is required. Without this, the device state will only get updated during the next bus-check, eg. via a manually issued lspci call. This commit adds a driver to handle out-of-band PCIe hot-(un)plug events on Microsoft Surface devices. On those devices, said events can be detected via GPIO interrupts, which are then forwarded to the corresponding ACPI DSM calls by this driver. The DSM then takes care of issuing the appropriate bus-/device-check, causing the PCI core to properly pick up the device change. Signed-off-by: Maximilian Luz Link: https://lore.kernel.org/r/20210205012657.1951753-1-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- MAINTAINERS | 6 + drivers/platform/surface/Kconfig | 19 ++ drivers/platform/surface/Makefile | 1 + drivers/platform/surface/surface_hotplug.c | 282 +++++++++++++++++++++++++++++ 4 files changed, 308 insertions(+) create mode 100644 drivers/platform/surface/surface_hotplug.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 34bfa5c1aec5..4fcf3df517a8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11805,6 +11805,12 @@ S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git F: drivers/platform/surface/ +MICROSOFT SURFACE HOT-PLUG DRIVER +M: Maximilian Luz +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: drivers/platform/surface/surface_hotplug.c + MICROSOFT SURFACE PRO 3 BUTTON DRIVER M: Chen Yu L: platform-driver-x86@vger.kernel.org diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig index 83b0a4c7b352..0847b2dc97bf 100644 --- a/drivers/platform/surface/Kconfig +++ b/drivers/platform/surface/Kconfig @@ -86,6 +86,25 @@ config SURFACE_GPE accordingly. It is required on those devices to allow wake-ups from suspend by opening the lid. +config SURFACE_HOTPLUG + tristate "Surface Hot-Plug Driver" + depends on GPIOLIB + help + Driver for out-of-band hot-plug event signaling on Microsoft Surface + devices with hot-pluggable PCIe cards. + + This driver is used on Surface Book (2 and 3) devices with a + hot-pluggable discrete GPU (dGPU). When not in use, the dGPU on those + devices can enter D3cold, which prevents in-band (standard) PCIe + hot-plug signaling. Thus, without this driver, detaching the base + containing the dGPU will not correctly update the state of the + corresponding PCIe device if it is in D3cold. This driver adds support + for out-of-band hot-plug notifications, ensuring that the device state + is properly updated even when the device in question is in D3cold. + + Select M or Y here, if you want to (fully) support hot-plugging of + dGPU devices on the Surface Book 2 and/or 3 during D3cold. + config SURFACE_PRO3_BUTTON tristate "Power/home/volume buttons driver for Microsoft Surface Pro 3/4 tablet" depends on INPUT diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile index 3eb971006877..990424c5f0c9 100644 --- a/drivers/platform/surface/Makefile +++ b/drivers/platform/surface/Makefile @@ -11,4 +11,5 @@ 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 +obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o diff --git a/drivers/platform/surface/surface_hotplug.c b/drivers/platform/surface/surface_hotplug.c new file mode 100644 index 000000000000..cfcc15cfbacb --- /dev/null +++ b/drivers/platform/surface/surface_hotplug.c @@ -0,0 +1,282 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Surface Book (2 and later) hot-plug driver. + * + * Surface Book devices (can) have a hot-pluggable discrete GPU (dGPU). This + * driver is responsible for out-of-band hot-plug event signaling on these + * devices. It is specifically required when the hot-plug device is in D3cold + * and can thus not generate PCIe hot-plug events itself. + * + * Event signaling is handled via ACPI, which will generate the appropriate + * device-check notifications to be picked up by the PCIe hot-plug driver. + * + * Copyright (C) 2019-2021 Maximilian Luz + */ + +#include +#include +#include +#include +#include +#include +#include + +static const struct acpi_gpio_params shps_base_presence_int = { 0, 0, false }; +static const struct acpi_gpio_params shps_base_presence = { 1, 0, false }; +static const struct acpi_gpio_params shps_device_power_int = { 2, 0, false }; +static const struct acpi_gpio_params shps_device_power = { 3, 0, false }; +static const struct acpi_gpio_params shps_device_presence_int = { 4, 0, false }; +static const struct acpi_gpio_params shps_device_presence = { 5, 0, false }; + +static const struct acpi_gpio_mapping shps_acpi_gpios[] = { + { "base_presence-int-gpio", &shps_base_presence_int, 1 }, + { "base_presence-gpio", &shps_base_presence, 1 }, + { "device_power-int-gpio", &shps_device_power_int, 1 }, + { "device_power-gpio", &shps_device_power, 1 }, + { "device_presence-int-gpio", &shps_device_presence_int, 1 }, + { "device_presence-gpio", &shps_device_presence, 1 }, + { }, +}; + +/* 5515a847-ed55-4b27-8352-cd320e10360a */ +static const guid_t shps_dsm_guid = + GUID_INIT(0x5515a847, 0xed55, 0x4b27, 0x83, 0x52, 0xcd, 0x32, 0x0e, 0x10, 0x36, 0x0a); + +#define SHPS_DSM_REVISION 1 + +enum shps_dsm_fn { + SHPS_DSM_FN_PCI_NUM_ENTRIES = 0x01, + SHPS_DSM_FN_PCI_GET_ENTRIES = 0x02, + SHPS_DSM_FN_IRQ_BASE_PRESENCE = 0x03, + SHPS_DSM_FN_IRQ_DEVICE_POWER = 0x04, + SHPS_DSM_FN_IRQ_DEVICE_PRESENCE = 0x05, +}; + +enum shps_irq_type { + /* NOTE: Must be in order of enum shps_dsm_fn above. */ + SHPS_IRQ_TYPE_BASE_PRESENCE = 0, + SHPS_IRQ_TYPE_DEVICE_POWER = 1, + SHPS_IRQ_TYPE_DEVICE_PRESENCE = 2, + SHPS_NUM_IRQS, +}; + +static const char *const shps_gpio_names[] = { + [SHPS_IRQ_TYPE_BASE_PRESENCE] = "base_presence", + [SHPS_IRQ_TYPE_DEVICE_POWER] = "device_power", + [SHPS_IRQ_TYPE_DEVICE_PRESENCE] = "device_presence", +}; + +struct shps_device { + struct mutex lock[SHPS_NUM_IRQS]; /* Protects update in shps_dsm_notify_irq() */ + struct gpio_desc *gpio[SHPS_NUM_IRQS]; + unsigned int irq[SHPS_NUM_IRQS]; +}; + +#define SHPS_IRQ_NOT_PRESENT ((unsigned int)-1) + +static enum shps_dsm_fn shps_dsm_fn_for_irq(enum shps_irq_type type) +{ + return SHPS_DSM_FN_IRQ_BASE_PRESENCE + type; +} + +static void shps_dsm_notify_irq(struct platform_device *pdev, enum shps_irq_type type) +{ + struct shps_device *sdev = platform_get_drvdata(pdev); + acpi_handle handle = ACPI_HANDLE(&pdev->dev); + union acpi_object *result; + union acpi_object param; + int value; + + mutex_lock(&sdev->lock[type]); + + value = gpiod_get_value_cansleep(sdev->gpio[type]); + if (value < 0) { + mutex_unlock(&sdev->lock[type]); + dev_err(&pdev->dev, "failed to get gpio: %d (irq=%d)\n", type, value); + return; + } + + dev_dbg(&pdev->dev, "IRQ notification via DSM (irq=%d, value=%d)\n", type, value); + + param.type = ACPI_TYPE_INTEGER; + param.integer.value = value; + + result = acpi_evaluate_dsm(handle, &shps_dsm_guid, SHPS_DSM_REVISION, + shps_dsm_fn_for_irq(type), ¶m); + + if (!result) { + dev_err(&pdev->dev, "IRQ notification via DSM failed (irq=%d, gpio=%d)\n", + type, value); + + } else if (result->type != ACPI_TYPE_BUFFER) { + dev_err(&pdev->dev, + "IRQ notification via DSM failed: unexpected result type (irq=%d, gpio=%d)\n", + type, value); + + } else if (result->buffer.length != 1 || result->buffer.pointer[0] != 0) { + dev_err(&pdev->dev, + "IRQ notification via DSM failed: unexpected result value (irq=%d, gpio=%d)\n", + type, value); + } + + mutex_unlock(&sdev->lock[type]); + + if (result) + ACPI_FREE(result); +} + +static irqreturn_t shps_handle_irq(int irq, void *data) +{ + struct platform_device *pdev = data; + struct shps_device *sdev = platform_get_drvdata(pdev); + int type; + + /* Figure out which IRQ we're handling. */ + for (type = 0; type < SHPS_NUM_IRQS; type++) + if (irq == sdev->irq[type]) + break; + + /* We should have found our interrupt, if not: this is a bug. */ + if (WARN(type >= SHPS_NUM_IRQS, "invalid IRQ number: %d\n", irq)) + return IRQ_HANDLED; + + /* Forward interrupt to ACPI via DSM. */ + shps_dsm_notify_irq(pdev, type); + return IRQ_HANDLED; +} + +static int shps_setup_irq(struct platform_device *pdev, enum shps_irq_type type) +{ + unsigned long flags = IRQF_ONESHOT | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING; + struct shps_device *sdev = platform_get_drvdata(pdev); + struct gpio_desc *gpiod; + acpi_handle handle = ACPI_HANDLE(&pdev->dev); + const char *irq_name; + const int dsm = shps_dsm_fn_for_irq(type); + int status, irq; + + /* + * Only set up interrupts that we actually need: The Surface Book 3 + * does not have a DSM for base presence, so don't set up an interrupt + * for that. + */ + if (!acpi_check_dsm(handle, &shps_dsm_guid, SHPS_DSM_REVISION, BIT(dsm))) { + dev_dbg(&pdev->dev, "IRQ notification via DSM not present (irq=%d)\n", type); + return 0; + } + + gpiod = devm_gpiod_get(&pdev->dev, shps_gpio_names[type], GPIOD_ASIS); + if (IS_ERR(gpiod)) + return PTR_ERR(gpiod); + + irq = gpiod_to_irq(gpiod); + if (irq < 0) + return irq; + + irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "shps-irq-%d", type); + if (!irq_name) + return -ENOMEM; + + status = devm_request_threaded_irq(&pdev->dev, irq, NULL, shps_handle_irq, + flags, irq_name, pdev); + if (status) + return status; + + dev_dbg(&pdev->dev, "set up irq %d as type %d\n", irq, type); + + sdev->gpio[type] = gpiod; + sdev->irq[type] = irq; + + return 0; +} + +static int surface_hotplug_remove(struct platform_device *pdev) +{ + struct shps_device *sdev = platform_get_drvdata(pdev); + int i; + + /* Ensure that IRQs have been fully handled and won't trigger any more. */ + for (i = 0; i < SHPS_NUM_IRQS; i++) { + if (sdev->irq[i] != SHPS_IRQ_NOT_PRESENT) + disable_irq(sdev->irq[i]); + + mutex_destroy(&sdev->lock[i]); + } + + return 0; +} + +static int surface_hotplug_probe(struct platform_device *pdev) +{ + struct shps_device *sdev; + int status, i; + + /* + * The MSHW0153 device is also present on the Surface Laptop 3, + * however that doesn't have a hot-pluggable PCIe device. It also + * doesn't have any GPIO interrupts/pins under the MSHW0153, so filter + * it out here. + */ + if (gpiod_count(&pdev->dev, NULL) < 0) + return -ENODEV; + + status = devm_acpi_dev_add_driver_gpios(&pdev->dev, shps_acpi_gpios); + if (status) + return status; + + sdev = devm_kzalloc(&pdev->dev, sizeof(*sdev), GFP_KERNEL); + if (!sdev) + return -ENOMEM; + + platform_set_drvdata(pdev, sdev); + + /* + * Initialize IRQs so that we can safely call surface_hotplug_remove() + * on errors. + */ + for (i = 0; i < SHPS_NUM_IRQS; i++) + sdev->irq[i] = SHPS_IRQ_NOT_PRESENT; + + /* Set up IRQs. */ + for (i = 0; i < SHPS_NUM_IRQS; i++) { + mutex_init(&sdev->lock[i]); + + status = shps_setup_irq(pdev, i); + if (status) { + dev_err(&pdev->dev, "failed to set up IRQ %d: %d\n", i, status); + goto err; + } + } + + /* Ensure everything is up-to-date. */ + for (i = 0; i < SHPS_NUM_IRQS; i++) + if (sdev->irq[i] != SHPS_IRQ_NOT_PRESENT) + shps_dsm_notify_irq(pdev, i); + + return 0; + +err: + surface_hotplug_remove(pdev); + return status; +} + +static const struct acpi_device_id surface_hotplug_acpi_match[] = { + { "MSHW0153", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, surface_hotplug_acpi_match); + +static struct platform_driver surface_hotplug_driver = { + .probe = surface_hotplug_probe, + .remove = surface_hotplug_remove, + .driver = { + .name = "surface_hotplug", + .acpi_match_table = surface_hotplug_acpi_match, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; +module_platform_driver(surface_hotplug_driver); + +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("Surface Hot-Plug Signaling Driver for Surface Book Devices"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3