]>
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 | ||
c4df71d4 JS |
30 | static ssize_t tpm_dev_transmit(struct tpm_chip *chip, struct tpm_space *space, |
31 | u8 *buf, size_t bufsiz) | |
32 | { | |
29b47ce9 JS |
33 | struct tpm_header *header = (void *)buf; |
34 | ssize_t ret, len; | |
c4df71d4 | 35 | |
29b47ce9 JS |
36 | ret = tpm2_prepare_space(chip, space, buf, bufsiz); |
37 | /* If the command is not implemented by the TPM, synthesize a | |
38 | * response with a TPM2_RC_COMMAND_CODE return for user-space. | |
39 | */ | |
40 | if (ret == -EOPNOTSUPP) { | |
41 | header->length = cpu_to_be32(sizeof(*header)); | |
42 | header->tag = cpu_to_be16(TPM2_ST_NO_SESSIONS); | |
43 | header->return_code = cpu_to_be32(TPM2_RC_COMMAND_CODE | | |
44 | TSS2_RESMGR_TPM_RC_LAYER); | |
45 | ret = sizeof(*header); | |
46 | } | |
47 | if (ret) | |
2f257402 | 48 | goto out_rc; |
29b47ce9 | 49 | |
47a6c28b | 50 | len = tpm_transmit(chip, buf, bufsiz); |
29b47ce9 JS |
51 | if (len < 0) |
52 | ret = len; | |
53 | ||
54 | if (!ret) | |
55 | ret = tpm2_commit_space(chip, space, buf, &len); | |
56 | ||
2f257402 | 57 | out_rc: |
29b47ce9 | 58 | return ret ? ret : len; |
c4df71d4 JS |
59 | } |
60 | ||
61 | static void tpm_dev_async_work(struct work_struct *work) | |
9e1b74a6 TS |
62 | { |
63 | struct file_priv *priv = | |
64 | container_of(work, struct file_priv, async_work); | |
65 | ssize_t ret; | |
66 | ||
67 | mutex_lock(&priv->buffer_mutex); | |
68 | priv->command_enqueued = false; | |
c4df71d4 JS |
69 | ret = tpm_dev_transmit(priv->chip, priv->space, priv->data_buffer, |
70 | sizeof(priv->data_buffer)); | |
9e1b74a6 TS |
71 | tpm_put_ops(priv->chip); |
72 | if (ret > 0) { | |
9488585b | 73 | priv->response_length = ret; |
9e1b74a6 TS |
74 | mod_timer(&priv->user_read_timer, jiffies + (120 * HZ)); |
75 | } | |
76 | mutex_unlock(&priv->buffer_mutex); | |
77 | wake_up_interruptible(&priv->async_wait); | |
78 | } | |
79 | ||
e99e88a9 | 80 | static void user_reader_timeout(struct timer_list *t) |
ecb38e2f | 81 | { |
e99e88a9 | 82 | struct file_priv *priv = from_timer(priv, t, user_read_timer); |
ecb38e2f JB |
83 | |
84 | pr_warn("TPM user space timeout is deprecated (pid=%d)\n", | |
85 | task_tgid_nr(current)); | |
86 | ||
9e1b74a6 | 87 | schedule_work(&priv->timeout_work); |
ecb38e2f JB |
88 | } |
89 | ||
9e1b74a6 | 90 | static void tpm_timeout_work(struct work_struct *work) |
ecb38e2f | 91 | { |
9e1b74a6 TS |
92 | struct file_priv *priv = container_of(work, struct file_priv, |
93 | timeout_work); | |
ecb38e2f JB |
94 | |
95 | mutex_lock(&priv->buffer_mutex); | |
9488585b TS |
96 | priv->response_read = true; |
97 | priv->response_length = 0; | |
ecb38e2f JB |
98 | memset(priv->data_buffer, 0, sizeof(priv->data_buffer)); |
99 | mutex_unlock(&priv->buffer_mutex); | |
9e1b74a6 | 100 | wake_up_interruptible(&priv->async_wait); |
ecb38e2f JB |
101 | } |
102 | ||
103 | void tpm_common_open(struct file *file, struct tpm_chip *chip, | |
c3d477a7 | 104 | struct file_priv *priv, struct tpm_space *space) |
ecb38e2f JB |
105 | { |
106 | priv->chip = chip; | |
c3d477a7 | 107 | priv->space = space; |
9488585b | 108 | priv->response_read = true; |
c3d477a7 | 109 | |
ecb38e2f | 110 | mutex_init(&priv->buffer_mutex); |
e99e88a9 | 111 | timer_setup(&priv->user_read_timer, user_reader_timeout, 0); |
9e1b74a6 | 112 | INIT_WORK(&priv->timeout_work, tpm_timeout_work); |
c4df71d4 | 113 | INIT_WORK(&priv->async_work, tpm_dev_async_work); |
9e1b74a6 | 114 | init_waitqueue_head(&priv->async_wait); |
ecb38e2f JB |
115 | file->private_data = priv; |
116 | } | |
117 | ||
118 | ssize_t tpm_common_read(struct file *file, char __user *buf, | |
119 | size_t size, loff_t *off) | |
120 | { | |
121 | struct file_priv *priv = file->private_data; | |
3ab2011e | 122 | ssize_t ret_size = 0; |
ecb38e2f JB |
123 | int rc; |
124 | ||
3ab2011e | 125 | mutex_lock(&priv->buffer_mutex); |
ecb38e2f | 126 | |
9488585b TS |
127 | if (priv->response_length) { |
128 | priv->response_read = true; | |
129 | ||
130 | ret_size = min_t(ssize_t, size, priv->response_length); | |
131 | if (!ret_size) { | |
132 | priv->response_length = 0; | |
133 | goto out; | |
9e1b74a6 | 134 | } |
ecb38e2f | 135 | |
9488585b TS |
136 | rc = copy_to_user(buf, priv->data_buffer + *off, ret_size); |
137 | if (rc) { | |
138 | memset(priv->data_buffer, 0, TPM_BUFSIZE); | |
139 | priv->response_length = 0; | |
140 | ret_size = -EFAULT; | |
141 | } else { | |
142 | memset(priv->data_buffer + *off, 0, ret_size); | |
143 | priv->response_length -= ret_size; | |
144 | *off += ret_size; | |
145 | } | |
ecb38e2f JB |
146 | } |
147 | ||
9488585b TS |
148 | out: |
149 | if (!priv->response_length) { | |
150 | *off = 0; | |
151 | del_singleshot_timer_sync(&priv->user_read_timer); | |
152 | flush_work(&priv->timeout_work); | |
153 | } | |
3ab2011e | 154 | mutex_unlock(&priv->buffer_mutex); |
ecb38e2f JB |
155 | return ret_size; |
156 | } | |
157 | ||
158 | ssize_t tpm_common_write(struct file *file, const char __user *buf, | |
c3d477a7 | 159 | size_t size, loff_t *off) |
ecb38e2f JB |
160 | { |
161 | struct file_priv *priv = file->private_data; | |
9e1b74a6 | 162 | int ret = 0; |
ecb38e2f | 163 | |
9e1b74a6 | 164 | if (size > TPM_BUFSIZE) |
3ab2011e TS |
165 | return -E2BIG; |
166 | ||
167 | mutex_lock(&priv->buffer_mutex); | |
168 | ||
ecb38e2f JB |
169 | /* Cannot perform a write until the read has cleared either via |
170 | * tpm_read or a user_read_timer timeout. This also prevents split | |
171 | * buffered writes from blocking here. | |
172 | */ | |
9488585b TS |
173 | if ((!priv->response_read && priv->response_length) || |
174 | priv->command_enqueued) { | |
9e1b74a6 TS |
175 | ret = -EBUSY; |
176 | goto out; | |
3ab2011e | 177 | } |
ecb38e2f | 178 | |
9e1b74a6 TS |
179 | if (copy_from_user(priv->data_buffer, buf, size)) { |
180 | ret = -EFAULT; | |
181 | goto out; | |
ecb38e2f JB |
182 | } |
183 | ||
9e1b74a6 TS |
184 | if (size < 6 || |
185 | size < be32_to_cpu(*((__be32 *)(priv->data_buffer + 2)))) { | |
186 | ret = -EINVAL; | |
187 | goto out; | |
ee70bc1e AS |
188 | } |
189 | ||
ecb38e2f JB |
190 | /* atomic tpm command send and result receive. We only hold the ops |
191 | * lock during this period so that the tpm can be unregistered even if | |
192 | * the char dev is held open. | |
193 | */ | |
194 | if (tpm_try_get_ops(priv->chip)) { | |
9e1b74a6 TS |
195 | ret = -EPIPE; |
196 | goto out; | |
ecb38e2f | 197 | } |
ecb38e2f | 198 | |
9488585b TS |
199 | priv->response_length = 0; |
200 | priv->response_read = false; | |
201 | *off = 0; | |
202 | ||
9e1b74a6 TS |
203 | /* |
204 | * If in nonblocking mode schedule an async job to send | |
205 | * the command return the size. | |
206 | * In case of error the err code will be returned in | |
207 | * the subsequent read call. | |
208 | */ | |
209 | if (file->f_flags & O_NONBLOCK) { | |
210 | priv->command_enqueued = true; | |
211 | queue_work(tpm_dev_wq, &priv->async_work); | |
ecb38e2f | 212 | mutex_unlock(&priv->buffer_mutex); |
9e1b74a6 | 213 | return size; |
ecb38e2f JB |
214 | } |
215 | ||
c4df71d4 JS |
216 | ret = tpm_dev_transmit(priv->chip, priv->space, priv->data_buffer, |
217 | sizeof(priv->data_buffer)); | |
9e1b74a6 TS |
218 | tpm_put_ops(priv->chip); |
219 | ||
220 | if (ret > 0) { | |
9488585b | 221 | priv->response_length = ret; |
9e1b74a6 TS |
222 | mod_timer(&priv->user_read_timer, jiffies + (120 * HZ)); |
223 | ret = size; | |
224 | } | |
225 | out: | |
ecb38e2f | 226 | mutex_unlock(&priv->buffer_mutex); |
9e1b74a6 TS |
227 | return ret; |
228 | } | |
229 | ||
230 | __poll_t tpm_common_poll(struct file *file, poll_table *wait) | |
231 | { | |
232 | struct file_priv *priv = file->private_data; | |
233 | __poll_t mask = 0; | |
234 | ||
235 | poll_wait(file, &priv->async_wait, wait); | |
71106292 | 236 | mutex_lock(&priv->buffer_mutex); |
ecb38e2f | 237 | |
71106292 TS |
238 | /* |
239 | * The response_length indicates if there is still response | |
240 | * (or part of it) to be consumed. Partial reads decrease it | |
241 | * by the number of bytes read, and write resets it the zero. | |
242 | */ | |
243 | if (priv->response_length) | |
9e1b74a6 TS |
244 | mask = EPOLLIN | EPOLLRDNORM; |
245 | else | |
246 | mask = EPOLLOUT | EPOLLWRNORM; | |
ecb38e2f | 247 | |
71106292 | 248 | mutex_unlock(&priv->buffer_mutex); |
9e1b74a6 | 249 | return mask; |
ecb38e2f JB |
250 | } |
251 | ||
252 | /* | |
253 | * Called on file close | |
254 | */ | |
255 | void tpm_common_release(struct file *file, struct file_priv *priv) | |
256 | { | |
9e1b74a6 | 257 | flush_work(&priv->async_work); |
ecb38e2f | 258 | del_singleshot_timer_sync(&priv->user_read_timer); |
9e1b74a6 | 259 | flush_work(&priv->timeout_work); |
ecb38e2f | 260 | file->private_data = NULL; |
9488585b | 261 | priv->response_length = 0; |
ecb38e2f | 262 | } |
9e1b74a6 TS |
263 | |
264 | int __init tpm_dev_common_init(void) | |
265 | { | |
266 | tpm_dev_wq = alloc_workqueue("tpm_dev_wq", WQ_MEM_RECLAIM, 0); | |
267 | ||
268 | return !tpm_dev_wq ? -ENOMEM : 0; | |
269 | } | |
270 | ||
271 | void __exit tpm_dev_common_exit(void) | |
272 | { | |
273 | if (tpm_dev_wq) { | |
274 | destroy_workqueue(tpm_dev_wq); | |
275 | tpm_dev_wq = NULL; | |
276 | } | |
277 | } |