]>
Commit | Line | Data |
---|---|---|
745b361e JS |
1 | /* |
2 | * Copyright (C) 2016 Intel Corporation | |
3 | * | |
4 | * Authors: | |
5 | * Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com> | |
6 | * | |
7 | * Maintained by: <tpmdd-devel@lists.sourceforge.net> | |
8 | * | |
9 | * This file contains TPM2 protocol implementations of the commands | |
10 | * used by the kernel internally. | |
11 | * | |
12 | * This program is free software; you can redistribute it and/or | |
13 | * modify it under the terms of the GNU General Public License | |
14 | * as published by the Free Software Foundation; version 2 | |
15 | * of the License. | |
16 | */ | |
17 | ||
18 | #include <linux/gfp.h> | |
19 | #include <asm/unaligned.h> | |
20 | #include "tpm.h" | |
21 | ||
22 | enum tpm2_handle_types { | |
23 | TPM2_HT_HMAC_SESSION = 0x02000000, | |
24 | TPM2_HT_POLICY_SESSION = 0x03000000, | |
25 | TPM2_HT_TRANSIENT = 0x80000000, | |
26 | }; | |
27 | ||
28 | struct tpm2_context { | |
29 | __be64 sequence; | |
30 | __be32 saved_handle; | |
31 | __be32 hierarchy; | |
32 | __be16 blob_size; | |
33 | } __packed; | |
34 | ||
4d57856a JB |
35 | static void tpm2_flush_sessions(struct tpm_chip *chip, struct tpm_space *space) |
36 | { | |
37 | int i; | |
38 | ||
39 | for (i = 0; i < ARRAY_SIZE(space->session_tbl); i++) { | |
40 | if (space->session_tbl[i]) | |
47a6c28b | 41 | tpm2_flush_context(chip, space->session_tbl[i]); |
4d57856a JB |
42 | } |
43 | } | |
44 | ||
745b361e JS |
45 | int tpm2_init_space(struct tpm_space *space) |
46 | { | |
47 | space->context_buf = kzalloc(PAGE_SIZE, GFP_KERNEL); | |
48 | if (!space->context_buf) | |
49 | return -ENOMEM; | |
50 | ||
4d57856a JB |
51 | space->session_buf = kzalloc(PAGE_SIZE, GFP_KERNEL); |
52 | if (space->session_buf == NULL) { | |
53 | kfree(space->context_buf); | |
54 | return -ENOMEM; | |
55 | } | |
56 | ||
745b361e JS |
57 | return 0; |
58 | } | |
59 | ||
4d57856a | 60 | void tpm2_del_space(struct tpm_chip *chip, struct tpm_space *space) |
745b361e | 61 | { |
4d57856a | 62 | mutex_lock(&chip->tpm_mutex); |
47a6c28b | 63 | if (!tpm_chip_start(chip)) { |
a3fbfae8 | 64 | tpm2_flush_sessions(chip, space); |
47a6c28b | 65 | tpm_chip_stop(chip); |
a3fbfae8 | 66 | } |
4d57856a | 67 | mutex_unlock(&chip->tpm_mutex); |
745b361e | 68 | kfree(space->context_buf); |
4d57856a | 69 | kfree(space->session_buf); |
745b361e JS |
70 | } |
71 | ||
72 | static int tpm2_load_context(struct tpm_chip *chip, u8 *buf, | |
73 | unsigned int *offset, u32 *handle) | |
74 | { | |
75 | struct tpm_buf tbuf; | |
76 | struct tpm2_context *ctx; | |
77 | unsigned int body_size; | |
78 | int rc; | |
79 | ||
80 | rc = tpm_buf_init(&tbuf, TPM2_ST_NO_SESSIONS, TPM2_CC_CONTEXT_LOAD); | |
81 | if (rc) | |
82 | return rc; | |
83 | ||
84 | ctx = (struct tpm2_context *)&buf[*offset]; | |
85 | body_size = sizeof(*ctx) + be16_to_cpu(ctx->blob_size); | |
86 | tpm_buf_append(&tbuf, &buf[*offset], body_size); | |
87 | ||
47a6c28b | 88 | rc = tpm_transmit_cmd(chip, &tbuf, 4, NULL); |
745b361e JS |
89 | if (rc < 0) { |
90 | dev_warn(&chip->dev, "%s: failed with a system error %d\n", | |
91 | __func__, rc); | |
92 | tpm_buf_destroy(&tbuf); | |
93 | return -EFAULT; | |
4d57856a JB |
94 | } else if (tpm2_rc_value(rc) == TPM2_RC_HANDLE || |
95 | rc == TPM2_RC_REFERENCE_H0) { | |
96 | /* | |
97 | * TPM_RC_HANDLE means that the session context can't | |
98 | * be loaded because of an internal counter mismatch | |
99 | * that makes the TPM think there might have been a | |
100 | * replay. This might happen if the context was saved | |
101 | * and loaded outside the space. | |
102 | * | |
103 | * TPM_RC_REFERENCE_H0 means the session has been | |
104 | * flushed outside the space | |
105 | */ | |
8c81c247 | 106 | *handle = 0; |
4d57856a | 107 | tpm_buf_destroy(&tbuf); |
8c81c247 | 108 | return -ENOENT; |
745b361e JS |
109 | } else if (rc > 0) { |
110 | dev_warn(&chip->dev, "%s: failed with a TPM error 0x%04X\n", | |
111 | __func__, rc); | |
112 | tpm_buf_destroy(&tbuf); | |
113 | return -EFAULT; | |
114 | } | |
115 | ||
116 | *handle = be32_to_cpup((__be32 *)&tbuf.data[TPM_HEADER_SIZE]); | |
117 | *offset += body_size; | |
118 | ||
119 | tpm_buf_destroy(&tbuf); | |
120 | return 0; | |
121 | } | |
122 | ||
123 | static int tpm2_save_context(struct tpm_chip *chip, u32 handle, u8 *buf, | |
124 | unsigned int buf_size, unsigned int *offset) | |
125 | { | |
126 | struct tpm_buf tbuf; | |
127 | unsigned int body_size; | |
128 | int rc; | |
129 | ||
130 | rc = tpm_buf_init(&tbuf, TPM2_ST_NO_SESSIONS, TPM2_CC_CONTEXT_SAVE); | |
131 | if (rc) | |
132 | return rc; | |
133 | ||
134 | tpm_buf_append_u32(&tbuf, handle); | |
135 | ||
47a6c28b | 136 | rc = tpm_transmit_cmd(chip, &tbuf, 0, NULL); |
745b361e JS |
137 | if (rc < 0) { |
138 | dev_warn(&chip->dev, "%s: failed with a system error %d\n", | |
139 | __func__, rc); | |
140 | tpm_buf_destroy(&tbuf); | |
141 | return -EFAULT; | |
142 | } else if (tpm2_rc_value(rc) == TPM2_RC_REFERENCE_H0) { | |
143 | tpm_buf_destroy(&tbuf); | |
144 | return -ENOENT; | |
145 | } else if (rc) { | |
146 | dev_warn(&chip->dev, "%s: failed with a TPM error 0x%04X\n", | |
147 | __func__, rc); | |
148 | tpm_buf_destroy(&tbuf); | |
149 | return -EFAULT; | |
150 | } | |
151 | ||
152 | body_size = tpm_buf_length(&tbuf) - TPM_HEADER_SIZE; | |
153 | if ((*offset + body_size) > buf_size) { | |
154 | dev_warn(&chip->dev, "%s: out of backing storage\n", __func__); | |
155 | tpm_buf_destroy(&tbuf); | |
156 | return -ENOMEM; | |
157 | } | |
158 | ||
159 | memcpy(&buf[*offset], &tbuf.data[TPM_HEADER_SIZE], body_size); | |
745b361e JS |
160 | *offset += body_size; |
161 | tpm_buf_destroy(&tbuf); | |
162 | return 0; | |
163 | } | |
164 | ||
304ff672 | 165 | void tpm2_flush_space(struct tpm_chip *chip) |
745b361e JS |
166 | { |
167 | struct tpm_space *space = &chip->work_space; | |
168 | int i; | |
169 | ||
170 | for (i = 0; i < ARRAY_SIZE(space->context_tbl); i++) | |
171 | if (space->context_tbl[i] && ~space->context_tbl[i]) | |
47a6c28b | 172 | tpm2_flush_context(chip, space->context_tbl[i]); |
4d57856a JB |
173 | |
174 | tpm2_flush_sessions(chip, space); | |
745b361e JS |
175 | } |
176 | ||
177 | static int tpm2_load_space(struct tpm_chip *chip) | |
178 | { | |
179 | struct tpm_space *space = &chip->work_space; | |
180 | unsigned int offset; | |
181 | int i; | |
182 | int rc; | |
183 | ||
184 | for (i = 0, offset = 0; i < ARRAY_SIZE(space->context_tbl); i++) { | |
185 | if (!space->context_tbl[i]) | |
186 | continue; | |
187 | ||
188 | /* sanity check, should never happen */ | |
189 | if (~space->context_tbl[i]) { | |
190 | dev_err(&chip->dev, "context table is inconsistent"); | |
191 | return -EFAULT; | |
192 | } | |
193 | ||
194 | rc = tpm2_load_context(chip, space->context_buf, &offset, | |
195 | &space->context_tbl[i]); | |
196 | if (rc) | |
197 | return rc; | |
198 | } | |
199 | ||
4d57856a JB |
200 | for (i = 0, offset = 0; i < ARRAY_SIZE(space->session_tbl); i++) { |
201 | u32 handle; | |
202 | ||
203 | if (!space->session_tbl[i]) | |
204 | continue; | |
205 | ||
206 | rc = tpm2_load_context(chip, space->session_buf, | |
207 | &offset, &handle); | |
208 | if (rc == -ENOENT) { | |
209 | /* load failed, just forget session */ | |
210 | space->session_tbl[i] = 0; | |
211 | } else if (rc) { | |
212 | tpm2_flush_space(chip); | |
213 | return rc; | |
214 | } | |
215 | if (handle != space->session_tbl[i]) { | |
216 | dev_warn(&chip->dev, "session restored to wrong handle\n"); | |
217 | tpm2_flush_space(chip); | |
218 | return -EFAULT; | |
219 | } | |
220 | } | |
221 | ||
745b361e JS |
222 | return 0; |
223 | } | |
224 | ||
225 | static bool tpm2_map_to_phandle(struct tpm_space *space, void *handle) | |
226 | { | |
227 | u32 vhandle = be32_to_cpup((__be32 *)handle); | |
228 | u32 phandle; | |
229 | int i; | |
230 | ||
231 | i = 0xFFFFFF - (vhandle & 0xFFFFFF); | |
4d57856a | 232 | if (i >= ARRAY_SIZE(space->context_tbl) || !space->context_tbl[i]) |
745b361e JS |
233 | return false; |
234 | ||
235 | phandle = space->context_tbl[i]; | |
236 | *((__be32 *)handle) = cpu_to_be32(phandle); | |
237 | return true; | |
238 | } | |
239 | ||
240 | static int tpm2_map_command(struct tpm_chip *chip, u32 cc, u8 *cmd) | |
241 | { | |
242 | struct tpm_space *space = &chip->work_space; | |
243 | unsigned int nr_handles; | |
244 | u32 attrs; | |
4557d4be | 245 | __be32 *handle; |
745b361e JS |
246 | int i; |
247 | ||
248 | i = tpm2_find_cc(chip, cc); | |
249 | if (i < 0) | |
250 | return -EINVAL; | |
251 | ||
252 | attrs = chip->cc_attrs_tbl[i]; | |
253 | nr_handles = (attrs >> TPM2_CC_ATTR_CHANDLES) & GENMASK(2, 0); | |
254 | ||
4557d4be | 255 | handle = (__be32 *)&cmd[TPM_HEADER_SIZE]; |
745b361e JS |
256 | for (i = 0; i < nr_handles; i++, handle++) { |
257 | if ((be32_to_cpu(*handle) & 0xFF000000) == TPM2_HT_TRANSIENT) { | |
258 | if (!tpm2_map_to_phandle(space, handle)) | |
259 | return -EINVAL; | |
260 | } | |
261 | } | |
262 | ||
263 | return 0; | |
264 | } | |
265 | ||
c3465a37 JS |
266 | static int tpm_find_and_validate_cc(struct tpm_chip *chip, |
267 | struct tpm_space *space, | |
268 | const void *cmd, size_t len) | |
269 | { | |
270 | const struct tpm_header *header = (const void *)cmd; | |
271 | int i; | |
272 | u32 cc; | |
273 | u32 attrs; | |
274 | unsigned int nr_handles; | |
275 | ||
276 | if (len < TPM_HEADER_SIZE || !chip->nr_commands) | |
277 | return -EINVAL; | |
278 | ||
279 | cc = be32_to_cpu(header->ordinal); | |
280 | ||
281 | i = tpm2_find_cc(chip, cc); | |
282 | if (i < 0) { | |
283 | dev_dbg(&chip->dev, "0x%04X is an invalid command\n", | |
284 | cc); | |
285 | return -EOPNOTSUPP; | |
286 | } | |
287 | ||
288 | attrs = chip->cc_attrs_tbl[i]; | |
289 | nr_handles = | |
290 | 4 * ((attrs >> TPM2_CC_ATTR_CHANDLES) & GENMASK(2, 0)); | |
291 | if (len < TPM_HEADER_SIZE + 4 * nr_handles) | |
292 | goto err_len; | |
293 | ||
294 | return cc; | |
295 | err_len: | |
296 | dev_dbg(&chip->dev, "%s: insufficient command length %zu", __func__, | |
297 | len); | |
298 | return -EINVAL; | |
299 | } | |
300 | ||
301 | int tpm2_prepare_space(struct tpm_chip *chip, struct tpm_space *space, u8 *cmd, | |
302 | size_t cmdsiz) | |
745b361e JS |
303 | { |
304 | int rc; | |
c3465a37 | 305 | int cc; |
745b361e JS |
306 | |
307 | if (!space) | |
308 | return 0; | |
309 | ||
c3465a37 JS |
310 | cc = tpm_find_and_validate_cc(chip, space, cmd, cmdsiz); |
311 | if (cc < 0) | |
312 | return cc; | |
313 | ||
745b361e JS |
314 | memcpy(&chip->work_space.context_tbl, &space->context_tbl, |
315 | sizeof(space->context_tbl)); | |
4d57856a JB |
316 | memcpy(&chip->work_space.session_tbl, &space->session_tbl, |
317 | sizeof(space->session_tbl)); | |
745b361e | 318 | memcpy(chip->work_space.context_buf, space->context_buf, PAGE_SIZE); |
4d57856a | 319 | memcpy(chip->work_space.session_buf, space->session_buf, PAGE_SIZE); |
745b361e JS |
320 | |
321 | rc = tpm2_load_space(chip); | |
322 | if (rc) { | |
323 | tpm2_flush_space(chip); | |
324 | return rc; | |
325 | } | |
326 | ||
327 | rc = tpm2_map_command(chip, cc, cmd); | |
328 | if (rc) { | |
329 | tpm2_flush_space(chip); | |
330 | return rc; | |
331 | } | |
332 | ||
c3465a37 | 333 | chip->last_cc = cc; |
745b361e JS |
334 | return 0; |
335 | } | |
336 | ||
4d57856a JB |
337 | static bool tpm2_add_session(struct tpm_chip *chip, u32 handle) |
338 | { | |
339 | struct tpm_space *space = &chip->work_space; | |
340 | int i; | |
341 | ||
342 | for (i = 0; i < ARRAY_SIZE(space->session_tbl); i++) | |
343 | if (space->session_tbl[i] == 0) | |
344 | break; | |
345 | ||
346 | if (i == ARRAY_SIZE(space->session_tbl)) | |
347 | return false; | |
348 | ||
349 | space->session_tbl[i] = handle; | |
350 | return true; | |
351 | } | |
352 | ||
745b361e JS |
353 | static u32 tpm2_map_to_vhandle(struct tpm_space *space, u32 phandle, bool alloc) |
354 | { | |
355 | int i; | |
356 | ||
357 | for (i = 0; i < ARRAY_SIZE(space->context_tbl); i++) { | |
358 | if (alloc) { | |
359 | if (!space->context_tbl[i]) { | |
360 | space->context_tbl[i] = phandle; | |
361 | break; | |
362 | } | |
363 | } else if (space->context_tbl[i] == phandle) | |
364 | break; | |
365 | } | |
366 | ||
367 | if (i == ARRAY_SIZE(space->context_tbl)) | |
368 | return 0; | |
369 | ||
370 | return TPM2_HT_TRANSIENT | (0xFFFFFF - i); | |
371 | } | |
372 | ||
373 | static int tpm2_map_response_header(struct tpm_chip *chip, u32 cc, u8 *rsp, | |
374 | size_t len) | |
375 | { | |
376 | struct tpm_space *space = &chip->work_space; | |
b34b77a9 | 377 | struct tpm_header *header = (struct tpm_header *)rsp; |
745b361e JS |
378 | u32 phandle; |
379 | u32 phandle_type; | |
380 | u32 vhandle; | |
381 | u32 attrs; | |
382 | int i; | |
383 | ||
384 | if (be32_to_cpu(header->return_code) != TPM2_RC_SUCCESS) | |
385 | return 0; | |
386 | ||
387 | i = tpm2_find_cc(chip, cc); | |
388 | /* sanity check, should never happen */ | |
389 | if (i < 0) | |
390 | return -EFAULT; | |
391 | ||
392 | attrs = chip->cc_attrs_tbl[i]; | |
393 | if (!((attrs >> TPM2_CC_ATTR_RHANDLE) & 1)) | |
394 | return 0; | |
395 | ||
396 | phandle = be32_to_cpup((__be32 *)&rsp[TPM_HEADER_SIZE]); | |
397 | phandle_type = phandle & 0xFF000000; | |
398 | ||
399 | switch (phandle_type) { | |
400 | case TPM2_HT_TRANSIENT: | |
401 | vhandle = tpm2_map_to_vhandle(space, phandle, true); | |
402 | if (!vhandle) | |
403 | goto out_no_slots; | |
404 | ||
405 | *(__be32 *)&rsp[TPM_HEADER_SIZE] = cpu_to_be32(vhandle); | |
406 | break; | |
407 | case TPM2_HT_HMAC_SESSION: | |
408 | case TPM2_HT_POLICY_SESSION: | |
4d57856a JB |
409 | if (!tpm2_add_session(chip, phandle)) |
410 | goto out_no_slots; | |
745b361e JS |
411 | break; |
412 | default: | |
413 | dev_err(&chip->dev, "%s: unknown handle 0x%08X\n", | |
414 | __func__, phandle); | |
415 | break; | |
c22780ff | 416 | } |
745b361e JS |
417 | |
418 | return 0; | |
419 | out_no_slots: | |
47a6c28b | 420 | tpm2_flush_context(chip, phandle); |
745b361e JS |
421 | dev_warn(&chip->dev, "%s: out of slots for 0x%08X\n", __func__, |
422 | phandle); | |
423 | return -ENOMEM; | |
424 | } | |
425 | ||
426 | struct tpm2_cap_handles { | |
427 | u8 more_data; | |
428 | __be32 capability; | |
429 | __be32 count; | |
430 | __be32 handles[]; | |
431 | } __packed; | |
432 | ||
433 | static int tpm2_map_response_body(struct tpm_chip *chip, u32 cc, u8 *rsp, | |
434 | size_t len) | |
435 | { | |
436 | struct tpm_space *space = &chip->work_space; | |
b34b77a9 | 437 | struct tpm_header *header = (struct tpm_header *)rsp; |
745b361e JS |
438 | struct tpm2_cap_handles *data; |
439 | u32 phandle; | |
440 | u32 phandle_type; | |
441 | u32 vhandle; | |
442 | int i; | |
443 | int j; | |
444 | ||
445 | if (cc != TPM2_CC_GET_CAPABILITY || | |
446 | be32_to_cpu(header->return_code) != TPM2_RC_SUCCESS) { | |
447 | return 0; | |
448 | } | |
449 | ||
450 | if (len < TPM_HEADER_SIZE + 9) | |
451 | return -EFAULT; | |
452 | ||
453 | data = (void *)&rsp[TPM_HEADER_SIZE]; | |
454 | if (be32_to_cpu(data->capability) != TPM2_CAP_HANDLES) | |
455 | return 0; | |
456 | ||
457 | if (len != TPM_HEADER_SIZE + 9 + 4 * be32_to_cpu(data->count)) | |
458 | return -EFAULT; | |
459 | ||
460 | for (i = 0, j = 0; i < be32_to_cpu(data->count); i++) { | |
461 | phandle = be32_to_cpup((__be32 *)&data->handles[i]); | |
462 | phandle_type = phandle & 0xFF000000; | |
463 | ||
464 | switch (phandle_type) { | |
465 | case TPM2_HT_TRANSIENT: | |
466 | vhandle = tpm2_map_to_vhandle(space, phandle, false); | |
467 | if (!vhandle) | |
468 | break; | |
469 | ||
470 | data->handles[j] = cpu_to_be32(vhandle); | |
471 | j++; | |
472 | break; | |
4d57856a JB |
473 | |
474 | default: | |
745b361e JS |
475 | data->handles[j] = cpu_to_be32(phandle); |
476 | j++; | |
477 | break; | |
745b361e JS |
478 | } |
479 | ||
480 | } | |
481 | ||
482 | header->length = cpu_to_be32(TPM_HEADER_SIZE + 9 + 4 * j); | |
483 | data->count = cpu_to_be32(j); | |
484 | return 0; | |
485 | } | |
486 | ||
487 | static int tpm2_save_space(struct tpm_chip *chip) | |
488 | { | |
489 | struct tpm_space *space = &chip->work_space; | |
490 | unsigned int offset; | |
491 | int i; | |
492 | int rc; | |
493 | ||
494 | for (i = 0, offset = 0; i < ARRAY_SIZE(space->context_tbl); i++) { | |
495 | if (!(space->context_tbl[i] && ~space->context_tbl[i])) | |
496 | continue; | |
497 | ||
498 | rc = tpm2_save_context(chip, space->context_tbl[i], | |
499 | space->context_buf, PAGE_SIZE, | |
500 | &offset); | |
501 | if (rc == -ENOENT) { | |
502 | space->context_tbl[i] = 0; | |
503 | continue; | |
504 | } else if (rc) | |
505 | return rc; | |
506 | ||
47a6c28b | 507 | tpm2_flush_context(chip, space->context_tbl[i]); |
745b361e JS |
508 | space->context_tbl[i] = ~0; |
509 | } | |
510 | ||
4d57856a JB |
511 | for (i = 0, offset = 0; i < ARRAY_SIZE(space->session_tbl); i++) { |
512 | if (!space->session_tbl[i]) | |
513 | continue; | |
514 | ||
515 | rc = tpm2_save_context(chip, space->session_tbl[i], | |
516 | space->session_buf, PAGE_SIZE, | |
517 | &offset); | |
518 | ||
519 | if (rc == -ENOENT) { | |
520 | /* handle error saving session, just forget it */ | |
521 | space->session_tbl[i] = 0; | |
522 | } else if (rc < 0) { | |
523 | tpm2_flush_space(chip); | |
524 | return rc; | |
525 | } | |
526 | } | |
527 | ||
745b361e JS |
528 | return 0; |
529 | } | |
530 | ||
531 | int tpm2_commit_space(struct tpm_chip *chip, struct tpm_space *space, | |
c3465a37 | 532 | void *buf, size_t *bufsiz) |
745b361e | 533 | { |
b34b77a9 | 534 | struct tpm_header *header = buf; |
745b361e JS |
535 | int rc; |
536 | ||
537 | if (!space) | |
538 | return 0; | |
539 | ||
c3465a37 | 540 | rc = tpm2_map_response_header(chip, chip->last_cc, buf, *bufsiz); |
745b361e JS |
541 | if (rc) { |
542 | tpm2_flush_space(chip); | |
aff0c20b | 543 | goto out; |
745b361e JS |
544 | } |
545 | ||
c3465a37 | 546 | rc = tpm2_map_response_body(chip, chip->last_cc, buf, *bufsiz); |
745b361e JS |
547 | if (rc) { |
548 | tpm2_flush_space(chip); | |
aff0c20b | 549 | goto out; |
745b361e JS |
550 | } |
551 | ||
552 | rc = tpm2_save_space(chip); | |
553 | if (rc) { | |
554 | tpm2_flush_space(chip); | |
aff0c20b | 555 | goto out; |
745b361e JS |
556 | } |
557 | ||
558 | *bufsiz = be32_to_cpu(header->length); | |
559 | ||
560 | memcpy(&space->context_tbl, &chip->work_space.context_tbl, | |
561 | sizeof(space->context_tbl)); | |
4d57856a JB |
562 | memcpy(&space->session_tbl, &chip->work_space.session_tbl, |
563 | sizeof(space->session_tbl)); | |
745b361e | 564 | memcpy(space->context_buf, chip->work_space.context_buf, PAGE_SIZE); |
4d57856a | 565 | memcpy(space->session_buf, chip->work_space.session_buf, PAGE_SIZE); |
745b361e JS |
566 | |
567 | return 0; | |
aff0c20b JS |
568 | out: |
569 | dev_err(&chip->dev, "%s: error %d\n", __func__, rc); | |
570 | return rc; | |
745b361e | 571 | } |