Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Linux/drivers/cxl/core/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 92 kB image not shown  

Quelle  region.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2022 Intel Corporation. All rights reserved. */
#include <linux/memregion.h>
#include <linux/genalloc.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/memory.h>
#include <linux/slab.h>
#include <linux/uuid.h>
#include <linux/sort.h>
#include <linux/idr.h>
#include <linux/memory-tiers.h>
#include <cxlmem.h>
#include <cxl.h>
#include "core.h"

/**
 * DOC: cxl core region
 *
 * CXL Regions represent mapped memory capacity in system physical address
 * space. Whereas the CXL Root Decoders identify the bounds of potential CXL
 * Memory ranges, Regions represent the active mapped capacity by the HDM
 * Decoder Capability structures throughout the Host Bridges, Switches, and
 * Endpoints in the topology.
 *
 * Region configuration has ordering constraints. UUID may be set at any time
 * but is only visible for persistent regions.
 * 1. Interleave granularity
 * 2. Interleave size
 * 3. Decoder targets
 */


static struct cxl_region *to_cxl_region(struct device *dev);

#define __ACCESS_ATTR_RO(_level, _name) {    \
 .attr = { .name = __stringify(_name), .mode = 0444 },  \
 .show = _name##_access##_level##_show,   \
}

#define ACCESS_DEVICE_ATTR_RO(level, name) \
 struct device_attribute dev_attr_access##level##_##name = __ACCESS_ATTR_RO(level, name)

#define ACCESS_ATTR_RO(level, attrib)           \
static ssize_t attrib##_access##level##_show(struct device *dev,       \
       struct device_attribute *attr,      \
       char *buf)         \
{               \
 struct cxl_region *cxlr = to_cxl_region(dev);         \
               \
 if (cxlr->coord[level].attrib == 0)          \
  return -ENOENT;            \
               \
 return sysfs_emit(buf, "%u\n", cxlr->coord[level].attrib);       \
}               \
static ACCESS_DEVICE_ATTR_RO(level, attrib)

ACCESS_ATTR_RO(0, read_bandwidth);
ACCESS_ATTR_RO(0, read_latency);
ACCESS_ATTR_RO(0, write_bandwidth);
ACCESS_ATTR_RO(0, write_latency);

#define ACCESS_ATTR_DECLARE(level, attrib) \
 (&dev_attr_access##level##_##attrib.attr)

static struct attribute *access0_coordinate_attrs[] = {
 ACCESS_ATTR_DECLARE(0, read_bandwidth),
 ACCESS_ATTR_DECLARE(0, write_bandwidth),
 ACCESS_ATTR_DECLARE(0, read_latency),
 ACCESS_ATTR_DECLARE(0, write_latency),
 NULL
};

ACCESS_ATTR_RO(1, read_bandwidth);
ACCESS_ATTR_RO(1, read_latency);
ACCESS_ATTR_RO(1, write_bandwidth);
ACCESS_ATTR_RO(1, write_latency);

static struct attribute *access1_coordinate_attrs[] = {
 ACCESS_ATTR_DECLARE(1, read_bandwidth),
 ACCESS_ATTR_DECLARE(1, write_bandwidth),
 ACCESS_ATTR_DECLARE(1, read_latency),
 ACCESS_ATTR_DECLARE(1, write_latency),
 NULL
};

#define ACCESS_VISIBLE(level)      \
static umode_t cxl_region_access##level##_coordinate_visible(  \
  struct kobject *kobj, struct attribute *a, int n) \
{         \
 struct device *dev = kobj_to_dev(kobj);    \
 struct cxl_region *cxlr = to_cxl_region(dev);   \
         \
 if (a == &dev_attr_access##level##_read_latency.attr &&  \
     cxlr->coord[level].read_latency == 0)   \
  return 0;      \
         \
 if (a == &dev_attr_access##level##_write_latency.attr && \
     cxlr->coord[level].write_latency == 0)   \
  return 0;      \
         \
 if (a == &dev_attr_access##level##_read_bandwidth.attr && \
     cxlr->coord[level].read_bandwidth == 0)   \
  return 0;      \
         \
 if (a == &dev_attr_access##level##_write_bandwidth.attr && \
     cxlr->coord[level].write_bandwidth == 0)   \
  return 0;      \
         \
 return a->mode;       \
}

ACCESS_VISIBLE(0);
ACCESS_VISIBLE(1);

static const struct attribute_group cxl_region_access0_coordinate_group = {
 .name = "access0",
 .attrs = access0_coordinate_attrs,
 .is_visible = cxl_region_access0_coordinate_visible,
};

static const struct attribute_group *get_cxl_region_access0_group(void)
{
 return &cxl_region_access0_coordinate_group;
}

static const struct attribute_group cxl_region_access1_coordinate_group = {
 .name = "access1",
 .attrs = access1_coordinate_attrs,
 .is_visible = cxl_region_access1_coordinate_visible,
};

static const struct attribute_group *get_cxl_region_access1_group(void)
{
 return &cxl_region_access1_coordinate_group;
}

static ssize_t uuid_show(struct device *dev, struct device_attribute *attr,
    char *buf)
{
 struct cxl_region *cxlr = to_cxl_region(dev);
 struct cxl_region_params *p = &cxlr->params;
 ssize_t rc;

 ACQUIRE(rwsem_read_intr, region_rwsem)(&cxl_rwsem.region);
 if ((rc = ACQUIRE_ERR(rwsem_read_intr, ®ion_rwsem)))
  return rc;
 if (cxlr->mode != CXL_PARTMODE_PMEM)
  return sysfs_emit(buf, "\n");
 return sysfs_emit(buf, "%pUb\n", &p->uuid);
}

static int is_dup(struct device *match, void *data)
{
 struct cxl_region_params *p;
 struct cxl_region *cxlr;
 uuid_t *uuid = data;

 if (!is_cxl_region(match))
  return 0;

 lockdep_assert_held(&cxl_rwsem.region);
 cxlr = to_cxl_region(match);
 p = &cxlr->params;

 if (uuid_equal(&p->uuid, uuid)) {
  dev_dbg(match, "already has uuid: %pUb\n", uuid);
  return -EBUSY;
 }

 return 0;
}

static ssize_t uuid_store(struct device *dev, struct device_attribute *attr,
     const char *buf, size_t len)
{
 struct cxl_region *cxlr = to_cxl_region(dev);
 struct cxl_region_params *p = &cxlr->params;
 uuid_t temp;
 ssize_t rc;

 if (len != UUID_STRING_LEN + 1)
  return -EINVAL;

 rc = uuid_parse(buf, &temp);
 if (rc)
  return rc;

 if (uuid_is_null(&temp))
  return -EINVAL;

 ACQUIRE(rwsem_write_kill, region_rwsem)(&cxl_rwsem.region);
 if ((rc = ACQUIRE_ERR(rwsem_write_kill, ®ion_rwsem)))
  return rc;

 if (uuid_equal(&p->uuid, &temp))
  return len;

 if (p->state >= CXL_CONFIG_ACTIVE)
  return -EBUSY;

 rc = bus_for_each_dev(&cxl_bus_type, NULL, &temp, is_dup);
 if (rc < 0)
  return rc;

 uuid_copy(&p->uuid, &temp);

 return len;
}
static DEVICE_ATTR_RW(uuid);

static struct cxl_region_ref *cxl_rr_load(struct cxl_port *port,
       struct cxl_region *cxlr)
{
 return xa_load(&port->regions, (unsigned long)cxlr);
}

static int cxl_region_invalidate_memregion(struct cxl_region *cxlr)
{
 if (!cpu_cache_has_invalidate_memregion()) {
  if (IS_ENABLED(CONFIG_CXL_REGION_INVALIDATION_TEST)) {
   dev_info_once(
    &cxlr->dev,
    "Bypassing cpu_cache_invalidate_memregion() for testing!\n");
   return 0;
  }
  dev_WARN(&cxlr->dev,
   "Failed to synchronize CPU cache state\n");
  return -ENXIO;
 }

 cpu_cache_invalidate_memregion(IORES_DESC_CXL);
 return 0;
}

static void cxl_region_decode_reset(struct cxl_region *cxlr, int count)
{
 struct cxl_region_params *p = &cxlr->params;
 int i;

 /*
 * Before region teardown attempt to flush, evict any data cached for
 * this region, or scream loudly about missing arch / platform support
 * for CXL teardown.
 */

 cxl_region_invalidate_memregion(cxlr);

 for (i = count - 1; i >= 0; i--) {
  struct cxl_endpoint_decoder *cxled = p->targets[i];
  struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
  struct cxl_port *iter = cxled_to_port(cxled);
  struct cxl_dev_state *cxlds = cxlmd->cxlds;
  struct cxl_ep *ep;

  if (cxlds->rcd)
   goto endpoint_reset;

  while (!is_cxl_root(to_cxl_port(iter->dev.parent)))
   iter = to_cxl_port(iter->dev.parent);

  for (ep = cxl_ep_load(iter, cxlmd); iter;
       iter = ep->next, ep = cxl_ep_load(iter, cxlmd)) {
   struct cxl_region_ref *cxl_rr;
   struct cxl_decoder *cxld;

   cxl_rr = cxl_rr_load(iter, cxlr);
   cxld = cxl_rr->decoder;
   if (cxld->reset)
    cxld->reset(cxld);
   set_bit(CXL_REGION_F_NEEDS_RESET, &cxlr->flags);
  }

endpoint_reset:
  cxled->cxld.reset(&cxled->cxld);
  set_bit(CXL_REGION_F_NEEDS_RESET, &cxlr->flags);
 }

 /* all decoders associated with this region have been torn down */
 clear_bit(CXL_REGION_F_NEEDS_RESET, &cxlr->flags);
}

static int commit_decoder(struct cxl_decoder *cxld)
{
 struct cxl_switch_decoder *cxlsd = NULL;

 if (cxld->commit)
  return cxld->commit(cxld);

 if (is_switch_decoder(&cxld->dev))
  cxlsd = to_cxl_switch_decoder(&cxld->dev);

 if (dev_WARN_ONCE(&cxld->dev, !cxlsd || cxlsd->nr_targets > 1,
     "->commit() is required\n"))
  return -ENXIO;
 return 0;
}

