.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
---|
1 | 2 | /* |
---|
2 | 3 | * Thunderbolt XDomain discovery protocol support |
---|
3 | 4 | * |
---|
4 | 5 | * Copyright (C) 2017, Intel Corporation |
---|
5 | 6 | * Authors: Michael Jamet <michael.jamet@intel.com> |
---|
6 | 7 | * Mika Westerberg <mika.westerberg@linux.intel.com> |
---|
7 | | - * |
---|
8 | | - * This program is free software; you can redistribute it and/or modify |
---|
9 | | - * it under the terms of the GNU General Public License version 2 as |
---|
10 | | - * published by the Free Software Foundation. |
---|
11 | 8 | */ |
---|
12 | 9 | |
---|
13 | 10 | #include <linux/device.h> |
---|
.. | .. |
---|
21 | 18 | #include "tb.h" |
---|
22 | 19 | |
---|
23 | 20 | #define XDOMAIN_DEFAULT_TIMEOUT 5000 /* ms */ |
---|
| 21 | +#define XDOMAIN_UUID_RETRIES 10 |
---|
24 | 22 | #define XDOMAIN_PROPERTIES_RETRIES 60 |
---|
25 | 23 | #define XDOMAIN_PROPERTIES_CHANGED_RETRIES 10 |
---|
26 | 24 | |
---|
.. | .. |
---|
223 | 221 | } |
---|
224 | 222 | |
---|
225 | 223 | return 0; |
---|
| 224 | +} |
---|
| 225 | + |
---|
| 226 | +static int tb_xdp_uuid_request(struct tb_ctl *ctl, u64 route, int retry, |
---|
| 227 | + uuid_t *uuid) |
---|
| 228 | +{ |
---|
| 229 | + struct tb_xdp_uuid_response res; |
---|
| 230 | + struct tb_xdp_uuid req; |
---|
| 231 | + int ret; |
---|
| 232 | + |
---|
| 233 | + memset(&req, 0, sizeof(req)); |
---|
| 234 | + tb_xdp_fill_header(&req.hdr, route, retry % 4, UUID_REQUEST, |
---|
| 235 | + sizeof(req)); |
---|
| 236 | + |
---|
| 237 | + memset(&res, 0, sizeof(res)); |
---|
| 238 | + ret = __tb_xdomain_request(ctl, &req, sizeof(req), |
---|
| 239 | + TB_CFG_PKG_XDOMAIN_REQ, &res, sizeof(res), |
---|
| 240 | + TB_CFG_PKG_XDOMAIN_RESP, |
---|
| 241 | + XDOMAIN_DEFAULT_TIMEOUT); |
---|
| 242 | + if (ret) |
---|
| 243 | + return ret; |
---|
| 244 | + |
---|
| 245 | + ret = tb_xdp_handle_error(&res.hdr); |
---|
| 246 | + if (ret) |
---|
| 247 | + return ret; |
---|
| 248 | + |
---|
| 249 | + uuid_copy(uuid, &res.src_uuid); |
---|
| 250 | + return 0; |
---|
| 251 | +} |
---|
| 252 | + |
---|
| 253 | +static int tb_xdp_uuid_response(struct tb_ctl *ctl, u64 route, u8 sequence, |
---|
| 254 | + const uuid_t *uuid) |
---|
| 255 | +{ |
---|
| 256 | + struct tb_xdp_uuid_response res; |
---|
| 257 | + |
---|
| 258 | + memset(&res, 0, sizeof(res)); |
---|
| 259 | + tb_xdp_fill_header(&res.hdr, route, sequence, UUID_RESPONSE, |
---|
| 260 | + sizeof(res)); |
---|
| 261 | + |
---|
| 262 | + uuid_copy(&res.src_uuid, uuid); |
---|
| 263 | + res.src_route_hi = upper_32_bits(route); |
---|
| 264 | + res.src_route_lo = lower_32_bits(route); |
---|
| 265 | + |
---|
| 266 | + return __tb_xdomain_response(ctl, &res, sizeof(res), |
---|
| 267 | + TB_CFG_PKG_XDOMAIN_RESP); |
---|
226 | 268 | } |
---|
227 | 269 | |
---|
228 | 270 | static int tb_xdp_error_response(struct tb_ctl *ctl, u64 route, u8 sequence, |
---|
.. | .. |
---|
459 | 501 | } |
---|
460 | 502 | EXPORT_SYMBOL_GPL(tb_unregister_protocol_handler); |
---|
461 | 503 | |
---|
| 504 | +static int rebuild_property_block(void) |
---|
| 505 | +{ |
---|
| 506 | + u32 *block, len; |
---|
| 507 | + int ret; |
---|
| 508 | + |
---|
| 509 | + ret = tb_property_format_dir(xdomain_property_dir, NULL, 0); |
---|
| 510 | + if (ret < 0) |
---|
| 511 | + return ret; |
---|
| 512 | + |
---|
| 513 | + len = ret; |
---|
| 514 | + |
---|
| 515 | + block = kcalloc(len, sizeof(u32), GFP_KERNEL); |
---|
| 516 | + if (!block) |
---|
| 517 | + return -ENOMEM; |
---|
| 518 | + |
---|
| 519 | + ret = tb_property_format_dir(xdomain_property_dir, block, len); |
---|
| 520 | + if (ret) { |
---|
| 521 | + kfree(block); |
---|
| 522 | + return ret; |
---|
| 523 | + } |
---|
| 524 | + |
---|
| 525 | + kfree(xdomain_property_block); |
---|
| 526 | + xdomain_property_block = block; |
---|
| 527 | + xdomain_property_block_len = len; |
---|
| 528 | + xdomain_property_block_gen++; |
---|
| 529 | + |
---|
| 530 | + return 0; |
---|
| 531 | +} |
---|
| 532 | + |
---|
| 533 | +static void finalize_property_block(void) |
---|
| 534 | +{ |
---|
| 535 | + const struct tb_property *nodename; |
---|
| 536 | + |
---|
| 537 | + /* |
---|
| 538 | + * On first XDomain connection we set up the the system |
---|
| 539 | + * nodename. This delayed here because userspace may not have it |
---|
| 540 | + * set when the driver is first probed. |
---|
| 541 | + */ |
---|
| 542 | + mutex_lock(&xdomain_lock); |
---|
| 543 | + nodename = tb_property_find(xdomain_property_dir, "deviceid", |
---|
| 544 | + TB_PROPERTY_TYPE_TEXT); |
---|
| 545 | + if (!nodename) { |
---|
| 546 | + tb_property_add_text(xdomain_property_dir, "deviceid", |
---|
| 547 | + utsname()->nodename); |
---|
| 548 | + rebuild_property_block(); |
---|
| 549 | + } |
---|
| 550 | + mutex_unlock(&xdomain_lock); |
---|
| 551 | +} |
---|
| 552 | + |
---|
462 | 553 | static void tb_xdp_handle_request(struct work_struct *work) |
---|
463 | 554 | { |
---|
464 | 555 | struct xdomain_request_work *xw = container_of(work, typeof(*xw), work); |
---|
.. | .. |
---|
486 | 577 | tb_xdp_error_response(ctl, route, sequence, ERROR_NOT_READY); |
---|
487 | 578 | goto out; |
---|
488 | 579 | } |
---|
| 580 | + |
---|
| 581 | + finalize_property_block(); |
---|
489 | 582 | |
---|
490 | 583 | switch (pkg->type) { |
---|
491 | 584 | case PROPERTIES_REQUEST: |
---|
.. | .. |
---|
515 | 608 | break; |
---|
516 | 609 | } |
---|
517 | 610 | |
---|
| 611 | + case UUID_REQUEST_OLD: |
---|
| 612 | + case UUID_REQUEST: |
---|
| 613 | + ret = tb_xdp_uuid_response(ctl, route, sequence, uuid); |
---|
| 614 | + break; |
---|
| 615 | + |
---|
518 | 616 | default: |
---|
| 617 | + tb_xdp_error_response(ctl, route, sequence, |
---|
| 618 | + ERROR_NOT_SUPPORTED); |
---|
519 | 619 | break; |
---|
520 | 620 | } |
---|
521 | 621 | |
---|
.. | .. |
---|
527 | 627 | out: |
---|
528 | 628 | kfree(xw->pkg); |
---|
529 | 629 | kfree(xw); |
---|
| 630 | + |
---|
| 631 | + tb_domain_put(tb); |
---|
530 | 632 | } |
---|
531 | 633 | |
---|
532 | | -static void |
---|
| 634 | +static bool |
---|
533 | 635 | tb_xdp_schedule_request(struct tb *tb, const struct tb_xdp_header *hdr, |
---|
534 | 636 | size_t size) |
---|
535 | 637 | { |
---|
.. | .. |
---|
537 | 639 | |
---|
538 | 640 | xw = kmalloc(sizeof(*xw), GFP_KERNEL); |
---|
539 | 641 | if (!xw) |
---|
540 | | - return; |
---|
| 642 | + return false; |
---|
541 | 643 | |
---|
542 | 644 | INIT_WORK(&xw->work, tb_xdp_handle_request); |
---|
543 | 645 | xw->pkg = kmemdup(hdr, size, GFP_KERNEL); |
---|
544 | | - xw->tb = tb; |
---|
| 646 | + if (!xw->pkg) { |
---|
| 647 | + kfree(xw); |
---|
| 648 | + return false; |
---|
| 649 | + } |
---|
| 650 | + xw->tb = tb_domain_get(tb); |
---|
545 | 651 | |
---|
546 | | - queue_work(tb->wq, &xw->work); |
---|
| 652 | + schedule_work(&xw->work); |
---|
| 653 | + return true; |
---|
547 | 654 | } |
---|
548 | 655 | |
---|
549 | 656 | /** |
---|
.. | .. |
---|
580 | 687 | * It should be null terminated but anything else is pretty much |
---|
581 | 688 | * allowed. |
---|
582 | 689 | */ |
---|
583 | | - return sprintf(buf, "%*pEp\n", (int)strlen(svc->key), svc->key); |
---|
| 690 | + return sprintf(buf, "%*pE\n", (int)strlen(svc->key), svc->key); |
---|
584 | 691 | } |
---|
585 | 692 | static DEVICE_ATTR_RO(key); |
---|
586 | 693 | |
---|
.. | .. |
---|
836 | 943 | } |
---|
837 | 944 | } |
---|
838 | 945 | |
---|
| 946 | +static void tb_xdomain_get_uuid(struct work_struct *work) |
---|
| 947 | +{ |
---|
| 948 | + struct tb_xdomain *xd = container_of(work, typeof(*xd), |
---|
| 949 | + get_uuid_work.work); |
---|
| 950 | + struct tb *tb = xd->tb; |
---|
| 951 | + uuid_t uuid; |
---|
| 952 | + int ret; |
---|
| 953 | + |
---|
| 954 | + ret = tb_xdp_uuid_request(tb->ctl, xd->route, xd->uuid_retries, &uuid); |
---|
| 955 | + if (ret < 0) { |
---|
| 956 | + if (xd->uuid_retries-- > 0) { |
---|
| 957 | + queue_delayed_work(xd->tb->wq, &xd->get_uuid_work, |
---|
| 958 | + msecs_to_jiffies(100)); |
---|
| 959 | + } else { |
---|
| 960 | + dev_dbg(&xd->dev, "failed to read remote UUID\n"); |
---|
| 961 | + } |
---|
| 962 | + return; |
---|
| 963 | + } |
---|
| 964 | + |
---|
| 965 | + if (uuid_equal(&uuid, xd->local_uuid)) { |
---|
| 966 | + dev_dbg(&xd->dev, "intra-domain loop detected\n"); |
---|
| 967 | + return; |
---|
| 968 | + } |
---|
| 969 | + |
---|
| 970 | + /* |
---|
| 971 | + * If the UUID is different, there is another domain connected |
---|
| 972 | + * so mark this one unplugged and wait for the connection |
---|
| 973 | + * manager to replace it. |
---|
| 974 | + */ |
---|
| 975 | + if (xd->remote_uuid && !uuid_equal(&uuid, xd->remote_uuid)) { |
---|
| 976 | + dev_dbg(&xd->dev, "remote UUID is different, unplugging\n"); |
---|
| 977 | + xd->is_unplugged = true; |
---|
| 978 | + return; |
---|
| 979 | + } |
---|
| 980 | + |
---|
| 981 | + /* First time fill in the missing UUID */ |
---|
| 982 | + if (!xd->remote_uuid) { |
---|
| 983 | + xd->remote_uuid = kmemdup(&uuid, sizeof(uuid_t), GFP_KERNEL); |
---|
| 984 | + if (!xd->remote_uuid) |
---|
| 985 | + return; |
---|
| 986 | + } |
---|
| 987 | + |
---|
| 988 | + /* Now we can start the normal properties exchange */ |
---|
| 989 | + queue_delayed_work(xd->tb->wq, &xd->properties_changed_work, |
---|
| 990 | + msecs_to_jiffies(100)); |
---|
| 991 | + queue_delayed_work(xd->tb->wq, &xd->get_properties_work, |
---|
| 992 | + msecs_to_jiffies(1000)); |
---|
| 993 | +} |
---|
| 994 | + |
---|
839 | 995 | static void tb_xdomain_get_properties(struct work_struct *work) |
---|
840 | 996 | { |
---|
841 | 997 | struct tb_xdomain *xd = container_of(work, typeof(*xd), |
---|
.. | .. |
---|
1042 | 1198 | |
---|
1043 | 1199 | static void start_handshake(struct tb_xdomain *xd) |
---|
1044 | 1200 | { |
---|
| 1201 | + xd->uuid_retries = XDOMAIN_UUID_RETRIES; |
---|
1045 | 1202 | xd->properties_retries = XDOMAIN_PROPERTIES_RETRIES; |
---|
1046 | 1203 | xd->properties_changed_retries = XDOMAIN_PROPERTIES_CHANGED_RETRIES; |
---|
1047 | 1204 | |
---|
1048 | | - /* Start exchanging properties with the other host */ |
---|
1049 | | - queue_delayed_work(xd->tb->wq, &xd->properties_changed_work, |
---|
1050 | | - msecs_to_jiffies(100)); |
---|
1051 | | - queue_delayed_work(xd->tb->wq, &xd->get_properties_work, |
---|
1052 | | - msecs_to_jiffies(1000)); |
---|
| 1205 | + if (xd->needs_uuid) { |
---|
| 1206 | + queue_delayed_work(xd->tb->wq, &xd->get_uuid_work, |
---|
| 1207 | + msecs_to_jiffies(100)); |
---|
| 1208 | + } else { |
---|
| 1209 | + /* Start exchanging properties with the other host */ |
---|
| 1210 | + queue_delayed_work(xd->tb->wq, &xd->properties_changed_work, |
---|
| 1211 | + msecs_to_jiffies(100)); |
---|
| 1212 | + queue_delayed_work(xd->tb->wq, &xd->get_properties_work, |
---|
| 1213 | + msecs_to_jiffies(1000)); |
---|
| 1214 | + } |
---|
1053 | 1215 | } |
---|
1054 | 1216 | |
---|
1055 | 1217 | static void stop_handshake(struct tb_xdomain *xd) |
---|
1056 | 1218 | { |
---|
| 1219 | + xd->uuid_retries = 0; |
---|
1057 | 1220 | xd->properties_retries = 0; |
---|
1058 | 1221 | xd->properties_changed_retries = 0; |
---|
1059 | 1222 | |
---|
| 1223 | + cancel_delayed_work_sync(&xd->get_uuid_work); |
---|
1060 | 1224 | cancel_delayed_work_sync(&xd->get_properties_work); |
---|
1061 | 1225 | cancel_delayed_work_sync(&xd->properties_changed_work); |
---|
1062 | 1226 | } |
---|
.. | .. |
---|
1099 | 1263 | * other domain is reached). |
---|
1100 | 1264 | * @route: Route string used to reach the other domain |
---|
1101 | 1265 | * @local_uuid: Our local domain UUID |
---|
1102 | | - * @remote_uuid: UUID of the other domain |
---|
| 1266 | + * @remote_uuid: UUID of the other domain (optional) |
---|
1103 | 1267 | * |
---|
1104 | 1268 | * Allocates new XDomain structure and returns pointer to that. The |
---|
1105 | 1269 | * object must be released by calling tb_xdomain_put(). |
---|
.. | .. |
---|
1108 | 1272 | u64 route, const uuid_t *local_uuid, |
---|
1109 | 1273 | const uuid_t *remote_uuid) |
---|
1110 | 1274 | { |
---|
| 1275 | + struct tb_switch *parent_sw = tb_to_switch(parent); |
---|
1111 | 1276 | struct tb_xdomain *xd; |
---|
| 1277 | + struct tb_port *down; |
---|
| 1278 | + |
---|
| 1279 | + /* Make sure the downstream domain is accessible */ |
---|
| 1280 | + down = tb_port_at(route, parent_sw); |
---|
| 1281 | + tb_port_unlock(down); |
---|
1112 | 1282 | |
---|
1113 | 1283 | xd = kzalloc(sizeof(*xd), GFP_KERNEL); |
---|
1114 | 1284 | if (!xd) |
---|
.. | .. |
---|
1118 | 1288 | xd->route = route; |
---|
1119 | 1289 | ida_init(&xd->service_ids); |
---|
1120 | 1290 | mutex_init(&xd->lock); |
---|
| 1291 | + INIT_DELAYED_WORK(&xd->get_uuid_work, tb_xdomain_get_uuid); |
---|
1121 | 1292 | INIT_DELAYED_WORK(&xd->get_properties_work, tb_xdomain_get_properties); |
---|
1122 | 1293 | INIT_DELAYED_WORK(&xd->properties_changed_work, |
---|
1123 | 1294 | tb_xdomain_properties_changed); |
---|
.. | .. |
---|
1126 | 1297 | if (!xd->local_uuid) |
---|
1127 | 1298 | goto err_free; |
---|
1128 | 1299 | |
---|
1129 | | - xd->remote_uuid = kmemdup(remote_uuid, sizeof(uuid_t), GFP_KERNEL); |
---|
1130 | | - if (!xd->remote_uuid) |
---|
1131 | | - goto err_free_local_uuid; |
---|
| 1300 | + if (remote_uuid) { |
---|
| 1301 | + xd->remote_uuid = kmemdup(remote_uuid, sizeof(uuid_t), |
---|
| 1302 | + GFP_KERNEL); |
---|
| 1303 | + if (!xd->remote_uuid) |
---|
| 1304 | + goto err_free_local_uuid; |
---|
| 1305 | + } else { |
---|
| 1306 | + xd->needs_uuid = true; |
---|
| 1307 | + } |
---|
1132 | 1308 | |
---|
1133 | 1309 | device_initialize(&xd->dev); |
---|
1134 | 1310 | xd->dev.parent = get_device(parent); |
---|
.. | .. |
---|
1286 | 1462 | static struct tb_xdomain *switch_find_xdomain(struct tb_switch *sw, |
---|
1287 | 1463 | const struct tb_xdomain_lookup *lookup) |
---|
1288 | 1464 | { |
---|
1289 | | - int i; |
---|
| 1465 | + struct tb_port *port; |
---|
1290 | 1466 | |
---|
1291 | | - for (i = 1; i <= sw->config.max_port_number; i++) { |
---|
1292 | | - struct tb_port *port = &sw->ports[i]; |
---|
| 1467 | + tb_switch_for_each_port(sw, port) { |
---|
1293 | 1468 | struct tb_xdomain *xd; |
---|
1294 | | - |
---|
1295 | | - if (tb_is_upstream_port(port)) |
---|
1296 | | - continue; |
---|
1297 | 1469 | |
---|
1298 | 1470 | if (port->xdomain) { |
---|
1299 | 1471 | xd = port->xdomain; |
---|
1300 | 1472 | |
---|
1301 | 1473 | if (lookup->uuid) { |
---|
1302 | | - if (uuid_equal(xd->remote_uuid, lookup->uuid)) |
---|
| 1474 | + if (xd->remote_uuid && |
---|
| 1475 | + uuid_equal(xd->remote_uuid, lookup->uuid)) |
---|
1303 | 1476 | return xd; |
---|
1304 | 1477 | } else if (lookup->link && |
---|
1305 | 1478 | lookup->link == xd->link && |
---|
.. | .. |
---|
1309 | 1482 | lookup->route == xd->route) { |
---|
1310 | 1483 | return xd; |
---|
1311 | 1484 | } |
---|
1312 | | - } else if (port->remote) { |
---|
| 1485 | + } else if (tb_port_has_remote(port)) { |
---|
1313 | 1486 | xd = switch_find_xdomain(port->remote->sw, lookup); |
---|
1314 | 1487 | if (xd) |
---|
1315 | 1488 | return xd; |
---|
.. | .. |
---|
1426 | 1599 | * handlers in turn. |
---|
1427 | 1600 | */ |
---|
1428 | 1601 | if (uuid_equal(&hdr->uuid, &tb_xdp_uuid)) { |
---|
1429 | | - if (type == TB_CFG_PKG_XDOMAIN_REQ) { |
---|
1430 | | - tb_xdp_schedule_request(tb, hdr, size); |
---|
1431 | | - return true; |
---|
1432 | | - } |
---|
| 1602 | + if (type == TB_CFG_PKG_XDOMAIN_REQ) |
---|
| 1603 | + return tb_xdp_schedule_request(tb, hdr, size); |
---|
1433 | 1604 | return false; |
---|
1434 | 1605 | } |
---|
1435 | 1606 | |
---|
.. | .. |
---|
1448 | 1619 | mutex_unlock(&xdomain_lock); |
---|
1449 | 1620 | |
---|
1450 | 1621 | return ret > 0; |
---|
1451 | | -} |
---|
1452 | | - |
---|
1453 | | -static int rebuild_property_block(void) |
---|
1454 | | -{ |
---|
1455 | | - u32 *block, len; |
---|
1456 | | - int ret; |
---|
1457 | | - |
---|
1458 | | - ret = tb_property_format_dir(xdomain_property_dir, NULL, 0); |
---|
1459 | | - if (ret < 0) |
---|
1460 | | - return ret; |
---|
1461 | | - |
---|
1462 | | - len = ret; |
---|
1463 | | - |
---|
1464 | | - block = kcalloc(len, sizeof(u32), GFP_KERNEL); |
---|
1465 | | - if (!block) |
---|
1466 | | - return -ENOMEM; |
---|
1467 | | - |
---|
1468 | | - ret = tb_property_format_dir(xdomain_property_dir, block, len); |
---|
1469 | | - if (ret) { |
---|
1470 | | - kfree(block); |
---|
1471 | | - return ret; |
---|
1472 | | - } |
---|
1473 | | - |
---|
1474 | | - kfree(xdomain_property_block); |
---|
1475 | | - xdomain_property_block = block; |
---|
1476 | | - xdomain_property_block_len = len; |
---|
1477 | | - xdomain_property_block_gen++; |
---|
1478 | | - |
---|
1479 | | - return 0; |
---|
1480 | 1622 | } |
---|
1481 | 1623 | |
---|
1482 | 1624 | static int update_xdomain(struct device *dev, void *data) |
---|
.. | .. |
---|
1583 | 1725 | |
---|
1584 | 1726 | int tb_xdomain_init(void) |
---|
1585 | 1727 | { |
---|
1586 | | - int ret; |
---|
1587 | | - |
---|
1588 | 1728 | xdomain_property_dir = tb_property_create_dir(NULL); |
---|
1589 | 1729 | if (!xdomain_property_dir) |
---|
1590 | 1730 | return -ENOMEM; |
---|
.. | .. |
---|
1593 | 1733 | * Initialize standard set of properties without any service |
---|
1594 | 1734 | * directories. Those will be added by service drivers |
---|
1595 | 1735 | * themselves when they are loaded. |
---|
| 1736 | + * |
---|
| 1737 | + * We also add node name later when first connection is made. |
---|
1596 | 1738 | */ |
---|
1597 | 1739 | tb_property_add_immediate(xdomain_property_dir, "vendorid", |
---|
1598 | 1740 | PCI_VENDOR_ID_INTEL); |
---|
1599 | 1741 | tb_property_add_text(xdomain_property_dir, "vendorid", "Intel Corp."); |
---|
1600 | 1742 | tb_property_add_immediate(xdomain_property_dir, "deviceid", 0x1); |
---|
1601 | | - tb_property_add_text(xdomain_property_dir, "deviceid", |
---|
1602 | | - utsname()->nodename); |
---|
1603 | 1743 | tb_property_add_immediate(xdomain_property_dir, "devicerv", 0x80000100); |
---|
1604 | 1744 | |
---|
1605 | | - ret = rebuild_property_block(); |
---|
1606 | | - if (ret) { |
---|
1607 | | - tb_property_free_dir(xdomain_property_dir); |
---|
1608 | | - xdomain_property_dir = NULL; |
---|
1609 | | - } |
---|
1610 | | - |
---|
1611 | | - return ret; |
---|
| 1745 | + return 0; |
---|
1612 | 1746 | } |
---|
1613 | 1747 | |
---|
1614 | 1748 | void tb_xdomain_exit(void) |
---|