blob: 25ce725035e77b81197fe4efc26dab599a799180 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright 2015 IBM Corp.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include "cxl.h"
static const __be32 *read_prop_string(const struct device_node *np,
const char *prop_name)
{
const __be32 *prop;
prop = of_get_property(np, prop_name, NULL);
if (cxl_verbose && prop)
pr_info("%s: %s\n", prop_name, (char *) prop);
return prop;
}
static const __be32 *read_prop_dword(const struct device_node *np,
const char *prop_name, u32 *val)
{
const __be32 *prop;
prop = of_get_property(np, prop_name, NULL);
if (prop)
*val = be32_to_cpu(prop[0]);
if (cxl_verbose && prop)
pr_info("%s: %#x (%u)\n", prop_name, *val, *val);
return prop;
}
static const __be64 *read_prop64_dword(const struct device_node *np,
const char *prop_name, u64 *val)
{
const __be64 *prop;
prop = of_get_property(np, prop_name, NULL);
if (prop)
*val = be64_to_cpu(prop[0]);
if (cxl_verbose && prop)
pr_info("%s: %#llx (%llu)\n", prop_name, *val, *val);
return prop;
}
static int read_handle(struct device_node *np, u64 *handle)
{
const __be32 *prop;
u64 size;
/* Get address and size of the node */
prop = of_get_address(np, 0, &size, NULL);
if (size)
return -EINVAL;
/* Helper to read a big number; size is in cells (not bytes) */
*handle = of_read_number(prop, of_n_addr_cells(np));
return 0;
}
static int read_phys_addr(struct device_node *np, char *prop_name,
struct cxl_afu *afu)
{
int i, len, entry_size, naddr, nsize, type;
u64 addr, size;
const __be32 *prop;
naddr = of_n_addr_cells(np);
nsize = of_n_size_cells(np);
prop = of_get_property(np, prop_name, &len);
if (prop) {
entry_size = naddr + nsize;
for (i = 0; i < (len / 4); i += entry_size, prop += entry_size) {
type = be32_to_cpu(prop[0]);
addr = of_read_number(prop, naddr);
size = of_read_number(&prop[naddr], nsize);
switch (type) {
case 0: /* unit address */
afu->guest->handle = addr;
break;
case 1: /* p2 area */
afu->guest->p2n_phys += addr;
afu->guest->p2n_size = size;
break;
case 2: /* problem state area */
afu->psn_phys += addr;
afu->adapter->ps_size = size;
break;
default:
pr_err("Invalid address type %d found in %s property of AFU\n",
type, prop_name);
return -EINVAL;
}
if (cxl_verbose)
pr_info("%s: %#x %#llx (size %#llx)\n",
prop_name, type, addr, size);
}
}
return 0;
}
static int read_vpd(struct cxl *adapter, struct cxl_afu *afu)
{
char vpd[256];
int rc;
size_t len = sizeof(vpd);
memset(vpd, 0, len);
if (adapter)
rc = cxl_guest_read_adapter_vpd(adapter, vpd, len);
else
rc = cxl_guest_read_afu_vpd(afu, vpd, len);
if (rc > 0) {
cxl_dump_debug_buffer(vpd, rc);
rc = 0;
}
return rc;
}
int cxl_of_read_afu_handle(struct cxl_afu *afu, struct device_node *afu_np)
{
if (read_handle(afu_np, &afu->guest->handle))
return -EINVAL;
pr_devel("AFU handle: 0x%.16llx\n", afu->guest->handle);
return 0;
}
int cxl_of_read_afu_properties(struct cxl_afu *afu, struct device_node *np)
{
int i, len, rc;
char *p;
const __be32 *prop;
u16 device_id, vendor_id;
u32 val = 0, class_code;
/* Properties are read in the same order as listed in PAPR */
if (cxl_verbose) {
pr_info("Dump of the 'ibm,coherent-platform-function' node properties:\n");
prop = of_get_property(np, "compatible", &len);
i = 0;
while (i < len) {
p = (char *) prop + i;
pr_info("compatible: %s\n", p);
i += strlen(p) + 1;
}
read_prop_string(np, "name");
}
rc = read_phys_addr(np, "reg", afu);
if (rc)
return rc;
rc = read_phys_addr(np, "assigned-addresses", afu);
if (rc)
return rc;
if (afu->psn_phys == 0)
afu->psa = false;
else
afu->psa = true;
if (cxl_verbose) {
read_prop_string(np, "ibm,loc-code");
read_prop_string(np, "device_type");
}
read_prop_dword(np, "ibm,#processes", &afu->max_procs_virtualised);
if (cxl_verbose) {
read_prop_dword(np, "ibm,scratchpad-size", &val);
read_prop_dword(np, "ibm,programmable", &val);
read_prop_string(np, "ibm,phandle");
read_vpd(NULL, afu);
}
read_prop_dword(np, "ibm,max-ints-per-process", &afu->guest->max_ints);
afu->irqs_max = afu->guest->max_ints;
prop = read_prop_dword(np, "ibm,min-ints-per-process", &afu->pp_irqs);
if (prop) {
/* One extra interrupt for the PSL interrupt is already
* included. Remove it now to keep only AFU interrupts and
* match the native case.
*/
afu->pp_irqs--;
}
if (cxl_verbose) {
read_prop_dword(np, "ibm,max-ints", &val);
read_prop_dword(np, "ibm,vpd-size", &val);
}
read_prop64_dword(np, "ibm,error-buffer-size", &afu->eb_len);
afu->eb_offset = 0;
if (cxl_verbose)
read_prop_dword(np, "ibm,config-record-type", &val);
read_prop64_dword(np, "ibm,config-record-size", &afu->crs_len);
afu->crs_offset = 0;
read_prop_dword(np, "ibm,#config-records", &afu->crs_num);
if (cxl_verbose) {
for (i = 0; i < afu->crs_num; i++) {
rc = cxl_ops->afu_cr_read16(afu, i, PCI_DEVICE_ID,
&device_id);
if (!rc)
pr_info("record %d - device-id: %#x\n",
i, device_id);
rc = cxl_ops->afu_cr_read16(afu, i, PCI_VENDOR_ID,
&vendor_id);
if (!rc)
pr_info("record %d - vendor-id: %#x\n",
i, vendor_id);
rc = cxl_ops->afu_cr_read32(afu, i, PCI_CLASS_REVISION,
&class_code);
if (!rc) {
class_code >>= 8;
pr_info("record %d - class-code: %#x\n",
i, class_code);
}
}
read_prop_dword(np, "ibm,function-number", &val);
read_prop_dword(np, "ibm,privileged-function", &val);
read_prop_dword(np, "vendor-id", &val);
read_prop_dword(np, "device-id", &val);
read_prop_dword(np, "revision-id", &val);
read_prop_dword(np, "class-code", &val);
read_prop_dword(np, "subsystem-vendor-id", &val);
read_prop_dword(np, "subsystem-id", &val);
}
/*
* if "ibm,process-mmio" doesn't exist then per-process mmio is
* not supported
*/
val = 0;
prop = read_prop_dword(np, "ibm,process-mmio", &val);
if (prop && val == 1)
afu->pp_psa = true;
else
afu->pp_psa = false;
if (cxl_verbose) {
read_prop_dword(np, "ibm,supports-aur", &val);
read_prop_dword(np, "ibm,supports-csrp", &val);
read_prop_dword(np, "ibm,supports-prr", &val);
}
prop = read_prop_dword(np, "ibm,function-error-interrupt", &val);
if (prop)
afu->serr_hwirq = val;
pr_devel("AFU handle: %#llx\n", afu->guest->handle);
pr_devel("p2n_phys: %#llx (size %#llx)\n",
afu->guest->p2n_phys, afu->guest->p2n_size);
pr_devel("psn_phys: %#llx (size %#llx)\n",
afu->psn_phys, afu->adapter->ps_size);
pr_devel("Max number of processes virtualised=%i\n",
afu->max_procs_virtualised);
pr_devel("Per-process irqs min=%i, max=%i\n", afu->pp_irqs,
afu->irqs_max);
pr_devel("Slice error interrupt=%#lx\n", afu->serr_hwirq);
return 0;
}
static int read_adapter_irq_config(struct cxl *adapter, struct device_node *np)
{
const __be32 *ranges;
int len, nranges, i;
struct irq_avail *cur;
ranges = of_get_property(np, "interrupt-ranges", &len);
if (ranges == NULL || len < (2 * sizeof(int)))
return -EINVAL;
/*
* encoded array of two cells per entry, each cell encoded as
* with encode-int
*/
nranges = len / (2 * sizeof(int));
if (nranges == 0 || (nranges * 2 * sizeof(int)) != len)
return -EINVAL;
adapter->guest->irq_avail = kcalloc(nranges, sizeof(struct irq_avail),
GFP_KERNEL);
if (adapter->guest->irq_avail == NULL)
return -ENOMEM;
adapter->guest->irq_base_offset = be32_to_cpu(ranges[0]);
for (i = 0; i < nranges; i++) {
cur = &adapter->guest->irq_avail[i];
cur->offset = be32_to_cpu(ranges[i * 2]);
cur->range = be32_to_cpu(ranges[i * 2 + 1]);
cur->bitmap = bitmap_zalloc(cur->range, GFP_KERNEL);
if (cur->bitmap == NULL)
goto err;
if (cur->offset < adapter->guest->irq_base_offset)
adapter->guest->irq_base_offset = cur->offset;
if (cxl_verbose)
pr_info("available IRQ range: %#lx-%#lx (%lu)\n",
cur->offset, cur->offset + cur->range - 1,
cur->range);
}
adapter->guest->irq_nranges = nranges;
spin_lock_init(&adapter->guest->irq_alloc_lock);
return 0;
err:
for (i--; i >= 0; i--) {
cur = &adapter->guest->irq_avail[i];
bitmap_free(cur->bitmap);
}
kfree(adapter->guest->irq_avail);
adapter->guest->irq_avail = NULL;
return -ENOMEM;
}
int cxl_of_read_adapter_handle(struct cxl *adapter, struct device_node *np)
{
if (read_handle(np, &adapter->guest->handle))
return -EINVAL;
pr_devel("Adapter handle: 0x%.16llx\n", adapter->guest->handle);
return 0;
}
int cxl_of_read_adapter_properties(struct cxl *adapter, struct device_node *np)
{
int rc, len, naddr, i;
char *p;
const __be32 *prop;
u32 val = 0;
/* Properties are read in the same order as listed in PAPR */
naddr = of_n_addr_cells(np);
if (cxl_verbose) {
pr_info("Dump of the 'ibm,coherent-platform-facility' node properties:\n");
read_prop_dword(np, "#address-cells", &val);
read_prop_dword(np, "#size-cells", &val);
prop = of_get_property(np, "compatible", &len);
i = 0;
while (i < len) {
p = (char *) prop + i;
pr_info("compatible: %s\n", p);
i += strlen(p) + 1;
}
read_prop_string(np, "name");
read_prop_string(np, "model");
prop = of_get_property(np, "reg", NULL);
if (prop) {
pr_info("reg: addr:%#llx size:%#x\n",
of_read_number(prop, naddr),
be32_to_cpu(prop[naddr]));
}
read_prop_string(np, "ibm,loc-code");
}
if ((rc = read_adapter_irq_config(adapter, np)))
return rc;
if (cxl_verbose) {
read_prop_string(np, "device_type");
read_prop_string(np, "ibm,phandle");
}
prop = read_prop_dword(np, "ibm,caia-version", &val);
if (prop) {
adapter->caia_major = (val & 0xFF00) >> 8;
adapter->caia_minor = val & 0xFF;
}
prop = read_prop_dword(np, "ibm,psl-revision", &val);
if (prop)
adapter->psl_rev = val;
prop = read_prop_string(np, "status");
if (prop) {
adapter->guest->status = kasprintf(GFP_KERNEL, "%s", (char *) prop);
if (adapter->guest->status == NULL)
return -ENOMEM;
}
prop = read_prop_dword(np, "vendor-id", &val);
if (prop)
adapter->guest->vendor = val;
prop = read_prop_dword(np, "device-id", &val);
if (prop)
adapter->guest->device = val;
if (cxl_verbose) {
read_prop_dword(np, "ibm,privileged-facility", &val);
read_prop_dword(np, "revision-id", &val);
read_prop_dword(np, "class-code", &val);
}
prop = read_prop_dword(np, "subsystem-vendor-id", &val);
if (prop)
adapter->guest->subsystem_vendor = val;
prop = read_prop_dword(np, "subsystem-id", &val);
if (prop)
adapter->guest->subsystem = val;
if (cxl_verbose)
read_vpd(adapter, NULL);
return 0;
}
static int cxl_of_remove(struct platform_device *pdev)
{
struct cxl *adapter;
int afu;
adapter = dev_get_drvdata(&pdev->dev);
for (afu = 0; afu < adapter->slices; afu++)
cxl_guest_remove_afu(adapter->afu[afu]);
cxl_guest_remove_adapter(adapter);
return 0;
}
static void cxl_of_shutdown(struct platform_device *pdev)
{
cxl_of_remove(pdev);
}
int cxl_of_probe(struct platform_device *pdev)
{
struct device_node *np = NULL;
struct device_node *afu_np = NULL;
struct cxl *adapter = NULL;
int ret;
int slice = 0, slice_ok = 0;
pr_devel("in %s\n", __func__);
np = pdev->dev.of_node;
if (np == NULL)
return -ENODEV;
/* init adapter */
adapter = cxl_guest_init_adapter(np, pdev);
if (IS_ERR(adapter)) {
dev_err(&pdev->dev, "guest_init_adapter failed: %li\n", PTR_ERR(adapter));
return PTR_ERR(adapter);
}
/* init afu */
for_each_child_of_node(np, afu_np) {
if ((ret = cxl_guest_init_afu(adapter, slice, afu_np)))
dev_err(&pdev->dev, "AFU %i failed to initialise: %i\n",
slice, ret);
else
slice_ok++;
slice++;
}
if (slice_ok == 0) {
dev_info(&pdev->dev, "No active AFU");
adapter->slices = 0;
}
return 0;
}
static const struct of_device_id cxl_of_match[] = {
{ .compatible = "ibm,coherent-platform-facility",},
{},
};
MODULE_DEVICE_TABLE(of, cxl_of_match);
struct platform_driver cxl_of_driver = {
.driver = {
.name = "cxl_of",
.of_match_table = cxl_of_match,
.owner = THIS_MODULE
},
.probe = cxl_of_probe,
.remove = cxl_of_remove,
.shutdown = cxl_of_shutdown,
};