static int cxl_region_decode_commit(struct cxl_region *cxlr)
{
 struct cxl_region_params *p = &cxlr->params;
 int i, rc = 0;

 for (i = 0; i < p->nr_targets; i++) {
  struct cxl_endpoint_decoder *cxled = p->targets[i];
  struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
  struct cxl_region_ref *cxl_rr;
  struct cxl_decoder *cxld;
  struct cxl_port *iter;
  struct cxl_ep *ep;

  /* commit bottom up */
  for (iter = cxled_to_port(cxled); !is_cxl_root(iter);
       iter = to_cxl_port(iter->dev.parent)) {
   cxl_rr = cxl_rr_load(iter, cxlr);
   cxld = cxl_rr->decoder;
   rc = commit_decoder(cxld);
   if (rc)
    break;
  }

  if (rc) {
   /* programming @iter failed, teardown */
   for (ep = cxl_ep_load(iter, cxlmd); ep && iter;
        iter = ep->next, ep = cxl_ep_load(iter, cxlmd)) {
    cxl_rr = cxl_rr_load(iter, cxlr);
    cxld = cxl_rr->decoder;
    if (cxld->reset)
     cxld->reset(cxld);
   }

   cxled->cxld.reset(&cxled->cxld);
   goto err;
  }
 }

 return 0;

err:
 /* undo the targets that were successfully committed */
 cxl_region_decode_reset(cxlr, i);
 return rc;
}

static int queue_reset(struct cxl_region *cxlr)
{
 struct cxl_region_params *p = &cxlr->params;
 int rc;

 ACQUIRE(rwsem_write_kill, rwsem)(&cxl_rwsem.region);
 if ((rc = ACQUIRE_ERR(rwsem_write_kill, &rwsem)))
  return rc;

 /* Already in the requested state? */
 if (p->state < CXL_CONFIG_COMMIT)
  return 0;

 p->state = CXL_CONFIG_RESET_PENDING;

 return 0;
}

static int __commit(struct cxl_region *cxlr)
{
 struct cxl_region_params *p = &cxlr->params;
 int rc;

 ACQUIRE(rwsem_write_kill, rwsem)(&cxl_rwsem.region);
 if ((rc = ACQUIRE_ERR(rwsem_write_kill, &rwsem)))
  return rc;

 /* Already in the requested state? */
 if (p->state >= CXL_CONFIG_COMMIT)
  return 0;

 /* Not ready to commit? */
 if (p->state < CXL_CONFIG_ACTIVE)
  return -ENXIO;

 /*
 * Invalidate caches before region setup to drop any speculative
 * consumption of this address space
 */

 rc = cxl_region_invalidate_memregion(cxlr);
 if (rc)
  return rc;

 rc = cxl_region_decode_commit(cxlr);
 if (rc)
  return rc;

 p->state = CXL_CONFIG_COMMIT;

 return 0;
}

static ssize_t commit_store(struct device *dev, struct device_attribute *attr,
       const char *buf, size_t len)
{
 struct cxl_region *cxlr = to_cxl_region(dev);
 struct cxl_region_params *p = &cxlr->params;
 bool commit;
 ssize_t rc;

 rc = kstrtobool(buf, &commit);
 if (rc)
  return rc;

 if (commit) {
  rc = __commit(cxlr);
  if (rc)
   return rc;
  return len;
 }

 rc = queue_reset(cxlr);
 if (rc)
  return rc;

 /*
 * Unmap the region and depend the reset-pending state to ensure
 * it does not go active again until post reset
 */

 device_release_driver(&cxlr->dev);

 /*
 * With the reset pending take cxl_rwsem.region unconditionally
 * to ensure the reset gets handled before returning.
 */

 guard(rwsem_write)(&cxl_rwsem.region);

 /*
 * Revalidate that the reset is still pending in case another
 * thread already handled this reset.
 */

 if (p->state == CXL_CONFIG_RESET_PENDING) {
  cxl_region_decode_reset(cxlr, p->interleave_ways);
  p->state = CXL_CONFIG_ACTIVE;
 }

 return len;
}

static ssize_t commit_show(struct device *dev, struct device_attribute *attr,
      char *buf)
{
 struct cxl_region *cxlr = to_cxl_region(dev);
 struct cxl_region_params *p = &cxlr->params;
 ssize_t rc;

 ACQUIRE(rwsem_read_intr, rwsem)(&cxl_rwsem.region);
 if ((rc = ACQUIRE_ERR(rwsem_read_intr, &rwsem)))
  return rc;
 return sysfs_emit(buf, "%d\n", p->state >= CXL_CONFIG_COMMIT);
}
static DEVICE_ATTR_RW(commit);

static umode_t cxl_region_visible(struct kobject *kobj, struct attribute *a,
      int n)
{
 struct device *dev = kobj_to_dev(kobj);
 struct cxl_region *cxlr = to_cxl_region(dev);

 /*
 * Support tooling that expects to find a 'uuid' attribute for all
 * regions regardless of mode.
 */

 if (a == &dev_attr_uuid.attr && cxlr->mode != CXL_PARTMODE_PMEM)
  return 0444;
 return a->mode;
}

static ssize_t interleave_ways_show(struct device *dev,
        struct device_attribute *attr, char *buf)
{
 struct cxl_region *cxlr = to_cxl_region(dev);
 struct cxl_region_params *p = &cxlr->params;
 int rc;

 ACQUIRE(rwsem_read_intr, rwsem)(&cxl_rwsem.region);
 if ((rc = ACQUIRE_ERR(rwsem_read_intr, &rwsem)))
  return rc;
 return sysfs_emit(buf, "%d\n", p->interleave_ways);
}

static const struct attribute_group *get_cxl_region_target_group(void);

static ssize_t interleave_ways_store(struct device *dev,
         struct device_attribute *attr,
         const char *buf, size_t len)
{
 struct cxl_root_decoder *cxlrd = to_cxl_root_decoder(dev->parent);
 struct cxl_decoder *cxld = &cxlrd->cxlsd.cxld;
 struct cxl_region *cxlr = to_cxl_region(dev);
 struct cxl_region_params *p = &cxlr->params;
 unsigned int val, save;
 int rc;
 u8 iw;

 rc = kstrtouint(buf, 0, &val);
 if (rc)
  return rc;

 rc = ways_to_eiw(val, &iw);
 if (rc)
  return rc;

 /*
 * Even for x3, x6, and x12 interleaves the region interleave must be a
 * power of 2 multiple of the host bridge interleave.
 */

 if (!is_power_of_2(val / cxld->interleave_ways) ||
     (val % cxld->interleave_ways)) {
  dev_dbg(&cxlr->dev, "invalid interleave: %d\n", val);
  return -EINVAL;
 }

 ACQUIRE(rwsem_write_kill, rwsem)(&cxl_rwsem.region);
 if ((rc = ACQUIRE_ERR(rwsem_write_kill, &rwsem)))
  return rc;

 if (p->state >= CXL_CONFIG_INTERLEAVE_ACTIVE)
  return -EBUSY;

 save = p->interleave_ways;
 p->interleave_ways = val;
 rc = sysfs_update_group(&cxlr->dev.kobj, get_cxl_region_target_group());
 if (rc) {
  p->interleave_ways = save;
  return rc;
 }

 return len;
}
static DEVICE_ATTR_RW(interleave_ways);

static ssize_t interleave_granularity_show(struct device *dev,
        struct device_attribute *attr,
        char *buf)
{
 struct cxl_region *cxlr = to_cxl_region(dev);
 struct cxl_region_params *p = &cxlr->params;
 int rc;

 ACQUIRE(rwsem_read_intr, rwsem)(&cxl_rwsem.region);
 if ((rc = ACQUIRE_ERR(rwsem_read_intr, &rwsem)))
  return rc;
 return sysfs_emit(buf, "%d\n", p->interleave_granularity);
}

static ssize_t interleave_granularity_store(struct device *dev,
         struct device_attribute *attr,
         const char *buf, size_t len)
{
 struct cxl_root_decoder *cxlrd = to_cxl_root_decoder(dev->parent);
 struct cxl_decoder *cxld = &cxlrd->cxlsd.cxld;
 struct cxl_region *cxlr = to_cxl_region(dev);
 struct cxl_region_params *p = &cxlr->params;
 int rc, val;
 u16 ig;

 rc = kstrtoint(buf, 0, &val);
 if (rc)
  return rc;

 rc = granularity_to_eig(val, &ig);
 if (rc)
  return rc;

 /*
 * When the host-bridge is interleaved, disallow region granularity !=
 * root granularity. Regions with a granularity less than the root
 * interleave result in needing multiple endpoints to support a single
 * slot in the interleave (possible to support in the future). Regions
 * with a granularity greater than the root interleave result in invalid
 * DPA translations (invalid to support).
 */

 if (cxld->interleave_ways > 1 && val != cxld->interleave_granularity)
  return -EINVAL;

 ACQUIRE(rwsem_write_kill, rwsem)(&cxl_rwsem.region);
 if ((rc = ACQUIRE_ERR(rwsem_write_kill, &rwsem)))
  return rc;

 if (p->state >= CXL_CONFIG_INTERLEAVE_ACTIVE)
  return -EBUSY;

 p->interleave_granularity = val;

 return len;
}
static DEVICE_ATTR_RW(interleave_granularity);

static ssize_t resource_show(struct device *dev, struct device_attribute *attr,
        char *buf)
{
 struct cxl_region *cxlr = to_cxl_region(dev);
 struct cxl_region_params *p = &cxlr->params;
 u64 resource = -1ULL;
 int rc;

 ACQUIRE(rwsem_read_intr, rwsem)(&cxl_rwsem.region);
 if ((rc = ACQUIRE_ERR(rwsem_read_intr, &rwsem)))
  return rc;

 if (p->res)
  resource = p->res->start;
 return sysfs_emit(buf, "%#llx\n", resource);
}
static DEVICE_ATTR_RO(resource);

static ssize_t mode_show(struct device *dev, struct device_attribute *attr,
    char *buf)
{
 struct cxl_region *cxlr = to_cxl_region(dev);
 const char *desc;

 if (cxlr->mode == CXL_PARTMODE_RAM)
  desc = "ram";
 else if (cxlr->mode == CXL_PARTMODE_PMEM)
  desc = "pmem";
 else
  desc = "";

 return sysfs_emit(buf, "%s\n", desc);
}
static DEVICE_ATTR_RO(mode);

