diff options
Diffstat (limited to 'drivers/misc/mei/hbm.c')
-rw-r--r-- | drivers/misc/mei/hbm.c | 281 |
1 files changed, 152 insertions, 129 deletions
diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c index 28cd74c073b9..4960288e543a 100644 --- a/drivers/misc/mei/hbm.c +++ b/drivers/misc/mei/hbm.c | |||
@@ -21,7 +21,41 @@ | |||
21 | 21 | ||
22 | #include "mei_dev.h" | 22 | #include "mei_dev.h" |
23 | #include "hbm.h" | 23 | #include "hbm.h" |
24 | #include "hw-me.h" | 24 | #include "client.h" |
25 | |||
26 | static const char *mei_cl_conn_status_str(enum mei_cl_connect_status status) | ||
27 | { | ||
28 | #define MEI_CL_CS(status) case MEI_CL_CONN_##status: return #status | ||
29 | switch (status) { | ||
30 | MEI_CL_CS(SUCCESS); | ||
31 | MEI_CL_CS(NOT_FOUND); | ||
32 | MEI_CL_CS(ALREADY_STARTED); | ||
33 | MEI_CL_CS(OUT_OF_RESOURCES); | ||
34 | MEI_CL_CS(MESSAGE_SMALL); | ||
35 | default: return "unknown"; | ||
36 | } | ||
37 | #undef MEI_CL_CCS | ||
38 | } | ||
39 | |||
40 | /** | ||
41 | * mei_cl_conn_status_to_errno - convert client connect response | ||
42 | * status to error code | ||
43 | * | ||
44 | * @status: client connect response status | ||
45 | * | ||
46 | * returns corresponding error code | ||
47 | */ | ||
48 | static int mei_cl_conn_status_to_errno(enum mei_cl_connect_status status) | ||
49 | { | ||
50 | switch (status) { | ||
51 | case MEI_CL_CONN_SUCCESS: return 0; | ||
52 | case MEI_CL_CONN_NOT_FOUND: return -ENOTTY; | ||
53 | case MEI_CL_CONN_ALREADY_STARTED: return -EBUSY; | ||
54 | case MEI_CL_CONN_OUT_OF_RESOURCES: return -EBUSY; | ||
55 | case MEI_CL_CONN_MESSAGE_SMALL: return -EINVAL; | ||
56 | default: return -EINVAL; | ||
57 | } | ||
58 | } | ||
25 | 59 | ||
26 | /** | 60 | /** |
27 | * mei_hbm_me_cl_allocate - allocates storage for me clients | 61 | * mei_hbm_me_cl_allocate - allocates storage for me clients |
@@ -100,33 +134,6 @@ bool mei_hbm_cl_addr_equal(struct mei_cl *cl, void *buf) | |||
100 | 134 | ||
101 | 135 | ||
102 | /** | 136 | /** |
103 | * is_treat_specially_client - checks if the message belongs | ||
104 | * to the file private data. | ||
105 | * | ||
106 | * @cl: private data of the file object | ||
107 | * @rs: connect response bus message | ||
108 | * | ||
109 | */ | ||
110 | static bool is_treat_specially_client(struct mei_cl *cl, | ||
111 | struct hbm_client_connect_response *rs) | ||
112 | { | ||
113 | if (mei_hbm_cl_addr_equal(cl, rs)) { | ||
114 | if (!rs->status) { | ||
115 | cl->state = MEI_FILE_CONNECTED; | ||
116 | cl->status = 0; | ||
117 | |||
118 | } else { | ||
119 | cl->state = MEI_FILE_DISCONNECTED; | ||
120 | cl->status = -ENODEV; | ||
121 | } | ||
122 | cl->timer_count = 0; | ||
123 | |||
124 | return true; | ||
125 | } | ||
126 | return false; | ||
127 | } | ||
128 | |||
129 | /** | ||
130 | * mei_hbm_idle - set hbm to idle state | 137 | * mei_hbm_idle - set hbm to idle state |
131 | * | 138 | * |
132 | * @dev: the device structure | 139 | * @dev: the device structure |
@@ -147,13 +154,13 @@ int mei_hbm_start_wait(struct mei_device *dev) | |||
147 | ret = wait_event_interruptible_timeout(dev->wait_recvd_msg, | 154 | ret = wait_event_interruptible_timeout(dev->wait_recvd_msg, |
148 | dev->hbm_state == MEI_HBM_IDLE || | 155 | dev->hbm_state == MEI_HBM_IDLE || |
149 | dev->hbm_state >= MEI_HBM_STARTED, | 156 | dev->hbm_state >= MEI_HBM_STARTED, |
150 | mei_secs_to_jiffies(MEI_INTEROP_TIMEOUT)); | 157 | mei_secs_to_jiffies(MEI_HBM_TIMEOUT)); |
151 | mutex_lock(&dev->device_lock); | 158 | mutex_lock(&dev->device_lock); |
152 | 159 | ||
153 | if (ret <= 0 && (dev->hbm_state <= MEI_HBM_START)) { | 160 | if (ret <= 0 && (dev->hbm_state <= MEI_HBM_START)) { |
154 | dev->hbm_state = MEI_HBM_IDLE; | 161 | dev->hbm_state = MEI_HBM_IDLE; |
155 | dev_err(&dev->pdev->dev, "waiting for mei start failed\n"); | 162 | dev_err(&dev->pdev->dev, "waiting for mei start failed\n"); |
156 | return -ETIMEDOUT; | 163 | return -ETIME; |
157 | } | 164 | } |
158 | return 0; | 165 | return 0; |
159 | } | 166 | } |
@@ -283,17 +290,18 @@ static int mei_hbm_prop_req(struct mei_device *dev) | |||
283 | } | 290 | } |
284 | 291 | ||
285 | /** | 292 | /** |
286 | * mei_hbm_stop_req_prepare - prepare stop request message | 293 | * mei_hbm_stop_req - send stop request message |
287 | * | 294 | * |
288 | * @dev - mei device | 295 | * @dev - mei device |
289 | * @mei_hdr - mei message header | 296 | * @cl: client info |
290 | * @data - hbm message body buffer | 297 | * |
298 | * This function returns -EIO on write failure | ||
291 | */ | 299 | */ |
292 | static void mei_hbm_stop_req_prepare(struct mei_device *dev, | 300 | static int mei_hbm_stop_req(struct mei_device *dev) |
293 | struct mei_msg_hdr *mei_hdr, unsigned char *data) | ||
294 | { | 301 | { |
302 | struct mei_msg_hdr *mei_hdr = &dev->wr_msg.hdr; | ||
295 | struct hbm_host_stop_request *req = | 303 | struct hbm_host_stop_request *req = |
296 | (struct hbm_host_stop_request *)data; | 304 | (struct hbm_host_stop_request *)dev->wr_msg.data; |
297 | const size_t len = sizeof(struct hbm_host_stop_request); | 305 | const size_t len = sizeof(struct hbm_host_stop_request); |
298 | 306 | ||
299 | mei_hbm_hdr(mei_hdr, len); | 307 | mei_hbm_hdr(mei_hdr, len); |
@@ -301,6 +309,8 @@ static void mei_hbm_stop_req_prepare(struct mei_device *dev, | |||
301 | memset(req, 0, len); | 309 | memset(req, 0, len); |
302 | req->hbm_cmd = HOST_STOP_REQ_CMD; | 310 | req->hbm_cmd = HOST_STOP_REQ_CMD; |
303 | req->reason = DRIVER_STOP_REQUEST; | 311 | req->reason = DRIVER_STOP_REQUEST; |
312 | |||
313 | return mei_write_message(dev, mei_hdr, dev->wr_msg.data); | ||
304 | } | 314 | } |
305 | 315 | ||
306 | /** | 316 | /** |
@@ -319,8 +329,7 @@ int mei_hbm_cl_flow_control_req(struct mei_device *dev, struct mei_cl *cl) | |||
319 | mei_hbm_hdr(mei_hdr, len); | 329 | mei_hbm_hdr(mei_hdr, len); |
320 | mei_hbm_cl_hdr(cl, MEI_FLOW_CONTROL_CMD, dev->wr_msg.data, len); | 330 | mei_hbm_cl_hdr(cl, MEI_FLOW_CONTROL_CMD, dev->wr_msg.data, len); |
321 | 331 | ||
322 | dev_dbg(&dev->pdev->dev, "sending flow control host client = %d, ME client = %d\n", | 332 | cl_dbg(dev, cl, "sending flow control\n"); |
323 | cl->host_client_id, cl->me_client_id); | ||
324 | 333 | ||
325 | return mei_write_message(dev, mei_hdr, dev->wr_msg.data); | 334 | return mei_write_message(dev, mei_hdr, dev->wr_msg.data); |
326 | } | 335 | } |
@@ -330,27 +339,34 @@ int mei_hbm_cl_flow_control_req(struct mei_device *dev, struct mei_cl *cl) | |||
330 | * | 339 | * |
331 | * @dev: the device structure | 340 | * @dev: the device structure |
332 | * @flow: flow control. | 341 | * @flow: flow control. |
342 | * | ||
343 | * return 0 on success, < 0 otherwise | ||
333 | */ | 344 | */ |
334 | static void mei_hbm_add_single_flow_creds(struct mei_device *dev, | 345 | static int mei_hbm_add_single_flow_creds(struct mei_device *dev, |
335 | struct hbm_flow_control *flow) | 346 | struct hbm_flow_control *flow) |
336 | { | 347 | { |
337 | struct mei_me_client *client; | 348 | struct mei_me_client *me_cl; |
338 | int i; | 349 | int id; |
339 | 350 | ||
340 | for (i = 0; i < dev->me_clients_num; i++) { | 351 | id = mei_me_cl_by_id(dev, flow->me_addr); |
341 | client = &dev->me_clients[i]; | 352 | if (id < 0) { |
342 | if (client && flow->me_addr == client->client_id) { | 353 | dev_err(&dev->pdev->dev, "no such me client %d\n", |
343 | if (client->props.single_recv_buf) { | 354 | flow->me_addr); |
344 | client->mei_flow_ctrl_creds++; | 355 | return id; |
345 | dev_dbg(&dev->pdev->dev, "recv flow ctrl msg ME %d (single).\n", | ||
346 | flow->me_addr); | ||
347 | dev_dbg(&dev->pdev->dev, "flow control credentials =%d.\n", | ||
348 | client->mei_flow_ctrl_creds); | ||
349 | } else { | ||
350 | BUG(); /* error in flow control */ | ||
351 | } | ||
352 | } | ||
353 | } | 356 | } |
357 | |||
358 | me_cl = &dev->me_clients[id]; | ||
359 | if (me_cl->props.single_recv_buf) { | ||
360 | me_cl->mei_flow_ctrl_creds++; | ||
361 | dev_dbg(&dev->pdev->dev, "recv flow ctrl msg ME %d (single).\n", | ||
362 | flow->me_addr); | ||
363 | dev_dbg(&dev->pdev->dev, "flow control credentials =%d.\n", | ||
364 | me_cl->mei_flow_ctrl_creds); | ||
365 | } else { | ||
366 | BUG(); /* error in flow control */ | ||
367 | } | ||
368 | |||
369 | return 0; | ||
354 | } | 370 | } |
355 | 371 | ||
356 | /** | 372 | /** |
@@ -362,8 +378,7 @@ static void mei_hbm_add_single_flow_creds(struct mei_device *dev, | |||
362 | static void mei_hbm_cl_flow_control_res(struct mei_device *dev, | 378 | static void mei_hbm_cl_flow_control_res(struct mei_device *dev, |
363 | struct hbm_flow_control *flow_control) | 379 | struct hbm_flow_control *flow_control) |
364 | { | 380 | { |
365 | struct mei_cl *cl = NULL; | 381 | struct mei_cl *cl; |
366 | struct mei_cl *next = NULL; | ||
367 | 382 | ||
368 | if (!flow_control->host_addr) { | 383 | if (!flow_control->host_addr) { |
369 | /* single receive buffer */ | 384 | /* single receive buffer */ |
@@ -372,7 +387,7 @@ static void mei_hbm_cl_flow_control_res(struct mei_device *dev, | |||
372 | } | 387 | } |
373 | 388 | ||
374 | /* normal connection */ | 389 | /* normal connection */ |
375 | list_for_each_entry_safe(cl, next, &dev->file_list, link) { | 390 | list_for_each_entry(cl, &dev->file_list, link) { |
376 | if (mei_hbm_cl_addr_equal(cl, flow_control)) { | 391 | if (mei_hbm_cl_addr_equal(cl, flow_control)) { |
377 | cl->mei_flow_ctrl_creds++; | 392 | cl->mei_flow_ctrl_creds++; |
378 | dev_dbg(&dev->pdev->dev, "flow ctrl msg for host %d ME %d.\n", | 393 | dev_dbg(&dev->pdev->dev, "flow ctrl msg for host %d ME %d.\n", |
@@ -405,6 +420,25 @@ int mei_hbm_cl_disconnect_req(struct mei_device *dev, struct mei_cl *cl) | |||
405 | } | 420 | } |
406 | 421 | ||
407 | /** | 422 | /** |
423 | * mei_hbm_cl_disconnect_rsp - sends disconnect respose to the FW | ||
424 | * | ||
425 | * @dev: the device structure | ||
426 | * @cl: a client to disconnect from | ||
427 | * | ||
428 | * This function returns -EIO on write failure | ||
429 | */ | ||
430 | int mei_hbm_cl_disconnect_rsp(struct mei_device *dev, struct mei_cl *cl) | ||
431 | { | ||
432 | struct mei_msg_hdr *mei_hdr = &dev->wr_msg.hdr; | ||
433 | const size_t len = sizeof(struct hbm_client_connect_response); | ||
434 | |||
435 | mei_hbm_hdr(mei_hdr, len); | ||
436 | mei_hbm_cl_hdr(cl, CLIENT_DISCONNECT_RES_CMD, dev->wr_msg.data, len); | ||
437 | |||
438 | return mei_write_message(dev, mei_hdr, dev->wr_msg.data); | ||
439 | } | ||
440 | |||
441 | /** | ||
408 | * mei_hbm_cl_disconnect_res - disconnect response from ME | 442 | * mei_hbm_cl_disconnect_res - disconnect response from ME |
409 | * | 443 | * |
410 | * @dev: the device structure | 444 | * @dev: the device structure |
@@ -414,29 +448,23 @@ static void mei_hbm_cl_disconnect_res(struct mei_device *dev, | |||
414 | struct hbm_client_connect_response *rs) | 448 | struct hbm_client_connect_response *rs) |
415 | { | 449 | { |
416 | struct mei_cl *cl; | 450 | struct mei_cl *cl; |
417 | struct mei_cl_cb *pos = NULL, *next = NULL; | 451 | struct mei_cl_cb *cb, *next; |
418 | 452 | ||
419 | dev_dbg(&dev->pdev->dev, | 453 | dev_dbg(&dev->pdev->dev, "hbm: disconnect response cl:host=%02d me=%02d status=%d\n", |
420 | "disconnect_response:\n" | 454 | rs->me_addr, rs->host_addr, rs->status); |
421 | "ME Client = %d\n" | 455 | |
422 | "Host Client = %d\n" | 456 | list_for_each_entry_safe(cb, next, &dev->ctrl_rd_list.list, list) { |
423 | "Status = %d\n", | 457 | cl = cb->cl; |
424 | rs->me_addr, | 458 | |
425 | rs->host_addr, | 459 | /* this should not happen */ |
426 | rs->status); | 460 | if (WARN_ON(!cl)) { |
427 | 461 | list_del(&cb->list); | |
428 | list_for_each_entry_safe(pos, next, &dev->ctrl_rd_list.list, list) { | ||
429 | cl = pos->cl; | ||
430 | |||
431 | if (!cl) { | ||
432 | list_del(&pos->list); | ||
433 | return; | 462 | return; |
434 | } | 463 | } |
435 | 464 | ||
436 | dev_dbg(&dev->pdev->dev, "list_for_each_entry_safe in ctrl_rd_list.\n"); | ||
437 | if (mei_hbm_cl_addr_equal(cl, rs)) { | 465 | if (mei_hbm_cl_addr_equal(cl, rs)) { |
438 | list_del(&pos->list); | 466 | list_del(&cb->list); |
439 | if (!rs->status) | 467 | if (rs->status == MEI_CL_DISCONN_SUCCESS) |
440 | cl->state = MEI_FILE_DISCONNECTED; | 468 | cl->state = MEI_FILE_DISCONNECTED; |
441 | 469 | ||
442 | cl->status = 0; | 470 | cl->status = 0; |
@@ -476,46 +504,41 @@ static void mei_hbm_cl_connect_res(struct mei_device *dev, | |||
476 | { | 504 | { |
477 | 505 | ||
478 | struct mei_cl *cl; | 506 | struct mei_cl *cl; |
479 | struct mei_cl_cb *pos = NULL, *next = NULL; | 507 | struct mei_cl_cb *cb, *next; |
480 | 508 | ||
481 | dev_dbg(&dev->pdev->dev, | 509 | dev_dbg(&dev->pdev->dev, "hbm: connect response cl:host=%02d me=%02d status=%s\n", |
482 | "connect_response:\n" | 510 | rs->me_addr, rs->host_addr, |
483 | "ME Client = %d\n" | 511 | mei_cl_conn_status_str(rs->status)); |
484 | "Host Client = %d\n" | ||
485 | "Status = %d\n", | ||
486 | rs->me_addr, | ||
487 | rs->host_addr, | ||
488 | rs->status); | ||
489 | 512 | ||
490 | /* if WD or iamthif client treat specially */ | 513 | cl = NULL; |
491 | 514 | ||
492 | if (is_treat_specially_client(&dev->wd_cl, rs)) { | 515 | list_for_each_entry_safe(cb, next, &dev->ctrl_rd_list.list, list) { |
493 | dev_dbg(&dev->pdev->dev, "successfully connected to WD client.\n"); | ||
494 | mei_watchdog_register(dev); | ||
495 | 516 | ||
496 | return; | 517 | cl = cb->cl; |
497 | } | 518 | /* this should not happen */ |
519 | if (WARN_ON(!cl)) { | ||
520 | list_del_init(&cb->list); | ||
521 | continue; | ||
522 | } | ||
498 | 523 | ||
499 | if (is_treat_specially_client(&dev->iamthif_cl, rs)) { | 524 | if (cb->fop_type != MEI_FOP_CONNECT) |
500 | dev->iamthif_state = MEI_IAMTHIF_IDLE; | 525 | continue; |
501 | return; | ||
502 | } | ||
503 | list_for_each_entry_safe(pos, next, &dev->ctrl_rd_list.list, list) { | ||
504 | 526 | ||
505 | cl = pos->cl; | 527 | if (mei_hbm_cl_addr_equal(cl, rs)) { |
506 | if (!cl) { | 528 | list_del(&cb->list); |
507 | list_del(&pos->list); | 529 | break; |
508 | return; | ||
509 | } | ||
510 | if (pos->fop_type == MEI_FOP_IOCTL) { | ||
511 | if (is_treat_specially_client(cl, rs)) { | ||
512 | list_del(&pos->list); | ||
513 | cl->status = 0; | ||
514 | cl->timer_count = 0; | ||
515 | break; | ||
516 | } | ||
517 | } | 530 | } |
518 | } | 531 | } |
532 | |||
533 | if (!cl) | ||
534 | return; | ||
535 | |||
536 | cl->timer_count = 0; | ||
537 | if (rs->status == MEI_CL_CONN_SUCCESS) | ||
538 | cl->state = MEI_FILE_CONNECTED; | ||
539 | else | ||
540 | cl->state = MEI_FILE_DISCONNECTED; | ||
541 | cl->status = mei_cl_conn_status_to_errno(rs->status); | ||
519 | } | 542 | } |
520 | 543 | ||
521 | 544 | ||
@@ -525,32 +548,34 @@ static void mei_hbm_cl_connect_res(struct mei_device *dev, | |||
525 | * | 548 | * |
526 | * @dev: the device structure. | 549 | * @dev: the device structure. |
527 | * @disconnect_req: disconnect request bus message from the me | 550 | * @disconnect_req: disconnect request bus message from the me |
551 | * | ||
552 | * returns -ENOMEM on allocation failure | ||
528 | */ | 553 | */ |
529 | static void mei_hbm_fw_disconnect_req(struct mei_device *dev, | 554 | static int mei_hbm_fw_disconnect_req(struct mei_device *dev, |
530 | struct hbm_client_connect_request *disconnect_req) | 555 | struct hbm_client_connect_request *disconnect_req) |
531 | { | 556 | { |
532 | struct mei_cl *cl, *next; | 557 | struct mei_cl *cl; |
533 | const size_t len = sizeof(struct hbm_client_connect_response); | 558 | struct mei_cl_cb *cb; |
534 | 559 | ||
535 | list_for_each_entry_safe(cl, next, &dev->file_list, link) { | 560 | list_for_each_entry(cl, &dev->file_list, link) { |
536 | if (mei_hbm_cl_addr_equal(cl, disconnect_req)) { | 561 | if (mei_hbm_cl_addr_equal(cl, disconnect_req)) { |
537 | dev_dbg(&dev->pdev->dev, "disconnect request host client %d ME client %d.\n", | 562 | dev_dbg(&dev->pdev->dev, "disconnect request host client %d ME client %d.\n", |
538 | disconnect_req->host_addr, | 563 | disconnect_req->host_addr, |
539 | disconnect_req->me_addr); | 564 | disconnect_req->me_addr); |
540 | cl->state = MEI_FILE_DISCONNECTED; | 565 | cl->state = MEI_FILE_DISCONNECTED; |
541 | cl->timer_count = 0; | 566 | cl->timer_count = 0; |
542 | if (cl == &dev->wd_cl) | 567 | |
543 | dev->wd_pending = false; | 568 | cb = mei_io_cb_init(cl, NULL); |
544 | else if (cl == &dev->iamthif_cl) | 569 | if (!cb) |
545 | dev->iamthif_timer = 0; | 570 | return -ENOMEM; |
546 | 571 | cb->fop_type = MEI_FOP_DISCONNECT_RSP; | |
547 | /* prepare disconnect response */ | 572 | cl_dbg(dev, cl, "add disconnect response as first\n"); |
548 | mei_hbm_hdr(&dev->wr_ext_msg.hdr, len); | 573 | list_add(&cb->list, &dev->ctrl_wr_list.list); |
549 | mei_hbm_cl_hdr(cl, CLIENT_DISCONNECT_RES_CMD, | 574 | |
550 | dev->wr_ext_msg.data, len); | ||
551 | break; | 575 | break; |
552 | } | 576 | } |
553 | } | 577 | } |
578 | return 0; | ||
554 | } | 579 | } |
555 | 580 | ||
556 | 581 | ||
@@ -629,10 +654,7 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) | |||
629 | dev_warn(&dev->pdev->dev, "hbm: start: version mismatch - stopping the driver.\n"); | 654 | dev_warn(&dev->pdev->dev, "hbm: start: version mismatch - stopping the driver.\n"); |
630 | 655 | ||
631 | dev->hbm_state = MEI_HBM_STOPPED; | 656 | dev->hbm_state = MEI_HBM_STOPPED; |
632 | mei_hbm_stop_req_prepare(dev, &dev->wr_msg.hdr, | 657 | if (mei_hbm_stop_req(dev)) { |
633 | dev->wr_msg.data); | ||
634 | if (mei_write_message(dev, &dev->wr_msg.hdr, | ||
635 | dev->wr_msg.data)) { | ||
636 | dev_err(&dev->pdev->dev, "hbm: start: failed to send stop request\n"); | 658 | dev_err(&dev->pdev->dev, "hbm: start: failed to send stop request\n"); |
637 | return -EIO; | 659 | return -EIO; |
638 | } | 660 | } |
@@ -778,10 +800,11 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) | |||
778 | 800 | ||
779 | case ME_STOP_REQ_CMD: | 801 | case ME_STOP_REQ_CMD: |
780 | dev_dbg(&dev->pdev->dev, "hbm: stop request: message received\n"); | 802 | dev_dbg(&dev->pdev->dev, "hbm: stop request: message received\n"); |
781 | |||
782 | dev->hbm_state = MEI_HBM_STOPPED; | 803 | dev->hbm_state = MEI_HBM_STOPPED; |
783 | mei_hbm_stop_req_prepare(dev, &dev->wr_ext_msg.hdr, | 804 | if (mei_hbm_stop_req(dev)) { |
784 | dev->wr_ext_msg.data); | 805 | dev_err(&dev->pdev->dev, "hbm: start: failed to send stop request\n"); |
806 | return -EIO; | ||
807 | } | ||
785 | break; | 808 | break; |
786 | default: | 809 | default: |
787 | BUG(); | 810 | BUG(); |