Print this page
8368 remove warlock leftovers from usr/src/uts

Split Close
Expand all
Collapse all
          --- old/usr/src/uts/common/io/ib/adapters/hermon/hermon_umap.c
          +++ new/usr/src/uts/common/io/ib/adapters/hermon/hermon_umap.c
↓ open down ↓ 482 lines elided ↑ open up ↑
 483  483          key  = off >> PAGESHIFT;
 484  484          type = key & MLNX_UMAP_RSRC_TYPE_MASK;
 485  485          key  = key >> MLNX_UMAP_RSRC_TYPE_SHIFT;
 486  486  
 487  487          /*
 488  488           * Allocate an entry to track the mapping and unmapping (specifically,
 489  489           * partial unmapping) of this resource.
 490  490           */
 491  491          dvm_track = (hermon_devmap_track_t *)kmem_zalloc(
 492  492              sizeof (hermon_devmap_track_t), KM_SLEEP);
 493      -        _NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*dvm_track))
 494  493          dvm_track->hdt_offset = off;
 495  494          dvm_track->hdt_state  = state;
 496  495          dvm_track->hdt_refcnt = 1;
 497  496          mutex_init(&dvm_track->hdt_lock, NULL, MUTEX_DRIVER,
 498  497              DDI_INTR_PRI(state->hs_intrmsi_pri));
 499  498  
 500  499          /*
 501  500           * Depending of the type of resource that has been mapped out, we
 502  501           * need to update the QP or CQ handle to reflect that it has, in
 503  502           * fact, been mapped.  This allows the driver code which frees a QP
↓ open down ↓ 95 lines elided ↑ open up ↑
 599  598          hermon_state_t          *state;
 600  599          hermon_devmap_track_t   *dvm_track, *new_dvm_track;
 601  600          uint_t                  maxprot;
 602  601          int                     status;
 603  602  
 604  603          /*
 605  604           * Extract the Hermon softstate pointer from "Hermon devmap tracking
 606  605           * structure" (in "pvtp").
 607  606           */
 608  607          dvm_track = (hermon_devmap_track_t *)pvtp;
 609      -        _NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*dvm_track))
 610  608          state = dvm_track->hdt_state;
 611  609  
 612  610          /*
 613  611           * Since this devmap_dup() entry point is generally called
 614  612           * when a process does fork(2), it is incumbent upon the driver
 615  613           * to insure that the child does not inherit a valid copy of
 616  614           * the parent's QP or CQ resource.  This is accomplished by using
 617  615           * devmap_devmem_remap() to invalidate the child's mapping to the
 618  616           * kernel memory.
 619  617           */
↓ open down ↓ 8 lines elided ↑ open up ↑
 628  626          /*
 629  627           * Allocate a new entry to track the subsequent unmapping
 630  628           * (specifically, all partial unmappings) of the child's newly
 631  629           * invalidated resource.  Note: Setting the "hdt_size" field to
 632  630           * zero here is an indication to the devmap_unmap() entry point
 633  631           * that this mapping is invalid, and that its subsequent unmapping
 634  632           * should not affect any of the parent's CQ or QP resources.
 635  633           */
 636  634          new_dvm_track = (hermon_devmap_track_t *)kmem_zalloc(
 637  635              sizeof (hermon_devmap_track_t), KM_SLEEP);
 638      -        _NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*new_dvm_track))
 639  636          new_dvm_track->hdt_offset = 0;
 640  637          new_dvm_track->hdt_state  = state;
 641  638          new_dvm_track->hdt_refcnt = 1;
 642  639          new_dvm_track->hdt_size   = 0;
 643  640          mutex_init(&new_dvm_track->hdt_lock, NULL, MUTEX_DRIVER,
 644  641              DDI_INTR_PRI(state->hs_intrmsi_pri));
 645  642          *new_pvtp = new_dvm_track;
 646  643  
 647  644          return (DDI_SUCCESS);
 648  645  }