static int alloc_hpa(struct cxl_region *cxlr, resource_size_t size)
{
 struct cxl_root_decoder *cxlrd = to_cxl_root_decoder(cxlr->dev.parent);
 struct cxl_region_params *p = &cxlr->params;
 struct resource *res;
 u64 remainder = 0;

 lockdep_assert_held_write(&cxl_rwsem.region);

 /* Nothing to do... */
 if (p->res && resource_size(p->res) == size)
  return 0;

 /* To change size the old size must be freed first */
 if (p->res)
  return -EBUSY;

 if (p->state >= CXL_CONFIG_INTERLEAVE_ACTIVE)
  return -EBUSY;

 /* ways, granularity and uuid (if PMEM) need to be set before HPA */
 if (!p->interleave_ways || !p->interleave_granularity ||
     (cxlr->mode == CXL_PARTMODE_PMEM && uuid_is_null(&p->uuid)))
  return -ENXIO;

 div64_u64_rem(size, (u64)SZ_256M * p->interleave_ways, &remainder);
 if (remainder)
  return -EINVAL;

 res = alloc_free_mem_region(cxlrd->res, size, SZ_256M,
        dev_name(&cxlr->dev));
 if (IS_ERR(res)) {
  dev_dbg(&cxlr->dev,
   "HPA allocation error (%ld) for size:%pap in %s %pr\n",
   PTR_ERR(res), &size, cxlrd->res->name, cxlrd->res);
  return PTR_ERR(res);
 }

 p->res = res;
 p->state = CXL_CONFIG_INTERLEAVE_ACTIVE;

 return 0;
}

static void cxl_region_iomem_release(struct cxl_region *cxlr)
{
 struct cxl_region_params *p = &cxlr->params;

 if (device_is_registered(&cxlr->dev))
  lockdep_assert_held_write(&cxl_rwsem.region);
 if (p->res) {
  /*
 * Autodiscovered regions may not have been able to insert their
 * resource.
 */

  if (p->res->parent)
   remove_resource(p->res);
  kfree(p->res);
  p->res = NULL;
 }
}

static int free_hpa(struct cxl_region *cxlr)
{
 struct cxl_region_params *p = &cxlr->params;

 lockdep_assert_held_write(&cxl_rwsem.region);

 if (!p->res)
  return 0;

 if (p->state >= CXL_CONFIG_ACTIVE)
  return -EBUSY;

 cxl_region_iomem_release(cxlr);
 p->state = CXL_CONFIG_IDLE;
 return 0;
}

static ssize_t size_store(struct device *dev, struct device_attribute *attr,
     const char *buf, size_t len)
{
 struct cxl_region *cxlr = to_cxl_region(dev);
 u64 val;
 int rc;

 rc = kstrtou64(buf, 0, &val);
 if (rc)
  return rc;

 ACQUIRE(rwsem_write_kill, rwsem)(&cxl_rwsem.region);
 if ((rc = ACQUIRE_ERR(rwsem_write_kill, &rwsem)))
  return rc;

 if (val)
  rc = alloc_hpa(cxlr, val);
 else
  rc = free_hpa(cxlr);

 if (rc)
  return rc;

 return len;
}

static ssize_t size_show(struct device *dev, struct device_attribute *attr,
    char *buf)
{
 struct cxl_region *cxlr = to_cxl_region(dev);
 struct cxl_region_params *p = &cxlr->params;
 u64 size = 0;
 ssize_t rc;

 ACQUIRE(rwsem_read_intr, rwsem)(&cxl_rwsem.region);
 if ((rc = ACQUIRE_ERR(rwsem_read_intr, &rwsem)))
  return rc;
 if (p->res)
  size = resource_size(p->res);
 return sysfs_emit(buf, "%#llx\n", size);
}
static DEVICE_ATTR_RW(size);

static struct attribute *cxl_region_attrs[] = {
 &dev_attr_uuid.attr,
 &dev_attr_commit.attr,
 &dev_attr_interleave_ways.attr,
 &dev_attr_interleave_granularity.attr,
 &dev_attr_resource.attr,
 &dev_attr_size.attr,
 &dev_attr_mode.attr,
 NULL,
};

static const struct attribute_group cxl_region_group = {
 .attrs = cxl_region_attrs,
 .is_visible = cxl_region_visible,
};

static size_t show_targetN(struct cxl_region *cxlr, char *buf, int pos)
{
 struct cxl_region_params *p = &cxlr->params;
 struct cxl_endpoint_decoder *cxled;
 int rc;

 ACQUIRE(rwsem_read_intr, rwsem)(&cxl_rwsem.region);
 if ((rc = ACQUIRE_ERR(rwsem_read_intr, &rwsem)))
  return rc;

 if (pos >= p->interleave_ways) {
  dev_dbg(&cxlr->dev, "position %d out of range %d\n", pos,
   p->interleave_ways);
  return -ENXIO;
 }

 cxled = p->targets[pos];
 if (!cxled)
  return sysfs_emit(buf, "\n");
 return sysfs_emit(buf, "%s\n", dev_name(&cxled->cxld.dev));
}

static int check_commit_order(struct device *dev, void *data)
{
 struct cxl_decoder *cxld = to_cxl_decoder(dev);

 /*
 * if port->commit_end is not the only free decoder, then out of
 * order shutdown has occurred, block further allocations until
 * that is resolved
 */

 if (((cxld->flags & CXL_DECODER_F_ENABLE) == 0))
  return -EBUSY;
 return 0;
}

static int match_free_decoder(struct device *dev, const void *data)
{
 struct cxl_port *port = to_cxl_port(dev->parent);
 struct cxl_decoder *cxld;
 int rc;

 if (!is_switch_decoder(dev))
  return 0;

 cxld = to_cxl_decoder(dev);

 if (cxld->id != port->commit_end + 1)
  return 0;

 if (cxld->region) {
  dev_dbg(dev->parent,
   "next decoder to commit (%s) is already reserved (%s)\n",
   dev_name(dev), dev_name(&cxld->region->dev));
  return 0;
 }

 rc = device_for_each_child_reverse_from(dev->parent, dev, NULL,
      check_commit_order);
 if (rc) {
  dev_dbg(dev->parent,
   "unable to allocate %s due to out of order shutdown\n",
   dev_name(dev));
  return 0;
 }
 return 1;
}

static bool region_res_match_cxl_range(const struct cxl_region_params *p,
           const struct range *range)
{
 if (!p->res)
  return false;

 /*
 * If an extended linear cache region then the CXL range is assumed
 * to be fronted by the DRAM range in current known implementation.
 * This assumption will be made until a variant implementation exists.
 */

 return p->res->start + p->cache_size == range->start &&
  p->res->end == range->end;
}

static int match_auto_decoder(struct device *dev, const void *data)
{
 const struct cxl_region_params *p = data;
 struct cxl_decoder *cxld;
 struct range *r;

 if (!is_switch_decoder(dev))
  return 0;

 cxld = to_cxl_decoder(dev);
 r = &cxld->hpa_range;

 if (region_res_match_cxl_range(p, r))
  return 1;

 return 0;
}

/**
 * cxl_port_pick_region_decoder() - assign or lookup a decoder for a region
 * @port: a port in the ancestry of the endpoint implied by @cxled
 * @cxled: endpoint decoder to be, or currently, mapped by @port
 * @cxlr: region to establish, or validate, decode @port
 *
 * In the region creation path cxl_port_pick_region_decoder() is an
 * allocator to find a free port. In the region assembly path, it is
 * recalling the decoder that platform firmware picked for validation
 * purposes.
 *
 * The result is recorded in a 'struct cxl_region_ref' in @port.
 */

static struct cxl_decoder *
cxl_port_pick_region_decoder(struct cxl_port *port,
        struct cxl_endpoint_decoder *cxled,
        struct cxl_region *cxlr)
{
 struct device *dev;

 if (port == cxled_to_port(cxled))
  return &cxled->cxld;

 if (test_bit(CXL_REGION_F_AUTO, &cxlr->flags))
  dev = device_find_child(&port->dev, &cxlr->params,
     match_auto_decoder);
 else
  dev = device_find_child(&port->dev, NULL, match_free_decoder);
 if (!dev)
  return NULL;
 /*
 * This decoder is pinned registered as long as the endpoint decoder is
 * registered, and endpoint decoder unregistration holds the
 * cxl_rwsem.region over unregister events, so no need to hold on to
 * this extra reference.
 */

 put_device(dev);
 return to_cxl_decoder(dev);
}

static bool auto_order_ok(struct cxl_port *port, struct cxl_region *cxlr_iter,
     struct cxl_decoder *cxld)
{
 struct cxl_region_ref *rr = cxl_rr_load(port, cxlr_iter);
 struct cxl_decoder *cxld_iter = rr->decoder;

 /*
 * Allow the out of order assembly of auto-discovered regions.
 * Per CXL Spec 3.1 8.2.4.20.12 software must commit decoders
 * in HPA order. Confirm that the decoder with the lesser HPA
 * starting address has the lesser id.
 */

 dev_dbg(&cxld->dev, "check for HPA violation %s:%d < %s:%d\n",
  dev_name(&cxld->dev), cxld->id,
  dev_name(&cxld_iter->dev), cxld_iter->id);

 if (cxld_iter->id > cxld->id)
  return true;

 return false;
}

static struct cxl_region_ref *
alloc_region_ref(struct cxl_port *port, struct cxl_region *cxlr,
   struct cxl_endpoint_decoder *cxled,
   struct cxl_decoder *cxld)
{
 struct cxl_region_params *p = &cxlr->params;
 struct cxl_region_ref *cxl_rr, *iter;
 unsigned long index;
 int rc;

 xa_for_each(&port->regions, index, iter) {
  struct cxl_region_params *ip = &iter->region->params;

  if (!ip->res || ip->res->start < p->res->start)
   continue;

  if (test_bit(CXL_REGION_F_AUTO, &cxlr->flags)) {
   if (auto_order_ok(port, iter->region, cxld))
    continue;
  }
  dev_dbg(&cxlr->dev, "%s: HPA order violation %s:%pr vs %pr\n",
   dev_name(&port->dev),
   dev_name(&iter->region->dev), ip->res, p->res);

  return ERR_PTR(-EBUSY);
 }

 cxl_rr = kzalloc(sizeof(*cxl_rr), GFP_KERNEL);
 if (!cxl_rr)
  return ERR_PTR(-ENOMEM);
 cxl_rr->port = port;
 cxl_rr->region = cxlr;
 cxl_rr->nr_targets = 1;
 xa_init(&cxl_rr->endpoints);

