1282 lines · 33.4 KB · C
RawDownload
// 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;			}		} 

Lines 1–500 of 1282.