]>
Commit | Line | Data |
---|---|---|
ab841160 OW |
1 | /* |
2 | * | |
3 | * Intel Management Engine Interface (Intel MEI) Linux driver | |
733ba91c | 4 | * Copyright (c) 2003-2012, Intel Corporation. |
ab841160 OW |
5 | * |
6 | * This program is free software; you can redistribute it and/or modify it | |
7 | * under the terms and conditions of the GNU General Public License, | |
8 | * version 2, as published by the Free Software Foundation. | |
9 | * | |
10 | * This program is distributed in the hope it will be useful, but WITHOUT | |
11 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
12 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
13 | * more details. | |
14 | * | |
15 | */ | |
16 | ||
ab841160 | 17 | #include <linux/pci.h> |
ab841160 | 18 | #include <linux/sched.h> |
9ca9050b TW |
19 | #include <linux/wait.h> |
20 | #include <linux/delay.h> | |
04bb139a | 21 | #include <linux/pm_runtime.h> |
ab841160 | 22 | |
4f3afe1d | 23 | #include <linux/mei.h> |
47a73801 TW |
24 | |
25 | #include "mei_dev.h" | |
0edb23fc | 26 | #include "hbm.h" |
90e0b5f1 TW |
27 | #include "client.h" |
28 | ||
29 | /** | |
30 | * mei_me_cl_by_uuid - locate index of me client | |
31 | * | |
32 | * @dev: mei device | |
a27a76d3 AU |
33 | * |
34 | * Locking: called under "dev->device_lock" lock | |
35 | * | |
d320832f | 36 | * returns me client or NULL if not found |
90e0b5f1 | 37 | */ |
d320832f TW |
38 | struct mei_me_client *mei_me_cl_by_uuid(const struct mei_device *dev, |
39 | const uuid_le *uuid) | |
90e0b5f1 | 40 | { |
a27a76d3 | 41 | int i; |
90e0b5f1 TW |
42 | |
43 | for (i = 0; i < dev->me_clients_num; ++i) | |
44 | if (uuid_le_cmp(*uuid, | |
a27a76d3 | 45 | dev->me_clients[i].props.protocol_name) == 0) |
d320832f | 46 | return &dev->me_clients[i]; |
90e0b5f1 | 47 | |
d320832f | 48 | return NULL; |
90e0b5f1 TW |
49 | } |
50 | ||
51 | ||
52 | /** | |
53 | * mei_me_cl_by_id return index to me_clients for client_id | |
54 | * | |
55 | * @dev: the device structure | |
56 | * @client_id: me client id | |
57 | * | |
58 | * Locking: called under "dev->device_lock" lock | |
59 | * | |
d320832f | 60 | * returns me client or NULL if not found |
90e0b5f1 TW |
61 | */ |
62 | ||
d320832f | 63 | struct mei_me_client *mei_me_cl_by_id(struct mei_device *dev, u8 client_id) |
90e0b5f1 TW |
64 | { |
65 | int i; | |
a27a76d3 | 66 | |
90e0b5f1 TW |
67 | for (i = 0; i < dev->me_clients_num; i++) |
68 | if (dev->me_clients[i].client_id == client_id) | |
d320832f | 69 | return &dev->me_clients[i]; |
90e0b5f1 | 70 | |
d320832f | 71 | return NULL; |
90e0b5f1 | 72 | } |
ab841160 | 73 | |
9ca9050b TW |
74 | |
75 | /** | |
cc99ecfd | 76 | * mei_cl_cmp_id - tells if the clients are the same |
9ca9050b | 77 | * |
cc99ecfd TW |
78 | * @cl1: host client 1 |
79 | * @cl2: host client 2 | |
80 | * | |
81 | * returns true - if the clients has same host and me ids | |
82 | * false - otherwise | |
83 | */ | |
84 | static inline bool mei_cl_cmp_id(const struct mei_cl *cl1, | |
85 | const struct mei_cl *cl2) | |
86 | { | |
87 | return cl1 && cl2 && | |
88 | (cl1->host_client_id == cl2->host_client_id) && | |
89 | (cl1->me_client_id == cl2->me_client_id); | |
90 | } | |
91 | ||
92 | /** | |
93 | * mei_io_list_flush - removes cbs belonging to cl. | |
94 | * | |
95 | * @list: an instance of our list structure | |
96 | * @cl: host client, can be NULL for flushing the whole list | |
97 | * @free: whether to free the cbs | |
9ca9050b | 98 | */ |
cc99ecfd TW |
99 | static void __mei_io_list_flush(struct mei_cl_cb *list, |
100 | struct mei_cl *cl, bool free) | |
9ca9050b TW |
101 | { |
102 | struct mei_cl_cb *cb; | |
103 | struct mei_cl_cb *next; | |
104 | ||
cc99ecfd | 105 | /* enable removing everything if no cl is specified */ |
9ca9050b | 106 | list_for_each_entry_safe(cb, next, &list->list, list) { |
cc99ecfd | 107 | if (!cl || (cb->cl && mei_cl_cmp_id(cl, cb->cl))) { |
9ca9050b | 108 | list_del(&cb->list); |
cc99ecfd TW |
109 | if (free) |
110 | mei_io_cb_free(cb); | |
111 | } | |
9ca9050b TW |
112 | } |
113 | } | |
114 | ||
cc99ecfd TW |
115 | /** |
116 | * mei_io_list_flush - removes list entry belonging to cl. | |
117 | * | |
118 | * @list: An instance of our list structure | |
119 | * @cl: host client | |
120 | */ | |
5456796b | 121 | void mei_io_list_flush(struct mei_cl_cb *list, struct mei_cl *cl) |
cc99ecfd TW |
122 | { |
123 | __mei_io_list_flush(list, cl, false); | |
124 | } | |
125 | ||
126 | ||
127 | /** | |
128 | * mei_io_list_free - removes cb belonging to cl and free them | |
129 | * | |
130 | * @list: An instance of our list structure | |
131 | * @cl: host client | |
132 | */ | |
133 | static inline void mei_io_list_free(struct mei_cl_cb *list, struct mei_cl *cl) | |
134 | { | |
135 | __mei_io_list_flush(list, cl, true); | |
136 | } | |
137 | ||
601a1efa TW |
138 | /** |
139 | * mei_io_cb_free - free mei_cb_private related memory | |
140 | * | |
141 | * @cb: mei callback struct | |
142 | */ | |
143 | void mei_io_cb_free(struct mei_cl_cb *cb) | |
144 | { | |
145 | if (cb == NULL) | |
146 | return; | |
147 | ||
148 | kfree(cb->request_buffer.data); | |
149 | kfree(cb->response_buffer.data); | |
150 | kfree(cb); | |
151 | } | |
9ca9050b | 152 | |
664df38b TW |
153 | /** |
154 | * mei_io_cb_init - allocate and initialize io callback | |
155 | * | |
156 | * @cl - mei client | |
393b148f | 157 | * @fp: pointer to file structure |
664df38b TW |
158 | * |
159 | * returns mei_cl_cb pointer or NULL; | |
160 | */ | |
161 | struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, struct file *fp) | |
162 | { | |
163 | struct mei_cl_cb *cb; | |
164 | ||
165 | cb = kzalloc(sizeof(struct mei_cl_cb), GFP_KERNEL); | |
166 | if (!cb) | |
167 | return NULL; | |
168 | ||
169 | mei_io_list_init(cb); | |
170 | ||
171 | cb->file_object = fp; | |
db3ed431 | 172 | cb->cl = cl; |
664df38b TW |
173 | cb->buf_idx = 0; |
174 | return cb; | |
175 | } | |
176 | ||
664df38b TW |
177 | /** |
178 | * mei_io_cb_alloc_req_buf - allocate request buffer | |
179 | * | |
393b148f MI |
180 | * @cb: io callback structure |
181 | * @length: size of the buffer | |
664df38b TW |
182 | * |
183 | * returns 0 on success | |
184 | * -EINVAL if cb is NULL | |
185 | * -ENOMEM if allocation failed | |
186 | */ | |
187 | int mei_io_cb_alloc_req_buf(struct mei_cl_cb *cb, size_t length) | |
188 | { | |
189 | if (!cb) | |
190 | return -EINVAL; | |
191 | ||
192 | if (length == 0) | |
193 | return 0; | |
194 | ||
195 | cb->request_buffer.data = kmalloc(length, GFP_KERNEL); | |
196 | if (!cb->request_buffer.data) | |
197 | return -ENOMEM; | |
198 | cb->request_buffer.size = length; | |
199 | return 0; | |
200 | } | |
201 | /** | |
83ce0741 | 202 | * mei_io_cb_alloc_resp_buf - allocate response buffer |
664df38b | 203 | * |
393b148f MI |
204 | * @cb: io callback structure |
205 | * @length: size of the buffer | |
664df38b TW |
206 | * |
207 | * returns 0 on success | |
208 | * -EINVAL if cb is NULL | |
209 | * -ENOMEM if allocation failed | |
210 | */ | |
211 | int mei_io_cb_alloc_resp_buf(struct mei_cl_cb *cb, size_t length) | |
212 | { | |
213 | if (!cb) | |
214 | return -EINVAL; | |
215 | ||
216 | if (length == 0) | |
217 | return 0; | |
218 | ||
219 | cb->response_buffer.data = kmalloc(length, GFP_KERNEL); | |
220 | if (!cb->response_buffer.data) | |
221 | return -ENOMEM; | |
222 | cb->response_buffer.size = length; | |
223 | return 0; | |
224 | } | |
225 | ||
601a1efa | 226 | |
9ca9050b TW |
227 | |
228 | /** | |
229 | * mei_cl_flush_queues - flushes queue lists belonging to cl. | |
230 | * | |
9ca9050b TW |
231 | * @cl: host client |
232 | */ | |
233 | int mei_cl_flush_queues(struct mei_cl *cl) | |
234 | { | |
c0abffbd AU |
235 | struct mei_device *dev; |
236 | ||
90e0b5f1 | 237 | if (WARN_ON(!cl || !cl->dev)) |
9ca9050b TW |
238 | return -EINVAL; |
239 | ||
c0abffbd AU |
240 | dev = cl->dev; |
241 | ||
242 | cl_dbg(dev, cl, "remove list entry belonging to cl\n"); | |
9ca9050b | 243 | mei_io_list_flush(&cl->dev->read_list, cl); |
cc99ecfd TW |
244 | mei_io_list_free(&cl->dev->write_list, cl); |
245 | mei_io_list_free(&cl->dev->write_waiting_list, cl); | |
9ca9050b TW |
246 | mei_io_list_flush(&cl->dev->ctrl_wr_list, cl); |
247 | mei_io_list_flush(&cl->dev->ctrl_rd_list, cl); | |
248 | mei_io_list_flush(&cl->dev->amthif_cmd_list, cl); | |
249 | mei_io_list_flush(&cl->dev->amthif_rd_complete_list, cl); | |
250 | return 0; | |
251 | } | |
252 | ||
ab841160 | 253 | |
9ca9050b | 254 | /** |
83ce0741 | 255 | * mei_cl_init - initializes cl. |
9ca9050b TW |
256 | * |
257 | * @cl: host client to be initialized | |
258 | * @dev: mei device | |
259 | */ | |
260 | void mei_cl_init(struct mei_cl *cl, struct mei_device *dev) | |
261 | { | |
262 | memset(cl, 0, sizeof(struct mei_cl)); | |
263 | init_waitqueue_head(&cl->wait); | |
264 | init_waitqueue_head(&cl->rx_wait); | |
265 | init_waitqueue_head(&cl->tx_wait); | |
266 | INIT_LIST_HEAD(&cl->link); | |
a7b71bc0 | 267 | INIT_LIST_HEAD(&cl->device_link); |
9ca9050b TW |
268 | cl->reading_state = MEI_IDLE; |
269 | cl->writing_state = MEI_IDLE; | |
270 | cl->dev = dev; | |
271 | } | |
272 | ||
273 | /** | |
274 | * mei_cl_allocate - allocates cl structure and sets it up. | |
275 | * | |
276 | * @dev: mei device | |
277 | * returns The allocated file or NULL on failure | |
278 | */ | |
279 | struct mei_cl *mei_cl_allocate(struct mei_device *dev) | |
280 | { | |
281 | struct mei_cl *cl; | |
282 | ||
283 | cl = kmalloc(sizeof(struct mei_cl), GFP_KERNEL); | |
284 | if (!cl) | |
285 | return NULL; | |
286 | ||
287 | mei_cl_init(cl, dev); | |
288 | ||
289 | return cl; | |
290 | } | |
291 | ||
90e0b5f1 TW |
292 | /** |
293 | * mei_cl_find_read_cb - find this cl's callback in the read list | |
294 | * | |
393b148f MI |
295 | * @cl: host client |
296 | * | |
90e0b5f1 TW |
297 | * returns cb on success, NULL on error |
298 | */ | |
299 | struct mei_cl_cb *mei_cl_find_read_cb(struct mei_cl *cl) | |
300 | { | |
301 | struct mei_device *dev = cl->dev; | |
31f88f57 | 302 | struct mei_cl_cb *cb; |
90e0b5f1 | 303 | |
31f88f57 | 304 | list_for_each_entry(cb, &dev->read_list.list, list) |
90e0b5f1 TW |
305 | if (mei_cl_cmp_id(cl, cb->cl)) |
306 | return cb; | |
307 | return NULL; | |
308 | } | |
309 | ||
83ce0741 | 310 | /** mei_cl_link: allocate host id in the host map |
9ca9050b | 311 | * |
781d0d89 | 312 | * @cl - host client |
83ce0741 | 313 | * @id - fixed host id or -1 for generic one |
393b148f | 314 | * |
781d0d89 | 315 | * returns 0 on success |
9ca9050b TW |
316 | * -EINVAL on incorrect values |
317 | * -ENONET if client not found | |
318 | */ | |
781d0d89 | 319 | int mei_cl_link(struct mei_cl *cl, int id) |
9ca9050b | 320 | { |
90e0b5f1 | 321 | struct mei_device *dev; |
22f96a0e | 322 | long open_handle_count; |
9ca9050b | 323 | |
781d0d89 | 324 | if (WARN_ON(!cl || !cl->dev)) |
9ca9050b TW |
325 | return -EINVAL; |
326 | ||
90e0b5f1 TW |
327 | dev = cl->dev; |
328 | ||
83ce0741 | 329 | /* If Id is not assigned get one*/ |
781d0d89 TW |
330 | if (id == MEI_HOST_CLIENT_ID_ANY) |
331 | id = find_first_zero_bit(dev->host_clients_map, | |
332 | MEI_CLIENTS_MAX); | |
9ca9050b | 333 | |
781d0d89 | 334 | if (id >= MEI_CLIENTS_MAX) { |
83ce0741 | 335 | dev_err(&dev->pdev->dev, "id exceeded %d", MEI_CLIENTS_MAX); |
e036cc57 TW |
336 | return -EMFILE; |
337 | } | |
338 | ||
22f96a0e TW |
339 | open_handle_count = dev->open_handle_count + dev->iamthif_open_count; |
340 | if (open_handle_count >= MEI_MAX_OPEN_HANDLE_COUNT) { | |
83ce0741 | 341 | dev_err(&dev->pdev->dev, "open_handle_count exceeded %d", |
e036cc57 TW |
342 | MEI_MAX_OPEN_HANDLE_COUNT); |
343 | return -EMFILE; | |
9ca9050b TW |
344 | } |
345 | ||
781d0d89 TW |
346 | dev->open_handle_count++; |
347 | ||
348 | cl->host_client_id = id; | |
349 | list_add_tail(&cl->link, &dev->file_list); | |
350 | ||
351 | set_bit(id, dev->host_clients_map); | |
352 | ||
353 | cl->state = MEI_FILE_INITIALIZING; | |
354 | ||
c0abffbd | 355 | cl_dbg(dev, cl, "link cl\n"); |
781d0d89 | 356 | return 0; |
9ca9050b | 357 | } |
781d0d89 | 358 | |
9ca9050b | 359 | /** |
90e0b5f1 | 360 | * mei_cl_unlink - remove me_cl from the list |
9ca9050b | 361 | * |
393b148f | 362 | * @cl: host client |
9ca9050b | 363 | */ |
90e0b5f1 | 364 | int mei_cl_unlink(struct mei_cl *cl) |
9ca9050b | 365 | { |
90e0b5f1 | 366 | struct mei_device *dev; |
90e0b5f1 | 367 | |
781d0d89 TW |
368 | /* don't shout on error exit path */ |
369 | if (!cl) | |
370 | return 0; | |
371 | ||
8e9a4a9a TW |
372 | /* wd and amthif might not be initialized */ |
373 | if (!cl->dev) | |
374 | return 0; | |
90e0b5f1 TW |
375 | |
376 | dev = cl->dev; | |
377 | ||
a14c44d8 TW |
378 | cl_dbg(dev, cl, "unlink client"); |
379 | ||
22f96a0e TW |
380 | if (dev->open_handle_count > 0) |
381 | dev->open_handle_count--; | |
382 | ||
383 | /* never clear the 0 bit */ | |
384 | if (cl->host_client_id) | |
385 | clear_bit(cl->host_client_id, dev->host_clients_map); | |
386 | ||
387 | list_del_init(&cl->link); | |
388 | ||
389 | cl->state = MEI_FILE_INITIALIZING; | |
390 | ||
90e0b5f1 | 391 | return 0; |
9ca9050b TW |
392 | } |
393 | ||
394 | ||
395 | void mei_host_client_init(struct work_struct *work) | |
396 | { | |
397 | struct mei_device *dev = container_of(work, | |
398 | struct mei_device, init_work); | |
399 | struct mei_client_properties *client_props; | |
400 | int i; | |
401 | ||
402 | mutex_lock(&dev->device_lock); | |
403 | ||
9ca9050b TW |
404 | for (i = 0; i < dev->me_clients_num; i++) { |
405 | client_props = &dev->me_clients[i].props; | |
406 | ||
1a1aca42 | 407 | if (!uuid_le_cmp(client_props->protocol_name, mei_amthif_guid)) |
9ca9050b TW |
408 | mei_amthif_host_init(dev); |
409 | else if (!uuid_le_cmp(client_props->protocol_name, mei_wd_guid)) | |
410 | mei_wd_host_init(dev); | |
59fcd7c6 SO |
411 | else if (!uuid_le_cmp(client_props->protocol_name, mei_nfc_guid)) |
412 | mei_nfc_host_init(dev); | |
413 | ||
9ca9050b TW |
414 | } |
415 | ||
416 | dev->dev_state = MEI_DEV_ENABLED; | |
6adb8efb | 417 | dev->reset_count = 0; |
9ca9050b TW |
418 | |
419 | mutex_unlock(&dev->device_lock); | |
04bb139a TW |
420 | |
421 | pm_runtime_mark_last_busy(&dev->pdev->dev); | |
422 | dev_dbg(&dev->pdev->dev, "rpm: autosuspend\n"); | |
423 | pm_runtime_autosuspend(&dev->pdev->dev); | |
9ca9050b TW |
424 | } |
425 | ||
6aae48ff TW |
426 | /** |
427 | * mei_hbuf_acquire: try to acquire host buffer | |
428 | * | |
429 | * @dev: the device structure | |
430 | * returns true if host buffer was acquired | |
431 | */ | |
432 | bool mei_hbuf_acquire(struct mei_device *dev) | |
433 | { | |
04bb139a TW |
434 | if (mei_pg_state(dev) == MEI_PG_ON || |
435 | dev->pg_event == MEI_PG_EVENT_WAIT) { | |
436 | dev_dbg(&dev->pdev->dev, "device is in pg\n"); | |
437 | return false; | |
438 | } | |
439 | ||
6aae48ff TW |
440 | if (!dev->hbuf_is_ready) { |
441 | dev_dbg(&dev->pdev->dev, "hbuf is not ready\n"); | |
442 | return false; | |
443 | } | |
444 | ||
445 | dev->hbuf_is_ready = false; | |
446 | ||
447 | return true; | |
448 | } | |
9ca9050b TW |
449 | |
450 | /** | |
83ce0741 | 451 | * mei_cl_disconnect - disconnect host client from the me one |
9ca9050b | 452 | * |
90e0b5f1 | 453 | * @cl: host client |
9ca9050b TW |
454 | * |
455 | * Locking: called under "dev->device_lock" lock | |
456 | * | |
457 | * returns 0 on success, <0 on failure. | |
458 | */ | |
90e0b5f1 | 459 | int mei_cl_disconnect(struct mei_cl *cl) |
9ca9050b | 460 | { |
90e0b5f1 | 461 | struct mei_device *dev; |
9ca9050b | 462 | struct mei_cl_cb *cb; |
fe2f17eb | 463 | int rets; |
9ca9050b | 464 | |
90e0b5f1 | 465 | if (WARN_ON(!cl || !cl->dev)) |
9ca9050b TW |
466 | return -ENODEV; |
467 | ||
90e0b5f1 TW |
468 | dev = cl->dev; |
469 | ||
c0abffbd AU |
470 | cl_dbg(dev, cl, "disconnecting"); |
471 | ||
9ca9050b TW |
472 | if (cl->state != MEI_FILE_DISCONNECTING) |
473 | return 0; | |
474 | ||
04bb139a TW |
475 | rets = pm_runtime_get(&dev->pdev->dev); |
476 | if (rets < 0 && rets != -EINPROGRESS) { | |
477 | pm_runtime_put_noidle(&dev->pdev->dev); | |
478 | cl_err(dev, cl, "rpm: get failed %d\n", rets); | |
479 | return rets; | |
480 | } | |
481 | ||
9ca9050b | 482 | cb = mei_io_cb_init(cl, NULL); |
04bb139a TW |
483 | if (!cb) { |
484 | rets = -ENOMEM; | |
485 | goto free; | |
486 | } | |
9ca9050b TW |
487 | |
488 | cb->fop_type = MEI_FOP_CLOSE; | |
6aae48ff | 489 | if (mei_hbuf_acquire(dev)) { |
9ca9050b TW |
490 | if (mei_hbm_cl_disconnect_req(dev, cl)) { |
491 | rets = -ENODEV; | |
c0abffbd | 492 | cl_err(dev, cl, "failed to disconnect.\n"); |
9ca9050b TW |
493 | goto free; |
494 | } | |
22b987a3 | 495 | cl->timer_count = MEI_CONNECT_TIMEOUT; |
9ca9050b TW |
496 | mdelay(10); /* Wait for hardware disconnection ready */ |
497 | list_add_tail(&cb->list, &dev->ctrl_rd_list.list); | |
498 | } else { | |
c0abffbd | 499 | cl_dbg(dev, cl, "add disconnect cb to control write list\n"); |
9ca9050b TW |
500 | list_add_tail(&cb->list, &dev->ctrl_wr_list.list); |
501 | ||
502 | } | |
503 | mutex_unlock(&dev->device_lock); | |
504 | ||
fe2f17eb | 505 | wait_event_timeout(dev->wait_recvd_msg, |
9ca9050b TW |
506 | MEI_FILE_DISCONNECTED == cl->state, |
507 | mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT)); | |
508 | ||
509 | mutex_lock(&dev->device_lock); | |
fe2f17eb | 510 | |
9ca9050b TW |
511 | if (MEI_FILE_DISCONNECTED == cl->state) { |
512 | rets = 0; | |
c0abffbd | 513 | cl_dbg(dev, cl, "successfully disconnected from FW client.\n"); |
9ca9050b | 514 | } else { |
fe2f17eb AU |
515 | cl_dbg(dev, cl, "timeout on disconnect from FW client.\n"); |
516 | rets = -ETIME; | |
9ca9050b TW |
517 | } |
518 | ||
519 | mei_io_list_flush(&dev->ctrl_rd_list, cl); | |
520 | mei_io_list_flush(&dev->ctrl_wr_list, cl); | |
521 | free: | |
04bb139a TW |
522 | cl_dbg(dev, cl, "rpm: autosuspend\n"); |
523 | pm_runtime_mark_last_busy(&dev->pdev->dev); | |
524 | pm_runtime_put_autosuspend(&dev->pdev->dev); | |
525 | ||
9ca9050b TW |
526 | mei_io_cb_free(cb); |
527 | return rets; | |
528 | } | |
529 | ||
530 | ||
531 | /** | |
90e0b5f1 TW |
532 | * mei_cl_is_other_connecting - checks if other |
533 | * client with the same me client id is connecting | |
9ca9050b | 534 | * |
9ca9050b TW |
535 | * @cl: private data of the file object |
536 | * | |
83ce0741 | 537 | * returns true if other client is connected, false - otherwise. |
9ca9050b | 538 | */ |
90e0b5f1 | 539 | bool mei_cl_is_other_connecting(struct mei_cl *cl) |
9ca9050b | 540 | { |
90e0b5f1 | 541 | struct mei_device *dev; |
31f88f57 | 542 | struct mei_cl *ocl; /* the other client */ |
9ca9050b | 543 | |
90e0b5f1 TW |
544 | if (WARN_ON(!cl || !cl->dev)) |
545 | return false; | |
546 | ||
547 | dev = cl->dev; | |
548 | ||
31f88f57 TW |
549 | list_for_each_entry(ocl, &dev->file_list, link) { |
550 | if (ocl->state == MEI_FILE_CONNECTING && | |
551 | ocl != cl && | |
552 | cl->me_client_id == ocl->me_client_id) | |
90e0b5f1 | 553 | return true; |
9ca9050b TW |
554 | |
555 | } | |
90e0b5f1 TW |
556 | |
557 | return false; | |
9ca9050b TW |
558 | } |
559 | ||
9f81abda | 560 | /** |
83ce0741 | 561 | * mei_cl_connect - connect host client to the me one |
9f81abda TW |
562 | * |
563 | * @cl: host client | |
564 | * | |
565 | * Locking: called under "dev->device_lock" lock | |
566 | * | |
567 | * returns 0 on success, <0 on failure. | |
568 | */ | |
569 | int mei_cl_connect(struct mei_cl *cl, struct file *file) | |
570 | { | |
571 | struct mei_device *dev; | |
572 | struct mei_cl_cb *cb; | |
9f81abda TW |
573 | int rets; |
574 | ||
575 | if (WARN_ON(!cl || !cl->dev)) | |
576 | return -ENODEV; | |
577 | ||
578 | dev = cl->dev; | |
579 | ||
04bb139a TW |
580 | rets = pm_runtime_get(&dev->pdev->dev); |
581 | if (rets < 0 && rets != -EINPROGRESS) { | |
582 | pm_runtime_put_noidle(&dev->pdev->dev); | |
583 | cl_err(dev, cl, "rpm: get failed %d\n", rets); | |
584 | return rets; | |
585 | } | |
586 | ||
9f81abda TW |
587 | cb = mei_io_cb_init(cl, file); |
588 | if (!cb) { | |
589 | rets = -ENOMEM; | |
590 | goto out; | |
591 | } | |
592 | ||
02a7eecc | 593 | cb->fop_type = MEI_FOP_CONNECT; |
9f81abda | 594 | |
6aae48ff TW |
595 | /* run hbuf acquire last so we don't have to undo */ |
596 | if (!mei_cl_is_other_connecting(cl) && mei_hbuf_acquire(dev)) { | |
e4d8270e | 597 | cl->state = MEI_FILE_CONNECTING; |
9f81abda TW |
598 | if (mei_hbm_cl_connect_req(dev, cl)) { |
599 | rets = -ENODEV; | |
600 | goto out; | |
601 | } | |
602 | cl->timer_count = MEI_CONNECT_TIMEOUT; | |
603 | list_add_tail(&cb->list, &dev->ctrl_rd_list.list); | |
604 | } else { | |
73ab4232 | 605 | cl->state = MEI_FILE_INITIALIZING; |
9f81abda TW |
606 | list_add_tail(&cb->list, &dev->ctrl_wr_list.list); |
607 | } | |
608 | ||
609 | mutex_unlock(&dev->device_lock); | |
285e2996 AU |
610 | wait_event_timeout(dev->wait_recvd_msg, |
611 | (cl->state == MEI_FILE_CONNECTED || | |
612 | cl->state == MEI_FILE_DISCONNECTED), | |
613 | mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT)); | |
9f81abda TW |
614 | mutex_lock(&dev->device_lock); |
615 | ||
616 | if (cl->state != MEI_FILE_CONNECTED) { | |
3e37ebb7 | 617 | cl->state = MEI_FILE_DISCONNECTED; |
285e2996 AU |
618 | /* something went really wrong */ |
619 | if (!cl->status) | |
620 | cl->status = -EFAULT; | |
9f81abda TW |
621 | |
622 | mei_io_list_flush(&dev->ctrl_rd_list, cl); | |
623 | mei_io_list_flush(&dev->ctrl_wr_list, cl); | |
9f81abda TW |
624 | } |
625 | ||
626 | rets = cl->status; | |
627 | ||
628 | out: | |
04bb139a TW |
629 | cl_dbg(dev, cl, "rpm: autosuspend\n"); |
630 | pm_runtime_mark_last_busy(&dev->pdev->dev); | |
631 | pm_runtime_put_autosuspend(&dev->pdev->dev); | |
632 | ||
9f81abda TW |
633 | mei_io_cb_free(cb); |
634 | return rets; | |
635 | } | |
636 | ||
9ca9050b | 637 | /** |
90e0b5f1 | 638 | * mei_cl_flow_ctrl_creds - checks flow_control credits for cl. |
9ca9050b | 639 | * |
9ca9050b TW |
640 | * @cl: private data of the file object |
641 | * | |
642 | * returns 1 if mei_flow_ctrl_creds >0, 0 - otherwise. | |
643 | * -ENOENT if mei_cl is not present | |
644 | * -EINVAL if single_recv_buf == 0 | |
645 | */ | |
90e0b5f1 | 646 | int mei_cl_flow_ctrl_creds(struct mei_cl *cl) |
9ca9050b | 647 | { |
90e0b5f1 | 648 | struct mei_device *dev; |
12d00665 | 649 | struct mei_me_client *me_cl; |
9ca9050b | 650 | |
90e0b5f1 TW |
651 | if (WARN_ON(!cl || !cl->dev)) |
652 | return -EINVAL; | |
653 | ||
654 | dev = cl->dev; | |
655 | ||
9ca9050b TW |
656 | if (!dev->me_clients_num) |
657 | return 0; | |
658 | ||
659 | if (cl->mei_flow_ctrl_creds > 0) | |
660 | return 1; | |
661 | ||
d320832f TW |
662 | me_cl = mei_me_cl_by_id(dev, cl->me_client_id); |
663 | if (!me_cl) { | |
12d00665 | 664 | cl_err(dev, cl, "no such me client %d\n", cl->me_client_id); |
d320832f | 665 | return -ENOENT; |
9ca9050b | 666 | } |
12d00665 | 667 | |
12d00665 AU |
668 | if (me_cl->mei_flow_ctrl_creds) { |
669 | if (WARN_ON(me_cl->props.single_recv_buf == 0)) | |
670 | return -EINVAL; | |
671 | return 1; | |
672 | } | |
673 | return 0; | |
9ca9050b TW |
674 | } |
675 | ||
676 | /** | |
90e0b5f1 | 677 | * mei_cl_flow_ctrl_reduce - reduces flow_control. |
9ca9050b | 678 | * |
9ca9050b | 679 | * @cl: private data of the file object |
393b148f | 680 | * |
9ca9050b TW |
681 | * @returns |
682 | * 0 on success | |
683 | * -ENOENT when me client is not found | |
684 | * -EINVAL when ctrl credits are <= 0 | |
685 | */ | |
90e0b5f1 | 686 | int mei_cl_flow_ctrl_reduce(struct mei_cl *cl) |
9ca9050b | 687 | { |
90e0b5f1 | 688 | struct mei_device *dev; |
12d00665 | 689 | struct mei_me_client *me_cl; |
9ca9050b | 690 | |
90e0b5f1 TW |
691 | if (WARN_ON(!cl || !cl->dev)) |
692 | return -EINVAL; | |
693 | ||
694 | dev = cl->dev; | |
695 | ||
d320832f TW |
696 | me_cl = mei_me_cl_by_id(dev, cl->me_client_id); |
697 | if (!me_cl) { | |
12d00665 | 698 | cl_err(dev, cl, "no such me client %d\n", cl->me_client_id); |
d320832f | 699 | return -ENOENT; |
12d00665 | 700 | } |
9ca9050b | 701 | |
d320832f | 702 | if (me_cl->props.single_recv_buf) { |
12d00665 AU |
703 | if (WARN_ON(me_cl->mei_flow_ctrl_creds <= 0)) |
704 | return -EINVAL; | |
705 | me_cl->mei_flow_ctrl_creds--; | |
706 | } else { | |
707 | if (WARN_ON(cl->mei_flow_ctrl_creds <= 0)) | |
708 | return -EINVAL; | |
709 | cl->mei_flow_ctrl_creds--; | |
9ca9050b | 710 | } |
12d00665 | 711 | return 0; |
9ca9050b TW |
712 | } |
713 | ||
ab841160 | 714 | /** |
393b148f | 715 | * mei_cl_read_start - the start read client message function. |
ab841160 | 716 | * |
90e0b5f1 | 717 | * @cl: host client |
ab841160 OW |
718 | * |
719 | * returns 0 on success, <0 on failure. | |
720 | */ | |
fcb136e1 | 721 | int mei_cl_read_start(struct mei_cl *cl, size_t length) |
ab841160 | 722 | { |
90e0b5f1 | 723 | struct mei_device *dev; |
ab841160 | 724 | struct mei_cl_cb *cb; |
d320832f | 725 | struct mei_me_client *me_cl; |
664df38b | 726 | int rets; |
ab841160 | 727 | |
90e0b5f1 TW |
728 | if (WARN_ON(!cl || !cl->dev)) |
729 | return -ENODEV; | |
730 | ||
731 | dev = cl->dev; | |
732 | ||
b950ac1d | 733 | if (!mei_cl_is_connected(cl)) |
ab841160 OW |
734 | return -ENODEV; |
735 | ||
d91aaed3 | 736 | if (cl->read_cb) { |
c0abffbd | 737 | cl_dbg(dev, cl, "read is pending.\n"); |
ab841160 OW |
738 | return -EBUSY; |
739 | } | |
d320832f TW |
740 | me_cl = mei_me_cl_by_id(dev, cl->me_client_id); |
741 | if (!me_cl) { | |
c0abffbd | 742 | cl_err(dev, cl, "no such me client %d\n", cl->me_client_id); |
7ca96aa2 | 743 | return -ENOTTY; |
664df38b | 744 | } |
ab841160 | 745 | |
04bb139a TW |
746 | rets = pm_runtime_get(&dev->pdev->dev); |
747 | if (rets < 0 && rets != -EINPROGRESS) { | |
748 | pm_runtime_put_noidle(&dev->pdev->dev); | |
749 | cl_err(dev, cl, "rpm: get failed %d\n", rets); | |
750 | return rets; | |
751 | } | |
752 | ||
664df38b | 753 | cb = mei_io_cb_init(cl, NULL); |
04bb139a TW |
754 | if (!cb) { |
755 | rets = -ENOMEM; | |
756 | goto out; | |
757 | } | |
ab841160 | 758 | |
fcb136e1 | 759 | /* always allocate at least client max message */ |
d320832f | 760 | length = max_t(size_t, length, me_cl->props.max_msg_length); |
fcb136e1 | 761 | rets = mei_io_cb_alloc_resp_buf(cb, length); |
664df38b | 762 | if (rets) |
04bb139a | 763 | goto out; |
ab841160 | 764 | |
4b8960b4 | 765 | cb->fop_type = MEI_FOP_READ; |
6aae48ff | 766 | if (mei_hbuf_acquire(dev)) { |
86113500 AU |
767 | rets = mei_hbm_cl_flow_control_req(dev, cl); |
768 | if (rets < 0) | |
04bb139a | 769 | goto out; |
04bb139a | 770 | |
fb601adb | 771 | list_add_tail(&cb->list, &dev->read_list.list); |
ab841160 | 772 | } else { |
fb601adb | 773 | list_add_tail(&cb->list, &dev->ctrl_wr_list.list); |
ab841160 | 774 | } |
accb884b CB |
775 | |
776 | cl->read_cb = cb; | |
777 | ||
04bb139a TW |
778 | out: |
779 | cl_dbg(dev, cl, "rpm: autosuspend\n"); | |
780 | pm_runtime_mark_last_busy(&dev->pdev->dev); | |
781 | pm_runtime_put_autosuspend(&dev->pdev->dev); | |
782 | ||
783 | if (rets) | |
784 | mei_io_cb_free(cb); | |
785 | ||
ab841160 OW |
786 | return rets; |
787 | } | |
788 | ||
21767546 | 789 | /** |
9d098192 | 790 | * mei_cl_irq_write - write a message to device |
21767546 TW |
791 | * from the interrupt thread context |
792 | * | |
793 | * @cl: client | |
794 | * @cb: callback block. | |
21767546 TW |
795 | * @cmpl_list: complete list. |
796 | * | |
797 | * returns 0, OK; otherwise error. | |
798 | */ | |
9d098192 TW |
799 | int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, |
800 | struct mei_cl_cb *cmpl_list) | |
21767546 | 801 | { |
136698e5 TW |
802 | struct mei_device *dev; |
803 | struct mei_msg_data *buf; | |
21767546 | 804 | struct mei_msg_hdr mei_hdr; |
136698e5 TW |
805 | size_t len; |
806 | u32 msg_slots; | |
9d098192 | 807 | int slots; |
2ebf8c94 | 808 | int rets; |
21767546 | 809 | |
136698e5 TW |
810 | if (WARN_ON(!cl || !cl->dev)) |
811 | return -ENODEV; | |
812 | ||
813 | dev = cl->dev; | |
814 | ||
815 | buf = &cb->request_buffer; | |
816 | ||
817 | rets = mei_cl_flow_ctrl_creds(cl); | |
818 | if (rets < 0) | |
819 | return rets; | |
820 | ||
821 | if (rets == 0) { | |
04bb139a | 822 | cl_dbg(dev, cl, "No flow control credentials: not sending.\n"); |
136698e5 TW |
823 | return 0; |
824 | } | |
825 | ||
9d098192 | 826 | slots = mei_hbuf_empty_slots(dev); |
136698e5 TW |
827 | len = buf->size - cb->buf_idx; |
828 | msg_slots = mei_data2slots(len); | |
829 | ||
21767546 TW |
830 | mei_hdr.host_addr = cl->host_client_id; |
831 | mei_hdr.me_addr = cl->me_client_id; | |
832 | mei_hdr.reserved = 0; | |
479327fc | 833 | mei_hdr.internal = cb->internal; |
21767546 | 834 | |
9d098192 | 835 | if (slots >= msg_slots) { |
21767546 TW |
836 | mei_hdr.length = len; |
837 | mei_hdr.msg_complete = 1; | |
838 | /* Split the message only if we can write the whole host buffer */ | |
9d098192 TW |
839 | } else if (slots == dev->hbuf_depth) { |
840 | msg_slots = slots; | |
841 | len = (slots * sizeof(u32)) - sizeof(struct mei_msg_hdr); | |
21767546 TW |
842 | mei_hdr.length = len; |
843 | mei_hdr.msg_complete = 0; | |
844 | } else { | |
845 | /* wait for next time the host buffer is empty */ | |
846 | return 0; | |
847 | } | |
848 | ||
c0abffbd | 849 | cl_dbg(dev, cl, "buf: size = %d idx = %lu\n", |
21767546 | 850 | cb->request_buffer.size, cb->buf_idx); |
21767546 | 851 | |
136698e5 | 852 | rets = mei_write_message(dev, &mei_hdr, buf->data + cb->buf_idx); |
2ebf8c94 TW |
853 | if (rets) { |
854 | cl->status = rets; | |
21767546 | 855 | list_move_tail(&cb->list, &cmpl_list->list); |
2ebf8c94 | 856 | return rets; |
21767546 TW |
857 | } |
858 | ||
859 | cl->status = 0; | |
4dfaa9f7 | 860 | cl->writing_state = MEI_WRITING; |
21767546 | 861 | cb->buf_idx += mei_hdr.length; |
4dfaa9f7 | 862 | |
21767546 TW |
863 | if (mei_hdr.msg_complete) { |
864 | if (mei_cl_flow_ctrl_reduce(cl)) | |
2ebf8c94 | 865 | return -EIO; |
21767546 TW |
866 | list_move_tail(&cb->list, &dev->write_waiting_list.list); |
867 | } | |
868 | ||
869 | return 0; | |
870 | } | |
871 | ||
4234a6de TW |
872 | /** |
873 | * mei_cl_write - submit a write cb to mei device | |
874 | assumes device_lock is locked | |
875 | * | |
876 | * @cl: host client | |
877 | * @cl: write callback with filled data | |
878 | * | |
83ce0741 | 879 | * returns number of bytes sent on success, <0 on failure. |
4234a6de TW |
880 | */ |
881 | int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking) | |
882 | { | |
883 | struct mei_device *dev; | |
884 | struct mei_msg_data *buf; | |
885 | struct mei_msg_hdr mei_hdr; | |
886 | int rets; | |
887 | ||
888 | ||
889 | if (WARN_ON(!cl || !cl->dev)) | |
890 | return -ENODEV; | |
891 | ||
892 | if (WARN_ON(!cb)) | |
893 | return -EINVAL; | |
894 | ||
895 | dev = cl->dev; | |
896 | ||
897 | ||
898 | buf = &cb->request_buffer; | |
899 | ||
c0abffbd | 900 | cl_dbg(dev, cl, "mei_cl_write %d\n", buf->size); |
4234a6de | 901 | |
04bb139a TW |
902 | rets = pm_runtime_get(&dev->pdev->dev); |
903 | if (rets < 0 && rets != -EINPROGRESS) { | |
904 | pm_runtime_put_noidle(&dev->pdev->dev); | |
905 | cl_err(dev, cl, "rpm: get failed %d\n", rets); | |
906 | return rets; | |
907 | } | |
4234a6de TW |
908 | |
909 | cb->fop_type = MEI_FOP_WRITE; | |
6aae48ff TW |
910 | cb->buf_idx = 0; |
911 | cl->writing_state = MEI_IDLE; | |
912 | ||
913 | mei_hdr.host_addr = cl->host_client_id; | |
914 | mei_hdr.me_addr = cl->me_client_id; | |
915 | mei_hdr.reserved = 0; | |
916 | mei_hdr.msg_complete = 0; | |
917 | mei_hdr.internal = cb->internal; | |
4234a6de TW |
918 | |
919 | rets = mei_cl_flow_ctrl_creds(cl); | |
920 | if (rets < 0) | |
921 | goto err; | |
922 | ||
6aae48ff TW |
923 | if (rets == 0) { |
924 | cl_dbg(dev, cl, "No flow control credentials: not sending.\n"); | |
925 | rets = buf->size; | |
926 | goto out; | |
927 | } | |
928 | if (!mei_hbuf_acquire(dev)) { | |
929 | cl_dbg(dev, cl, "Cannot acquire the host buffer: not sending.\n"); | |
4234a6de TW |
930 | rets = buf->size; |
931 | goto out; | |
932 | } | |
4234a6de TW |
933 | |
934 | /* Check for a maximum length */ | |
935 | if (buf->size > mei_hbuf_max_len(dev)) { | |
936 | mei_hdr.length = mei_hbuf_max_len(dev); | |
937 | mei_hdr.msg_complete = 0; | |
938 | } else { | |
939 | mei_hdr.length = buf->size; | |
940 | mei_hdr.msg_complete = 1; | |
941 | } | |
942 | ||
2ebf8c94 TW |
943 | rets = mei_write_message(dev, &mei_hdr, buf->data); |
944 | if (rets) | |
4234a6de | 945 | goto err; |
4234a6de TW |
946 | |
947 | cl->writing_state = MEI_WRITING; | |
948 | cb->buf_idx = mei_hdr.length; | |
949 | ||
4234a6de TW |
950 | out: |
951 | if (mei_hdr.msg_complete) { | |
7ca96aa2 AU |
952 | rets = mei_cl_flow_ctrl_reduce(cl); |
953 | if (rets < 0) | |
4234a6de | 954 | goto err; |
7ca96aa2 | 955 | |
4234a6de TW |
956 | list_add_tail(&cb->list, &dev->write_waiting_list.list); |
957 | } else { | |
958 | list_add_tail(&cb->list, &dev->write_list.list); | |
959 | } | |
960 | ||
961 | ||
962 | if (blocking && cl->writing_state != MEI_WRITE_COMPLETE) { | |
963 | ||
964 | mutex_unlock(&dev->device_lock); | |
7ca96aa2 AU |
965 | rets = wait_event_interruptible(cl->tx_wait, |
966 | cl->writing_state == MEI_WRITE_COMPLETE); | |
4234a6de | 967 | mutex_lock(&dev->device_lock); |
7ca96aa2 AU |
968 | /* wait_event_interruptible returns -ERESTARTSYS */ |
969 | if (rets) { | |
970 | if (signal_pending(current)) | |
971 | rets = -EINTR; | |
972 | goto err; | |
973 | } | |
4234a6de | 974 | } |
7ca96aa2 AU |
975 | |
976 | rets = buf->size; | |
4234a6de | 977 | err: |
04bb139a TW |
978 | cl_dbg(dev, cl, "rpm: autosuspend\n"); |
979 | pm_runtime_mark_last_busy(&dev->pdev->dev); | |
980 | pm_runtime_put_autosuspend(&dev->pdev->dev); | |
981 | ||
4234a6de TW |
982 | return rets; |
983 | } | |
984 | ||
985 | ||
db086fa9 TW |
986 | /** |
987 | * mei_cl_complete - processes completed operation for a client | |
988 | * | |
989 | * @cl: private data of the file object. | |
990 | * @cb: callback block. | |
991 | */ | |
992 | void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb) | |
993 | { | |
994 | if (cb->fop_type == MEI_FOP_WRITE) { | |
995 | mei_io_cb_free(cb); | |
996 | cb = NULL; | |
997 | cl->writing_state = MEI_WRITE_COMPLETE; | |
998 | if (waitqueue_active(&cl->tx_wait)) | |
999 | wake_up_interruptible(&cl->tx_wait); | |
1000 | ||
1001 | } else if (cb->fop_type == MEI_FOP_READ && | |
1002 | MEI_READING == cl->reading_state) { | |
1003 | cl->reading_state = MEI_READ_COMPLETE; | |
1004 | if (waitqueue_active(&cl->rx_wait)) | |
1005 | wake_up_interruptible(&cl->rx_wait); | |
1006 | else | |
1007 | mei_cl_bus_rx_event(cl); | |
1008 | ||
1009 | } | |
1010 | } | |
1011 | ||
4234a6de | 1012 | |
074b4c01 TW |
1013 | /** |
1014 | * mei_cl_all_disconnect - disconnect forcefully all connected clients | |
1015 | * | |
1016 | * @dev - mei device | |
1017 | */ | |
1018 | ||
1019 | void mei_cl_all_disconnect(struct mei_device *dev) | |
1020 | { | |
31f88f57 | 1021 | struct mei_cl *cl; |
074b4c01 | 1022 | |
31f88f57 | 1023 | list_for_each_entry(cl, &dev->file_list, link) { |
074b4c01 TW |
1024 | cl->state = MEI_FILE_DISCONNECTED; |
1025 | cl->mei_flow_ctrl_creds = 0; | |
074b4c01 TW |
1026 | cl->timer_count = 0; |
1027 | } | |
1028 | } | |
1029 | ||
1030 | ||
1031 | /** | |
5290801c | 1032 | * mei_cl_all_wakeup - wake up all readers and writers they can be interrupted |
074b4c01 TW |
1033 | * |
1034 | * @dev - mei device | |
1035 | */ | |
5290801c | 1036 | void mei_cl_all_wakeup(struct mei_device *dev) |
074b4c01 | 1037 | { |
31f88f57 TW |
1038 | struct mei_cl *cl; |
1039 | list_for_each_entry(cl, &dev->file_list, link) { | |
074b4c01 | 1040 | if (waitqueue_active(&cl->rx_wait)) { |
c0abffbd | 1041 | cl_dbg(dev, cl, "Waking up reading client!\n"); |
074b4c01 TW |
1042 | wake_up_interruptible(&cl->rx_wait); |
1043 | } | |
5290801c | 1044 | if (waitqueue_active(&cl->tx_wait)) { |
c0abffbd | 1045 | cl_dbg(dev, cl, "Waking up writing client!\n"); |
5290801c TW |
1046 | wake_up_interruptible(&cl->tx_wait); |
1047 | } | |
074b4c01 TW |
1048 | } |
1049 | } | |
1050 | ||
1051 | /** | |
1052 | * mei_cl_all_write_clear - clear all pending writes | |
1053 | ||
1054 | * @dev - mei device | |
1055 | */ | |
1056 | void mei_cl_all_write_clear(struct mei_device *dev) | |
1057 | { | |
cc99ecfd TW |
1058 | mei_io_list_free(&dev->write_list, NULL); |
1059 | mei_io_list_free(&dev->write_waiting_list, NULL); | |
074b4c01 TW |
1060 | } |
1061 | ||
1062 |