 rc = xa_insert(&port->regions, (unsigned long)cxlr, cxl_rr, GFP_KERNEL);
 if (rc) {
  dev_dbg(&cxlr->dev,
   "%s: failed to track region reference: %d\n",
   dev_name(&port->dev), rc);
  kfree(cxl_rr);
  return ERR_PTR(rc);
 }

 return cxl_rr;
}

static void cxl_rr_free_decoder(struct cxl_region_ref *cxl_rr)
{
 struct cxl_region *cxlr = cxl_rr->region;
 struct cxl_decoder *cxld = cxl_rr->decoder;

 if (!cxld)
  return;

 dev_WARN_ONCE(&cxlr->dev, cxld->region != cxlr, "region mismatch\n");
 if (cxld->region == cxlr) {
  cxld->region = NULL;
  put_device(&cxlr->dev);
 }
}

static void free_region_ref(struct cxl_region_ref *cxl_rr)
{
 struct cxl_port *port = cxl_rr->port;
 struct cxl_region *cxlr = cxl_rr->region;

 cxl_rr_free_decoder(cxl_rr);
 xa_erase(&port->regions, (unsigned long)cxlr);
 xa_destroy(&cxl_rr->endpoints);
 kfree(cxl_rr);
}

static int cxl_rr_ep_add(struct cxl_region_ref *cxl_rr,
    struct cxl_endpoint_decoder *cxled)
{
 int rc;
 struct cxl_port *port = cxl_rr->port;
 struct cxl_region *cxlr = cxl_rr->region;
 struct cxl_decoder *cxld = cxl_rr->decoder;
 struct cxl_ep *ep = cxl_ep_load(port, cxled_to_memdev(cxled));

 if (ep) {
  rc = xa_insert(&cxl_rr->endpoints, (unsigned long)cxled, ep,
          GFP_KERNEL);
  if (rc)
   return rc;
 }
 cxl_rr->nr_eps++;

 if (!cxld->region) {
  cxld->region = cxlr;
  get_device(&cxlr->dev);
 }

 return 0;
}

static int cxl_rr_assign_decoder(struct cxl_port *port, struct cxl_region *cxlr,
     struct cxl_endpoint_decoder *cxled,
     struct cxl_region_ref *cxl_rr,
     struct cxl_decoder *cxld)
{
 if (cxld->region) {
  dev_dbg(&cxlr->dev, "%s: %s already attached to %s\n",
   dev_name(&port->dev), dev_name(&cxld->dev),
   dev_name(&cxld->region->dev));
  return -EBUSY;
 }

 /*
 * Endpoints should already match the region type, but backstop that
 * assumption with an assertion. Switch-decoders change mapping-type
 * based on what is mapped when they are assigned to a region.
 */

 dev_WARN_ONCE(&cxlr->dev,
        port == cxled_to_port(cxled) &&
         cxld->target_type != cxlr->type,
        "%s:%s mismatch decoder type %d -> %d\n",
        dev_name(&cxled_to_memdev(cxled)->dev),
        dev_name(&cxld->dev), cxld->target_type, cxlr->type);
 cxld->target_type = cxlr->type;
 cxl_rr->decoder = cxld;
 return 0;
}

/**
 * cxl_port_attach_region() - track a region's interest in a port by endpoint
 * @port: port to add a new region reference 'struct cxl_region_ref'
 * @cxlr: region to attach to @port
 * @cxled: endpoint decoder used to create or further pin a region reference
 * @pos: interleave position of @cxled in @cxlr
 *
 * The attach event is an opportunity to validate CXL decode setup
 * constraints and record metadata needed for programming HDM decoders,
 * in particular decoder target lists.
 *
 * The steps are:
 *
 * - validate that there are no other regions with a higher HPA already
 *   associated with @port
 * - establish a region reference if one is not already present
 *
 *   - additionally allocate a decoder instance that will host @cxlr on
 *     @port
 *
 * - pin the region reference by the endpoint
 * - account for how many entries in @port's target list are needed to
 *   cover all of the added endpoints.
 */

static int cxl_port_attach_region(struct cxl_port *port,
      struct cxl_region *cxlr,
      struct cxl_endpoint_decoder *cxled, int pos)
{
 struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
 struct cxl_ep *ep = cxl_ep_load(port, cxlmd);
 struct cxl_region_ref *cxl_rr;
 bool nr_targets_inc = false;
 struct cxl_decoder *cxld;
 unsigned long index;
 int rc = -EBUSY;

 lockdep_assert_held_write(&cxl_rwsem.region);

 cxl_rr = cxl_rr_load(port, cxlr);
 if (cxl_rr) {
  struct cxl_ep *ep_iter;
  int found = 0;

  /*
 * Walk the existing endpoints that have been attached to
 * @cxlr at @port and see if they share the same 'next' port
 * in the downstream direction. I.e. endpoints that share common
 * upstream switch.
 */

  xa_for_each(&cxl_rr->endpoints, index, ep_iter) {
   if (ep_iter == ep)
    continue;
   if (ep_iter->next == ep->next) {
    found++;
    break;
   }
  }

  /*
 * New target port, or @port is an endpoint port that always
 * accounts its own local decode as a target.
 */

  if (!found || !ep->next) {
   cxl_rr->nr_targets++;
   nr_targets_inc = true;
  }
 } else {
  struct cxl_decoder *cxld;

  cxld = cxl_port_pick_region_decoder(port, cxled, cxlr);
  if (!cxld) {
   dev_dbg(&cxlr->dev, "%s: no decoder available\n",
    dev_name(&port->dev));
   return -EBUSY;
  }

  cxl_rr = alloc_region_ref(port, cxlr, cxled, cxld);
  if (IS_ERR(cxl_rr)) {
   dev_dbg(&cxlr->dev,
    "%s: failed to allocate region reference\n",
    dev_name(&port->dev));
   return PTR_ERR(cxl_rr);
  }
  nr_targets_inc = true;

  rc = cxl_rr_assign_decoder(port, cxlr, cxled, cxl_rr, cxld);
  if (rc)
   goto out_erase;
 }
 cxld = cxl_rr->decoder;

 /*
 * the number of targets should not exceed the target_count
 * of the decoder
 */

 if (is_switch_decoder(&cxld->dev)) {
  struct cxl_switch_decoder *cxlsd;

  cxlsd = to_cxl_switch_decoder(&cxld->dev);
  if (cxl_rr->nr_targets > cxlsd->nr_targets) {
   dev_dbg(&cxlr->dev,
    "%s:%s %s add: %s:%s @ %d overflows targets: %d\n",
    dev_name(port->uport_dev), dev_name(&port->dev),
    dev_name(&cxld->dev), dev_name(&cxlmd->dev),
    dev_name(&cxled->cxld.dev), pos,
    cxlsd->nr_targets);
   rc = -ENXIO;
   goto out_erase;
  }
 }

 rc = cxl_rr_ep_add(cxl_rr, cxled);
 if (rc) {
  dev_dbg(&cxlr->dev,
   "%s: failed to track endpoint %s:%s reference\n",
   dev_name(&port->dev), dev_name(&cxlmd->dev),
   dev_name(&cxld->dev));
  goto out_erase;
 }

 dev_dbg(&cxlr->dev,
  "%s:%s %s add: %s:%s @ %d next: %s nr_eps: %d nr_targets: %d\n",
  dev_name(port->uport_dev), dev_name(&port->dev),
  dev_name(&cxld->dev), dev_name(&cxlmd->dev),
  dev_name(&cxled->cxld.dev), pos,
  ep ? ep->next ? dev_name(ep->next->uport_dev) :
          dev_name(&cxlmd->dev) :
      "none",
  cxl_rr->nr_eps, cxl_rr->nr_targets);

 return 0;
out_erase:
 if (nr_targets_inc)
  cxl_rr->nr_targets--;
 if (cxl_rr->nr_eps == 0)
  free_region_ref(cxl_rr);
 return rc;
}

static void cxl_port_detach_region(struct cxl_port *port,
       struct cxl_region *cxlr,
       struct cxl_endpoint_decoder *cxled)
{
 struct cxl_region_ref *cxl_rr;
 struct cxl_ep *ep = NULL;

 lockdep_assert_held_write(&cxl_rwsem.region);

 cxl_rr = cxl_rr_load(port, cxlr);
 if (!cxl_rr)
  return;

 /*
 * Endpoint ports do not carry cxl_ep references, and they
 * never target more than one endpoint by definition
 */

 if (cxl_rr->decoder == &cxled->cxld)
  cxl_rr->nr_eps--;
 else
  ep = xa_erase(&cxl_rr->endpoints, (unsigned long)cxled);
 if (ep) {
  struct cxl_ep *ep_iter;
  unsigned long index;
  int found = 0;

  cxl_rr->nr_eps--;
  xa_for_each(&cxl_rr->endpoints, index, ep_iter) {
   if (ep_iter->next == ep->next) {
    found++;
    break;
   }
  }
  if (!found)
   cxl_rr->nr_targets--;
 }

 if (cxl_rr->nr_eps == 0)
  free_region_ref(cxl_rr);
}

static int check_last_peer(struct cxl_endpoint_decoder *cxled,
      struct cxl_ep *ep, struct cxl_region_ref *cxl_rr,
      int distance)
{
 struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
 struct cxl_region *cxlr = cxl_rr->region;
 struct cxl_region_params *p = &cxlr->params;
 struct cxl_endpoint_decoder *cxled_peer;
 struct cxl_port *port = cxl_rr->port;
 struct cxl_memdev *cxlmd_peer;
 struct cxl_ep *ep_peer;
 int pos = cxled->pos;

 /*
 * If this position wants to share a dport with the last endpoint mapped
 * then that endpoint, at index 'position - distance', must also be
 * mapped by this dport.
 */

 if (pos < distance) {
  dev_dbg(&cxlr->dev, "%s:%s: cannot host %s:%s at %d\n",
   dev_name(port->uport_dev), dev_name(&port->dev),
   dev_name(&cxlmd->dev), dev_name(&cxled->cxld.dev), pos);
  return -ENXIO;
 }
 cxled_peer = p->targets[pos - distance];
 cxlmd_peer = cxled_to_memdev(cxled_peer);
 ep_peer = cxl_ep_load(port, cxlmd_peer);
 if (ep->dport != ep_peer->dport) {
  dev_dbg(&cxlr->dev,
   "%s:%s: %s:%s pos %d mismatched peer %s:%s\n",
   dev_name(port->uport_dev), dev_name(&port->dev),
   dev_name(&cxlmd->dev), dev_name(&cxled->cxld.dev), pos,
   dev_name(&cxlmd_peer->dev),
   dev_name(&cxled_peer->cxld.dev));
  return -ENXIO;
 }

 return 0;
}