↓ open down ↓ 18 lines elided ↑ open up ↑
 667  664          uint64_t                key, value;
 668  665          uint_t                  type;
 669  666          uint_t                  size;
 670  667          int                     status;
 671  668  
 672  669          /*
 673  670           * Extract the Hermon softstate pointer from "Hermon devmap tracking
 674  671           * structure" (in "pvtp").
 675  672           */
 676  673          dvm_track = (hermon_devmap_track_t *)pvtp;
 677      -        _NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*dvm_track))
 678  674          state     = dvm_track->hdt_state;
 679  675  
 680  676          /*
 681  677           * Extract the "offset" from the "Hermon devmap tracking structure".
 682  678           * Note: The input argument "off" is ignored here because the
 683  679           * Hermon mapping interfaces define a very specific meaning to
 684  680           * each "logical offset".  Also extract the "key" and "type" encoded
 685  681           * in the logical offset.
 686  682           */
 687  683          key  = dvm_track->hdt_offset >> PAGESHIFT;
↓ open down ↓ 200 lines elided ↑ open up ↑
 888  884          key  = off >> PAGESHIFT;
 889  885          type = key & MLNX_UMAP_RSRC_TYPE_MASK;
 890  886          key  = key >> MLNX_UMAP_RSRC_TYPE_SHIFT;
 891  887  
 892  888          /*
 893  889           * Allocate an entry to track the mapping and unmapping (specifically,
 894  890           * partial unmapping) of this resource.
 895  891           */
 896  892          dvm_track = (hermon_devmap_track_t *)kmem_zalloc(
 897  893              sizeof (hermon_devmap_track_t), KM_SLEEP);
 898      -        _NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*dvm_track))
 899  894          dvm_track->hdt_offset = off;
 900  895          dvm_track->hdt_state  = state;
 901  896          dvm_track->hdt_refcnt = 1;
 902  897          mutex_init(&dvm_track->hdt_lock, NULL, MUTEX_DRIVER,
 903  898              DDI_INTR_PRI(state->hs_intrmsi_pri));
 904  899  
 905  900          /*
 906  901           * Depending of the type of resource that has been mapped out, we
 907  902           * need to update the QP or CQ handle to reflect that it has, in
 908  903           * fact, been mapped.  This allows the driver code which frees a QP
↓ open down ↓ 89 lines elided ↑ open up ↑
 998  993          hermon_state_t          *state;
 999  994          hermon_devmap_track_t   *dvm_track, *new_dvm_track;
1000  995          uint_t                  maxprot;
1001  996          int                     status;
1002  997  
1003  998          /*
1004  999           * Extract the Hermon softstate pointer from "Hermon devmap tracking
1005 1000           * structure" (in "pvtp").
1006 1001           */
1007 1002          dvm_track = (hermon_devmap_track_t *)pvtp;
1008      -        _NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*dvm_track))
1009 1003          state = dvm_track->hdt_state;
1010 1004  
1011 1005          /*
1012 1006           * Since this devmap_dup() entry point is generally called
1013 1007           * when a process does fork(2), it is incumbent upon the driver
1014 1008           * to insure that the child does not inherit a valid copy of
1015 1009           * the parent's QP or CQ resource.  This is accomplished by using
1016 1010           * devmap_devmem_remap() to invalidate the child's mapping to the
1017 1011           * kernel memory.
1018 1012           */
↓ open down ↓ 8 lines elided ↑ open up ↑
1027 1021          /*
1028 1022           * Allocate a new entry to track the subsequent unmapping
1029 1023           * (specifically, all partial unmappings) of the child's newly
1030 1024           * invalidated resource.  Note: Setting the "hdt_size" field to
1031 1025           * zero here is an indication to the devmap_unmap() entry point
1032 1026           * that this mapping is invalid, and that its subsequent unmapping
1033 1027           * should not affect any of the parent's CQ or QP resources.
1034 1028           */
1035 1029          new_dvm_track = (hermon_devmap_track_t *)kmem_zalloc(
1036 1030              sizeof (hermon_devmap_track_t), KM_SLEEP);
1037      -        _NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*new_dvm_track))
1038 1031          new_dvm_track->hdt_offset = 0;
1039 1032          new_dvm_track->hdt_state  = state;
1040 1033          new_dvm_track->hdt_refcnt = 1;
1041 1034          new_dvm_track->hdt_size   = 0;
1042 1035          mutex_init(&new_dvm_track->hdt_lock, NULL, MUTEX_DRIVER,
1043 1036              DDI_INTR_PRI(state->hs_intrmsi_pri));
1044 1037          *new_pvtp = new_dvm_track;
1045 1038  
1046 1039          return (DDI_SUCCESS);
1047 1040  }
↓ open down ↓ 18 lines elided ↑ open up ↑
1066 1059          uint64_t                key, value;
1067 1060          uint_t                  type;
1068 1061          uint_t                  size;
1069 1062          int                     status;
1070 1063  
1071 1064          /*
1072 1065           * Extract the Hermon softstate pointer from "Hermon devmap tracking
1073 1066           * structure" (in "pvtp").
1074 1067           */
1075 1068          dvm_track = (hermon_devmap_track_t *)pvtp;
1076      -        _NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*dvm_track))
1077 1069          state     = dvm_track->hdt_state;
1078 1070  
1079 1071          /*
1080 1072           * Extract the "offset" from the "Hermon devmap tracking structure".
1081 1073           * Note: The input argument "off" is ignored here because the
1082 1074           * Hermon mapping interfaces define a very specific meaning to
1083 1075           * each "logical offset".  Also extract the "key" and "type" encoded
1084 1076           * in the logical offset.
1085 1077           */
1086 1078          key  = dvm_track->hdt_offset >> PAGESHIFT;
↓ open down ↓ 172 lines elided ↑ open up ↑
1259 1251          /*
1260 1252           * Allocate an entry to track the mapping and unmapping of this
1261 1253           * resource.  Note:  We don't need to initialize the "refcnt" or
1262 1254           * "offset" fields here, nor do we need to initialize the mutex
1263 1255           * used with the "refcnt".  Since UAR pages are single pages, they
1264 1256           * are not subject to "partial" unmappings.  This makes these other
1265 1257           * fields unnecessary.
1266 1258           */
1267 1259          dvm_track = (hermon_devmap_track_t *)kmem_zalloc(
1268 1260              sizeof (hermon_devmap_track_t), KM_SLEEP);
1269      -        _NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*dvm_track))
1270 1261          dvm_track->hdt_state  = state;
1271 1262          dvm_track->hdt_size   = (uint_t)PAGESIZE;
1272 1263  
1273 1264          /*
1274 1265           * Pass the private "Hermon devmap tracking structure" back.  This
1275 1266           * pointer will be returned in a subsequent "unmap" callback.
1276 1267           */
1277 1268          *pvtp = dvm_track;
1278 1269  
1279 1270          return (DDI_SUCCESS);
↓ open down ↓ 19 lines elided ↑ open up ↑
1299 1290           * structure" (in "pvtp").  Note: If the tracking structure is NULL
1300 1291           * here, it means that the mapping corresponds to an invalid mapping.
1301 1292           * In this case, it can be safely ignored ("new_pvtp" set to NULL).
1302 1293           */
1303 1294          dvm_track = (hermon_devmap_track_t *)pvtp;
1304 1295          if (dvm_track == NULL) {
1305 1296                  *new_pvtp = NULL;
1306 1297                  return (DDI_SUCCESS);
1307 1298          }
1308 1299  
1309      -        _NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*dvm_track))
1310 1300          state = dvm_track->hdt_state;
1311 1301  
1312 1302          /*
1313 1303           * Since this devmap_dup() entry point is generally called
1314 1304           * when a process does fork(2), it is incumbent upon the driver
1315 1305           * to insure that the child does not inherit a valid copy of
1316 1306           * the parent's resource.  This is accomplished by using
1317 1307           * devmap_devmem_remap() to invalidate the child's mapping to the
1318 1308           * kernel memory.
1319 1309           */
↓ open down ↓ 31 lines elided ↑ open up ↑
1351 1341          hermon_devmap_track_t   *dvm_track;
1352 1342  
1353 1343          /*
1354 1344           * Free up the "Hermon devmap tracking structure" (in "pvtp").
1355 1345           * There cannot be "partial" unmappings here because all UAR pages
1356 1346           * are single pages.  Note: If the tracking structure is NULL here,
1357 1347           * it means that the mapping corresponds to an invalid mapping.  In
1358 1348           * this case, it can be safely ignored.
1359 1349           */
1360 1350          dvm_track = (hermon_devmap_track_t *)pvtp;
1361      -        _NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*dvm_track))
1362 1351          if (dvm_track == NULL) {
1363 1352                  return;
1364 1353          }
1365 1354  
1366 1355          kmem_free(dvm_track, sizeof (hermon_devmap_track_t));
1367 1356  }
1368 1357  
1369 1358  
1370 1359  /*
1371 1360   * hermon_umap_ci_data_in()
↓ open down ↓ 61 lines elided ↑ open up ↑
1433 1422  
1434 1423          /* Check for valid MR handle pointer */
1435 1424          if (mr == NULL) {
1436 1425                  return (IBT_MR_HDL_INVALID);
1437 1426          }
1438 1427  
1439 1428          /* Check for valid MR input structure size */
1440 1429          if (data_sz < sizeof (ibt_mr_data_in_t)) {
1441 1430                  return (IBT_INSUFF_RESOURCE);
1442 1431          }
1443      -        _NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*data))
1444 1432  
1445 1433          /*
1446 1434           * Ensure that the MR corresponds to userland memory and that it is
1447 1435           * a currently valid memory region as well.
1448 1436           */
1449 1437          mutex_enter(&mr->mr_lock);
1450 1438          if ((mr->mr_is_umem == 0) || (mr->mr_umemcookie == NULL)) {
1451 1439                  mutex_exit(&mr->mr_lock);
1452 1440                  return (IBT_MR_HDL_INVALID);
1453 1441          }
↓ open down ↓ 96 lines elided ↑ open up ↑
1550 1538  {
1551 1539          /* Check for valid CQ handle pointer */
1552 1540          if (cq == NULL) {
1553 1541                  return (IBT_CQ_HDL_INVALID);
1554 1542          }
1555 1543  
1556 1544          /* Check for valid CQ mapping structure size */
1557 1545          if (data_sz < sizeof (mlnx_umap_cq_data_out_t)) {
1558 1546                  return (IBT_INSUFF_RESOURCE);
1559 1547          }
1560      -        _NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*data))
1561 1548  
1562 1549          /* deal with cq_alloc() verses cq_resize() */
1563 1550          if (cq->cq_resize_hdl) {
1564 1551                  data->mcq_maplen = cq->cq_resize_hdl->cq_cqinfo.qa_size;
1565 1552                  data->mcq_numcqe = cq->cq_resize_hdl->cq_bufsz;
1566 1553          } else {
1567 1554                  data->mcq_maplen = cq->cq_cqinfo.qa_size;
1568 1555                  data->mcq_numcqe = cq->cq_bufsz;
1569 1556          }
1570 1557  
↓ open down ↓ 42 lines elided ↑ open up ↑
1613 1600  {
1614 1601          /* Check for valid QP handle pointer */
1615 1602          if (qp == NULL) {
1616 1603                  return (IBT_QP_HDL_INVALID);
1617 1604          }
1618 1605  
1619 1606          /* Check for valid QP mapping structure size */
1620 1607          if (data_sz < sizeof (mlnx_umap_qp_data_out_t)) {
1621 1608                  return (IBT_INSUFF_RESOURCE);
1622 1609          }
1623      -        _NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*data))
1624 1610  
1625 1611          /*
1626 1612           * If it has passed all the checks, then fill in all the useful
1627 1613           * mapping information (including the mapping offset that will be
1628 1614           * passed back to the devmap() interface during a subsequent mmap()
1629 1615           * call.
1630 1616           *
1631 1617           * The "offset" for QP mmap()'s looks like this:
1632 1618           * +----------------------------------------+--------+--------------+
1633 1619           * |               QP Number                |  0x44  | Reserved (0) |
↓ open down ↓ 70 lines elided ↑ open up ↑
1704 1690  {
1705 1691          /* Check for valid SRQ handle pointer */
1706 1692          if (srq == NULL) {
1707 1693                  return (IBT_SRQ_HDL_INVALID);
1708 1694          }
1709 1695  
1710 1696          /* Check for valid SRQ mapping structure size */
1711 1697          if (data_sz < sizeof (mlnx_umap_srq_data_out_t)) {
1712 1698                  return (IBT_INSUFF_RESOURCE);
1713 1699          }
1714      -        _NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*data))
1715 1700  
1716 1701          /*
1717 1702           * If it has passed all the checks, then fill in all the useful
1718 1703           * mapping information (including the mapping offset that will be
1719 1704           * passed back to the devmap() interface during a subsequent mmap()
1720 1705           * call.
1721 1706           *
1722 1707           * The "offset" for SRQ mmap()'s looks like this:
1723 1708           * +----------------------------------------+--------+--------------+
1724 1709           * |               SRQ Number               |  0x66  | Reserved (0) |
↓ open down ↓ 40 lines elided ↑ open up ↑
1765 1750  {
1766 1751          /* Check for valid PD handle pointer */
1767 1752          if (pd == NULL) {
1768 1753                  return (IBT_PD_HDL_INVALID);
1769 1754          }
1770 1755  
1771 1756          /* Check for valid PD mapping structure size */
1772 1757          if (data_sz < sizeof (mlnx_umap_pd_data_out_t)) {
1773 1758                  return (IBT_INSUFF_RESOURCE);
1774 1759          }
1775      -        _NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*data))
1776 1760  
1777 1761          /*
1778 1762           * If it has passed all the checks, then fill the PD table index
1779 1763           * (the PD table allocated index for the PD pd_pdnum).
1780 1764           */
1781 1765          data->mpd_rev           = MLNX_UMAP_IF_VERSION;
1782 1766          data->mpd_pdnum         = pd->pd_pdnum;
1783 1767  
1784 1768          return (DDI_SUCCESS);
1785 1769  }
↓ open down ↓ 50 lines elided ↑ open up ↑
1836 1820  hermon_umap_db_entry_t *
1837 1821  hermon_umap_db_alloc(uint_t instance, uint64_t key, uint_t type, uint64_t value)
1838 1822  {
1839 1823          hermon_umap_db_entry_t  *umapdb;
1840 1824  
1841 1825          /* Allocate an entry to add to the "userland resources database" */
1842 1826          umapdb = kmem_zalloc(sizeof (hermon_umap_db_entry_t), KM_NOSLEEP);
1843 1827          if (umapdb == NULL) {
1844 1828                  return (NULL);
1845 1829          }
1846      -        _NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*umapdb))
1847 1830  
1848 1831          /* Fill in the fields in the database entry */
1849 1832          umapdb->hdbe_common.hdb_instance  = instance;
1850 1833          umapdb->hdbe_common.hdb_type      = type;
1851 1834          umapdb->hdbe_common.hdb_key       = key;
1852 1835          umapdb->hdbe_common.hdb_value     = value;
1853 1836  
1854 1837          return (umapdb);
1855 1838  }
1856 1839  
↓ open down ↓ 28 lines elided ↑ open up ↑
1885 1868   *    Context: Can be called from user or kernel context.
1886 1869   */
1887 1870  void
1888 1871  hermon_umap_db_add_nolock(hermon_umap_db_entry_t *umapdb)
1889 1872  {
1890 1873          hermon_umap_db_query_t  query;
1891 1874          avl_index_t             where;
1892 1875  
1893 1876          ASSERT(MUTEX_HELD(&hermon_userland_rsrc_db.hdl_umapdb_lock));
1894 1877  
1895      -        _NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*umapdb))
1896      -
1897 1878          /*
1898 1879           * Copy the common portion of the "to-be-added" database entry
1899 1880           * into the "hermon_umap_db_query_t" structure.  We use this structure
1900 1881           * (with no flags set) to find the appropriate location in the
1901 1882           * "userland resources database" for the new entry to be added.
1902 1883           *
1903 1884           * Note: we expect that this entry should not be found in the
1904 1885           * database (unless something bad has happened).
1905 1886           */
1906 1887          query.hqdb_common = umapdb->hdbe_common;
↓ open down ↓ 55 lines elided ↑ open up ↑
1962 1943  
1963 1944          /*
1964 1945           * Perform the database query.  If no entry is found, then
1965 1946           * return failure, else continue.
1966 1947           */
1967 1948          entry = (hermon_umap_db_entry_t *)avl_find(
1968 1949              &hermon_userland_rsrc_db.hdl_umapdb_avl, &query, &where);
1969 1950          if (entry == NULL) {
1970 1951                  return (DDI_FAILURE);
1971 1952          }
1972      -        _NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*entry))
1973 1953  
1974 1954          /*
1975 1955           * If the flags argument specifies that the entry should
1976 1956           * be removed if found, then call avl_remove() to remove
1977 1957           * the entry from the database.
1978 1958           */
1979 1959          if (flags & HERMON_UMAP_DB_REMOVE) {
1980 1960  
1981 1961                  avl_remove(&hermon_userland_rsrc_db.hdl_umapdb_avl, entry);
1982 1962  
↓ open down ↓ 43 lines elided ↑ open up ↑
2026 2006          /*
2027 2007           * If this was userland memory, then we need to remove its entry
2028 2008           * from the "userland resources database".  Note:  We use the
2029 2009           * HERMON_UMAP_DB_IGNORE_INSTANCE flag here because we don't know
2030 2010           * which instance was used when the entry was added (but we want
2031 2011           * to know after the entry is found using the other search criteria).
2032 2012           */
2033 2013          status = hermon_umap_db_find(0, (uint64_t)(uintptr_t)umem_cookie,
2034 2014              MLNX_UMAP_MRMEM_RSRC, &value, (HERMON_UMAP_DB_REMOVE |
2035 2015              HERMON_UMAP_DB_IGNORE_INSTANCE), &umapdb);
2036      -        _NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*umapdb))
2037 2016          if (status == DDI_SUCCESS) {
2038 2017                  instance = umapdb->hdbe_common.hdb_instance;
2039 2018                  state = ddi_get_soft_state(hermon_statep, instance);
2040 2019                  if (state == NULL) {
2041 2020                          cmn_err(CE_WARN, "Unable to match Hermon instance\n");
2042 2021                          return;
2043 2022                  }
2044 2023  
2045 2024                  /* Free the database entry */
2046 2025                  hermon_umap_db_free(umapdb);
↓ open down ↓ 38 lines elided ↑ open up ↑
2085 2064  /*
2086 2065   * hermon_umap_db_compare()
2087 2066   *    Context: Can be called from user or kernel context.
2088 2067   */
2089 2068  static int
2090 2069  hermon_umap_db_compare(const void *q, const void *e)
2091 2070  {
2092 2071          hermon_umap_db_common_t *entry_common, *query_common;
2093 2072          uint_t                  query_flags;
2094 2073  
2095      -        _NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*((hermon_umap_db_query_t *)q)))
2096      -
2097 2074          entry_common = &((hermon_umap_db_entry_t *)e)->hdbe_common;
2098 2075          query_common = &((hermon_umap_db_query_t *)q)->hqdb_common;
2099 2076          query_flags  = ((hermon_umap_db_query_t *)q)->hqdb_flags;
2100 2077  
2101 2078          /*
2102 2079           * The first comparison is done on the "key" value in "query"
2103 2080           * and "entry".  If they are not equal, then the appropriate
2104 2081           * search direction is returned.  Else, we continue by
2105 2082           * comparing "type".
2106 2083           */
↓ open down ↓ 181 lines elided ↑ open up ↑
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX