// 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(sizeof(*client), GFP_KERNEL); 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. Load more
// 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(sizeof(*client), GFP_KERNEL); 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; } }