static int check_interleave_cap(struct cxl_decoder *cxld, int iw, int ig)
{
 struct cxl_port *port = to_cxl_port(cxld->dev.parent);
 struct cxl_hdm *cxlhdm = dev_get_drvdata(&port->dev);
 unsigned int interleave_mask;
 u8 eiw;
 u16 eig;
 int high_pos, low_pos;

 if (!test_bit(iw, &cxlhdm->iw_cap_mask))
  return -ENXIO;
 /*
 * Per CXL specification r3.1(8.2.4.20.13 Decoder Protection),
 * if eiw < 8:
 *   DPAOFFSET[51: eig + 8] = HPAOFFSET[51: eig + 8 + eiw]
 *   DPAOFFSET[eig + 7: 0]  = HPAOFFSET[eig + 7: 0]
 *
 *   when the eiw is 0, all the bits of HPAOFFSET[51: 0] are used, the
 *   interleave bits are none.
 *
 * if eiw >= 8:
 *   DPAOFFSET[51: eig + 8] = HPAOFFSET[51: eig + eiw] / 3
 *   DPAOFFSET[eig + 7: 0]  = HPAOFFSET[eig + 7: 0]
 *
 *   when the eiw is 8, all the bits of HPAOFFSET[51: 0] are used, the
 *   interleave bits are none.
 */

 ways_to_eiw(iw, &eiw);
 if (eiw == 0 || eiw == 8)
  return 0;

 granularity_to_eig(ig, &eig);
 if (eiw > 8)
  high_pos = eiw + eig - 1;
 else
  high_pos = eiw + eig + 7;
 low_pos = eig + 8;
 interleave_mask = GENMASK(high_pos, low_pos);
 if (interleave_mask & ~cxlhdm->interleave_mask)
  return -ENXIO;

 return 0;
}

static int cxl_port_setup_targets(struct cxl_port *port,
      struct cxl_region *cxlr,
      struct cxl_endpoint_decoder *cxled)
{
 struct cxl_root_decoder *cxlrd = to_cxl_root_decoder(cxlr->dev.parent);
 int parent_iw, parent_ig, ig, iw, rc, inc = 0, pos = cxled->pos;
 struct cxl_port *parent_port = to_cxl_port(port->dev.parent);
 struct cxl_region_ref *cxl_rr = cxl_rr_load(port, cxlr);
 struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
 struct cxl_ep *ep = cxl_ep_load(port, cxlmd);
 struct cxl_region_params *p = &cxlr->params;
 struct cxl_decoder *cxld = cxl_rr->decoder;
 struct cxl_switch_decoder *cxlsd;
 struct cxl_port *iter = port;
 u16 eig, peig;
 u8 eiw, peiw;

 /*
 * While root level decoders support x3, x6, x12, switch level
 * decoders only support powers of 2 up to x16.
 */

 if (!is_power_of_2(cxl_rr->nr_targets)) {
  dev_dbg(&cxlr->dev, "%s:%s: invalid target count %d\n",
   dev_name(port->uport_dev), dev_name(&port->dev),
   cxl_rr->nr_targets);
  return -EINVAL;
 }

 cxlsd = to_cxl_switch_decoder(&cxld->dev);
 if (cxl_rr->nr_targets_set) {
  int i, distance = 1;
  struct cxl_region_ref *cxl_rr_iter;

  /*
 * The "distance" between peer downstream ports represents which
 * endpoint positions in the region interleave a given port can
 * host.
 *
 * For example, at the root of a hierarchy the distance is
 * always 1 as every index targets a different host-bridge. At
 * each subsequent switch level those ports map every Nth region
 * position where N is the width of the switch == distance.
 */

  do {
   cxl_rr_iter = cxl_rr_load(iter, cxlr);
   distance *= cxl_rr_iter->nr_targets;
   iter = to_cxl_port(iter->dev.parent);
  } while (!is_cxl_root(iter));
  distance *= cxlrd->cxlsd.cxld.interleave_ways;

  for (i = 0; i < cxl_rr->nr_targets_set; i++)
   if (ep->dport == cxlsd->target[i]) {
    rc = check_last_peer(cxled, ep, cxl_rr,
           distance);
    if (rc)
     return rc;
    goto out_target_set;
   }
  goto add_target;
 }

 if (is_cxl_root(parent_port)) {
  /*
 * Root decoder IG is always set to value in CFMWS which
 * may be different than this region's IG.  We can use the
 * region's IG here since interleave_granularity_store()
 * does not allow interleaved host-bridges with
 * root IG != region IG.
 */

  parent_ig = p->interleave_granularity;
  parent_iw = cxlrd->cxlsd.cxld.interleave_ways;
  /*
 * For purposes of address bit routing, use power-of-2 math for
 * switch ports.
 */

  if (!is_power_of_2(parent_iw))
   parent_iw /= 3;
 } else {
  struct cxl_region_ref *parent_rr;
  struct cxl_decoder *parent_cxld;

  parent_rr = cxl_rr_load(parent_port, cxlr);
  parent_cxld = parent_rr->decoder;
  parent_ig = parent_cxld->interleave_granularity;
  parent_iw = parent_cxld->interleave_ways;
 }

 rc = granularity_to_eig(parent_ig, &peig);
 if (rc) {
  dev_dbg(&cxlr->dev, "%s:%s: invalid parent granularity: %d\n",
   dev_name(parent_port->uport_dev),
   dev_name(&parent_port->dev), parent_ig);
  return rc;
 }

 rc = ways_to_eiw(parent_iw, &peiw);
 if (rc) {
  dev_dbg(&cxlr->dev, "%s:%s: invalid parent interleave: %d\n",
   dev_name(parent_port->uport_dev),
   dev_name(&parent_port->dev), parent_iw);
  return rc;
 }

 iw = cxl_rr->nr_targets;
 rc = ways_to_eiw(iw, &eiw);
 if (rc) {
  dev_dbg(&cxlr->dev, "%s:%s: invalid port interleave: %d\n",
   dev_name(port->uport_dev), dev_name(&port->dev), iw);
  return rc;
 }

 /*
 * Interleave granularity is a multiple of @parent_port granularity.
 * Multiplier is the parent port interleave ways.
 */

 rc = granularity_to_eig(parent_ig * parent_iw, &eig);
 if (rc) {
  dev_dbg(&cxlr->dev,
   "%s: invalid granularity calculation (%d * %d)\n",
   dev_name(&parent_port->dev), parent_ig, parent_iw);
  return rc;
 }

 rc = eig_to_granularity(eig, &ig);
 if (rc) {
  dev_dbg(&cxlr->dev, "%s:%s: invalid interleave: %d\n",
   dev_name(port->uport_dev), dev_name(&port->dev),
   256 << eig);
  return rc;
 }

 if (iw > 8 || iw > cxlsd->nr_targets) {
  dev_dbg(&cxlr->dev,
   "%s:%s:%s: ways: %d overflows targets: %d\n",
   dev_name(port->uport_dev), dev_name(&port->dev),
   dev_name(&cxld->dev), iw, cxlsd->nr_targets);
  return -ENXIO;
 }

 if (test_bit(CXL_REGION_F_AUTO, &cxlr->flags)) {
  if (cxld->interleave_ways != iw ||
      (iw > 1 && cxld->interleave_granularity != ig) ||
      !region_res_match_cxl_range(p, &cxld->hpa_range) ||
      ((cxld->flags & CXL_DECODER_F_ENABLE) == 0)) {
   dev_err(&cxlr->dev,
    "%s:%s %s expected iw: %d ig: %d %pr\n",
    dev_name(port->uport_dev), dev_name(&port->dev),
    __func__, iw, ig, p->res);
   dev_err(&cxlr->dev,
    "%s:%s %s got iw: %d ig: %d state: %s %#llx:%#llx\n",
    dev_name(port->uport_dev), dev_name(&port->dev),
    __func__, cxld->interleave_ways,
    cxld->interleave_granularity,
    (cxld->flags & CXL_DECODER_F_ENABLE) ?
     "enabled" :
     "disabled",
    cxld->hpa_range.start, cxld->hpa_range.end);
   return -ENXIO;
  }
 } else {
  rc = check_interleave_cap(cxld, iw, ig);
  if (rc) {
   dev_dbg(&cxlr->dev,
    "%s:%s iw: %d ig: %d is not supported\n",
    dev_name(port->uport_dev),
    dev_name(&port->dev), iw, ig);
   return rc;
  }

  cxld->interleave_ways = iw;
  cxld->interleave_granularity = ig;
  cxld->hpa_range = (struct range) {
   .start = p->res->start,
   .end = p->res->end,
  };
 }
 dev_dbg(&cxlr->dev, "%s:%s iw: %d ig: %d\n", dev_name(port->uport_dev),
  dev_name(&port->dev), iw, ig);
add_target:
 if (cxl_rr->nr_targets_set == cxl_rr->nr_targets) {
  dev_dbg(&cxlr->dev,
   "%s:%s: targets full trying to add %s:%s at %d\n",
   dev_name(port->uport_dev), dev_name(&port->dev),
   dev_name(&cxlmd->dev), dev_name(&cxled->cxld.dev), pos);
  return -ENXIO;
 }
 if (test_bit(CXL_REGION_F_AUTO, &cxlr->flags)) {
  if (cxlsd->target[cxl_rr->nr_targets_set] != ep->dport) {
   dev_dbg(&cxlr->dev, "%s:%s: %s expected %s at %d\n",
    dev_name(port->uport_dev), dev_name(&port->dev),
    dev_name(&cxlsd->cxld.dev),
    dev_name(ep->dport->dport_dev),
    cxl_rr->nr_targets_set);
   return -ENXIO;
  }
 } else
  cxlsd->target[cxl_rr->nr_targets_set] = ep->dport;
 inc = 1;
out_target_set:
 cxl_rr->nr_targets_set += inc;
 dev_dbg(&cxlr->dev, "%s:%s target[%d] = %s for %s:%s @ %d\n",
  dev_name(port->uport_dev), dev_name(&port->dev),
  cxl_rr->nr_targets_set - 1, dev_name(ep->dport->dport_dev),
  dev_name(&cxlmd->dev), dev_name(&cxled->cxld.dev), pos);

 return 0;
}

