cregit-Linux how code gets into the kernel

Release 4.11 drivers/char/tpm/tpm_vtpm_proxy.c

Directory: drivers/char/tpm
/*
 * Copyright (C) 2015, 2016 IBM Corporation
 * Copyright (C) 2016 Intel Corporation
 *
 * Author: Stefan Berger <stefanb@us.ibm.com>
 *
 * Maintained by: <tpmdd-devel@lists.sourceforge.net>
 *
 * Device driver for vTPM (vTPM proxy driver)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, version 2 of the
 * License.
 *
 */

#include <linux/types.h>
#include <linux/spinlock.h>
#include <linux/uaccess.h>
#include <linux/wait.h>
#include <linux/miscdevice.h>
#include <linux/vtpm_proxy.h>
#include <linux/file.h>
#include <linux/anon_inodes.h>
#include <linux/poll.h>
#include <linux/compat.h>

#include "tpm.h"


#define VTPM_PROXY_REQ_COMPLETE_FLAG  BIT(0)


struct proxy_dev {
	
struct tpm_chip *chip;

	
u32 flags;                   /* public API flags */

	
wait_queue_head_t wq;

	
struct mutex buf_lock;       /* protect buffer and flags */

	
long state;                  /* internal state */

#define STATE_OPENED_FLAG        BIT(0)

#define STATE_WAIT_RESPONSE_FLAG BIT(1)  /* waiting for emulator response */

#define STATE_REGISTERED_FLAG	 BIT(2)

	
size_t req_len;              /* length of queued TPM request */
	
size_t resp_len;             /* length of queued TPM response */
	
u8 buffer[TPM_BUFSIZE];      /* request/response buffer */

	
struct work_struct work;     /* task that retrieves TPM timeouts */
};

/* all supported flags */

#define VTPM_PROXY_FLAGS_ALL  (VTPM_PROXY_FLAG_TPM2)


static struct workqueue_struct *workqueue;

static void vtpm_proxy_delete_device(struct proxy_dev *proxy_dev);

/*
 * Functions related to 'server side'
 */

/**
 * vtpm_proxy_fops_read - Read TPM commands on 'server side'
 *
 * @filp: file pointer
 * @buf: read buffer
 * @count: number of bytes to read
 * @off: offset
 *
 * Return:
 *      Number of bytes read or negative error code
 */

static ssize_t vtpm_proxy_fops_read(struct file *filp, char __user *buf, size_t count, loff_t *off) { struct proxy_dev *proxy_dev = filp->private_data; size_t len; int sig, rc; sig = wait_event_interruptible(proxy_dev->wq, proxy_dev->req_len != 0 || !(proxy_dev->state & STATE_OPENED_FLAG)); if (sig) return -EINTR; mutex_lock(&proxy_dev->buf_lock); if (!(proxy_dev->state & STATE_OPENED_FLAG)) { mutex_unlock(&proxy_dev->buf_lock); return -EPIPE; } len = proxy_dev->req_len; if (count < len) { mutex_unlock(&proxy_dev->buf_lock); pr_debug("Invalid size in recv: count=%zd, req_len=%zd\n", count, len); return -EIO; } rc = copy_to_user(buf, proxy_dev->buffer, len); memset(proxy_dev->buffer, 0, len); proxy_dev->req_len = 0; if (!rc) proxy_dev->state |= STATE_WAIT_RESPONSE_FLAG; mutex_unlock(&proxy_dev->buf_lock); if (rc) return -EFAULT; return len; }

Contributors

PersonTokensPropCommitsCommitProp
Stefan Berger200100.00%1100.00%
Total200100.00%1100.00%

/** * vtpm_proxy_fops_write - Write TPM responses on 'server side' * * @filp: file pointer * @buf: write buffer * @count: number of bytes to write * @off: offset * * Return: * Number of bytes read or negative error value */
static ssize_t vtpm_proxy_fops_write(struct file *filp, const char __user *buf, size_t count, loff_t *off) { struct proxy_dev *proxy_dev = filp->private_data; mutex_lock(&proxy_dev->buf_lock); if (!(proxy_dev->state & STATE_OPENED_FLAG)) { mutex_unlock(&proxy_dev->buf_lock); return -EPIPE; } if (count > sizeof(proxy_dev->buffer) || !(proxy_dev->state & STATE_WAIT_RESPONSE_FLAG)) { mutex_unlock(&proxy_dev->buf_lock); return -EIO; } proxy_dev->state &= ~STATE_WAIT_RESPONSE_FLAG; proxy_dev->req_len = 0; if (copy_from_user(proxy_dev->buffer, buf, count)) { mutex_unlock(&proxy_dev->buf_lock); return -EFAULT; } proxy_dev->resp_len = count; mutex_unlock(&proxy_dev->buf_lock); wake_up_interruptible(&proxy_dev->wq); return count; }

