// 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>
*/
/* * 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;
}
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);
/* * 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)
/* 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;
}
/* Must be executed with ddev->write_lock held. */ staticvoid __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;
/* 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);
/* * 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;
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;
/* * 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);
/* 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);
}
/* * 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));
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.