static void cxl_port_reset_targets(struct cxl_port *port,
       struct cxl_region *cxlr)
{
 struct cxl_region_ref *cxl_rr = cxl_rr_load(port, cxlr);
 struct cxl_decoder *cxld;

 /*
 * After the last endpoint has been detached the entire cxl_rr may now
 * be gone.
 */

 if (!cxl_rr)
  return;
 cxl_rr->nr_targets_set = 0;

 cxld = cxl_rr->decoder;
 cxld->hpa_range = (struct range) {
  .start = 0,
  .end = -1,
 };
}

static void cxl_region_teardown_targets(struct cxl_region *cxlr)
{
 struct cxl_region_params *p = &cxlr->params;
 struct cxl_endpoint_decoder *cxled;
 struct cxl_dev_state *cxlds;
 struct cxl_memdev *cxlmd;
 struct cxl_port *iter;
 struct cxl_ep *ep;
 int i;

 /*
 * In the auto-discovery case skip automatic teardown since the
 * address space is already active
 */

 if (test_bit(CXL_REGION_F_AUTO, &cxlr->flags))
  return;

 for (i = 0; i < p->nr_targets; i++) {
  cxled = p->targets[i];
  cxlmd = cxled_to_memdev(cxled);
  cxlds = cxlmd->cxlds;

  if (cxlds->rcd)
   continue;

  iter = cxled_to_port(cxled);
  while (!is_cxl_root(to_cxl_port(iter->dev.parent)))
   iter = to_cxl_port(iter->dev.parent);

  for (ep = cxl_ep_load(iter, cxlmd); iter;
       iter = ep->next, ep = cxl_ep_load(iter, cxlmd))
   cxl_port_reset_targets(iter, cxlr);
 }
}

static int cxl_region_setup_targets(struct cxl_region *cxlr)
{
 struct cxl_region_params *p = &cxlr->params;
 struct cxl_endpoint_decoder *cxled;
 struct cxl_dev_state *cxlds;
 int i, rc, rch = 0, vh = 0;
 struct cxl_memdev *cxlmd;
 struct cxl_port *iter;
 struct cxl_ep *ep;

 for (i = 0; i < p->nr_targets; i++) {
  cxled = p->targets[i];
  cxlmd = cxled_to_memdev(cxled);
  cxlds = cxlmd->cxlds;

  /* validate that all targets agree on topology */
  if (!cxlds->rcd) {
   vh++;
  } else {
   rch++;
   continue;
  }

  iter = cxled_to_port(cxled);
  while (!is_cxl_root(to_cxl_port(iter->dev.parent)))
   iter = to_cxl_port(iter->dev.parent);

  /*
 * Descend the topology tree programming / validating
 * targets while looking for conflicts.
 */

  for (ep = cxl_ep_load(iter, cxlmd); iter;
       iter = ep->next, ep = cxl_ep_load(iter, cxlmd)) {
   rc = cxl_port_setup_targets(iter, cxlr, cxled);
   if (rc) {
    cxl_region_teardown_targets(cxlr);
    return rc;
   }
  }
 }

 if (rch && vh) {
  dev_err(&cxlr->dev, "mismatched CXL topologies detected\n");
  cxl_region_teardown_targets(cxlr);
  return -ENXIO;
 }

 return 0;
}

static int cxl_region_validate_position(struct cxl_region *cxlr,
     struct cxl_endpoint_decoder *cxled,
     int pos)
{
 struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
 struct cxl_region_params *p = &cxlr->params;
 int i;

 if (pos < 0 || pos >= p->interleave_ways) {
  dev_dbg(&cxlr->dev, "position %d out of range %d\n", pos,
   p->interleave_ways);
  return -ENXIO;
 }

 if (p->targets[pos] == cxled)
  return 0;

 if (p->targets[pos]) {
  struct cxl_endpoint_decoder *cxled_target = p->targets[pos];
  struct cxl_memdev *cxlmd_target = cxled_to_memdev(cxled_target);

  dev_dbg(&cxlr->dev, "position %d already assigned to %s:%s\n",
   pos, dev_name(&cxlmd_target->dev),
   dev_name(&cxled_target->cxld.dev));
  return -EBUSY;
 }

 for (i = 0; i < p->interleave_ways; i++) {
  struct cxl_endpoint_decoder *cxled_target;
  struct cxl_memdev *cxlmd_target;

  cxled_target = p->targets[i];
  if (!cxled_target)
   continue;

  cxlmd_target = cxled_to_memdev(cxled_target);
  if (cxlmd_target == cxlmd) {
   dev_dbg(&cxlr->dev,
    "%s already specified at position %d via: %s\n",
    dev_name(&cxlmd->dev), pos,
    dev_name(&cxled_target->cxld.dev));
   return -EBUSY;
  }
 }

 return 0;
}

static int cxl_region_attach_position(struct cxl_region *cxlr,
          struct cxl_root_decoder *cxlrd,
          struct cxl_endpoint_decoder *cxled,
          const struct cxl_dport *dport, int pos)
{
 struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
 struct cxl_switch_decoder *cxlsd = &cxlrd->cxlsd;
 struct cxl_decoder *cxld = &cxlsd->cxld;
 int iw = cxld->interleave_ways;
 struct cxl_port *iter;
 int rc;

 if (dport != cxlrd->cxlsd.target[pos % iw]) {
  dev_dbg(&cxlr->dev, "%s:%s invalid target position for %s\n",
   dev_name(&cxlmd->dev), dev_name(&cxled->cxld.dev),
   dev_name(&cxlrd->cxlsd.cxld.dev));
  return -ENXIO;
 }

 for (iter = cxled_to_port(cxled); !is_cxl_root(iter);
      iter = to_cxl_port(iter->dev.parent)) {
  rc = cxl_port_attach_region(iter, cxlr, cxled, pos);
  if (rc)
   goto err;
 }

 return 0;

err:
 for (iter = cxled_to_port(cxled); !is_cxl_root(iter);
      iter = to_cxl_port(iter->dev.parent))
  cxl_port_detach_region(iter, cxlr, cxled);
 return rc;
}

static int cxl_region_attach_auto(struct cxl_region *cxlr,
      struct cxl_endpoint_decoder *cxled, int pos)
{
 struct cxl_region_params *p = &cxlr->params;

 if (cxled->state != CXL_DECODER_STATE_AUTO) {
  dev_err(&cxlr->dev,
   "%s: unable to add decoder to autodetected region\n",
   dev_name(&cxled->cxld.dev));
  return -EINVAL;
 }

 if (pos >= 0) {
  dev_dbg(&cxlr->dev, "%s: expected auto position, not %d\n",
   dev_name(&cxled->cxld.dev), pos);
  return -EINVAL;
 }

 if (p->nr_targets >= p->interleave_ways) {
  dev_err(&cxlr->dev, "%s: no more target slots available\n",
   dev_name(&cxled->cxld.dev));
  return -ENXIO;
 }

 /*
 * Temporarily record the endpoint decoder into the target array. Yes,
 * this means that userspace can view devices in the wrong position
 * before the region activates, and must be careful to understand when
 * it might be racing region autodiscovery.
 */

 pos = p->nr_targets;
 p->targets[pos] = cxled;
 cxled->pos = pos;
 p->nr_targets++;

 return 0;
}

static int cmp_interleave_pos(const void *a, const void *b)
{
 struct cxl_endpoint_decoder *cxled_a = *(typeof(cxled_a) *)a;
 struct cxl_endpoint_decoder *cxled_b = *(typeof(cxled_b) *)b;

 return cxled_a->pos - cxled_b->pos;
}

static int match_switch_decoder_by_range(struct device *dev,
      const void *data)
{
 struct cxl_switch_decoder *cxlsd;
 const struct range *r1, *r2 = data;


 if (!is_switch_decoder(dev))
  return 0;

 cxlsd = to_cxl_switch_decoder(dev);
 r1 = &cxlsd->cxld.hpa_range;

 if (is_root_decoder(dev))
  return range_contains(r1, r2);
 return (r1->start == r2->start && r1->end == r2->end);
}

static int find_pos_and_ways(struct cxl_port *port, struct range *range,
        int *pos, int *ways)
{
 struct cxl_switch_decoder *cxlsd;
 struct cxl_port *parent;
 struct device *dev;
 int rc = -ENXIO;

 parent = parent_port_of(port);
 if (!parent)
  return rc;

 dev = device_find_child(&parent->dev, range,
    match_switch_decoder_by_range);
 if (!dev) {
  dev_err(port->uport_dev,
   "failed to find decoder mapping %#llx-%#llx\n",
   range->start, range->end);
  return rc;
 }
 cxlsd = to_cxl_switch_decoder(dev);
 *ways = cxlsd->cxld.interleave_ways;

 for (int i = 0; i < *ways; i++) {
  if (cxlsd->target[i] == port->parent_dport) {
   *pos = i;
   rc = 0;
   break;
  }
 }
 put_device(dev);

 if (rc)
  dev_err(port->uport_dev,
   "failed to find %s:%s in target list of %s\n",
   dev_name(&port->dev),
   dev_name(port->parent_dport->dport_dev),
   dev_name(&cxlsd->cxld.dev));

 return rc;
}

/**
 * cxl_calc_interleave_pos() - calculate an endpoint position in a region
 * @cxled: endpoint decoder member of given region
 *
 * The endpoint position is calculated by traversing the topology from
 * the endpoint to the root decoder and iteratively applying this
 * calculation:
 *
 *    position = position * parent_ways + parent_pos;
 *
 * ...where @position is inferred from switch and root decoder target lists.
 *
 * Return: position >= 0 on success
 *    -ENXIO on failure
 */

