]>
Commit | Line | Data |
---|---|---|
ecb38e2f JB |
1 | /* |
2 | * Copyright (C) 2004 IBM Corporation | |
3 | * Authors: | |
4 | * Leendert van Doorn <leendert@watson.ibm.com> | |
5 | * Dave Safford <safford@watson.ibm.com> | |
6 | * Reiner Sailer <sailer@watson.ibm.com> | |
7 | * Kylene Hall <kjhall@us.ibm.com> | |
8 | * | |
9 | * Copyright (C) 2013 Obsidian Research Corp | |
10 | * Jason Gunthorpe <jgunthorpe@obsidianresearch.com> | |
11 | * | |
12 | * Device file system interface to the TPM | |
13 | * | |
14 | * This program is free software; you can redistribute it and/or | |
15 | * modify it under the terms of the GNU General Public License as | |
16 | * published by the Free Software Foundation, version 2 of the | |
17 | * License. | |
18 | * | |
19 | */ | |
9e1b74a6 | 20 | #include <linux/poll.h> |
ecb38e2f JB |
21 | #include <linux/slab.h> |
22 | #include <linux/uaccess.h> | |
9e1b74a6 | 23 | #include <linux/workqueue.h> |
ecb38e2f JB |
24 | #include "tpm.h" |
25 | #include "tpm-dev.h" | |
26 | ||
9e1b74a6 TS |
27 | static struct workqueue_struct *tpm_dev_wq; |
28 | static DEFINE_MUTEX(tpm_dev_wq_lock); | |
29 | ||
30 | static void tpm_async_work(struct work_struct *work) | |
31 | { | |
32 | struct file_priv *priv = | |
33 | container_of(work, struct file_priv, async_work); | |
34 | ssize_t ret; | |
35 | ||
36 | mutex_lock(&priv->buffer_mutex); | |
37 | priv->command_enqueued = false; | |
38 | ret = tpm_transmit(priv->chip, priv->space, priv->data_buffer, | |
39 | sizeof(priv->data_buffer), 0); | |
40 | ||
41 | tpm_put_ops(priv->chip); | |
42 | if (ret > 0) { | |
9488585b | 43 | priv->response_length = ret; |
9e1b74a6 TS |
44 | mod_timer(&priv->user_read_timer, jiffies + (120 * HZ)); |
45 | } | |
46 | mutex_unlock(&priv->buffer_mutex); | |
47 | wake_up_interruptible(&priv->async_wait); | |
48 | } | |
49 | ||
e99e88a9 | 50 | static void user_reader_timeout(struct timer_list *t) |
ecb38e2f | 51 | { |
e99e88a9 | 52 | struct file_priv *priv = from_timer(priv, t, user_read_timer); |
ecb38e2f JB |
53 | |
54 | pr_warn("TPM user space timeout is deprecated (pid=%d)\n", | |
55 | task_tgid_nr(current)); | |
56 | ||
9e1b74a6 | 57 | schedule_work(&priv->timeout_work); |
ecb38e2f JB |
58 | } |
59 | ||
9e1b74a6 | 60 | static void tpm_timeout_work(struct work_struct *work) |
ecb38e2f | 61 | { |
9e1b74a6 TS |
62 | struct file_priv *priv = container_of(work, struct file_priv, |
63 | timeout_work); | |
ecb38e2f JB |
64 | |
65 | mutex_lock(&priv->buffer_mutex); | |
9488585b TS |
66 | priv->response_read = true; |
67 | priv->response_length = 0; | |
ecb38e2f JB |
68 | memset(priv->data_buffer, 0, sizeof(priv->data_buffer)); |
69 | mutex_unlock(&priv->buffer_mutex); | |
9e1b74a6 | 70 | wake_up_interruptible(&priv->async_wait); |
ecb38e2f JB |
71 | } |
72 | ||
73 | void tpm_common_open(struct file *file, struct tpm_chip *chip, | |
c3d477a7 | 74 | struct file_priv *priv, struct tpm_space *space) |
ecb38e2f JB |
75 | { |
76 | priv->chip = chip; | |
c3d477a7 | 77 | priv->space = space; |
9488585b | 78 | priv->response_read = true; |
c3d477a7 | 79 | |
ecb38e2f | 80 | mutex_init(&priv->buffer_mutex); |
e99e88a9 | 81 | timer_setup(&priv->user_read_timer, user_reader_timeout, 0); |
9e1b74a6 TS |
82 | INIT_WORK(&priv->timeout_work, tpm_timeout_work); |
83 | INIT_WORK(&priv->async_work, tpm_async_work); | |
84 | init_waitqueue_head(&priv->async_wait); | |
ecb38e2f JB |
85 | file->private_data = priv; |
86 | } | |
87 | ||
88 | ssize_t tpm_common_read(struct file *file, char __user *buf, | |
89 | size_t size, loff_t *off) | |
90 | { | |
91 | struct file_priv *priv = file->private_data; | |
3ab2011e | 92 | ssize_t ret_size = 0; |
ecb38e2f JB |
93 | int rc; |
94 | ||
3ab2011e | 95 | mutex_lock(&priv->buffer_mutex); |
ecb38e2f | 96 | |
9488585b TS |
97 | if (priv->response_length) { |
98 | priv->response_read = true; | |
99 | ||
100 | ret_size = min_t(ssize_t, size, priv->response_length); | |
101 | if (!ret_size) { | |
102 | priv->response_length = 0; | |
103 | goto out; | |
9e1b74a6 | 104 | } |
ecb38e2f | 105 | |
9488585b TS |
106 | rc = copy_to_user(buf, priv->data_buffer + *off, ret_size); |
107 | if (rc) { | |
108 | memset(priv->data_buffer, 0, TPM_BUFSIZE); | |
109 | priv->response_length = 0; | |
110 | ret_size = -EFAULT; | |
111 | } else { | |
112 | memset(priv->data_buffer + *off, 0, ret_size); | |
113 | priv->response_length -= ret_size; | |
114 | *off += ret_size; | |
115 | } | |
ecb38e2f JB |
116 | } |
117 | ||
9488585b TS |
118 | out: |
119 | if (!priv->response_length) { | |
120 | *off = 0; | |
121 | del_singleshot_timer_sync(&priv->user_read_timer); | |
122 | flush_work(&priv->timeout_work); | |
123 | } | |
3ab2011e | 124 | mutex_unlock(&priv->buffer_mutex); |
ecb38e2f JB |
125 | return ret_size; |
126 | } | |
127 | ||
128 | ssize_t tpm_common_write(struct file *file, const char __user *buf, | |
c3d477a7 | 129 | size_t size, loff_t *off) |
ecb38e2f JB |
130 | { |
131 | struct file_priv *priv = file->private_data; | |
9e1b74a6 | 132 | int ret = 0; |
ecb38e2f | 133 | |
9e1b74a6 | 134 | if (size > TPM_BUFSIZE) |
3ab2011e TS |
135 | return -E2BIG; |
136 | ||
137 | mutex_lock(&priv->buffer_mutex); | |
138 | ||
ecb38e2f JB |
139 | /* Cannot perform a write until the read has cleared either via |
140 | * tpm_read or a user_read_timer timeout. This also prevents split | |
141 | * buffered writes from blocking here. | |
142 | */ | |
9488585b TS |
143 | if ((!priv->response_read && priv->response_length) || |
144 | priv->command_enqueued) { | |
9e1b74a6 TS |
145 | ret = -EBUSY; |
146 | goto out; | |
3ab2011e | 147 | } |
ecb38e2f | 148 | |
9e1b74a6 TS |
149 | if (copy_from_user(priv->data_buffer, buf, size)) { |
150 | ret = -EFAULT; | |
151 | goto out; | |
ecb38e2f JB |
152 | } |
153 | ||
9e1b74a6 TS |
154 | if (size < 6 || |
155 | size < be32_to_cpu(*((__be32 *)(priv->data_buffer + 2)))) { | |
156 | ret = -EINVAL; | |
157 | goto out; | |
ee70bc1e AS |
158 | } |
159 | ||
ecb38e2f JB |
160 | /* atomic tpm command send and result receive. We only hold the ops |
161 | * lock during this period so that the tpm can be unregistered even if | |
162 | * the char dev is held open. | |
163 | */ | |
164 | if (tpm_try_get_ops(priv->chip)) { | |
9e1b74a6 TS |
165 | ret = -EPIPE; |
166 | goto out; | |
ecb38e2f | 167 | } |
ecb38e2f | 168 | |
9488585b TS |
169 | priv->response_length = 0; |
170 | priv->response_read = false; | |
171 | *off = 0; | |
172 | ||
9e1b74a6 TS |
173 | /* |
174 | * If in nonblocking mode schedule an async job to send | |
175 | * the command return the size. | |
176 | * In case of error the err code will be returned in | |
177 | * the subsequent read call. | |
178 | */ | |
179 | if (file->f_flags & O_NONBLOCK) { | |
180 | priv->command_enqueued = true; | |
181 | queue_work(tpm_dev_wq, &priv->async_work); | |
ecb38e2f | 182 | mutex_unlock(&priv->buffer_mutex); |
9e1b74a6 | 183 | return size; |
ecb38e2f JB |
184 | } |
185 | ||
9e1b74a6 TS |
186 | ret = tpm_transmit(priv->chip, priv->space, priv->data_buffer, |
187 | sizeof(priv->data_buffer), 0); | |
188 | tpm_put_ops(priv->chip); | |
189 | ||
190 | if (ret > 0) { | |
9488585b | 191 | priv->response_length = ret; |
9e1b74a6 TS |
192 | mod_timer(&priv->user_read_timer, jiffies + (120 * HZ)); |
193 | ret = size; | |
194 | } | |
195 | out: | |
ecb38e2f | 196 | mutex_unlock(&priv->buffer_mutex); |
9e1b74a6 TS |
197 | return ret; |
198 | } | |
199 | ||
200 | __poll_t tpm_common_poll(struct file *file, poll_table *wait) | |
201 | { | |
202 | struct file_priv *priv = file->private_data; | |
203 | __poll_t mask = 0; | |
204 | ||
205 | poll_wait(file, &priv->async_wait, wait); | |
ecb38e2f | 206 | |
9488585b | 207 | if (!priv->response_read || priv->response_length) |
9e1b74a6 TS |
208 | mask = EPOLLIN | EPOLLRDNORM; |
209 | else | |
210 | mask = EPOLLOUT | EPOLLWRNORM; | |
ecb38e2f | 211 | |
9e1b74a6 | 212 | return mask; |
ecb38e2f JB |
213 | } |
214 | ||
215 | /* | |
216 | * Called on file close | |
217 | */ | |
218 | void tpm_common_release(struct file *file, struct file_priv *priv) | |
219 | { | |
9e1b74a6 | 220 | flush_work(&priv->async_work); |
ecb38e2f | 221 | del_singleshot_timer_sync(&priv->user_read_timer); |
9e1b74a6 | 222 | flush_work(&priv->timeout_work); |
ecb38e2f | 223 | file->private_data = NULL; |
9488585b | 224 | priv->response_length = 0; |
ecb38e2f | 225 | } |
9e1b74a6 TS |
226 | |
227 | int __init tpm_dev_common_init(void) | |
228 | { | |
229 | tpm_dev_wq = alloc_workqueue("tpm_dev_wq", WQ_MEM_RECLAIM, 0); | |
230 | ||
231 | return !tpm_dev_wq ? -ENOMEM : 0; | |
232 | } | |
233 | ||
234 | void __exit tpm_dev_common_exit(void) | |
235 | { | |
236 | if (tpm_dev_wq) { | |
237 | destroy_workqueue(tpm_dev_wq); | |
238 | tpm_dev_wq = NULL; | |
239 | } | |
240 | } |