Skip to content

Commit

Permalink
misc: Add support for Surface System Aggregator Module
Browse files Browse the repository at this point in the history
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
qzed committed Aug 17, 2020
1 parent 97f5e70 commit 16f3286
Show file tree
Hide file tree
Showing 31 changed files with 13,307 additions and 0 deletions.
1 change: 1 addition & 0 deletions drivers/misc/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -481,4 +481,5 @@ source "drivers/misc/ocxl/Kconfig"
source "drivers/misc/cardreader/Kconfig"
source "drivers/misc/habanalabs/Kconfig"
source "drivers/misc/uacce/Kconfig"
source "drivers/misc/surface_sam/Kconfig"
endmenu
1 change: 1 addition & 0 deletions drivers/misc/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,4 @@ obj-$(CONFIG_PVPANIC) += pvpanic.o
obj-$(CONFIG_HABANA_AI) += habanalabs/
obj-$(CONFIG_UACCE) += uacce/
obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o
obj-$(CONFIG_SURFACE_SAM) += surface_sam/
46 changes: 46 additions & 0 deletions drivers/misc/surface_sam/Kconfig
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"
14 changes: 14 additions & 0 deletions drivers/misc/surface_sam/Makefile
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
276 changes: 276 additions & 0 deletions drivers/misc/surface_sam/bus.c
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);
}
14 changes: 14 additions & 0 deletions drivers/misc/surface_sam/bus.h
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 */
Loading

0 comments on commit 16f3286

Please sign in to comment.