static int cxl_calc_interleave_pos(struct cxl_endpoint_decoder *cxled)
{
 struct cxl_port *iter, *port = cxled_to_port(cxled);
 struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
 struct range *range = &cxled->cxld.hpa_range;
 int parent_ways = 0, parent_pos = 0, pos = 0;
 int rc;

 /*
 * Example: the expected interleave order of the 4-way region shown
 * below is: mem0, mem2, mem1, mem3
 *
 *   root_port
 *                 /      \
 *      host_bridge_0    host_bridge_1
 *        |    |           |    |
 *       mem0 mem1        mem2 mem3
 *
 * In the example the calculator will iterate twice. The first iteration
 * uses the mem position in the host-bridge and the ways of the host-
 * bridge to generate the first, or local, position. The second
 * iteration uses the host-bridge position in the root_port and the ways
 * of the root_port to refine the position.
 *
 * A trace of the calculation per endpoint looks like this:
 * mem0: pos = 0 * 2 + 0    mem2: pos = 0 * 2 + 0
 *       pos = 0 * 2 + 0          pos = 0 * 2 + 1
 *       pos: 0                   pos: 1
 *
 * mem1: pos = 0 * 2 + 1    mem3: pos = 0 * 2 + 1
 *       pos = 1 * 2 + 0          pos = 1 * 2 + 1
 *       pos: 2                   pos = 3
 *
 * Note that while this example is simple, the method applies to more
 * complex topologies, including those with switches.
 */


 /* Iterate from endpoint to root_port refining the position */
 for (iter = port; iter; iter = parent_port_of(iter)) {
  if (is_cxl_root(iter))
   break;

  rc = find_pos_and_ways(iter, range, &parent_pos, &parent_ways);
  if (rc)
   return rc;

  pos = pos * parent_ways + parent_pos;
 }

 dev_dbg(&cxlmd->dev,
  "decoder:%s parent:%s port:%s range:%#llx-%#llx pos:%d\n",
  dev_name(&cxled->cxld.dev), dev_name(cxlmd->dev.parent),
  dev_name(&port->dev), range->start, range->end, pos);

 return pos;
}

static int cxl_region_sort_targets(struct cxl_region *cxlr)
{
 struct cxl_region_params *p = &cxlr->params;
 int i, rc = 0;

 for (i = 0; i < p->nr_targets; i++) {
  struct cxl_endpoint_decoder *cxled = p->targets[i];

  cxled->pos = cxl_calc_interleave_pos(cxled);
  /*
 * Record that sorting failed, but still continue to calc
 * cxled->pos so that follow-on code paths can reliably
 * do p->targets[cxled->pos] to self-reference their entry.
 */

  if (cxled->pos < 0)
   rc = -ENXIO;
 }
 /* Keep the cxlr target list in interleave position order */
 sort(p->targets, p->nr_targets, sizeof(p->targets[0]),
      cmp_interleave_pos, NULL);

 dev_dbg(&cxlr->dev, "region sort %s\n", rc ? "failed" : "successful");
 return rc;
}

static int cxl_region_attach(struct cxl_region *cxlr,
        struct cxl_endpoint_decoder *cxled, int pos)
{
 struct cxl_root_decoder *cxlrd = to_cxl_root_decoder(cxlr->dev.parent);
 struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
 struct cxl_dev_state *cxlds = cxlmd->cxlds;
 struct cxl_region_params *p = &cxlr->params;
 struct cxl_port *ep_port, *root_port;
 struct cxl_dport *dport;
 int rc = -ENXIO;

 rc = check_interleave_cap(&cxled->cxld, p->interleave_ways,
      p->interleave_granularity);
 if (rc) {
  dev_dbg(&cxlr->dev, "%s iw: %d ig: %d is not supported\n",
   dev_name(&cxled->cxld.dev), p->interleave_ways,
   p->interleave_granularity);
  return rc;
 }

 if (cxled->part < 0) {
  dev_dbg(&cxlr->dev, "%s dead\n", dev_name(&cxled->cxld.dev));
  return -ENODEV;
 }

 if (cxlds->part[cxled->part].mode != cxlr->mode) {
  dev_dbg(&cxlr->dev, "%s region mode: %d mismatch\n",
   dev_name(&cxled->cxld.dev), cxlr->mode);
  return -EINVAL;
 }

 /* all full of members, or interleave config not established? */
 if (p->state > CXL_CONFIG_INTERLEAVE_ACTIVE) {
  dev_dbg(&cxlr->dev, "region already active\n");
  return -EBUSY;
 }

 if (p->state < CXL_CONFIG_INTERLEAVE_ACTIVE) {
  dev_dbg(&cxlr->dev, "interleave config missing\n");
  return -ENXIO;
 }

 if (p->nr_targets >= p->interleave_ways) {
  dev_dbg(&cxlr->dev, "region already has %d endpoints\n",
   p->nr_targets);
  return -EINVAL;
 }

 ep_port = cxled_to_port(cxled);
 root_port = cxlrd_to_port(cxlrd);
 dport = cxl_find_dport_by_dev(root_port, ep_port->host_bridge);
 if (!dport) {
  dev_dbg(&cxlr->dev, "%s:%s invalid target for %s\n",
   dev_name(&cxlmd->dev), dev_name(&cxled->cxld.dev),
   dev_name(cxlr->dev.parent));
  return -ENXIO;
 }

 if (cxled->cxld.target_type != cxlr->type) {
  dev_dbg(&cxlr->dev, "%s:%s type mismatch: %d vs %d\n",
   dev_name(&cxlmd->dev), dev_name(&cxled->cxld.dev),
   cxled->cxld.target_type, cxlr->type);
  return -ENXIO;
 }

 if (!cxled->dpa_res) {
  dev_dbg(&cxlr->dev, "%s:%s: missing DPA allocation.\n",
   dev_name(&cxlmd->dev), dev_name(&cxled->cxld.dev));
  return -ENXIO;
 }

 if (resource_size(cxled->dpa_res) * p->interleave_ways + p->cache_size !=
     resource_size(p->res)) {
  dev_dbg(&cxlr->dev,
   "%s:%s-size-%#llx * ways-%d + cache-%#llx != region-size-%#llx\n",
   dev_name(&cxlmd->dev), dev_name(&cxled->cxld.dev),
   (u64)resource_size(cxled->dpa_res), p->interleave_ways,
   (u64)p->cache_size, (u64)resource_size(p->res));
  return -EINVAL;
 }

 cxl_region_perf_data_calculate(cxlr, cxled);

 if (test_bit(CXL_REGION_F_AUTO, &cxlr->flags)) {
  int i;

  rc = cxl_region_attach_auto(cxlr, cxled, pos);
  if (rc)
   return rc;

  /* await more targets to arrive... */
  if (p->nr_targets < p->interleave_ways)
   return 0;

  /*
 * All targets are here, which implies all PCI enumeration that
 * affects this region has been completed. Walk the topology to
 * sort the devices into their relative region decode position.
 */

  rc = cxl_region_sort_targets(cxlr);
  if (rc)
   return rc;

  for (i = 0; i < p->nr_targets; i++) {
   cxled = p->targets[i];
   ep_port = cxled_to_port(cxled);
   dport = cxl_find_dport_by_dev(root_port,
            ep_port->host_bridge);
   rc = cxl_region_attach_position(cxlr, cxlrd, cxled,
       dport, i);
   if (rc)
    return rc;
  }

  rc = cxl_region_setup_targets(cxlr);
  if (rc)
   return rc;

  /*
 * If target setup succeeds in the autodiscovery case
 * then the region is already committed.
 */

  p->state = CXL_CONFIG_COMMIT;
  cxl_region_shared_upstream_bandwidth_update(cxlr);

  return 0;
 }

 rc = cxl_region_validate_position(cxlr, cxled, pos);
 if (rc)
  return rc;

 rc = cxl_region_attach_position(cxlr, cxlrd, cxled, dport, pos);
 if (rc)
  return rc;

 p->targets[pos] = cxled;
 cxled->pos = pos;
 p->nr_targets++;

 if (p->nr_targets == p->interleave_ways) {
  rc = cxl_region_setup_targets(cxlr);
  if (rc)
   return rc;
  p->state = CXL_CONFIG_ACTIVE;
  cxl_region_shared_upstream_bandwidth_update(cxlr);
 }

 cxled->cxld.interleave_ways = p->interleave_ways;
 cxled->cxld.interleave_granularity = p->interleave_granularity;
 cxled->cxld.hpa_range = (struct range) {
  .start = p->res->start,
  .end = p->res->end,
 };

 if (p->nr_targets != p->interleave_ways)
  return 0;

 /*
 * Test the auto-discovery position calculator function
 * against this successfully created user-defined region.
 * A fail message here means that this interleave config
 * will fail when presented as CXL_REGION_F_AUTO.
 */

 for (int i = 0; i < p->nr_targets; i++) {
  struct cxl_endpoint_decoder *cxled = p->targets[i];
  int test_pos;

  test_pos = cxl_calc_interleave_pos(cxled);
  dev_dbg(&cxled->cxld.dev,
   "Test cxl_calc_interleave_pos(): %s test_pos:%d cxled->pos:%d\n",
   (test_pos == cxled->pos) ? "success" : "fail",
   test_pos, cxled->pos);
 }

 return 0;
}

static struct cxl_region *
__cxl_decoder_detach(struct cxl_region *cxlr,
       struct cxl_endpoint_decoder *cxled, int pos,
       enum cxl_detach_mode mode)
{
 struct cxl_region_params *p;

 lockdep_assert_held_write(&cxl_rwsem.region);

 if (!cxled) {
  p = &cxlr->params;

  if (pos >= p->interleave_ways) {
   dev_dbg(&cxlr->dev, "position %d out of range %d\n",
    pos, p->interleave_ways);
   return NULL;
  }

  if (!p->targets[pos])
   return NULL;
  cxled = p->targets[pos];
 } else {
  cxlr = cxled->cxld.region;
  if (!cxlr)
   return NULL;
  p = &cxlr->params;
 }

 if (mode == DETACH_INVALIDATE)
  cxled->part = -1;

 if (p->state > CXL_CONFIG_ACTIVE) {
  cxl_region_decode_reset(cxlr, p->interleave_ways);
  p->state = CXL_CONFIG_ACTIVE;
 }

 for (struct cxl_port *iter = cxled_to_port(cxled); !is_cxl_root(iter);
      iter = to_cxl_port(iter->dev.parent))
  cxl_port_detach_region(iter, cxlr, cxled);

 if (cxled->pos < 0 || cxled->pos >= p->interleave_ways ||
     p->targets[cxled->pos] != cxled) {
  struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);

  dev_WARN_ONCE(&cxlr->dev, 1, "expected %s:%s at position %d\n",
         dev_name(&cxlmd->dev), dev_name(&cxled->cxld.dev),
         cxled->pos);
  return NULL;
 }

 if (p->state == CXL_CONFIG_ACTIVE) {
  p->state = CXL_CONFIG_INTERLEAVE_ACTIVE;
  cxl_region_teardown_targets(cxlr);
 }
 p->targets[cxled->pos] = NULL;
 p->nr_targets--;
 cxled->cxld.hpa_range = (struct range) {
  .start = 0,
  .end = -1,
 };

 get_device(&cxlr->dev);
 return cxlr;
}