Contributors

PersonTokensPropCommitsCommitProp
Stefan Berger165100.00%1100.00%
Total165100.00%1100.00%

/* * vtpm_proxy_fops_poll - Poll status on 'server side' * * @filp: file pointer * @wait: poll table * * Return: Poll flags */
static unsigned int vtpm_proxy_fops_poll(struct file *filp, poll_table *wait) { struct proxy_dev *proxy_dev = filp->private_data; unsigned ret; poll_wait(filp, &proxy_dev->wq, wait); ret = POLLOUT; mutex_lock(&proxy_dev->buf_lock); if (proxy_dev->req_len) ret |= POLLIN | POLLRDNORM; if (!(proxy_dev->state & STATE_OPENED_FLAG)) ret |= POLLHUP; mutex_unlock(&proxy_dev->buf_lock); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Stefan Berger90100.00%1100.00%
Total90100.00%1100.00%

/* * vtpm_proxy_fops_open - Open vTPM device on 'server side' * * @filp: file pointer * * Called when setting up the anonymous file descriptor */
static void vtpm_proxy_fops_open(struct file *filp) { struct proxy_dev *proxy_dev = filp->private_data; proxy_dev->state |= STATE_OPENED_FLAG; }

Contributors

PersonTokensPropCommitsCommitProp
Stefan Berger26100.00%1100.00%
Total26100.00%1100.00%

/** * vtpm_proxy_fops_undo_open - counter-part to vtpm_fops_open * Call to undo vtpm_proxy_fops_open * *@proxy_dev: tpm proxy device */
static void vtpm_proxy_fops_undo_open(struct proxy_dev *proxy_dev) { mutex_lock(&proxy_dev->buf_lock); proxy_dev->state &= ~STATE_OPENED_FLAG; mutex_unlock(&proxy_dev->buf_lock); /* no more TPM responses -- wake up anyone waiting for them */ wake_up_interruptible(&proxy_dev->wq); }

Contributors

PersonTokensPropCommitsCommitProp
Stefan Berger43100.00%1100.00%
Total43100.00%1100.00%

/* * vtpm_proxy_fops_release - Close 'server side' * * @inode: inode * @filp: file pointer * Return: * Always returns 0. */
static int vtpm_proxy_fops_release(struct inode *inode, struct file *filp) { struct proxy_dev *proxy_dev = filp->private_data; filp->private_data = NULL; vtpm_proxy_delete_device(proxy_dev); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Stefan Berger39100.00%1100.00%
Total39100.00%1100.00%

static const struct file_operations vtpm_proxy_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .read = vtpm_proxy_fops_read, .write = vtpm_proxy_fops_write, .poll = vtpm_proxy_fops_poll, .release = vtpm_proxy_fops_release, }; /* * Functions invoked by the core TPM driver to send TPM commands to * 'server side' and receive responses from there. */ /* * Called when core TPM driver reads TPM responses from 'server side' * * @chip: tpm chip to use * @buf: receive buffer * @count: bytes to read * Return: * Number of TPM response bytes read, negative error value otherwise */
static int vtpm_proxy_tpm_op_recv(struct tpm_chip *chip, u8 *buf, size_t count) { struct proxy_dev *proxy_dev = dev_get_drvdata(&chip->dev); size_t len; /* process gone ? */ mutex_lock(&proxy_dev->buf_lock); if (!(proxy_dev->state & STATE_OPENED_FLAG)) { mutex_unlock(&proxy_dev->buf_lock); return -EPIPE; } len = proxy_dev->resp_len; if (count < len) { dev_err(&chip->dev, "Invalid size in recv: count=%zd, resp_len=%zd\n", count, len); len = -EIO; goto out; } memcpy(buf, proxy_dev->buffer, len); proxy_dev->resp_len = 0; out: mutex_unlock(&proxy_dev->buf_lock); return len; }

Contributors

PersonTokensPropCommitsCommitProp
Stefan Berger134100.00%1100.00%
Total134100.00%1100.00%

/* * Called when core TPM driver forwards TPM requests to 'server side'. * * @chip: tpm chip to use * @buf: send buffer * @count: bytes to send * * Return: * 0 in case of success, negative error value otherwise. */
static int vtpm_proxy_tpm_op_send(struct tpm_chip *chip, u8 *buf, size_t count) { struct proxy_dev *proxy_dev = dev_get_drvdata(&chip->dev); int rc = 0; if (count > sizeof(proxy_dev->buffer)) { dev_err(&chip->dev, "Invalid size in send: count=%zd, buffer size=%zd\n", count, sizeof(proxy_dev->buffer)); return -EIO; } mutex_lock(&proxy_dev->buf_lock); if (!(proxy_dev->state & STATE_OPENED_FLAG)) { mutex_unlock(&proxy_dev->buf_lock); return -EPIPE; } proxy_dev->resp_len = 0; proxy_dev->req_len = count; memcpy(proxy_dev->buffer, buf, count); proxy_dev->state &= ~STATE_WAIT_RESPONSE_FLAG; mutex_unlock(&proxy_dev->buf_lock); wake_up_interruptible(&proxy_dev->wq); return rc; }

Contributors

PersonTokensPropCommitsCommitProp
Stefan Berger154100.00%1100.00%
Total154100.00%1100.00%


static void vtpm_proxy_tpm_op_cancel(struct tpm_chip *chip) { /* not supported */ }

Contributors

PersonTokensPropCommitsCommitProp
Stefan Berger12100.00%1100.00%
Total12100.00%1100.00%


static u8 vtpm_proxy_tpm_op_status(struct tpm_chip *chip) { struct proxy_dev *proxy_dev = dev_get_drvdata(&chip->dev); if (proxy_dev->resp_len) return VTPM_PROXY_REQ_COMPLETE_FLAG; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Stefan Berger36100.00%1100.00%
Total36100.00%1100.00%


static bool vtpm_proxy_tpm_req_canceled(struct tpm_chip *chip, u8 status) { struct proxy_dev *proxy_dev = dev_get_drvdata(&chip->dev); bool ret; mutex_lock(&proxy_dev->buf_lock); ret = !(proxy_dev->state & STATE_OPENED_FLAG); mutex_unlock(&proxy_dev->buf_lock); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Stefan Berger60100.00%1100.00%
Total60100.00%1100.00%

static const struct tpm_class_ops vtpm_proxy_tpm_ops = { .flags = TPM_OPS_AUTO_STARTUP, .recv = vtpm_proxy_tpm_op_recv, .send = vtpm_proxy_tpm_op_send, .cancel = vtpm_proxy_tpm_op_cancel, .status = vtpm_proxy_tpm_op_status, .req_complete_mask = VTPM_PROXY_REQ_COMPLETE_FLAG, .req_complete_val = VTPM_PROXY_REQ_COMPLETE_FLAG, .req_canceled = vtpm_proxy_tpm_req_canceled, }; /* * Code related to the startup of the TPM 2 and startup of TPM 1.2 + * retrieval of timeouts and durations. */
static void vtpm_proxy_work(struct work_struct *work) { struct proxy_dev *proxy_dev = container_of(work, struct proxy_dev, work); int rc; rc = tpm_chip_register(proxy_dev->chip); if (rc) vtpm_proxy_fops_undo_open(proxy_dev); else proxy_dev->state |= STATE_REGISTERED_FLAG; }

Contributors

PersonTokensPropCommitsCommitProp
Stefan Berger4787.04%150.00%
Jason Gunthorpe712.96%150.00%
Total54100.00%2100.00%

/* * vtpm_proxy_work_stop: make sure the work has finished * * This function is useful when user space closed the fd * while the driver still determines timeouts. */
static void vtpm_proxy_work_stop(struct proxy_dev *proxy_dev) { vtpm_proxy_fops_undo_open(proxy_dev); flush_work(&proxy_dev->work); }

Contributors

PersonTokensPropCommitsCommitProp
Stefan Berger24100.00%1100.00%
Total24100.00%1100.00%

/* * vtpm_proxy_work_start: Schedule the work for TPM 1.2 & 2 initialization */
static inline void vtpm_proxy_work_start(struct proxy_dev *proxy_dev) { queue_work(workqueue, &proxy_dev->work); }

Contributors

PersonTokensPropCommitsCommitProp
Stefan Berger22100.00%1100.00%
Total22100.00%1100.00%

/* * Code related to creation and deletion of device pairs */
static struct proxy_dev *vtpm_proxy_create_proxy_dev(void) { struct proxy_dev *proxy_dev; struct tpm_chip *chip; int err; proxy_dev = kzalloc(sizeof(*proxy_dev), GFP_KERNEL); if (proxy_dev == NULL) return ERR_PTR(-ENOMEM); init_waitqueue_head(&proxy_dev->wq); mutex_init(&proxy_dev->buf_lock); INIT_WORK(&proxy_dev->work, vtpm_proxy_work); chip = tpm_chip_alloc(NULL, &vtpm_proxy_tpm_ops); if (IS_ERR(chip)) { err = PTR_ERR(chip); goto err_proxy_dev_free; } dev_set_drvdata(&chip->dev, proxy_dev); proxy_dev->chip = chip; return proxy_dev; err_proxy_dev_free: kfree(proxy_dev); return ERR_PTR(err); }

Contributors

PersonTokensPropCommitsCommitProp
Stefan Berger136100.00%1100.00%
Total136100.00%1100.00%

/* * Undo what has been done in vtpm_create_proxy_dev */
static inline void vtpm_proxy_delete_proxy_dev(struct proxy_dev *proxy_dev) { put_device(&proxy_dev->chip->dev); /* frees chip */ kfree(proxy_dev); }

Contributors

PersonTokensPropCommitsCommitProp
Stefan Berger28100.00%1100.00%
Total28100.00%1100.00%

/* * Create a /dev/tpm%d and 'server side' file descriptor pair * * Return: * Returns file pointer on success, an error value otherwise */
static struct file *vtpm_proxy_create_device( struct vtpm_proxy_new_dev *vtpm_new_dev) { struct proxy_dev *proxy_dev; int rc, fd; struct file *file; if (vtpm_new_dev->flags & ~VTPM_PROXY_FLAGS_ALL) return ERR_PTR(-EOPNOTSUPP); proxy_dev = vtpm_proxy_create_proxy_dev(); if (IS_ERR(proxy_dev)) return ERR_CAST(proxy_dev); proxy_dev->flags = vtpm_new_dev->flags; /* setup an anonymous file for the server-side */ fd = get_unused_fd_flags(O_RDWR); if (fd < 0) { rc = fd; goto err_delete_proxy_dev; } file = anon_inode_getfile("[vtpms]", &vtpm_proxy_fops, proxy_dev, O_RDWR); if (IS_ERR(file)) { rc = PTR_ERR(file); goto err_put_unused_fd; } /* from now on we can unwind with put_unused_fd() + fput() */ /* simulate an open() on the server side */ vtpm_proxy_fops_open(file); if (proxy_dev->flags & VTPM_PROXY_FLAG_TPM2) proxy_dev->chip->flags |= TPM_CHIP_FLAG_TPM2; vtpm_proxy_work_start(proxy_dev); vtpm_new_dev->fd = fd; vtpm_new_dev->major = MAJOR(proxy_dev->chip->dev.devt); vtpm_new_dev->minor = MINOR(proxy_dev->chip->dev.devt); vtpm_new_dev->tpm_num = proxy_dev->chip->dev_num; return file; err_put_unused_fd: put_unused_fd(fd); err_delete_proxy_dev: vtpm_proxy_delete_proxy_dev(proxy_dev); return ERR_PTR(rc); }

Contributors

PersonTokensPropCommitsCommitProp
Stefan Berger223100.00%1100.00%
Total223100.00%1100.00%

/* * Counter part to vtpm_create_device. */
static void vtpm_proxy_delete_device(struct proxy_dev *proxy_dev) { vtpm_proxy_work_stop(proxy_dev); /* * A client may hold the 'ops' lock, so let it know that the server * side shuts down before we try to grab the 'ops' lock when * unregistering the chip. */ vtpm_proxy_fops_undo_open(proxy_dev); if (proxy_dev->state & STATE_REGISTERED_FLAG) tpm_chip_unregister(proxy_dev->chip); vtpm_proxy_delete_proxy_dev(proxy_dev); }

Contributors

PersonTokensPropCommitsCommitProp
Stefan Berger3480.95%150.00%
Jason Gunthorpe819.05%150.00%
Total42100.00%2100.00%

/* * Code related to the control device /dev/vtpmx */ /** * vtpmx_ioc_new_dev - handler for the %VTPM_PROXY_IOC_NEW_DEV ioctl * @file: /dev/vtpmx * @ioctl: the ioctl number * @arg: pointer to the struct vtpmx_proxy_new_dev * * Creates an anonymous file that is used by the process acting as a TPM to * communicate with the client processes. The function will also add a new TPM * device through which data is proxied to this TPM acting process. The caller * will be provided with a file descriptor to communicate with the clients and * major and minor numbers for the TPM device. */
static long vtpmx_ioc_new_dev(struct file *file, unsigned int ioctl, unsigned long arg) { void __user *argp = (void __user *)arg; struct vtpm_proxy_new_dev __user *vtpm_new_dev_p; struct vtpm_proxy_new_dev vtpm_new_dev; struct file *vtpm_file; if (!capable(CAP_SYS_ADMIN)) return -EPERM; vtpm_new_dev_p = argp; if (copy_from_user(&vtpm_new_dev, vtpm_new_dev_p, sizeof(vtpm_new_dev))) return -EFAULT; vtpm_file = vtpm_proxy_create_device(&vtpm_new_dev); if (IS_ERR(vtpm_file)) return PTR_ERR(vtpm_file); if (copy_to_user(vtpm_new_dev_p, &vtpm_new_dev, sizeof(vtpm_new_dev))) { put_unused_fd(vtpm_new_dev.fd); fput(vtpm_file); return -EFAULT; } fd_install(vtpm_new_dev.fd, vtpm_file); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Stefan Berger13793.20%133.33%
Jarkko Sakkinen106.80%266.67%
Total147100.00%3100.00%

/* * vtpmx_fops_ioctl: ioctl on /dev/vtpmx * * Return: * Returns 0 on success, a negative error code otherwise. */
static long vtpmx_fops_ioctl(struct file *f, unsigned int ioctl, unsigned long arg) { switch (ioctl) { case VTPM_PROXY_IOC_NEW_DEV: return vtpmx_ioc_new_dev(f, ioctl, arg); default: return -ENOIOCTLCMD; } }

Contributors

PersonTokensPropCommitsCommitProp
Jarkko Sakkinen3683.72%150.00%
Stefan Berger716.28%150.00%
Total43100.00%2100.00%

#ifdef CONFIG_COMPAT
static long vtpmx_fops_compat_ioctl(struct file *f, unsigned int ioctl, unsigned long arg) { return vtpmx_fops_ioctl(f, ioctl, (unsigned long)compat_ptr(arg)); }

Contributors

PersonTokensPropCommitsCommitProp
Stefan Berger36100.00%1100.00%
Total36100.00%1100.00%

#endif static const struct file_operations vtpmx_fops = { .owner = THIS_MODULE, .unlocked_ioctl = vtpmx_fops_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = vtpmx_fops_compat_ioctl, #endif .llseek = noop_llseek, }; static struct miscdevice vtpmx_miscdev = { .minor = MISC_DYNAMIC_MINOR, .name = "vtpmx", .fops = &vtpmx_fops, };
static int vtpmx_init(void) { return misc_register(&vtpmx_miscdev); }

Contributors

PersonTokensPropCommitsCommitProp
Stefan Berger15100.00%1100.00%
Total15100.00%1100.00%


static void vtpmx_cleanup(void) { misc_deregister(&vtpmx_miscdev); }

Contributors

PersonTokensPropCommitsCommitProp
Stefan Berger14100.00%1100.00%
Total14100.00%1100.00%


static int __init vtpm_module_init(void) { int rc; rc = vtpmx_init(); if (rc) { pr_err("couldn't create vtpmx device\n"); return rc; } workqueue = create_workqueue("tpm-vtpm"); if (!workqueue) { pr_err("couldn't create workqueue\n"); rc = -ENOMEM; goto err_vtpmx_cleanup; } return 0; err_vtpmx_cleanup: vtpmx_cleanup(); return rc; }

Contributors

PersonTokensPropCommitsCommitProp
Stefan Berger69100.00%1100.00%
Total69100.00%1100.00%


static void __exit vtpm_module_exit(void) { destroy_workqueue(workqueue); vtpmx_cleanup(); }

Contributors

PersonTokensPropCommitsCommitProp
Stefan Berger17100.00%1100.00%
Total17100.00%1100.00%

module_init(vtpm_module_init); module_exit(vtpm_module_exit); MODULE_AUTHOR("Stefan Berger (stefanb@us.ibm.com)"); MODULE_DESCRIPTION("vTPM Driver"); MODULE_VERSION("0.1"); MODULE_LICENSE("GPL");

Overall Contributors

PersonTokensPropCommitsCommitProp
Stefan Berger206296.18%116.67%
Jarkko Sakkinen482.24%233.33%
Jason Gunthorpe241.12%233.33%
Tomas Winkler100.47%116.67%
Total2144100.00%6100.00%
Directory: drivers/char/tpm
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with cregit.