diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-19 10:09:22 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-19 10:09:22 -0700 |
| commit | f4cdf7ca9a1fdcca413157df19753f388a5a224e (patch) | |
| tree | 60a31895b2446cd7fa27514e8c2843534a0de0da /drivers/char/tpm/tpm-dev-common.c | |
| download | linux-stable-f4cdf7ca9a1fdcca413157df19753f388a5a224e.tar.gz linux-stable-f4cdf7ca9a1fdcca413157df19753f388a5a224e.zip | |
Merge tag 'media/v7.3-1' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-mediagrafted
Pull media updates from Mauro Carvalho Chehab:
- v4l2-core: added ISP statistics support and per-block validation
- v4l2-core: Allow unknown HDR10 white point and luminance
- New camera sensors: Sony IMX678 and IMX471m, Himax HM1092 IR sensor
- New codec: Milos: VPU v2.0 codec support
- isp driver: gained support for Dreamchip RPPX1 ISP framework
- vsp1 driver: gained support for RZ/T2H and RZ/N2H
- Novalake driver: gained CVS support for new NVL hardware
- dvb-core: fix feed leak on failed DMX_ADD_PID
- several driver fixes, cleanups and minor improvements
* tag 'media/v7.3-1' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media: (308 commits)
media: ipu-bridge: check all DMI entries when overriding sensor rotation
media: v4l2-async: avoid deleting unlinked ASC entry on link error
media: rzg2l-cru: Align bytesperline to hardware DMA stride requirement
media: intel/ipu6: fix async notifier cleanup leak on parse error
media: staging/ipu7: fix async notifier UAF on probe error path
media: amd: isp4: fix self-deadlock in isp4sd_pwron_and_init() error path
media: amd: isp4: release partial allocations in isp4if_alloc_fw_gpumem()
media: rcar-isp: Fix VSPX reference leaks
media: rcar-isp: Release ISPCORE resources
media: i2c: imx415: Release runtime PM reference on VBLANK error
media: i2c: imx415: Return test pattern write errors
media: renesas: vsp1: Declare index variables in for loop statement
media: renesas: vsp1: Make reset control optional to support platforms without a reset line
media: dt-bindings: media: renesas,vsp1: Document RZ/T2H and RZ/N2H SoCs
media: dt-bindings: media: renesas,fcp: Document RZ/T2H and RZ/N2H SoCs
media: nxp: imx8-isi: Add additional 32-bit RGB format support
media: nxp: imx8-isi: Add 16-bit raw Bayer format support
media: nxp: imx8-isi: Implement per-stream reference counting for multiplexed streams
media: nxp: imx8-isi: Use BIT_ULL() for 64-bit stream masks
media: nxp: imx8-isi: Correct color map between V4L2 and ISI
...
Diffstat (limited to 'drivers/char/tpm/tpm-dev-common.c')
| -rw-r--r-- | drivers/char/tpm/tpm-dev-common.c | 290 |
1 files changed, 290 insertions, 0 deletions
diff --git a/drivers/char/tpm/tpm-dev-common.c b/drivers/char/tpm/tpm-dev-common.c new file mode 100644 index 000000000..f942c0c8e --- /dev/null +++ b/drivers/char/tpm/tpm-dev-common.c @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2004 IBM Corporation + * Authors: + * Leendert van Doorn <leendert@watson.ibm.com> + * Dave Safford <safford@watson.ibm.com> + * Reiner Sailer <sailer@watson.ibm.com> + * Kylene Hall <kjhall@us.ibm.com> + * + * Copyright (C) 2013 Obsidian Research Corp + * Jason Gunthorpe <jgunthorpe@obsidianresearch.com> + * + * Device file system interface to the TPM + */ +#include <linux/poll.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/workqueue.h> +#include "tpm.h" +#include "tpm-dev.h" + +static struct workqueue_struct *tpm_dev_wq; + +static ssize_t tpm_dev_transmit(struct tpm_chip *chip, struct tpm_space *space, + u8 *buf, size_t bufsiz) +{ + struct tpm_header *header = (void *)buf; + ssize_t ret, len; + + if (chip->flags & TPM_CHIP_FLAG_TPM2) + tpm2_end_auth_session(chip); + + ret = tpm2_prepare_space(chip, space, buf, bufsiz); + /* If the command is not implemented by the TPM, synthesize a + * response with a TPM2_RC_COMMAND_CODE return for user-space. + */ + if (ret == -EOPNOTSUPP) { + header->length = cpu_to_be32(sizeof(*header)); + header->tag = cpu_to_be16(TPM2_ST_NO_SESSIONS); + header->return_code = cpu_to_be32(TPM2_RC_COMMAND_CODE | + TSS2_RESMGR_TPM_RC_LAYER); + ret = sizeof(*header); + } + if (ret) + goto out_rc; + + len = tpm_transmit(chip, buf, bufsiz); + if (len < 0) + ret = len; + + if (!ret) + ret = tpm2_commit_space(chip, space, buf, &len); + else + tpm2_flush_space(chip); + +out_rc: + return ret ? ret : len; +} + +static void tpm_dev_async_work(struct work_struct *work) +{ + struct file_priv *priv = + container_of(work, struct file_priv, async_work); + ssize_t ret; + + mutex_lock(&priv->buffer_mutex); + priv->command_enqueued = false; + ret = tpm_try_get_ops(priv->chip); + if (ret) { + priv->response_length = ret; + goto out; + } + + ret = tpm_dev_transmit(priv->chip, priv->space, priv->data_buffer, + sizeof(priv->data_buffer)); + tpm_put_ops(priv->chip); + + /* + * If ret is > 0 then tpm_dev_transmit returned the size of the + * response. If ret is < 0 then tpm_dev_transmit failed and + * returned an error code. + */ + if (ret != 0) { + priv->response_length = ret; + mod_timer(&priv->user_read_timer, jiffies + (120 * HZ)); + } +out: + mutex_unlock(&priv->buffer_mutex); + wake_up_interruptible(&priv->async_wait); +} + +static void user_reader_timeout(struct timer_list *t) +{ + struct file_priv *priv = timer_container_of(priv, t, user_read_timer); + + pr_warn("TPM user space timeout is deprecated (pid=%d)\n", + task_tgid_nr(current)); + + schedule_work(&priv->timeout_work); +} + +static void tpm_timeout_work(struct work_struct *work) +{ + struct file_priv *priv = container_of(work, struct file_priv, + timeout_work); + + mutex_lock(&priv->buffer_mutex); + priv->response_read = true; + priv->response_length = 0; + memset(priv->data_buffer, 0, sizeof(priv->data_buffer)); + mutex_unlock(&priv->buffer_mutex); + wake_up_interruptible(&priv->async_wait); +} + +void tpm_common_open(struct file *file, struct tpm_chip *chip, + struct file_priv *priv, struct tpm_space *space) +{ + priv->chip = chip; + priv->space = space; + priv->response_read = true; + + mutex_init(&priv->buffer_mutex); + timer_setup(&priv->user_read_timer, user_reader_timeout, 0); + INIT_WORK(&priv->timeout_work, tpm_timeout_work); + INIT_WORK(&priv->async_work, tpm_dev_async_work); + init_waitqueue_head(&priv->async_wait); + file->private_data = priv; +} + +ssize_t tpm_common_read(struct file *file, char __user *buf, + size_t size, loff_t *off) +{ + struct file_priv *priv = file->private_data; + ssize_t ret_size = 0; + int rc; + + mutex_lock(&priv->buffer_mutex); + + if (priv->response_length) { + priv->response_read = true; + + ret_size = min_t(ssize_t, size, priv->response_length); + if (ret_size <= 0) { + priv->response_length = 0; + goto out; + } + + rc = copy_to_user(buf, priv->data_buffer + *off, ret_size); + if (rc) { + memset(priv->data_buffer, 0, TPM_BUFSIZE); + priv->response_length = 0; + ret_size = -EFAULT; + } else { + memset(priv->data_buffer + *off, 0, ret_size); + priv->response_length -= ret_size; + *off += ret_size; + } + } + +out: + if (!priv->response_length) { + *off = 0; + timer_delete_sync(&priv->user_read_timer); + flush_work(&priv->timeout_work); + } + mutex_unlock(&priv->buffer_mutex); + return ret_size; +} + +ssize_t tpm_common_write(struct file *file, const char __user *buf, + size_t size, loff_t *off) +{ + struct file_priv *priv = file->private_data; + int ret = 0; + + if (size > TPM_BUFSIZE) + return -E2BIG; + + mutex_lock(&priv->buffer_mutex); + + /* Cannot perform a write until the read has cleared either via + * tpm_read or a user_read_timer timeout. This also prevents split + * buffered writes from blocking here. + */ + if ((!priv->response_read && priv->response_length) || + priv->command_enqueued) { + ret = -EBUSY; + goto out; + } + + if (copy_from_user(priv->data_buffer, buf, size)) { + ret = -EFAULT; + goto out; + } + + if (size < 6 || + size < be32_to_cpu(*((__be32 *)(priv->data_buffer + 2)))) { + ret = -EINVAL; + goto out; + } + + priv->response_length = 0; + priv->response_read = false; + *off = 0; + + /* + * If in nonblocking mode schedule an async job to send + * the command return the size. + * In case of error the err code will be returned in + * the subsequent read call. + */ + if (file->f_flags & O_NONBLOCK) { + priv->command_enqueued = true; + queue_work(tpm_dev_wq, &priv->async_work); + mutex_unlock(&priv->buffer_mutex); + return size; + } + + /* atomic tpm command send and result receive. We only hold the ops + * lock during this period so that the tpm can be unregistered even if + * the char dev is held open. + */ + if (tpm_try_get_ops(priv->chip)) { + ret = -EPIPE; + goto out; + } + + ret = tpm_dev_transmit(priv->chip, priv->space, priv->data_buffer, + sizeof(priv->data_buffer)); + tpm_put_ops(priv->chip); + + if (ret > 0) { + priv->response_length = ret; + mod_timer(&priv->user_read_timer, jiffies + (120 * HZ)); + ret = size; + } +out: + mutex_unlock(&priv->buffer_mutex); + return ret; +} + +__poll_t tpm_common_poll(struct file *file, poll_table *wait) +{ + struct file_priv *priv = file->private_data; + __poll_t mask = 0; + + poll_wait(file, &priv->async_wait, wait); + mutex_lock(&priv->buffer_mutex); + + /* + * The response_length indicates if there is still response + * (or part of it) to be consumed. Partial reads decrease it + * by the number of bytes read, and write resets it the zero. + */ + if (priv->response_length) + mask = EPOLLIN | EPOLLRDNORM; + else + mask = EPOLLOUT | EPOLLWRNORM; + + mutex_unlock(&priv->buffer_mutex); + return mask; +} + +/* + * Called on file close + */ +void tpm_common_release(struct file *file, struct file_priv *priv) +{ + flush_work(&priv->async_work); + timer_delete_sync(&priv->user_read_timer); + flush_work(&priv->timeout_work); + file->private_data = NULL; + priv->response_length = 0; +} + +int __init tpm_dev_common_init(void) +{ + tpm_dev_wq = alloc_workqueue("tpm_dev_wq", WQ_MEM_RECLAIM | WQ_PERCPU, + 0); + + return !tpm_dev_wq ? -ENOMEM : 0; +} + +void __exit tpm_dev_common_exit(void) +{ + if (tpm_dev_wq) { + destroy_workqueue(tpm_dev_wq); + tpm_dev_wq = NULL; + } +} |