/*
 * Cleanup a decoder's interest in a region. There are 2 cases to
 * handle, removing an unknown @cxled from a known position in a region
 * (detach_target()) or removing a known @cxled from an unknown @cxlr
 * (cxld_unregister())
 *
 * When the detachment finds a region release the region driver.
 */

int cxl_decoder_detach(struct cxl_region *cxlr,
         struct cxl_endpoint_decoder *cxled, int pos,
         enum cxl_detach_mode mode)
{
 struct cxl_region *detach;

 /* when the decoder is being destroyed lock unconditionally */
 if (mode == DETACH_INVALIDATE) {
  guard(rwsem_write)(&cxl_rwsem.region);
  detach = __cxl_decoder_detach(cxlr, cxled, pos, mode);
 } else {
  int rc;

  ACQUIRE(rwsem_write_kill, rwsem)(&cxl_rwsem.region);
  if ((rc = ACQUIRE_ERR(rwsem_write_kill, &rwsem)))
   return rc;
  detach = __cxl_decoder_detach(cxlr, cxled, pos, mode);
 }

 if (detach) {
  device_release_driver(&detach->dev);
  put_device(&detach->dev);
 }
 return 0;
}

static int __attach_target(struct cxl_region *cxlr,
      struct cxl_endpoint_decoder *cxled, int pos,
      unsigned int state)
{
 int rc;

 if (state == TASK_INTERRUPTIBLE) {
  ACQUIRE(rwsem_write_kill, rwsem)(&cxl_rwsem.region);
  if ((rc = ACQUIRE_ERR(rwsem_write_kill, &rwsem)))
   return rc;
  guard(rwsem_read)(&cxl_rwsem.dpa);
  return cxl_region_attach(cxlr, cxled, pos);
 }
 guard(rwsem_write)(&cxl_rwsem.region);
 guard(rwsem_read)(&cxl_rwsem.dpa);
 return cxl_region_attach(cxlr, cxled, pos);
}

static int attach_target(struct cxl_region *cxlr,
    struct cxl_endpoint_decoder *cxled, int pos,
    unsigned int state)
{
 int rc = __attach_target(cxlr, cxled, pos, state);

 if (rc == 0)
  return 0;

 dev_warn(cxled->cxld.dev.parent, "failed to attach %s to %s: %d\n",
   dev_name(&cxled->cxld.dev), dev_name(&cxlr->dev), rc);
 return rc;
}

static int detach_target(struct cxl_region *cxlr, int pos)
{
 return cxl_decoder_detach(cxlr, NULL, pos, DETACH_ONLY);
}

static size_t store_targetN(struct cxl_region *cxlr, const char *buf, int pos,
       size_t len)
{
 int rc;

 if (sysfs_streq(buf, "\n"))
  rc = detach_target(cxlr, pos);
 else {
  struct device *dev;

  dev = bus_find_device_by_name(&cxl_bus_type, NULL, buf);
  if (!dev)
   return -ENODEV;

  if (!is_endpoint_decoder(dev)) {
   rc = -EINVAL;
   goto out;
  }

  rc = attach_target(cxlr, to_cxl_endpoint_decoder(dev), pos,
       TASK_INTERRUPTIBLE);
out:
  put_device(dev);
 }

 if (rc < 0)
  return rc;
 return len;
}

#define TARGET_ATTR_RW(n)                                              \
static ssize_t target##n##_show(                                       \
 struct device *dev, struct device_attribute *attr, char *buf)  \
{                                                                      \
 return show_targetN(to_cxl_region(dev), buf, (n));             \
}                                                                      \
static ssize_t target##n##_store(struct device *dev,                   \
     struct device_attribute *attr,        \
     const char *buf, size_t len)          \
{                                                                      \
 return store_targetN(to_cxl_region(dev), buf, (n), len);       \
}                                                                      \
static DEVICE_ATTR_RW(target##n)

TARGET_ATTR_RW(0);
TARGET_ATTR_RW(1);
TARGET_ATTR_RW(2);
TARGET_ATTR_RW(3);
TARGET_ATTR_RW(4);
TARGET_ATTR_RW(5);
TARGET_ATTR_RW(6);
TARGET_ATTR_RW(7);
TARGET_ATTR_RW(8);
TARGET_ATTR_RW(9);
TARGET_ATTR_RW(10);
TARGET_ATTR_RW(11);
TARGET_ATTR_RW(12);
TARGET_ATTR_RW(13);
TARGET_ATTR_RW(14);
TARGET_ATTR_RW(15);

static struct attribute *target_attrs[] = {
 &dev_attr_target0.attr,
 &dev_attr_target1.attr,
 &dev_attr_target2.attr,
 &dev_attr_target3.attr,
 &dev_attr_target4.attr,
 &dev_attr_target5.attr,
 &dev_attr_target6.attr,
 &dev_attr_target7.attr,
 &dev_attr_target8.attr,
 &dev_attr_target9.attr,
 &dev_attr_target10.attr,
 &dev_attr_target11.attr,
 &dev_attr_target12.attr,
 &dev_attr_target13.attr,
 &dev_attr_target14.attr,
 &dev_attr_target15.attr,
 NULL,
};

static umode_t cxl_region_target_visible(struct kobject *kobj,
      struct attribute *a, int n)
{
 struct device *dev = kobj_to_dev(kobj);
 struct cxl_region *cxlr = to_cxl_region(dev);
 struct cxl_region_params *p = &cxlr->params;

 if (n < p->interleave_ways)
  return a->mode;
 return 0;
}

static const struct attribute_group cxl_region_target_group = {
 .attrs = target_attrs,
 .is_visible = cxl_region_target_visible,
};

static const struct attribute_group *get_cxl_region_target_group(void)
{
 return &cxl_region_target_group;
}

static const struct attribute_group *region_groups[] = {
 &cxl_base_attribute_group,
 &cxl_region_group,
 &cxl_region_target_group,
 &cxl_region_access0_coordinate_group,
 &cxl_region_access1_coordinate_group,
 NULL,
};

static void cxl_region_release(struct device *dev)
{
 struct cxl_root_decoder *cxlrd = to_cxl_root_decoder(dev->parent);
 struct cxl_region *cxlr = to_cxl_region(dev);
 int id = atomic_read(&cxlrd->region_id);

 /*
 * Try to reuse the recently idled id rather than the cached
 * next id to prevent the region id space from increasing
 * unnecessarily.
 */

 if (cxlr->id < id)
  if (atomic_try_cmpxchg(&cxlrd->region_id, &id, cxlr->id)) {
   memregion_free(id);
   goto out;
  }

 memregion_free(cxlr->id);
out:
 put_device(dev->parent);
 kfree(cxlr);
}

const struct device_type cxl_region_type = {
 .name = "cxl_region",
 .release = cxl_region_release,
 .groups = region_groups
};

bool is_cxl_region(struct device *dev)
{
 return dev->type == &cxl_region_type;
}
EXPORT_SYMBOL_NS_GPL(is_cxl_region, "CXL");

static struct cxl_region *to_cxl_region(struct device *dev)
{
 if (dev_WARN_ONCE(dev, dev->type != &cxl_region_type,
     "not a cxl_region device\n"))
  return NULL;

 return container_of(dev, struct cxl_region, dev);
}

static void unregister_region(void *_cxlr)
{
 struct cxl_region *cxlr = _cxlr;
 struct cxl_region_params *p = &cxlr->params;
 int i;

 device_del(&cxlr->dev);

 /*
 * Now that region sysfs is shutdown, the parameter block is now
 * read-only, so no need to hold the region rwsem to access the
 * region parameters.
 */

 for (i = 0; i < p->interleave_ways; i++)
  detach_target(cxlr, i);

 cxl_region_iomem_release(cxlr);
 put_device(&cxlr->dev);
}

static struct lock_class_key cxl_region_key;

static struct cxl_region *cxl_region_alloc(struct cxl_root_decoder *cxlrd, int id)
{
 struct cxl_region *cxlr;
 struct device *dev;

 cxlr = kzalloc(sizeof(*cxlr), GFP_KERNEL);
 if (!cxlr) {
  memregion_free(id);
  return ERR_PTR(-ENOMEM);
 }

 dev = &cxlr->dev;
 device_initialize(dev);
 lockdep_set_class(&dev->mutex, &cxl_region_key);
 dev->parent = &cxlrd->cxlsd.cxld.dev;
 /*
 * Keep root decoder pinned through cxl_region_release to fixup
 * region id allocations
 */

 get_device(dev->parent);
 device_set_pm_not_required(dev);
 dev->bus = &cxl_bus_type;
 dev->type = &cxl_region_type;
 cxlr->id = id;

 return cxlr;
}

static bool cxl_region_update_coordinates(struct cxl_region *cxlr, int nid)
{
 int cset = 0;
 int rc;

 for (int i = 0; i < ACCESS_COORDINATE_MAX; i++) {
  if (cxlr->coord[i].read_bandwidth) {
   rc = 0;
   if (cxl_need_node_perf_attrs_update(nid))
    node_set_perf_attrs(nid, &cxlr->coord[i], i);
   else
    rc = cxl_update_hmat_access_coordinates(nid, cxlr, i);

   if (rc == 0)
    cset++;
  }
 }

 if (!cset)
  return false;

 rc = sysfs_update_group(&cxlr->dev.kobj, get_cxl_region_access0_group());
 if (rc)
  dev_dbg(&cxlr->dev, "Failed to update access0 group\n");

 rc = sysfs_update_group(&cxlr->dev.kobj, get_cxl_region_access1_group());
 if (rc)
  dev_dbg(&cxlr->dev, "Failed to update access1 group\n");

 return true;
}

static int cxl_region_perf_attrs_callback(struct notifier_block *nb,
       unsigned long action, void *arg)
{
 struct cxl_region *cxlr = container_of(nb, struct cxl_region,
            node_notifier);
 struct node_notify *nn = arg;
 int nid = nn->nid;
 int region_nid;

 if (action != NODE_ADDED_FIRST_MEMORY)
  return NOTIFY_DONE;

 /*
 * No need to hold cxl_rwsem.region; region parameters are stable
 * within the cxl_region driver.
 */

 region_nid = phys_to_target_node(cxlr->params.res->start);
 if (nid != region_nid)
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=98 H=94 G=95

¤ Dauer der Verarbeitung: 0.13 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.