-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
misc: Add support for Surface System Aggregator Module
Add support for the Surface System Aggregator Module (SSAM), an embedded controller that can be found on 5th and later generation Microsoft Surface devices. The responsibilities of this EC vary from device to device. It provides battery information on all 5th and later generation devices, temperature sensor and cooling capability access, functionality for clipboard detaching on the Surface Books (2 and 3), as well as HID-over-SSAM input devices, including keyboard on the Surface Laptop 1 and 2, and keyboard as well as touchpad input on the Surface Laptop 3 and Surface Book 3.
- Loading branch information
Showing
31 changed files
with
13,307 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
menuconfig SURFACE_SAM | ||
depends on ACPI | ||
tristate "Microsoft Surface/System Aggregator Module and Platform Drivers" | ||
help | ||
Drivers for the Surface/System Aggregator Module (SAM) of Microsoft | ||
Surface devices. | ||
|
||
SAM is an embedded controller that provides access to various | ||
functionalities on these devices, including battery status, keyboard | ||
events (on the Laptops) and many more. | ||
|
||
Say M/Y here if you have a Microsoft Surface device with a SAM device | ||
(i.e. 5th generation or later). | ||
|
||
config SURFACE_SAM_SSH | ||
tristate "Surface Serial Hub Driver" | ||
depends on SURFACE_SAM | ||
depends on SERIAL_DEV_BUS | ||
select CRC_CCITT | ||
default m | ||
help | ||
Surface Serial Hub driver for 5th generation (or later) Microsoft | ||
Surface devices. | ||
|
||
This is the base driver for the embedded serial controller found on | ||
5th generation (and later) Microsoft Surface devices (e.g. Book 2, | ||
Laptop, Laptop 2, Pro 2017, Pro 6, ...). This driver itself only | ||
provides access to the embedded controller (SAM) and subsequent | ||
drivers are required for the respective functionalities. | ||
|
||
If you have a 5th generation (or later) Microsoft Surface device, say | ||
Y or M here. | ||
|
||
config SURFACE_SAM_SSH_ERROR_INJECTION | ||
bool "Surface Serial Hub Error Injection Capabilities" | ||
depends on SURFACE_SAM_SSH | ||
depends on FUNCTION_ERROR_INJECTION | ||
default n | ||
help | ||
Enable error injection capabilities for the Surface Serial Hub. | ||
This is used to debug the driver, specifically the communication | ||
interface. It is not required for normal use. | ||
|
||
If you are not sure, say N here. | ||
|
||
source "drivers/misc/surface_sam/clients/Kconfig" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# SPDX-License-Identifier: GPL-2.0-or-later | ||
|
||
# For include/trace/define_trace.h to include ssam_trace.h | ||
CFLAGS_core.o = -I$(src) | ||
|
||
obj-$(CONFIG_SURFACE_SAM_SSH) += surface_sam_ssh.o | ||
obj-$(CONFIG_SURFACE_SAM_SSH) += clients/ | ||
|
||
surface_sam_ssh-objs := core.o | ||
surface_sam_ssh-objs += ssh_parser.o | ||
surface_sam_ssh-objs += ssh_packet_layer.o | ||
surface_sam_ssh-objs += ssh_request_layer.o | ||
surface_sam_ssh-objs += controller.o | ||
surface_sam_ssh-objs += bus.o |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,276 @@ | ||
// SPDX-License-Identifier: GPL-2.0-or-later | ||
|
||
#include <linux/device.h> | ||
#include <linux/uuid.h> | ||
|
||
#include "bus.h" | ||
#include "controller.h" | ||
|
||
|
||
static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, | ||
char *buf) | ||
{ | ||
struct ssam_device *sdev = to_ssam_device(dev); | ||
|
||
return snprintf(buf, PAGE_SIZE - 1, "ssam:c%02Xt%02Xi%02xf%02X\n", | ||
sdev->uid.category, sdev->uid.channel, | ||
sdev->uid.instance, sdev->uid.function); | ||
} | ||
static DEVICE_ATTR_RO(modalias); | ||
|
||
static struct attribute *ssam_device_attrs[] = { | ||
&dev_attr_modalias.attr, | ||
NULL, | ||
}; | ||
ATTRIBUTE_GROUPS(ssam_device); | ||
|
||
static int ssam_device_uevent(struct device *dev, struct kobj_uevent_env *env) | ||
{ | ||
struct ssam_device *sdev = to_ssam_device(dev); | ||
|
||
return add_uevent_var(env, "MODALIAS=ssam:c%02Xt%02Xi%02xf%02X", | ||
sdev->uid.category, sdev->uid.channel, | ||
sdev->uid.instance, sdev->uid.function); | ||
} | ||
|
||
static void ssam_device_release(struct device *dev) | ||
{ | ||
struct ssam_device *sdev = to_ssam_device(dev); | ||
|
||
ssam_controller_put(sdev->ctrl); | ||
kfree(sdev); | ||
} | ||
|
||
const struct device_type ssam_device_type = { | ||
.name = "ssam_client", | ||
.groups = ssam_device_groups, | ||
.uevent = ssam_device_uevent, | ||
.release = ssam_device_release, | ||
}; | ||
EXPORT_SYMBOL_GPL(ssam_device_type); | ||
|
||
|
||
struct ssam_device *ssam_device_alloc(struct ssam_controller *ctrl, | ||
struct ssam_device_uid uid) | ||
{ | ||
struct ssam_device *sdev; | ||
|
||
sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); | ||
if (!sdev) | ||
return NULL; | ||
|
||
device_initialize(&sdev->dev); | ||
sdev->dev.bus = &ssam_bus_type; | ||
sdev->dev.type = &ssam_device_type; | ||
sdev->dev.parent = ssam_controller_device(ctrl); | ||
sdev->ctrl = ssam_controller_get(ctrl); | ||
sdev->uid = uid; | ||
|
||
dev_set_name(&sdev->dev, "%02x:%02x:%02x:%02x", | ||
sdev->uid.category, sdev->uid.channel, sdev->uid.instance, | ||
sdev->uid.function); | ||
|
||
return sdev; | ||
} | ||
EXPORT_SYMBOL_GPL(ssam_device_alloc); | ||
|
||
int ssam_device_add(struct ssam_device *sdev) | ||
{ | ||
enum ssam_controller_state state; | ||
int status; | ||
|
||
/* | ||
* Ensure that we can only add new devices to a controller if it has | ||
* been started and is not going away soon. This works in combination | ||
* with ssam_controller_remove_clients to ensure driver presence for the | ||
* controller device, i.e. it ensures that the controller (sdev->ctrl) | ||
* is always valid and can be used for requests as long as the client | ||
* device we add here is registered as child under it. This essentially | ||
* guarantees that the client driver can always expect the preconditions | ||
* for functions like ssam_request_sync (controller has to be started | ||
* and is not suspended) to hold and thus does not have to check for | ||
* them. | ||
* | ||
* Note that for this to work, the controller has to be a parent device. | ||
* If it is not a direct parent, care has to be taken that the device is | ||
* removed via ssam_device_remove, as device_unregister does not remove | ||
* child devices recursively. | ||
*/ | ||
ssam_controller_statelock(sdev->ctrl); | ||
|
||
state = smp_load_acquire(&sdev->ctrl->state); | ||
if (state != SSAM_CONTROLLER_STARTED) { | ||
ssam_controller_stateunlock(sdev->ctrl); | ||
return -ENXIO; | ||
} | ||
|
||
status = device_add(&sdev->dev); | ||
|
||
ssam_controller_stateunlock(sdev->ctrl); | ||
return status; | ||
} | ||
EXPORT_SYMBOL_GPL(ssam_device_add); | ||
|
||
void ssam_device_remove(struct ssam_device *sdev) | ||
{ | ||
device_unregister(&sdev->dev); | ||
} | ||
EXPORT_SYMBOL_GPL(ssam_device_remove); | ||
|
||
|
||
static inline bool ssam_device_id_compatible(const struct ssam_device_id *id, | ||
struct ssam_device_uid uid) | ||
{ | ||
if (id->category != uid.category) | ||
return false; | ||
|
||
if ((id->match_flags & SSAM_MATCH_CHANNEL) && id->channel != uid.channel) | ||
return false; | ||
|
||
if ((id->match_flags & SSAM_MATCH_INSTANCE) && id->instance != uid.instance) | ||
return false; | ||
|
||
if ((id->match_flags & SSAM_MATCH_FUNCTION) && id->function != uid.function) | ||
return false; | ||
|
||
return true; | ||
} | ||
|
||
static inline bool ssam_device_id_is_null(const struct ssam_device_id *id) | ||
{ | ||
return id->category == 0 | ||
&& id->channel == 0 | ||
&& id->instance == 0 | ||
&& id->function == 0; | ||
} | ||
|
||
const struct ssam_device_id *ssam_device_id_match( | ||
const struct ssam_device_id *table, | ||
const struct ssam_device_uid uid) | ||
{ | ||
const struct ssam_device_id *id; | ||
|
||
for (id = table; !ssam_device_id_is_null(id); ++id) | ||
if (ssam_device_id_compatible(id, uid)) | ||
return id; | ||
|
||
return NULL; | ||
} | ||
EXPORT_SYMBOL_GPL(ssam_device_id_match); | ||
|
||
const struct ssam_device_id *ssam_device_get_match( | ||
const struct ssam_device *dev) | ||
{ | ||
const struct ssam_device_driver *sdrv; | ||
|
||
sdrv = to_ssam_device_driver(dev->dev.driver); | ||
if (!sdrv) | ||
return NULL; | ||
|
||
if (!sdrv->match_table) | ||
return NULL; | ||
|
||
return ssam_device_id_match(sdrv->match_table, dev->uid); | ||
} | ||
EXPORT_SYMBOL_GPL(ssam_device_get_match); | ||
|
||
const void *ssam_device_get_match_data(const struct ssam_device *dev) | ||
{ | ||
const struct ssam_device_id *id; | ||
|
||
id = ssam_device_get_match(dev); | ||
if (!id) | ||
return NULL; | ||
|
||
return (const void *)id->driver_data; | ||
} | ||
EXPORT_SYMBOL_GPL(ssam_device_get_match_data); | ||
|
||
|
||
static int ssam_bus_match(struct device *dev, struct device_driver *drv) | ||
{ | ||
struct ssam_device_driver *sdrv = to_ssam_device_driver(drv); | ||
struct ssam_device *sdev = to_ssam_device(dev); | ||
|
||
if (!is_ssam_device(dev)) | ||
return 0; | ||
|
||
return !!ssam_device_id_match(sdrv->match_table, sdev->uid); | ||
} | ||
|
||
static int ssam_bus_probe(struct device *dev) | ||
{ | ||
struct ssam_device_driver *sdrv = to_ssam_device_driver(dev->driver); | ||
|
||
return sdrv->probe(to_ssam_device(dev)); | ||
} | ||
|
||
static int ssam_bus_remove(struct device *dev) | ||
{ | ||
struct ssam_device_driver *sdrv = to_ssam_device_driver(dev->driver); | ||
|
||
if (sdrv->remove) | ||
sdrv->remove(to_ssam_device(dev)); | ||
|
||
return 0; | ||
} | ||
|
||
struct bus_type ssam_bus_type = { | ||
.name = "ssam", | ||
.match = ssam_bus_match, | ||
.probe = ssam_bus_probe, | ||
.remove = ssam_bus_remove, | ||
}; | ||
EXPORT_SYMBOL_GPL(ssam_bus_type); | ||
|
||
|
||
int __ssam_device_driver_register(struct ssam_device_driver *sdrv, | ||
struct module *owner) | ||
{ | ||
sdrv->driver.owner = owner; | ||
sdrv->driver.bus = &ssam_bus_type; | ||
|
||
/* force drivers to async probe so I/O is possible in probe */ | ||
sdrv->driver.probe_type = PROBE_PREFER_ASYNCHRONOUS; | ||
|
||
return driver_register(&sdrv->driver); | ||
} | ||
EXPORT_SYMBOL_GPL(__ssam_device_driver_register); | ||
|
||
void ssam_device_driver_unregister(struct ssam_device_driver *sdrv) | ||
{ | ||
driver_unregister(&sdrv->driver); | ||
} | ||
EXPORT_SYMBOL_GPL(ssam_device_driver_unregister); | ||
|
||
|
||
static int ssam_remove_device(struct device *dev, void *_data) | ||
{ | ||
struct ssam_device *sdev = to_ssam_device(dev); | ||
|
||
if (is_ssam_device(dev)) | ||
ssam_device_remove(sdev); | ||
|
||
return 0; | ||
} | ||
|
||
/* | ||
* Controller lock should be held during this call and subsequent | ||
* de-initialization. | ||
*/ | ||
void ssam_controller_remove_clients(struct ssam_controller *ctrl) | ||
{ | ||
struct device *dev = ssam_controller_device(ctrl); | ||
|
||
device_for_each_child_reverse(dev, NULL, ssam_remove_device); | ||
} | ||
|
||
|
||
int ssam_bus_register(void) | ||
{ | ||
return bus_register(&ssam_bus_type); | ||
} | ||
|
||
void ssam_bus_unregister(void) { | ||
return bus_unregister(&ssam_bus_type); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/* SPDX-License-Identifier: GPL-2.0-or-later */ | ||
|
||
#ifndef _SSAM_BUS_H | ||
#define _SSAM_BUS_H | ||
|
||
#include <linux/surface_aggregator_module.h> | ||
|
||
|
||
void ssam_controller_remove_clients(struct ssam_controller *ctrl); | ||
|
||
int ssam_bus_register(void); | ||
void ssam_bus_unregister(void); | ||
|
||
#endif /* _SSAM_BUS_H */ |
Oops, something went wrong.