| 1 | |
| 2 | |
| 3 | * CDX bus driver.
|
| 4 | *
|
| 5 | * Copyright (C) 2022-2023, Advanced Micro Devices, Inc.
|
| 6 |
|
| 7 |
|
| 8 | |
| 9 | * Architecture Overview
|
| 10 | * =====================
|
| 11 | * CDX is a Hardware Architecture designed for AMD FPGA devices. It
|
| 12 | * consists of sophisticated mechanism for interaction between FPGA,
|
| 13 | * Firmware and the APUs (Application CPUs).
|
| 14 | *
|
| 15 | * Firmware resides on RPU (Realtime CPUs) which interacts with
|
| 16 | * the FPGA program manager and the APUs. The RPU provides memory-mapped
|
| 17 | * interface (RPU if) which is used to communicate with APUs.
|
| 18 | *
|
| 19 | * The diagram below shows an overview of the CDX architecture:
|
| 20 | *
|
| 21 | * +--------------------------------------+
|
| 22 | * | Application CPUs (APU) |
|
| 23 | * | |
|
| 24 | * | CDX device drivers|
|
| 25 | * | Linux OS | |
|
| 26 | * | CDX bus |
|
| 27 | * | | |
|
| 28 | * | CDX controller |
|
| 29 | * | | |
|
| 30 | * +-----------------------------|--------+
|
| 31 | * | (discover, config,
|
| 32 | * | reset, rescan)
|
| 33 | * |
|
| 34 | * +------------------------| RPU if |----+
|
| 35 | * | | |
|
| 36 | * | V |
|
| 37 | * | Realtime CPUs (RPU) |
|
| 38 | * | |
|
| 39 | * +--------------------------------------+
|
| 40 | * |
|
| 41 | * +---------------------|----------------+
|
| 42 | * | FPGA | |
|
| 43 | * | +-----------------------+ |
|
| 44 | * | | | | |
|
| 45 | * | +-------+ +-------+ +-------+ |
|
| 46 | * | | dev 1 | | dev 2 | | dev 3 | |
|
| 47 | * | +-------+ +-------+ +-------+ |
|
| 48 | * +--------------------------------------+
|
| 49 | *
|
| 50 | * The RPU firmware extracts the device information from the loaded FPGA
|
| 51 | * image and implements a mechanism that allows the APU drivers to
|
| 52 | * enumerate such devices (device personality and resource details) via
|
| 53 | * a dedicated communication channel. RPU mediates operations such as
|
| 54 | * discover, reset and rescan of the FPGA devices for the APU. This is
|
| 55 | * done using memory mapped interface provided by the RPU to APU.
|
| 56 |
|
| 57 |
|
| 58 | #include <linux/init.h>
|
| 59 | #include <linux/irqdomain.h>
|
| 60 | #include <linux/kernel.h>
|
| 61 | #include <linux/of.h>
|
| 62 | #include <linux/of_device.h>
|
| 63 | #include <linux/of_platform.h>
|
| 64 | #include <linux/platform_device.h>
|
| 65 | #include <linux/slab.h>
|
| 66 | #include <linux/mm.h>
|
| 67 | #include <linux/idr.h>
|
| 68 | #include <linux/cdx/cdx_bus.h>
|
| 69 | #include <linux/iommu.h>
|
| 70 | #include <linux/dma-map-ops.h>
|
| 71 | #include <linux/debugfs.h>
|
| 72 | #include "cdx.h"
|
| 73 |
|
| 74 |
|
| 75 | #define CDX_DEFAULT_DMA_MASK (~0ULL)
|
| 76 | #define MAX_CDX_CONTROLLERS 16
|
| 77 |
|
| 78 |
|
| 79 | static DEFINE_IDA(cdx_controller_ida);
|
| 80 |
|
| 81 | static DEFINE_MUTEX(cdx_controller_lock);
|
| 82 |
|
| 83 | static struct dentry *cdx_debugfs_dir;
|
| 84 |
|
| 85 | static char *compat_node_name = "xlnx,versal-net-cdx";
|
| 86 |
|
| 87 | static void cdx_destroy_res_attr(struct cdx_device *cdx_dev, int num);
|
| 88 |
|
| 89 | |
| 90 | * cdx_dev_reset - Reset a CDX device
|
| 91 | * @dev: CDX device
|
| 92 | *
|
| 93 | * Return: -errno on failure, 0 on success.
|
| 94 |
|
| 95 | int cdx_dev_reset(struct device *dev)
|
| 96 | {
|
| 97 | struct cdx_device *cdx_dev = to_cdx_device(dev);
|
| 98 | struct cdx_controller *cdx = cdx_dev->cdx;
|
| 99 | struct cdx_device_config dev_config = {0};
|
| 100 | struct cdx_driver *cdx_drv;
|
| 101 | int ret;
|
| 102 |
|
| 103 | cdx_drv = to_cdx_driver(dev->driver);
|
| 104 |
|
| 105 | if (cdx_drv && cdx_drv->reset_prepare)
|
| 106 | cdx_drv->reset_prepare(cdx_dev);
|
| 107 |
|
| 108 | dev_config.type = CDX_DEV_RESET_CONF;
|
| 109 | ret = cdx->ops->dev_configure(cdx, cdx_dev->bus_num,
|
| 110 | cdx_dev->dev_num, &dev_config);
|
| 111 | if (ret)
|
| 112 | dev_err(dev, "cdx device reset failed\n");
|
| 113 |
|
| 114 |
|
| 115 | if (cdx_drv && cdx_drv->reset_done)
|
| 116 | cdx_drv->reset_done(cdx_dev);
|
| 117 |
|
| 118 | return ret;
|
| 119 | }
|
| 120 | EXPORT_SYMBOL_GPL(cdx_dev_reset);
|
| 121 |
|
| 122 | |
| 123 | * reset_cdx_device - Reset a CDX device
|
| 124 | * @dev: CDX device
|
| 125 | * @data: This is always passed as NULL, and is not used in this API,
|
| 126 | * but is required here as the device_for_each_child() API expects
|
| 127 | * the passed function to have this as an argument.
|
| 128 | *
|
| 129 | * Return: -errno on failure, 0 on success.
|
| 130 |
|
| 131 | static int reset_cdx_device(struct device *dev, void *data)
|
| 132 | {
|
| 133 | return cdx_dev_reset(dev);
|
| 134 | }
|
| 135 |
|
| 136 | |
| 137 | * cdx_unregister_device - Unregister a CDX device
|
| 138 | * @dev: CDX device
|
| 139 | * @data: This is always passed as NULL, and is not used in this API,
|
| 140 | * but is required here as the bus_for_each_dev() API expects
|
| 141 | * the passed function (cdx_unregister_device) to have this
|
| 142 | * as an argument.
|
| 143 | *
|
| 144 | * Return: 0 on success.
|
| 145 |
|
| 146 | static int cdx_unregister_device(struct device *dev,
|
| 147 | void *data)
|
| 148 | {
|
| 149 | struct cdx_device *cdx_dev = to_cdx_device(dev);
|
| 150 | struct cdx_controller *cdx = cdx_dev->cdx;
|
| 151 |
|
| 152 | if (cdx_dev->is_bus) {
|
| 153 | device_for_each_child(dev, NULL, cdx_unregister_device);
|
| 154 | if (cdx_dev->enabled && cdx->ops->bus_disable)
|
| 155 | cdx->ops->bus_disable(cdx, cdx_dev->bus_num);
|
| 156 | } else {
|
| 157 | cdx_destroy_res_attr(cdx_dev, MAX_CDX_DEV_RESOURCES);
|
| 158 | debugfs_remove_recursive(cdx_dev->debugfs_dir);
|
| 159 | }
|
| 160 |
|
| 161 | |
| 162 | * Do not free cdx_dev here as it would be freed in
|
| 163 | * cdx_device_release() called from within put_device().
|
| 164 |
|
| 165 | device_del(&cdx_dev->dev);
|
| 166 | put_device(&cdx_dev->dev);
|
| 167 |
|
| 168 | return 0;
|
| 169 | }
|
| 170 |
|
| 171 | static void cdx_unregister_devices(const struct bus_type *bus)
|
| 172 | {
|
| 173 |
|
| 174 | bus_for_each_dev(bus, NULL, NULL, cdx_unregister_device);
|
| 175 | }
|
| 176 |
|
| 177 | |
| 178 | * cdx_match_one_device - Tell if a CDX device structure has a matching
|
| 179 | * CDX device id structure
|
| 180 | * @id: single CDX device id structure to match
|
| 181 | * @dev: the CDX device structure to match against
|
| 182 | *
|
| 183 | * Return: matching cdx_device_id structure or NULL if there is no match.
|
| 184 |
|
| 185 | static inline const struct cdx_device_id *
|
| 186 | cdx_match_one_device(const struct cdx_device_id *id,
|
| 187 | const struct cdx_device *dev)
|
| 188 | {
|
| 189 |
|
| 190 | if ((id->vendor == CDX_ANY_ID || id->vendor == dev->vendor) &&
|
| 191 | (id->device == CDX_ANY_ID || id->device == dev->device) &&
|
| 192 | (id->subvendor == CDX_ANY_ID || id->subvendor == dev->subsystem_vendor) &&
|
| 193 | (id->subdevice == CDX_ANY_ID || id->subdevice == dev->subsystem_device) &&
|
| 194 | !((id->class ^ dev->class) & id->class_mask))
|
| 195 | return id;
|
| 196 | return NULL;
|
| 197 | }
|
| 198 |
|
| 199 | |
| 200 | * cdx_match_id - See if a CDX device matches a given cdx_id table
|
| 201 | * @ids: array of CDX device ID structures to search in
|
| 202 | * @dev: the CDX device structure to match against.
|
| 203 | *
|
| 204 | * Used by a driver to check whether a CDX device is in its list of
|
| 205 | * supported devices. Returns the matching cdx_device_id structure or
|
| 206 | * NULL if there is no match.
|
| 207 | *
|
| 208 | * Return: matching cdx_device_id structure or NULL if there is no match.
|
| 209 |
|
| 210 | static inline const struct cdx_device_id *
|
| 211 | cdx_match_id(const struct cdx_device_id *ids, struct cdx_device *dev)
|
| 212 | {
|
| 213 | if (ids) {
|
| 214 | while (ids->vendor || ids->device) {
|
| 215 | if (cdx_match_one_device(ids, dev))
|
| 216 | return ids;
|
| 217 | ids++;
|
| 218 | }
|
| 219 | }
|
| 220 | return NULL;
|
| 221 | }
|
| 222 |
|
| 223 | int cdx_set_master(struct cdx_device *cdx_dev)
|
| 224 | {
|
| 225 | struct cdx_controller *cdx = cdx_dev->cdx;
|
| 226 | struct cdx_device_config dev_config;
|
| 227 | int ret = -EOPNOTSUPP;
|
| 228 |
|
| 229 | dev_config.type = CDX_DEV_BUS_MASTER_CONF;
|
| 230 | dev_config.bus_master_enable = true;
|
| 231 | if (cdx->ops->dev_configure)
|
| 232 | ret = cdx->ops->dev_configure(cdx, cdx_dev->bus_num,
|
| 233 | cdx_dev->dev_num, &dev_config);
|
| 234 |
|
| 235 | return ret;
|
| 236 | }
|
| 237 | EXPORT_SYMBOL_GPL(cdx_set_master);
|
| 238 |
|
| 239 | int cdx_clear_master(struct cdx_device *cdx_dev)
|
| 240 | {
|
| 241 | struct cdx_controller *cdx = cdx_dev->cdx;
|
| 242 | struct cdx_device_config dev_config;
|
| 243 | int ret = -EOPNOTSUPP;
|
| 244 |
|
| 245 | dev_config.type = CDX_DEV_BUS_MASTER_CONF;
|
| 246 | dev_config.bus_master_enable = false;
|
| 247 | if (cdx->ops->dev_configure)
|
| 248 | ret = cdx->ops->dev_configure(cdx, cdx_dev->bus_num,
|
| 249 | cdx_dev->dev_num, &dev_config);
|
| 250 |
|
| 251 | return ret;
|
| 252 | }
|
| 253 | EXPORT_SYMBOL_GPL(cdx_clear_master);
|
| 254 |
|
| 255 | |
| 256 | * cdx_bus_match - device to driver matching callback
|
| 257 | * @dev: the cdx device to match against
|
| 258 | * @drv: the device driver to search for matching cdx device
|
| 259 | * structures
|
| 260 | *
|
| 261 | * Return: true on success, false otherwise.
|
| 262 |
|
| 263 | static int cdx_bus_match(struct device *dev, const struct device_driver *drv)
|
| 264 | {
|
| 265 | struct cdx_device *cdx_dev = to_cdx_device(dev);
|
| 266 | const struct cdx_driver *cdx_drv = to_cdx_driver(drv);
|
| 267 | const struct cdx_device_id *found_id = NULL;
|
| 268 | const struct cdx_device_id *ids;
|
| 269 | int ret;
|
| 270 |
|
| 271 | if (cdx_dev->is_bus)
|
| 272 | return false;
|
| 273 |
|
| 274 | ids = cdx_drv->match_id_table;
|
| 275 |
|
| 276 |
|
| 277 | ret = device_match_driver_override(dev, drv);
|
| 278 | if (ret == 0)
|
| 279 | return false;
|
| 280 |
|
| 281 | found_id = cdx_match_id(ids, cdx_dev);
|
| 282 | if (!found_id)
|
| 283 | return false;
|
| 284 |
|
| 285 | do {
|
| 286 | |
| 287 | * In case override_only was set, enforce driver_override
|
| 288 | * matching.
|
| 289 |
|
| 290 | if (!found_id->override_only)
|
| 291 | return true;
|
| 292 | if (ret > 0)
|
| 293 | return true;
|
| 294 |
|
| 295 | ids = found_id + 1;
|
| 296 | found_id = cdx_match_id(ids, cdx_dev);
|
| 297 | } while (found_id);
|
| 298 |
|
| 299 | return false;
|
| 300 | }
|
| 301 |
|
| 302 | static int cdx_probe(struct device *dev)
|
| 303 | {
|
| 304 | struct cdx_driver *cdx_drv = to_cdx_driver(dev->driver);
|
| 305 | struct cdx_device *cdx_dev = to_cdx_device(dev);
|
| 306 | struct cdx_controller *cdx = cdx_dev->cdx;
|
| 307 | int error;
|
| 308 |
|
| 309 | |
| 310 | * Setup MSI device data so that generic MSI alloc/free can
|
| 311 | * be used by the device driver.
|
| 312 |
|
| 313 | if (IS_ENABLED(CONFIG_GENERIC_MSI_IRQ) && cdx->msi_domain) {
|
| 314 | error = msi_setup_device_data(&cdx_dev->dev);
|
| 315 | if (error)
|
| 316 | return error;
|
| 317 | }
|
| 318 |
|
| 319 | error = cdx_drv->probe(cdx_dev);
|
| 320 | if (error) {
|
| 321 | dev_err_probe(dev, error, "%s failed\n", __func__);
|
| 322 | return error;
|
| 323 | }
|
| 324 |
|
| 325 | return 0;
|
| 326 | }
|
| 327 |
|
| 328 | static void cdx_remove(struct device *dev)
|
| 329 | {
|
| 330 | struct cdx_driver *cdx_drv = to_cdx_driver(dev->driver);
|
| 331 | struct cdx_device *cdx_dev = to_cdx_device(dev);
|
| 332 |
|
| 333 | if (cdx_drv && cdx_drv->remove)
|
| 334 | cdx_drv->remove(cdx_dev);
|
| 335 | }
|
| 336 |
|
| 337 | static void cdx_shutdown(struct device *dev)
|
| 338 | {
|
| 339 | struct cdx_driver *cdx_drv = to_cdx_driver(dev->driver);
|
| 340 | struct cdx_device *cdx_dev = to_cdx_device(dev);
|
| 341 | struct cdx_controller *cdx = cdx_dev->cdx;
|
| 342 |
|
| 343 | if (cdx_dev->is_bus && cdx_dev->enabled && cdx->ops->bus_disable)
|
| 344 | cdx->ops->bus_disable(cdx, cdx_dev->bus_num);
|
| 345 | if (cdx_drv && cdx_drv->shutdown)
|
| 346 | cdx_drv->shutdown(cdx_dev);
|
| 347 | }
|
| 348 |
|
| 349 | static int cdx_dma_configure(struct device *dev)
|
| 350 | {
|
| 351 | struct cdx_driver *cdx_drv = to_cdx_driver(dev->driver);
|
| 352 | struct cdx_device *cdx_dev = to_cdx_device(dev);
|
| 353 | struct cdx_controller *cdx = cdx_dev->cdx;
|
| 354 | u32 input_id = cdx_dev->req_id;
|
| 355 | int ret;
|
| 356 |
|
| 357 | ret = of_dma_configure_id(dev, cdx->dev->of_node, 0, &input_id);
|
| 358 | if (ret && ret != -EPROBE_DEFER) {
|
| 359 | dev_err(dev, "of_dma_configure_id() failed\n");
|
| 360 | return ret;
|
| 361 | }
|
| 362 |
|
| 363 |
|
| 364 | if (!ret && dev->driver && !cdx_drv->driver_managed_dma) {
|
| 365 | ret = iommu_device_use_default_domain(dev);
|
| 366 | if (ret)
|
| 367 | arch_teardown_dma_ops(dev);
|
| 368 | }
|
| 369 |
|
| 370 | return 0;
|
| 371 | }
|
| 372 |
|
| 373 | static void cdx_dma_cleanup(struct device *dev)
|
| 374 | {
|
| 375 | struct cdx_driver *cdx_drv = to_cdx_driver(dev->driver);
|
| 376 |
|
| 377 | if (!cdx_drv->driver_managed_dma)
|
| 378 | iommu_device_unuse_default_domain(dev);
|
| 379 | }
|
| 380 |
|
| 381 |
|
| 382 | #define cdx_config_attr(field, format_string) \
|
| 383 | static ssize_t \
|
| 384 | field##_show(struct device *dev, struct device_attribute *attr, char *buf) \
|
| 385 | { \
|
| 386 | struct cdx_device *cdx_dev = to_cdx_device(dev); \
|
| 387 | return sysfs_emit(buf, format_string, cdx_dev->field); \
|
| 388 | } \
|
| 389 | static DEVICE_ATTR_RO(field)
|
| 390 |
|
| 391 | cdx_config_attr(vendor, "0x%04x\n");
|
| 392 | cdx_config_attr(device, "0x%04x\n");
|
| 393 | cdx_config_attr(subsystem_vendor, "0x%04x\n");
|
| 394 | cdx_config_attr(subsystem_device, "0x%04x\n");
|
| 395 | cdx_config_attr(revision, "0x%02x\n");
|
| 396 | cdx_config_attr(class, "0x%06x\n");
|
| 397 |
|
| 398 | static ssize_t remove_store(struct device *dev,
|
| 399 | struct device_attribute *attr,
|
| 400 | const char *buf, size_t count)
|
| 401 | {
|
| 402 | bool val;
|
| 403 |
|
| 404 | if (kstrtobool(buf, &val) < 0)
|
| 405 | return -EINVAL;
|
| 406 |
|
| 407 | if (!val)
|
| 408 | return -EINVAL;
|
| 409 |
|
| 410 | if (device_remove_file_self(dev, attr)) {
|
| 411 | int ret;
|
| 412 |
|
| 413 | ret = cdx_unregister_device(dev, NULL);
|
| 414 | if (ret)
|
| 415 | return ret;
|
| 416 | }
|
| 417 |
|
| 418 | return count;
|
| 419 | }
|
| 420 | static DEVICE_ATTR_WO(remove);
|
| 421 |
|
| 422 | static ssize_t reset_store(struct device *dev, struct device_attribute *attr,
|
| 423 | const char *buf, size_t count)
|
| 424 | {
|
| 425 | struct cdx_device *cdx_dev = to_cdx_device(dev);
|
| 426 | bool val;
|
| 427 | int ret;
|
| 428 |
|
| 429 | if (kstrtobool(buf, &val) < 0)
|
| 430 | return -EINVAL;
|
| 431 |
|
| 432 | if (!val)
|
| 433 | return -EINVAL;
|
| 434 |
|
| 435 | if (cdx_dev->is_bus)
|
| 436 |
|
| 437 | ret = device_for_each_child(dev, NULL, reset_cdx_device);
|
| 438 | else
|
| 439 | ret = cdx_dev_reset(dev);
|
| 440 |
|
| 441 | return ret < 0 ? ret : count;
|
| 442 | }
|
| 443 | static DEVICE_ATTR_WO(reset);
|
| 444 |
|
| 445 | static ssize_t modalias_show(struct device *dev, struct device_attribute *attr,
|
| 446 | char *buf)
|
| 447 | {
|
| 448 | struct cdx_device *cdx_dev = to_cdx_device(dev);
|
| 449 |
|
| 450 | return sprintf(buf, "cdx:v%04Xd%04Xsv%04Xsd%04Xc%06X\n", cdx_dev->vendor,
|
| 451 | cdx_dev->device, cdx_dev->subsystem_vendor, cdx_dev->subsystem_device,
|
| 452 | cdx_dev->class);
|
| 453 | }
|
| 454 | static DEVICE_ATTR_RO(modalias);
|
| 455 |
|
| 456 | static ssize_t enable_store(struct device *dev, struct device_attribute *attr,
|
| 457 | const char *buf, size_t count)
|
| 458 | {
|
| 459 | struct cdx_device *cdx_dev = to_cdx_device(dev);
|
| 460 | struct cdx_controller *cdx = cdx_dev->cdx;
|
| 461 | bool enable;
|
| 462 | int ret;
|
| 463 |
|
| 464 | if (kstrtobool(buf, &enable) < 0)
|
| 465 | return -EINVAL;
|
| 466 |
|
| 467 | if (enable == cdx_dev->enabled)
|
| 468 | return count;
|
| 469 |
|
| 470 | if (enable && cdx->ops->bus_enable)
|
| 471 | ret = cdx->ops->bus_enable(cdx, cdx_dev->bus_num);
|
| 472 | else if (!enable && cdx->ops->bus_disable)
|
| 473 | ret = cdx->ops->bus_disable(cdx, cdx_dev->bus_num);
|
| 474 | else
|
| 475 | ret = -EOPNOTSUPP;
|
| 476 |
|
| 477 | if (!ret)
|
| 478 | cdx_dev->enabled = enable;
|
| 479 |
|
| 480 | return ret < 0 ? ret : count;
|
| 481 | }
|
| 482 |
|
| 483 | static ssize_t enable_show(struct device *dev, struct device_attribute *attr, char *buf)
|
| 484 | {
|
| 485 | struct cdx_device *cdx_dev = to_cdx_device(dev);
|
| 486 |
|
| 487 | return sysfs_emit(buf, "%u\n", cdx_dev->enabled);
|
| 488 | }
|
| 489 | static DEVICE_ATTR_RW(enable);
|
| 490 |
|
| 491 | static umode_t cdx_dev_attrs_are_visible(struct kobject *kobj, struct attribute *a, int n)
|
| 492 | {
|
| 493 | struct device *dev = kobj_to_dev(kobj);
|
| 494 | struct cdx_device *cdx_dev;
|
| 495 |
|
| 496 | cdx_dev = to_cdx_device(dev);
|
| 497 | if (!cdx_dev->is_bus)
|
| 498 | return a->mode;
|
| 499 |
|
| 500 | return 0;
|
| 501 | }
|
| 502 |
|
| 503 | static umode_t cdx_bus_attrs_are_visible(struct kobject *kobj, struct attribute *a, int n)
|
| 504 | {
|
| 505 | struct device *dev = kobj_to_dev(kobj);
|
| 506 | struct cdx_device *cdx_dev;
|
| 507 |
|
| 508 | cdx_dev = to_cdx_device(dev);
|
| 509 | if (cdx_dev->is_bus)
|
| 510 | return a->mode;
|
| 511 |
|
| 512 | return 0;
|
| 513 | }
|
| 514 |
|
| 515 | static struct attribute *cdx_dev_attrs[] = {
|
| 516 | &dev_attr_remove.attr,
|
| 517 | &dev_attr_reset.attr,
|
| 518 | &dev_attr_vendor.attr,
|
| 519 | &dev_attr_device.attr,
|
| 520 | &dev_attr_subsystem_vendor.attr,
|
| 521 | &dev_attr_subsystem_device.attr,
|
| 522 | &dev_attr_class.attr,
|
| 523 | &dev_attr_revision.attr,
|
| 524 | &dev_attr_modalias.attr,
|
| 525 | NULL,
|
| 526 | };
|
| 527 |
|
| 528 | static const struct attribute_group cdx_dev_group = {
|
| 529 | .attrs = cdx_dev_attrs,
|
| 530 | .is_visible = cdx_dev_attrs_are_visible,
|
| 531 | };
|
| 532 |
|
| 533 | static struct attribute *cdx_bus_dev_attrs[] = {
|
| 534 | &dev_attr_enable.attr,
|
| 535 | &dev_attr_reset.attr,
|
| 536 | NULL,
|
| 537 | };
|
| 538 |
|
| 539 | static const struct attribute_group cdx_bus_dev_group = {
|
| 540 | .attrs = cdx_bus_dev_attrs,
|
| 541 | .is_visible = cdx_bus_attrs_are_visible,
|
| 542 | };
|
| 543 |
|
| 544 | static const struct attribute_group *cdx_dev_groups[] = {
|
| 545 | &cdx_dev_group,
|
| 546 | &cdx_bus_dev_group,
|
| 547 | NULL,
|
| 548 | };
|
| 549 |
|
| 550 | static int cdx_debug_resource_show(struct seq_file *s, void *data)
|
| 551 | {
|
| 552 | struct cdx_device *cdx_dev = s->private;
|
| 553 | int i;
|
| 554 |
|
| 555 | for (i = 0; i < MAX_CDX_DEV_RESOURCES; i++) {
|
| 556 | struct resource *res = &cdx_dev->res[i];
|
| 557 |
|
| 558 | seq_printf(s, "%pr\n", res);
|
| 559 | }
|
| 560 |
|
| 561 | return 0;
|
| 562 | }
|
| 563 | DEFINE_SHOW_ATTRIBUTE(cdx_debug_resource);
|
| 564 |
|
| 565 | static void cdx_device_debugfs_init(struct cdx_device *cdx_dev)
|
| 566 | {
|
| 567 | cdx_dev->debugfs_dir = debugfs_create_dir(dev_name(&cdx_dev->dev), cdx_debugfs_dir);
|
| 568 | if (IS_ERR(cdx_dev->debugfs_dir))
|
| 569 | return;
|
| 570 |
|
| 571 | debugfs_create_file("resource", 0444, cdx_dev->debugfs_dir, cdx_dev,
|
| 572 | &cdx_debug_resource_fops);
|
| 573 | }
|
| 574 |
|
| 575 | static ssize_t rescan_store(const struct bus_type *bus,
|
| 576 | const char *buf, size_t count)
|
| 577 | {
|
| 578 | struct cdx_controller *cdx;
|
| 579 | struct platform_device *pd;
|
| 580 | bool val;
|
| 581 |
|
| 582 | if (kstrtobool(buf, &val) < 0)
|
| 583 | return -EINVAL;
|
| 584 |
|
| 585 | if (!val)
|
| 586 | return -EINVAL;
|
| 587 |
|
| 588 | guard(mutex)(&cdx_controller_lock);
|
| 589 |
|
| 590 |
|
| 591 | cdx_unregister_devices(&cdx_bus_type);
|
| 592 |
|
| 593 |
|
| 594 | for_each_compatible_node_scoped(np, NULL, compat_node_name) {
|
| 595 | pd = of_find_device_by_node(np);
|
| 596 | if (!pd)
|
| 597 | return -EINVAL;
|
| 598 |
|
| 599 | cdx = platform_get_drvdata(pd);
|
| 600 | if (cdx && cdx->controller_registered && cdx->ops->scan)
|
| 601 | cdx->ops->scan(cdx);
|
| 602 |
|
| 603 | put_device(&pd->dev);
|
| 604 | }
|
| 605 |
|
| 606 | return count;
|
| 607 | }
|
| 608 | static BUS_ATTR_WO(rescan);
|
| 609 |
|
| 610 | static struct attribute *cdx_bus_attrs[] = {
|
| 611 | &bus_attr_rescan.attr,
|
| 612 | NULL,
|
| 613 | };
|
| 614 | ATTRIBUTE_GROUPS(cdx_bus);
|
| 615 |
|
| 616 | const struct bus_type cdx_bus_type = {
|
| 617 | .name = "cdx",
|
| 618 | .driver_override = true,
|
| 619 | .match = cdx_bus_match,
|
| 620 | .probe = cdx_probe,
|
| 621 | .remove = cdx_remove,
|
| 622 | .shutdown = cdx_shutdown,
|
| 623 | .dma_configure = cdx_dma_configure,
|
| 624 | .dma_cleanup = cdx_dma_cleanup,
|
| 625 | .bus_groups = cdx_bus_groups,
|
| 626 | .dev_groups = cdx_dev_groups,
|
| 627 | };
|
| 628 | EXPORT_SYMBOL_GPL(cdx_bus_type);
|
| 629 |
|
| 630 | int __cdx_driver_register(struct cdx_driver *cdx_driver,
|
| 631 | struct module *owner)
|
| 632 | {
|
| 633 | int error;
|
| 634 |
|
| 635 | cdx_driver->driver.owner = owner;
|
| 636 | cdx_driver->driver.bus = &cdx_bus_type;
|
| 637 |
|
| 638 | error = driver_register(&cdx_driver->driver);
|
| 639 | if (error) {
|
| 640 | pr_err("driver_register() failed for %s: %d\n",
|
| 641 | cdx_driver->driver.name, error);
|
| 642 | return error;
|
| 643 | }
|
| 644 |
|
| 645 | return 0;
|
| 646 | }
|
| 647 | EXPORT_SYMBOL_GPL(__cdx_driver_register);
|
| 648 |
|
| 649 | void cdx_driver_unregister(struct cdx_driver *cdx_driver)
|
| 650 | {
|
| 651 | driver_unregister(&cdx_driver->driver);
|
| 652 | }
|
| 653 | EXPORT_SYMBOL_GPL(cdx_driver_unregister);
|
| 654 |
|
| 655 | static void cdx_device_release(struct device *dev)
|
| 656 | {
|
| 657 | struct cdx_device *cdx_dev = to_cdx_device(dev);
|
| 658 |
|
| 659 | kfree(cdx_dev);
|
| 660 | }
|
| 661 |
|
| 662 | static const struct vm_operations_struct cdx_phys_vm_ops = {
|
| 663 | #ifdef CONFIG_HAVE_IOREMAP_PROT
|
| 664 | .access = generic_access_phys,
|
| 665 | #endif
|
| 666 | };
|
| 667 |
|
| 668 | |
| 669 | * cdx_mmap_resource - map a CDX resource into user memory space
|
| 670 | * @fp: File pointer. Not used in this function, but required where
|
| 671 | * this API is registered as a callback.
|
| 672 | * @kobj: kobject for mapping
|
| 673 | * @attr: struct bin_attribute for the file being mapped
|
| 674 | * @vma: struct vm_area_struct passed into the mmap
|
| 675 | *
|
| 676 | * Use the regular CDX mapping routines to map a CDX resource into userspace.
|
| 677 | *
|
| 678 | * Return: true on success, false otherwise.
|
| 679 |
|
| 680 | static int cdx_mmap_resource(struct file *fp, struct kobject *kobj,
|
| 681 | const struct bin_attribute *attr,
|
| 682 | struct vm_area_struct *vma)
|
| 683 | {
|
| 684 | struct cdx_device *cdx_dev = to_cdx_device(kobj_to_dev(kobj));
|
| 685 | int num = (unsigned long)attr->private;
|
| 686 | struct resource *res;
|
| 687 | unsigned long size;
|
| 688 |
|
| 689 | res = &cdx_dev->res[num];
|
| 690 | if (iomem_is_exclusive(res->start))
|
| 691 | return -EINVAL;
|
| 692 |
|
| 693 |
|
| 694 | size = ((cdx_resource_len(cdx_dev, num) - 1) >> PAGE_SHIFT) + 1;
|
| 695 | if (vma->vm_pgoff + vma_pages(vma) > size)
|
| 696 | return -EINVAL;
|
| 697 |
|
| 698 | |
| 699 | * Map memory region and vm->vm_pgoff is expected to be an
|
| 700 | * offset within that region.
|
| 701 |
|
| 702 | vma->vm_page_prot = pgprot_device(vma->vm_page_prot);
|
| 703 | vma->vm_pgoff += (cdx_resource_start(cdx_dev, num) >> PAGE_SHIFT);
|
| 704 | vma->vm_ops = &cdx_phys_vm_ops;
|
| 705 | return io_remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
|
| 706 | vma->vm_end - vma->vm_start,
|
| 707 | vma->vm_page_prot);
|
| 708 | }
|
| 709 |
|
| 710 | static void cdx_destroy_res_attr(struct cdx_device *cdx_dev, int num)
|
| 711 | {
|
| 712 | int i;
|
| 713 |
|
| 714 |
|
| 715 | for (i = 0; i < num; i++) {
|
| 716 | struct bin_attribute *res_attr;
|
| 717 |
|
| 718 | res_attr = cdx_dev->res_attr[i];
|
| 719 | if (res_attr) {
|
| 720 | sysfs_remove_bin_file(&cdx_dev->dev.kobj, res_attr);
|
| 721 | kfree(res_attr);
|
| 722 | }
|
| 723 | }
|
| 724 | }
|
| 725 |
|
| 726 | #define CDX_RES_ATTR_NAME_LEN 10
|
| 727 | static int cdx_create_res_attr(struct cdx_device *cdx_dev, int num)
|
| 728 | {
|
| 729 | struct bin_attribute *res_attr;
|
| 730 | char *res_attr_name;
|
| 731 | int ret;
|
| 732 |
|
| 733 | res_attr = kzalloc(sizeof(*res_attr) + CDX_RES_ATTR_NAME_LEN, GFP_ATOMIC);
|
| 734 | if (!res_attr)
|
| 735 | return -ENOMEM;
|
| 736 |
|
| 737 | res_attr_name = (char *)(res_attr + 1);
|
| 738 |
|
| 739 | sysfs_bin_attr_init(res_attr);
|
| 740 |
|
| 741 | sprintf(res_attr_name, "resource%d", num);
|
| 742 |
|
| 743 | res_attr->mmap = cdx_mmap_resource;
|
| 744 | res_attr->attr.name = res_attr_name;
|
| 745 | res_attr->attr.mode = 0600;
|
| 746 | res_attr->size = cdx_resource_len(cdx_dev, num);
|
| 747 | res_attr->private = (void *)(unsigned long)num;
|
| 748 | ret = sysfs_create_bin_file(&cdx_dev->dev.kobj, res_attr);
|
| 749 | if (ret) {
|
| 750 | kfree(res_attr);
|
| 751 | return ret;
|
| 752 | }
|
| 753 |
|
| 754 | cdx_dev->res_attr[num] = res_attr;
|
| 755 |
|
| 756 | return ret;
|
| 757 | }
|
| 758 |
|
| 759 | int cdx_device_add(struct cdx_dev_params *dev_params)
|
| 760 | {
|
| 761 | struct cdx_controller *cdx = dev_params->cdx;
|
| 762 | struct cdx_device *cdx_dev;
|
| 763 | int ret, i;
|
| 764 |
|
| 765 | cdx_dev = kzalloc_obj(*cdx_dev);
|
| 766 | if (!cdx_dev)
|
| 767 | return -ENOMEM;
|
| 768 |
|
| 769 |
|
| 770 | memcpy(cdx_dev->res, dev_params->res, sizeof(struct resource) *
|
| 771 | dev_params->res_count);
|
| 772 | cdx_dev->res_count = dev_params->res_count;
|
| 773 |
|
| 774 |
|
| 775 | cdx_dev->req_id = dev_params->req_id;
|
| 776 | cdx_dev->msi_dev_id = dev_params->msi_dev_id;
|
| 777 | cdx_dev->vendor = dev_params->vendor;
|
| 778 | cdx_dev->device = dev_params->device;
|
| 779 | cdx_dev->subsystem_vendor = dev_params->subsys_vendor;
|
| 780 | cdx_dev->subsystem_device = dev_params->subsys_device;
|
| 781 | cdx_dev->class = dev_params->class;
|
| 782 | cdx_dev->revision = dev_params->revision;
|
| 783 | cdx_dev->bus_num = dev_params->bus_num;
|
| 784 | cdx_dev->dev_num = dev_params->dev_num;
|
| 785 | cdx_dev->cdx = dev_params->cdx;
|
| 786 | cdx_dev->dma_mask = CDX_DEFAULT_DMA_MASK;
|
| 787 |
|
| 788 |
|
| 789 | device_initialize(&cdx_dev->dev);
|
| 790 | cdx_dev->dev.parent = dev_params->parent;
|
| 791 | cdx_dev->dev.bus = &cdx_bus_type;
|
| 792 | cdx_dev->dev.dma_mask = &cdx_dev->dma_mask;
|
| 793 | cdx_dev->dev.release = cdx_device_release;
|
| 794 | cdx_dev->msi_write_pending = false;
|
| 795 | mutex_init(&cdx_dev->irqchip_lock);
|
| 796 |
|
| 797 |
|
| 798 | dev_set_name(&cdx_dev->dev, "cdx-%02x:%02x",
|
| 799 | ((cdx->id << CDX_CONTROLLER_ID_SHIFT) | (cdx_dev->bus_num & CDX_BUS_NUM_MASK)),
|
| 800 | cdx_dev->dev_num);
|
| 801 |
|
| 802 | if (IS_ENABLED(CONFIG_GENERIC_MSI_IRQ) && cdx->msi_domain) {
|
| 803 | cdx_dev->num_msi = dev_params->num_msi;
|
| 804 | dev_set_msi_domain(&cdx_dev->dev, cdx->msi_domain);
|
| 805 | }
|
| 806 |
|
| 807 | ret = device_add(&cdx_dev->dev);
|
| 808 | if (ret) {
|
| 809 | dev_err(&cdx_dev->dev,
|
| 810 | "cdx device add failed: %d", ret);
|
| 811 | goto fail;
|
| 812 | }
|
| 813 |
|
| 814 |
|
| 815 | for (i = 0; i < MAX_CDX_DEV_RESOURCES; i++) {
|
| 816 | if (cdx_resource_flags(cdx_dev, i) & IORESOURCE_MEM) {
|
| 817 |
|
| 818 | if (!cdx_resource_len(cdx_dev, i))
|
| 819 | continue;
|
| 820 |
|
| 821 | ret = cdx_create_res_attr(cdx_dev, i);
|
| 822 | if (ret != 0) {
|
| 823 | dev_err(&cdx_dev->dev,
|
| 824 | "cdx device resource<%d> file creation failed: %d", i, ret);
|
| 825 | goto resource_create_fail;
|
| 826 | }
|
| 827 | }
|
| 828 | }
|
| 829 |
|
| 830 | cdx_device_debugfs_init(cdx_dev);
|
| 831 |
|
| 832 | return 0;
|
| 833 | resource_create_fail:
|
| 834 | cdx_destroy_res_attr(cdx_dev, i);
|
| 835 | device_del(&cdx_dev->dev);
|
| 836 | fail:
|
| 837 | |
| 838 | * Do not free cdx_dev here as it would be freed in
|
| 839 | * cdx_device_release() called from put_device().
|
| 840 |
|
| 841 | put_device(&cdx_dev->dev);
|
| 842 |
|
| 843 | return ret;
|
| 844 | }
|
| 845 | EXPORT_SYMBOL_NS_GPL(cdx_device_add, "CDX_BUS_CONTROLLER");
|
| 846 |
|
| 847 | struct device *cdx_bus_add(struct cdx_controller *cdx, u8 bus_num)
|
| 848 | {
|
| 849 | struct cdx_device *cdx_dev;
|
| 850 | int ret;
|
| 851 |
|
| 852 | cdx_dev = kzalloc_obj(*cdx_dev);
|
| 853 | if (!cdx_dev)
|
| 854 | return NULL;
|
| 855 |
|
| 856 | device_initialize(&cdx_dev->dev);
|
| 857 | cdx_dev->cdx = cdx;
|
| 858 |
|
| 859 | cdx_dev->dev.parent = cdx->dev;
|
| 860 | cdx_dev->dev.bus = &cdx_bus_type;
|
| 861 | cdx_dev->dev.release = cdx_device_release;
|
| 862 | cdx_dev->is_bus = true;
|
| 863 | cdx_dev->bus_num = bus_num;
|
| 864 |
|
| 865 | dev_set_name(&cdx_dev->dev, "cdx-%02x",
|
| 866 | ((cdx->id << CDX_CONTROLLER_ID_SHIFT) | (bus_num & CDX_BUS_NUM_MASK)));
|
| 867 |
|
| 868 | ret = device_add(&cdx_dev->dev);
|
| 869 | if (ret) {
|
| 870 | dev_err(&cdx_dev->dev, "cdx bus device add failed: %d\n", ret);
|
| 871 | goto device_add_fail;
|
| 872 | }
|
| 873 |
|
| 874 | if (cdx->ops->bus_enable) {
|
| 875 | ret = cdx->ops->bus_enable(cdx, bus_num);
|
| 876 | if (ret && ret != -EALREADY) {
|
| 877 | dev_err(cdx->dev, "cdx bus enable failed: %d\n", ret);
|
| 878 | goto bus_enable_fail;
|
| 879 | }
|
| 880 | }
|
| 881 |
|
| 882 | cdx_dev->enabled = true;
|
| 883 | return &cdx_dev->dev;
|
| 884 |
|
| 885 | bus_enable_fail:
|
| 886 | device_del(&cdx_dev->dev);
|
| 887 | device_add_fail:
|
| 888 | put_device(&cdx_dev->dev);
|
| 889 |
|
| 890 | return NULL;
|
| 891 | }
|
| 892 | EXPORT_SYMBOL_NS_GPL(cdx_bus_add, "CDX_BUS_CONTROLLER");
|
| 893 |
|
| 894 | int cdx_register_controller(struct cdx_controller *cdx)
|
| 895 | {
|
| 896 | int ret;
|
| 897 |
|
| 898 | ret = ida_alloc_range(&cdx_controller_ida, 0, MAX_CDX_CONTROLLERS - 1, GFP_KERNEL);
|
| 899 | if (ret < 0) {
|
| 900 | dev_err(cdx->dev,
|
| 901 | "No free index available. Maximum controllers already registered\n");
|
| 902 | cdx->id = (u8)MAX_CDX_CONTROLLERS;
|
| 903 | return ret;
|
| 904 | }
|
| 905 |
|
| 906 | mutex_lock(&cdx_controller_lock);
|
| 907 | cdx->id = ret;
|
| 908 |
|
| 909 |
|
| 910 | if (cdx->ops->scan)
|
| 911 | cdx->ops->scan(cdx);
|
| 912 | cdx->controller_registered = true;
|
| 913 | mutex_unlock(&cdx_controller_lock);
|
| 914 |
|
| 915 | return 0;
|
| 916 | }
|
| 917 | EXPORT_SYMBOL_NS_GPL(cdx_register_controller, "CDX_BUS_CONTROLLER");
|
| 918 |
|
| 919 | void cdx_unregister_controller(struct cdx_controller *cdx)
|
| 920 | {
|
| 921 | if (cdx->id >= MAX_CDX_CONTROLLERS)
|
| 922 | return;
|
| 923 |
|
| 924 | mutex_lock(&cdx_controller_lock);
|
| 925 |
|
| 926 | cdx->controller_registered = false;
|
| 927 | device_for_each_child(cdx->dev, NULL, cdx_unregister_device);
|
| 928 | ida_free(&cdx_controller_ida, cdx->id);
|
| 929 |
|
| 930 | mutex_unlock(&cdx_controller_lock);
|
| 931 | }
|
| 932 | EXPORT_SYMBOL_NS_GPL(cdx_unregister_controller, "CDX_BUS_CONTROLLER");
|
| 933 |
|
| 934 | static int __init cdx_bus_init(void)
|
| 935 | {
|
| 936 | int ret;
|
| 937 |
|
| 938 | ret = bus_register(&cdx_bus_type);
|
| 939 | if (!ret)
|
| 940 | cdx_debugfs_dir = debugfs_create_dir(cdx_bus_type.name, NULL);
|
| 941 |
|
| 942 | return ret;
|
| 943 | }
|
| 944 | postcore_initcall(cdx_bus_init);
|