Something went wrong. Try again.
Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283// SPDX-License-Identifier: GPL-2.0+/* * Surface Book (gen. 2 and later) detachment system (DTX) driver. * * Provides a user-space interface to properly handle clipboard/tablet * (containing screen and processor) detachment from the base of the device * (containing the keyboard and optionally a discrete GPU). Allows to * acknowledge (to speed things up), abort (e.g. in case the dGPU is still in * use), or request detachment via user-space. * * Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com> */
#include <linux/fs.h>#include <linux/input.h>#include <linux/ioctl.h>#include <linux/kernel.h>#include <linux/kfifo.h>#include <linux/kref.h>#include <linux/miscdevice.h>#include <linux/module.h>#include <linux/mutex.h>#include <linux/platform_device.h>#include <linux/poll.h>#include <linux/rwsem.h>#include <linux/slab.h>#include <linux/workqueue.h>
#include <linux/surface_aggregator/controller.h>#include <linux/surface_aggregator/device.h>#include <linux/surface_aggregator/dtx.h>
/* -- SSAM interface. ------------------------------------------------------- */
enum sam_event_cid_bas { SAM_EVENT_CID_DTX_CONNECTION = 0x0c, SAM_EVENT_CID_DTX_REQUEST = 0x0e, SAM_EVENT_CID_DTX_CANCEL = 0x0f, SAM_EVENT_CID_DTX_LATCH_STATUS = 0x11,};
enum ssam_bas_base_state { SSAM_BAS_BASE_STATE_DETACH_SUCCESS = 0x00, SSAM_BAS_BASE_STATE_ATTACHED = 0x01, SSAM_BAS_BASE_STATE_NOT_FEASIBLE = 0x02,};
enum ssam_bas_latch_status { SSAM_BAS_LATCH_STATUS_CLOSED = 0x00, SSAM_BAS_LATCH_STATUS_OPENED = 0x01, SSAM_BAS_LATCH_STATUS_FAILED_TO_OPEN = 0x02, SSAM_BAS_LATCH_STATUS_FAILED_TO_REMAIN_OPEN = 0x03, SSAM_BAS_LATCH_STATUS_FAILED_TO_CLOSE = 0x04,};
enum ssam_bas_cancel_reason { SSAM_BAS_CANCEL_REASON_NOT_FEASIBLE = 0x00, /* Low battery. */ SSAM_BAS_CANCEL_REASON_TIMEOUT = 0x02, SSAM_BAS_CANCEL_REASON_FAILED_TO_OPEN = 0x03, SSAM_BAS_CANCEL_REASON_FAILED_TO_REMAIN_OPEN = 0x04, SSAM_BAS_CANCEL_REASON_FAILED_TO_CLOSE = 0x05,};
struct ssam_bas_base_info { u8 state; u8 base_id;} __packed;
static_assert(sizeof(struct ssam_bas_base_info) == 2);
SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_lock, { .target_category = SSAM_SSH_TC_BAS, .target_id = SSAM_SSH_TID_SAM, .command_id = 0x06, .instance_id = 0x00,});
SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_unlock, { .target_category = SSAM_SSH_TC_BAS, .target_id = SSAM_SSH_TID_SAM, .command_id = 0x07, .instance_id = 0x00,});
SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_request, { .target_category = SSAM_SSH_TC_BAS, .target_id = SSAM_SSH_TID_SAM, .command_id = 0x08, .instance_id = 0x00,});
SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_confirm, { .target_category = SSAM_SSH_TC_BAS, .target_id = SSAM_SSH_TID_SAM, .command_id = 0x09, .instance_id = 0x00,});
SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_heartbeat, { .target_category = SSAM_SSH_TC_BAS, .target_id = SSAM_SSH_TID_SAM, .command_id = 0x0a, .instance_id = 0x00,});
SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_cancel, { .target_category = SSAM_SSH_TC_BAS, .target_id = SSAM_SSH_TID_SAM, .command_id = 0x0b, .instance_id = 0x00,});
SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_base, struct ssam_bas_base_info, { .target_category = SSAM_SSH_TC_BAS, .target_id = SSAM_SSH_TID_SAM, .command_id = 0x0c, .instance_id = 0x00,});
SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_device_mode, u8, { .target_category = SSAM_SSH_TC_BAS, .target_id = SSAM_SSH_TID_SAM, .command_id = 0x0d, .instance_id = 0x00,});
SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_latch_status, u8, { .target_category = SSAM_SSH_TC_BAS, .target_id = SSAM_SSH_TID_SAM, .command_id = 0x11, .instance_id = 0x00,});
/* -- Main structures. ------------------------------------------------------ */
enum sdtx_device_state { SDTX_DEVICE_SHUTDOWN_BIT = BIT(0), SDTX_DEVICE_DIRTY_BASE_BIT = BIT(1), SDTX_DEVICE_DIRTY_MODE_BIT = BIT(2), SDTX_DEVICE_DIRTY_LATCH_BIT = BIT(3),};
struct sdtx_device { struct kref kref; struct rw_semaphore lock; /* Guards device and controller reference. */
struct device *dev; struct ssam_controller *ctrl; unsigned long flags;
struct miscdevice mdev; wait_queue_head_t waitq; struct mutex write_lock; /* Guards order of events/notifications. */ struct rw_semaphore client_lock; /* Guards client list. */ struct list_head client_list;
struct delayed_work state_work; struct { struct ssam_bas_base_info base; u8 device_mode; u8 latch_status; } state;
struct delayed_work mode_work; struct input_dev *mode_switch;
struct ssam_event_notifier notif;};
enum sdtx_client_state { SDTX_CLIENT_EVENTS_ENABLED_BIT = BIT(0),};
struct sdtx_client { struct sdtx_device *ddev; struct list_head node; unsigned long flags;
struct fasync_struct *fasync;
struct mutex read_lock; /* Guards FIFO buffer read access. */ DECLARE_KFIFO(buffer, u8, 512);};
static void __sdtx_device_release(struct kref *kref){ struct sdtx_device *ddev = container_of(kref, struct sdtx_device, kref);
mutex_destroy(&ddev->write_lock); kfree(ddev);}
static struct sdtx_device *sdtx_device_get(struct sdtx_device *ddev){ if (ddev) kref_get(&ddev->kref);
return ddev;}
static void sdtx_device_put(struct sdtx_device *ddev){ if (ddev) kref_put(&ddev->kref, __sdtx_device_release);}
/* -- Firmware value translations. ------------------------------------------ */
static u16 sdtx_translate_base_state(struct sdtx_device *ddev, u8 state){ switch (state) { case SSAM_BAS_BASE_STATE_ATTACHED: return SDTX_BASE_ATTACHED;
case SSAM_BAS_BASE_STATE_DETACH_SUCCESS: return SDTX_BASE_DETACHED;
case SSAM_BAS_BASE_STATE_NOT_FEASIBLE: return SDTX_DETACH_NOT_FEASIBLE;
default: dev_err(ddev->dev, "unknown base state: %#04x\n", state); return SDTX_UNKNOWN(state); }}
static u16 sdtx_translate_latch_status(struct sdtx_device *ddev, u8 status){ switch (status) { case SSAM_BAS_LATCH_STATUS_CLOSED: return SDTX_LATCH_CLOSED;
case SSAM_BAS_LATCH_STATUS_OPENED: return SDTX_LATCH_OPENED;
case SSAM_BAS_LATCH_STATUS_FAILED_TO_OPEN: return SDTX_ERR_FAILED_TO_OPEN;
case SSAM_BAS_LATCH_STATUS_FAILED_TO_REMAIN_OPEN: return SDTX_ERR_FAILED_TO_REMAIN_OPEN;
case SSAM_BAS_LATCH_STATUS_FAILED_TO_CLOSE: return SDTX_ERR_FAILED_TO_CLOSE;
default: dev_err(ddev->dev, "unknown latch status: %#04x\n", status); return SDTX_UNKNOWN(status); }}
static u16 sdtx_translate_cancel_reason(struct sdtx_device *ddev, u8 reason){ switch (reason) { case SSAM_BAS_CANCEL_REASON_NOT_FEASIBLE: return SDTX_DETACH_NOT_FEASIBLE;
case SSAM_BAS_CANCEL_REASON_TIMEOUT: return SDTX_DETACH_TIMEDOUT;
case SSAM_BAS_CANCEL_REASON_FAILED_TO_OPEN: return SDTX_ERR_FAILED_TO_OPEN;
case SSAM_BAS_CANCEL_REASON_FAILED_TO_REMAIN_OPEN: return SDTX_ERR_FAILED_TO_REMAIN_OPEN;
case SSAM_BAS_CANCEL_REASON_FAILED_TO_CLOSE: return SDTX_ERR_FAILED_TO_CLOSE;
default: dev_err(ddev->dev, "unknown cancel reason: %#04x\n", reason); return SDTX_UNKNOWN(reason); }}
/* -- IOCTLs. --------------------------------------------------------------- */
static int sdtx_ioctl_get_base_info(struct sdtx_device *ddev, struct sdtx_base_info __user *buf){ struct ssam_bas_base_info raw; struct sdtx_base_info info; int status;
lockdep_assert_held_read(&ddev->lock);
status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &raw); if (status < 0) return status;
info.state = sdtx_translate_base_state(ddev, raw.state); info.base_id = SDTX_BASE_TYPE_SSH(raw.base_id);
if (copy_to_user(buf, &info, sizeof(info))) return -EFAULT;
return 0;}
static int sdtx_ioctl_get_device_mode(struct sdtx_device *ddev, u16 __user *buf){ u8 mode; int status;
lockdep_assert_held_read(&ddev->lock);
status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode); if (status < 0) return status;
return put_user(mode, buf);}
static int sdtx_ioctl_get_latch_status(struct sdtx_device *ddev, u16 __user *buf){ u8 latch; int status;
lockdep_assert_held_read(&ddev->lock);
status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &latch); if (status < 0) return status;
return put_user(sdtx_translate_latch_status(ddev, latch), buf);}
static long __surface_dtx_ioctl(struct sdtx_client *client, unsigned int cmd, unsigned long arg){ struct sdtx_device *ddev = client->ddev;
lockdep_assert_held_read(&ddev->lock);
switch (cmd) { case SDTX_IOCTL_EVENTS_ENABLE: set_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags); return 0;
case SDTX_IOCTL_EVENTS_DISABLE: clear_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags); return 0;
case SDTX_IOCTL_LATCH_LOCK: return ssam_retry(ssam_bas_latch_lock, ddev->ctrl);
case SDTX_IOCTL_LATCH_UNLOCK: return ssam_retry(ssam_bas_latch_unlock, ddev->ctrl);
case SDTX_IOCTL_LATCH_REQUEST: return ssam_retry(ssam_bas_latch_request, ddev->ctrl);
case SDTX_IOCTL_LATCH_CONFIRM: return ssam_retry(ssam_bas_latch_confirm, ddev->ctrl);
case SDTX_IOCTL_LATCH_HEARTBEAT: return ssam_retry(ssam_bas_latch_heartbeat, ddev->ctrl);
case SDTX_IOCTL_LATCH_CANCEL: return ssam_retry(ssam_bas_latch_cancel, ddev->ctrl);
case SDTX_IOCTL_GET_BASE_INFO: return sdtx_ioctl_get_base_info(ddev, (struct sdtx_base_info __user *)arg);
case SDTX_IOCTL_GET_DEVICE_MODE: return sdtx_ioctl_get_device_mode(ddev, (u16 __user *)arg);
case SDTX_IOCTL_GET_LATCH_STATUS: return sdtx_ioctl_get_latch_status(ddev, (u16 __user *)arg);
default: return -EINVAL; }}
static long surface_dtx_ioctl(struct file *file, unsigned int cmd, unsigned long arg){ struct sdtx_client *client = file->private_data; long status;
if (down_read_killable(&client->ddev->lock)) return -ERESTARTSYS;
if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags)) { up_read(&client->ddev->lock); return -ENODEV; }
status = __surface_dtx_ioctl(client, cmd, arg);
up_read(&client->ddev->lock); return status;}
/* -- File operations. ------------------------------------------------------ */
static int surface_dtx_open(struct inode *inode, struct file *file){ struct sdtx_device *ddev = container_of(file->private_data, struct sdtx_device, mdev); struct sdtx_client *client;
/* Initialize client. */ client = kzalloc_obj(*client); if (!client) return -ENOMEM;
client->ddev = sdtx_device_get(ddev);
INIT_LIST_HEAD(&client->node);
mutex_init(&client->read_lock); INIT_KFIFO(client->buffer);
file->private_data = client;
/* Attach client. */ down_write(&ddev->client_lock);
/* * Do not add a new client if the device has been shut down. Note that * it's enough to hold the client_lock here as, during shutdown, we * only acquire that lock and remove clients after marking the device * as shut down. */ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) { up_write(&ddev->client_lock); mutex_destroy(&client->read_lock); sdtx_device_put(client->ddev); kfree(client); return -ENODEV; }
list_add_tail(&client->node, &ddev->client_list); up_write(&ddev->client_lock);
stream_open(inode, file); return 0;}
static int surface_dtx_release(struct inode *inode, struct file *file){ struct sdtx_client *client = file->private_data;
/* Detach client. */ down_write(&client->ddev->client_lock); list_del(&client->node); up_write(&client->ddev->client_lock);
/* Free client. */ sdtx_device_put(client->ddev); mutex_destroy(&client->read_lock); kfree(client);
return 0;}
static ssize_t surface_dtx_read(struct file *file, char __user *buf, size_t count, loff_t *offs){ struct sdtx_client *client = file->private_data; struct sdtx_device *ddev = client->ddev; unsigned int copied; int status = 0;
if (down_read_killable(&ddev->lock)) return -ERESTARTSYS;
/* Make sure we're not shut down. */ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) { up_read(&ddev->lock); return -ENODEV; }
do { /* Check availability, wait if necessary. */ if (kfifo_is_empty(&client->buffer)) { up_read(&ddev->lock);
if (file->f_flags & O_NONBLOCK) return -EAGAIN;
status = wait_event_interruptible(ddev->waitq, !kfifo_is_empty(&client->buffer) || test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)); if (status < 0) return status;
if (down_read_killable(&ddev->lock)) return -ERESTARTSYS;
/* Need to check that we're not shut down again. */ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) { up_read(&ddev->lock); return -ENODEV; } }
/* Try to read from FIFO. */ if (mutex_lock_interruptible(&client->read_lock)) { up_read(&ddev->lock); return -ERESTARTSYS; }
status = kfifo_to_user(&client->buffer, buf, count, &copied); mutex_unlock(&client->read_lock);
if (status < 0) { up_read(&ddev->lock); return status; }
/* We might not have gotten anything, check this here. */ if (copied == 0 && (file->f_flags & O_NONBLOCK)) { up_read(&ddev->lock); return -EAGAIN; } } while (copied == 0);
up_read(&ddev->lock); return copied;}
static __poll_t surface_dtx_poll(struct file *file, struct poll_table_struct *pt){ struct sdtx_client *client = file->private_data; __poll_t events = 0;
if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags)) return EPOLLHUP | EPOLLERR;
poll_wait(file, &client->ddev->waitq, pt);
if (!kfifo_is_empty(&client->buffer)) events |= EPOLLIN | EPOLLRDNORM;
return events;}
static int surface_dtx_fasync(int fd, struct file *file, int on){ struct sdtx_client *client = file->private_data;
return fasync_helper(fd, file, on, &client->fasync);}
static const struct file_operations surface_dtx_fops = { .owner = THIS_MODULE, .open = surface_dtx_open, .release = surface_dtx_release, .read = surface_dtx_read, .poll = surface_dtx_poll, .fasync = surface_dtx_fasync, .unlocked_ioctl = surface_dtx_ioctl, .compat_ioctl = surface_dtx_ioctl,};
/* -- Event handling/forwarding. -------------------------------------------- */
/* * The device operation mode is not immediately updated on the EC when the * base has been connected, i.e. querying the device mode inside the * connection event callback yields an outdated value. Thus, we can only * determine the new tablet-mode switch and device mode values after some * time. * * These delays have been chosen by experimenting. We first delay on connect * events, then check and validate the device mode against the base state and * if invalid delay again by the "recheck" delay. */#define SDTX_DEVICE_MODE_DELAY_CONNECT msecs_to_jiffies(100)#define SDTX_DEVICE_MODE_DELAY_RECHECK msecs_to_jiffies(100)
struct sdtx_status_event { struct sdtx_event e; __u16 v;} __packed;
struct sdtx_base_info_event { struct sdtx_event e; struct sdtx_base_info v;} __packed;
union sdtx_generic_event { struct sdtx_event common; struct sdtx_status_event status; struct sdtx_base_info_event base;};
static void sdtx_update_device_mode(struct sdtx_device *ddev, unsigned long delay);
/* Must be executed with ddev->write_lock held. */static void sdtx_push_event(struct sdtx_device *ddev, struct sdtx_event *evt){ const size_t len = sizeof(struct sdtx_event) + evt->length; struct sdtx_client *client;
lockdep_assert_held(&ddev->write_lock);
down_read(&ddev->client_lock); list_for_each_entry(client, &ddev->client_list, node) { if (!test_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags)) continue;
if (likely(kfifo_avail(&client->buffer) >= len)) kfifo_in(&client->buffer, (const u8 *)evt, len); else dev_warn(ddev->dev, "event buffer overrun\n");
kill_fasync(&client->fasync, SIGIO, POLL_IN); } up_read(&ddev->client_lock);
wake_up_interruptible(&ddev->waitq);}
static u32 sdtx_notifier(struct ssam_event_notifier *nf, const struct ssam_event *in){ struct sdtx_device *ddev = container_of(nf, struct sdtx_device, notif); union sdtx_generic_event event; size_t len;
/* Validate event payload length. */ switch (in->command_id) { case SAM_EVENT_CID_DTX_CONNECTION: len = 2 * sizeof(u8); break;
case SAM_EVENT_CID_DTX_REQUEST: len = 0; break;
case SAM_EVENT_CID_DTX_CANCEL: len = sizeof(u8); break;
case SAM_EVENT_CID_DTX_LATCH_STATUS: len = sizeof(u8); break;
default: return 0; }
if (in->length != len) { dev_err(ddev->dev, "unexpected payload size for event %#04x: got %u, expected %zu\n", in->command_id, in->length, len); return 0; }
mutex_lock(&ddev->write_lock);
/* Translate event. */ switch (in->command_id) { case SAM_EVENT_CID_DTX_CONNECTION: clear_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags);
/* If state has not changed: do not send new event. */ if (ddev->state.base.state == in->data[0] && ddev->state.base.base_id == in->data[1]) goto out;
ddev->state.base.state = in->data[0]; ddev->state.base.base_id = in->data[1];
event.base.e.length = sizeof(struct sdtx_base_info); event.base.e.code = SDTX_EVENT_BASE_CONNECTION; event.base.v.state = sdtx_translate_base_state(ddev, in->data[0]); event.base.v.base_id = SDTX_BASE_TYPE_SSH(in->data[1]); break;
case SAM_EVENT_CID_DTX_REQUEST: event.common.code = SDTX_EVENT_REQUEST; event.common.length = 0; break;
case SAM_EVENT_CID_DTX_CANCEL: event.status.e.length = sizeof(u16); event.status.e.code = SDTX_EVENT_CANCEL; event.status.v = sdtx_translate_cancel_reason(ddev, in->data[0]); break;
case SAM_EVENT_CID_DTX_LATCH_STATUS: clear_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags);
/* If state has not changed: do not send new event. */ if (ddev->state.latch_status == in->data[0]) goto out;
ddev->state.latch_status = in->data[0];
event.status.e.length = sizeof(u16); event.status.e.code = SDTX_EVENT_LATCH_STATUS; event.status.v = sdtx_translate_latch_status(ddev, in->data[0]); break; }
sdtx_push_event(ddev, &event.common);
/* Update device mode on base connection change. */ if (in->command_id == SAM_EVENT_CID_DTX_CONNECTION) { unsigned long delay;
delay = in->data[0] ? SDTX_DEVICE_MODE_DELAY_CONNECT : 0; sdtx_update_device_mode(ddev, delay); }
out: mutex_unlock(&ddev->write_lock); return SSAM_NOTIF_HANDLED;}
/* -- State update functions. ----------------------------------------------- */
static bool sdtx_device_mode_invalid(u8 mode, u8 base_state){ return ((base_state == SSAM_BAS_BASE_STATE_ATTACHED) && (mode == SDTX_DEVICE_MODE_TABLET)) || ((base_state == SSAM_BAS_BASE_STATE_DETACH_SUCCESS) && (mode != SDTX_DEVICE_MODE_TABLET));}
static void sdtx_device_mode_workfn(struct work_struct *work){ struct sdtx_device *ddev = container_of(work, struct sdtx_device, mode_work.work); struct sdtx_status_event event; struct ssam_bas_base_info base; int status, tablet; u8 mode;
/* Get operation mode. */ status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode); if (status) { dev_err(ddev->dev, "failed to get device mode: %d\n", status); return; }
/* Get base info. */ status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &base); if (status) { dev_err(ddev->dev, "failed to get base info: %d\n", status); return; }
/* * In some cases (specifically when attaching the base), the device * mode isn't updated right away. Thus we check if the device mode * makes sense for the given base state and try again later if it * doesn't. */ if (sdtx_device_mode_invalid(mode, base.state)) { dev_dbg(ddev->dev, "device mode is invalid, trying again\n"); sdtx_update_device_mode(ddev, SDTX_DEVICE_MODE_DELAY_RECHECK); return; }
mutex_lock(&ddev->write_lock); clear_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags);
/* Avoid sending duplicate device-mode events. */ if (ddev->state.device_mode == mode) { mutex_unlock(&ddev->write_lock); return; }
ddev->state.device_mode = mode;
event.e.length = sizeof(u16); event.e.code = SDTX_EVENT_DEVICE_MODE; event.v = mode;
sdtx_push_event(ddev, &event.e);
/* Send SW_TABLET_MODE event. */ tablet = mode != SDTX_DEVICE_MODE_LAPTOP; input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet); input_sync(ddev->mode_switch);
mutex_unlock(&ddev->write_lock);}
static void sdtx_update_device_mode(struct sdtx_device *ddev, unsigned long delay){ schedule_delayed_work(&ddev->mode_work, delay);}
/* Must be executed with ddev->write_lock held. */static void __sdtx_device_state_update_base(struct sdtx_device *ddev, struct ssam_bas_base_info info){ struct sdtx_base_info_event event;
lockdep_assert_held(&ddev->write_lock);
/* Prevent duplicate events. */ if (ddev->state.base.state == info.state && ddev->state.base.base_id == info.base_id) return;
ddev->state.base = info;
event.e.length = sizeof(struct sdtx_base_info); event.e.code = SDTX_EVENT_BASE_CONNECTION; event.v.state = sdtx_translate_base_state(ddev, info.state); event.v.base_id = SDTX_BASE_TYPE_SSH(info.base_id);
sdtx_push_event(ddev, &event.e);}
/* Must be executed with ddev->write_lock held. */static void __sdtx_device_state_update_mode(struct sdtx_device *ddev, u8 mode){ struct sdtx_status_event event; int tablet;
/* * Note: This function must be called after updating the base state * via __sdtx_device_state_update_base(), as we rely on the updated * base state value in the validity check below. */
lockdep_assert_held(&ddev->write_lock);
if (sdtx_device_mode_invalid(mode, ddev->state.base.state)) { dev_dbg(ddev->dev, "device mode is invalid, trying again\n"); sdtx_update_device_mode(ddev, SDTX_DEVICE_MODE_DELAY_RECHECK); return; }
/* Prevent duplicate events. */ if (ddev->state.device_mode == mode) return;
ddev->state.device_mode = mode;
/* Send event. */ event.e.length = sizeof(u16); event.e.code = SDTX_EVENT_DEVICE_MODE; event.v = mode;
sdtx_push_event(ddev, &event.e);
/* Send SW_TABLET_MODE event. */ tablet = mode != SDTX_DEVICE_MODE_LAPTOP; input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet); input_sync(ddev->mode_switch);}
/* Must be executed with ddev->write_lock held. */static void __sdtx_device_state_update_latch(struct sdtx_device *ddev, u8 status){ struct sdtx_status_event event;
lockdep_assert_held(&ddev->write_lock);
/* Prevent duplicate events. */ if (ddev->state.latch_status == status) return;
ddev->state.latch_status = status;
event.e.length = sizeof(struct sdtx_base_info); event.e.code = SDTX_EVENT_BASE_CONNECTION; event.v = sdtx_translate_latch_status(ddev, status);
sdtx_push_event(ddev, &event.e);}
static void sdtx_device_state_workfn(struct work_struct *work){ struct sdtx_device *ddev = container_of(work, struct sdtx_device, state_work.work); struct ssam_bas_base_info base; u8 mode, latch; int status;
/* Mark everything as dirty. */ set_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags); set_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags); set_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags);
/* * Ensure that the state gets marked as dirty before continuing to * query it. Necessary to ensure that clear_bit() calls in * sdtx_notifier() and sdtx_device_mode_workfn() actually clear these * bits if an event is received while updating the state here. */ smp_mb__after_atomic();
status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &base); if (status) { dev_err(ddev->dev, "failed to get base state: %d\n", status); return; }
status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode); if (status) { dev_err(ddev->dev, "failed to get device mode: %d\n", status); return; }
status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &latch); if (status) { dev_err(ddev->dev, "failed to get latch status: %d\n", status); return; }
mutex_lock(&ddev->write_lock);
/* * If the respective dirty-bit has been cleared, an event has been * received, updating this state. The queried state may thus be out of * date. At this point, we can safely assume that the state provided * by the event is either up to date, or we're about to receive * another event updating it. */
if (test_and_clear_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags)) __sdtx_device_state_update_base(ddev, base);
if (test_and_clear_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags)) __sdtx_device_state_update_mode(ddev, mode);
if (test_and_clear_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags)) __sdtx_device_state_update_latch(ddev, latch);
mutex_unlock(&ddev->write_lock);}
static void sdtx_update_device_state(struct sdtx_device *ddev, unsigned long delay){ schedule_delayed_work(&ddev->state_work, delay);}
/* -- Common device initialization. ----------------------------------------- */
static int sdtx_device_init(struct sdtx_device *ddev, struct device *dev, struct ssam_controller *ctrl){ int status, tablet_mode;
/* Basic initialization. */ kref_init(&ddev->kref); init_rwsem(&ddev->lock); ddev->dev = dev; ddev->ctrl = ctrl;
ddev->mdev.minor = MISC_DYNAMIC_MINOR; ddev->mdev.name = "surface_dtx"; ddev->mdev.nodename = "surface/dtx"; ddev->mdev.fops = &surface_dtx_fops;
ddev->notif.base.priority = 1; ddev->notif.base.fn = sdtx_notifier; ddev->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; ddev->notif.event.id.target_category = SSAM_SSH_TC_BAS; ddev->notif.event.id.instance = 0; ddev->notif.event.mask = SSAM_EVENT_MASK_NONE; ddev->notif.event.flags = SSAM_EVENT_SEQUENCED;
init_waitqueue_head(&ddev->waitq); mutex_init(&ddev->write_lock); init_rwsem(&ddev->client_lock); INIT_LIST_HEAD(&ddev->client_list);
INIT_DELAYED_WORK(&ddev->mode_work, sdtx_device_mode_workfn); INIT_DELAYED_WORK(&ddev->state_work, sdtx_device_state_workfn);
/* * Get current device state. We want to guarantee that events are only * sent when state actually changes. Thus we cannot use special * "uninitialized" values, as that would cause problems when manually * querying the state in surface_dtx_pm_complete(). I.e. we would not * be able to detect state changes there if no change event has been * received between driver initialization and first device suspension. * * Note that we also need to do this before registering the event * notifier, as that may access the state values. */ status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &ddev->state.base); if (status) return status;
status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &ddev->state.device_mode); if (status) return status;
status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &ddev->state.latch_status); if (status) return status;
/* Set up tablet mode switch. */ ddev->mode_switch = input_allocate_device(); if (!ddev->mode_switch) return -ENOMEM;
ddev->mode_switch->name = "Microsoft Surface DTX Device Mode Switch"; ddev->mode_switch->phys = "ssam/01:11:01:00:00/input0"; ddev->mode_switch->id.bustype = BUS_HOST; ddev->mode_switch->dev.parent = ddev->dev;
tablet_mode = (ddev->state.device_mode != SDTX_DEVICE_MODE_LAPTOP); input_set_capability(ddev->mode_switch, EV_SW, SW_TABLET_MODE); input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet_mode);
status = input_register_device(ddev->mode_switch); if (status) { input_free_device(ddev->mode_switch); return status; }
/* Set up event notifier. */ status = ssam_notifier_register(ddev->ctrl, &ddev->notif); if (status) goto err_notif;
/* Register miscdevice. */ status = misc_register(&ddev->mdev); if (status) goto err_mdev;
/* * Update device state in case it has changed between getting the * initial mode and registering the event notifier. */ sdtx_update_device_state(ddev, 0); return 0;
err_notif: ssam_notifier_unregister(ddev->ctrl, &ddev->notif); cancel_delayed_work_sync(&ddev->mode_work);err_mdev: input_unregister_device(ddev->mode_switch); return status;}
static struct sdtx_device *sdtx_device_create(struct device *dev, struct ssam_controller *ctrl){ struct sdtx_device *ddev; int status;
ddev = kzalloc_obj(*ddev); if (!ddev) return ERR_PTR(-ENOMEM);
status = sdtx_device_init(ddev, dev, ctrl); if (status) { sdtx_device_put(ddev); return ERR_PTR(status); }
return ddev;}
static void sdtx_device_destroy(struct sdtx_device *ddev){ struct sdtx_client *client;
/* * Mark device as shut-down. Prevent new clients from being added and * new operations from being executed. */ set_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags);
/* Disable notifiers, prevent new events from arriving. */ ssam_notifier_unregister(ddev->ctrl, &ddev->notif);
/* Stop mode_work, prevent access to mode_switch. */ cancel_delayed_work_sync(&ddev->mode_work);
/* Stop state_work. */ cancel_delayed_work_sync(&ddev->state_work);
/* With mode_work canceled, we can unregister the mode_switch. */ input_unregister_device(ddev->mode_switch);
/* Wake up async clients. */ down_write(&ddev->client_lock); list_for_each_entry(client, &ddev->client_list, node) { kill_fasync(&client->fasync, SIGIO, POLL_HUP); } up_write(&ddev->client_lock);
/* Wake up blocking clients. */ wake_up_interruptible(&ddev->waitq);
/* * Wait for clients to finish their current operation. After this, the * controller and device references are guaranteed to be no longer in * use. */ down_write(&ddev->lock); ddev->dev = NULL; ddev->ctrl = NULL; up_write(&ddev->lock);
/* Finally remove the misc-device. */ misc_deregister(&ddev->mdev);
/* * We're now guaranteed that sdtx_device_open() won't be called any * more, so we can now drop out reference. */ sdtx_device_put(ddev);}
/* -- PM ops. --------------------------------------------------------------- */
#ifdef CONFIG_PM_SLEEP
static void surface_dtx_pm_complete(struct device *dev){ struct sdtx_device *ddev = dev_get_drvdata(dev);
/* * Normally, the EC will store events while suspended (i.e. in * display-off state) and release them when resumed (i.e. transitioned * to display-on state). During hibernation, however, the EC will be * shut down and does not store events. Furthermore, events might be * dropped during prolonged suspension (it is currently unknown how * big this event buffer is and how it behaves on overruns). * * To prevent any problems, we update the device state here. We do * this delayed to ensure that any events sent by the EC directly * after resuming will be handled first. The delay below has been * chosen (experimentally), so that there should be ample time for * these events to be handled, before we check and, if necessary, * update the state. */ sdtx_update_device_state(ddev, msecs_to_jiffies(1000));}
static const struct dev_pm_ops surface_dtx_pm_ops = { .complete = surface_dtx_pm_complete,};
#else /* CONFIG_PM_SLEEP */
static const struct dev_pm_ops surface_dtx_pm_ops = {};
#endif /* CONFIG_PM_SLEEP */
/* -- Platform driver. ------------------------------------------------------ */
static int surface_dtx_platform_probe(struct platform_device *pdev){ struct ssam_controller *ctrl; struct sdtx_device *ddev;
/* Link to EC. */ ctrl = ssam_client_bind(&pdev->dev); if (IS_ERR(ctrl)) return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl);
ddev = sdtx_device_create(&pdev->dev, ctrl); if (IS_ERR(ddev)) return PTR_ERR(ddev);
platform_set_drvdata(pdev, ddev); return 0;}
static void surface_dtx_platform_remove(struct platform_device *pdev){ sdtx_device_destroy(platform_get_drvdata(pdev));}
static const struct acpi_device_id surface_dtx_acpi_match[] = { { "MSHW0133", 0 }, { },};MODULE_DEVICE_TABLE(acpi, surface_dtx_acpi_match);
static struct platform_driver surface_dtx_platform_driver = { .probe = surface_dtx_platform_probe, .remove = surface_dtx_platform_remove, .driver = { .name = "surface_dtx_pltf", .acpi_match_table = surface_dtx_acpi_match, .pm = &surface_dtx_pm_ops, .probe_type = PROBE_PREFER_ASYNCHRONOUS, },};
/* -- SSAM device driver. --------------------------------------------------- */
#ifdef CONFIG_SURFACE_AGGREGATOR_BUS
static int surface_dtx_ssam_probe(struct ssam_device *sdev){ struct sdtx_device *ddev;
ddev = sdtx_device_create(&sdev->dev, sdev->ctrl); if (IS_ERR(ddev)) return PTR_ERR(ddev);
ssam_device_set_drvdata(sdev, ddev); return 0;}
static void surface_dtx_ssam_remove(struct ssam_device *sdev){ sdtx_device_destroy(ssam_device_get_drvdata(sdev));}
static const struct ssam_device_id surface_dtx_ssam_match[] = { { SSAM_SDEV(BAS, SAM, 0x00, 0x00) }, { },};MODULE_DEVICE_TABLE(ssam, surface_dtx_ssam_match);
static struct ssam_device_driver surface_dtx_ssam_driver = { .probe = surface_dtx_ssam_probe, .remove = surface_dtx_ssam_remove, .match_table = surface_dtx_ssam_match, .driver = { .name = "surface_dtx", .pm = &surface_dtx_pm_ops, .probe_type = PROBE_PREFER_ASYNCHRONOUS, },};
static int ssam_dtx_driver_register(void){ return ssam_device_driver_register(&surface_dtx_ssam_driver);}
static void ssam_dtx_driver_unregister(void){ ssam_device_driver_unregister(&surface_dtx_ssam_driver);}
#else /* CONFIG_SURFACE_AGGREGATOR_BUS */
static int ssam_dtx_driver_register(void){ return 0;}
static void ssam_dtx_driver_unregister(void){}
#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */
/* -- Module setup. --------------------------------------------------------- */
static int __init surface_dtx_init(void){ int status;
status = ssam_dtx_driver_register(); if (status) return status;
status = platform_driver_register(&surface_dtx_platform_driver); if (status) ssam_dtx_driver_unregister();
return status;}module_init(surface_dtx_init);
static void __exit surface_dtx_exit(void){ platform_driver_unregister(&surface_dtx_platform_driver); ssam_dtx_driver_unregister();}module_exit(surface_dtx_exit);
MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");MODULE_DESCRIPTION("Detachment-system driver for Surface System Aggregator Module");MODULE_LICENSE("GPL");