cxl: Add guest-specific code
authorChristophe Lombard <clombard@linux.vnet.ibm.com>
Fri, 4 Mar 2016 11:26:36 +0000 (12:26 +0100)
committerMichael Ellerman <mpe@ellerman.id.au>
Wed, 9 Mar 2016 12:36:52 +0000 (23:36 +1100)
The new of.c file contains code to parse the device tree to find out
about cxl adapters and AFUs.

guest.c implements the guest-specific callbacks for the backend API.

The process element ID is not known until the context is attached, so
we have to separate the context ID assigned by the cxl driver from the
process element ID visible to the user applications. In bare-metal,
the 2 IDs match.

Co-authored-by: Frederic Barrat <fbarrat@linux.vnet.ibm.com>
Signed-off-by: Frederic Barrat <fbarrat@linux.vnet.ibm.com>
Signed-off-by: Christophe Lombard <clombard@linux.vnet.ibm.com>
Reviewed-by: Manoj Kumar <manoj@linux.vnet.ibm.com>
Acked-by: Ian Munsie <imunsie@au1.ibm.com>
[mpe: Fix SMP=n build, fix PSERIES=n build, minor whitespace fixes]
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
drivers/misc/cxl/Makefile
drivers/misc/cxl/api.c
drivers/misc/cxl/context.c
drivers/misc/cxl/cxl.h
drivers/misc/cxl/file.c
drivers/misc/cxl/guest.c [new file with mode: 0644]
drivers/misc/cxl/hcalls.c
drivers/misc/cxl/hcalls.h
drivers/misc/cxl/main.c
drivers/misc/cxl/of.c [new file with mode: 0644]

index be2ac5ce349f0d5cad3fdfd8220645321ab622c3..a2f49cf4a1689c98e9b048de40bc47391171e264 100644 (file)
@@ -4,6 +4,7 @@ ccflags-$(CONFIG_PPC_WERROR)    += -Werror
 cxl-y                          += main.o file.o irq.o fault.o native.o
 cxl-y                          += context.o sysfs.o debugfs.o pci.o trace.o
 cxl-y                          += vphb.o api.o
+cxl-$(CONFIG_PPC_PSERIES)      += guest.o of.o hcalls.o
 obj-$(CONFIG_CXL)              += cxl.o
 obj-$(CONFIG_CXL_BASE)         += base.o
 
index 31eb842ee6c087dfde572c1d19a47205ad3b37ca..325f9578a556eb4242d04d315098526b1062485a 100644 (file)
@@ -191,7 +191,7 @@ EXPORT_SYMBOL_GPL(cxl_start_context);
 
 int cxl_process_element(struct cxl_context *ctx)
 {
-       return ctx->pe;
+       return ctx->external_pe;
 }
 EXPORT_SYMBOL_GPL(cxl_process_element);
 
index 200837f7612b65c4bc578ff15aad9066e86d4106..180c85a328255eccc19150d5f0453dd6f4be97be 100644 (file)
@@ -95,8 +95,12 @@ int cxl_context_init(struct cxl_context *ctx, struct cxl_afu *afu, bool master,
                return i;
 
        ctx->pe = i;
-       if (cpu_has_feature(CPU_FTR_HVMODE))
+       if (cpu_has_feature(CPU_FTR_HVMODE)) {
                ctx->elem = &ctx->afu->native->spa[i];
+               ctx->external_pe = ctx->pe;
+       } else {
+               ctx->external_pe = -1; /* assigned when attaching */
+       }
        ctx->pe_inserted = false;
 
        /*
index 3a1fabd41072d58f143a6d433f363c4893f1cde0..4372a87ff3aaea825569ec6946cac64c61f40352 100644 (file)
@@ -433,6 +433,12 @@ struct cxl_irq_name {
        char *name;
 };
 
+struct irq_avail {
+       irq_hw_number_t offset;
+       irq_hw_number_t range;
+       unsigned long   *bitmap;
+};
+
 /*
  * This is a cxl context.  If the PSL is in dedicated mode, there will be one
  * of these per AFU.  If in AFU directed there can be lots of these.
@@ -488,7 +494,19 @@ struct cxl_context {
 
        struct cxl_process_element *elem;
 
-       int pe; /* process element handle */
+       /*
+        * pe is the process element handle, assigned by this driver when the
+        * context is initialized.
+        *
+        * external_pe is the PE shown outside of cxl.
+        * On bare-metal, pe=external_pe, because we decide what the handle is.
+        * In a guest, we only find out about the pe used by pHyp when the
+        * context is attached, and that's the value we want to report outside
+        * of cxl.
+        */
+       int pe;
+       int external_pe;
+
        u32 irq_count;
        bool pe_inserted;
        bool master;
@@ -782,6 +800,7 @@ void cxl_pci_vphb_reconfigure(struct cxl_afu *afu);
 void cxl_pci_vphb_remove(struct cxl_afu *afu);
 
 extern struct pci_driver cxl_pci_driver;
+extern struct platform_driver cxl_of_driver;
 int afu_allocate_irqs(struct cxl_context *ctx, u32 count);
 
 int afu_open(struct inode *inode, struct file *file);
@@ -792,6 +811,21 @@ unsigned int afu_poll(struct file *file, struct poll_table_struct *poll);
 ssize_t afu_read(struct file *file, char __user *buf, size_t count, loff_t *off);
 extern const struct file_operations afu_fops;
 
+struct cxl *cxl_guest_init_adapter(struct device_node *np, struct platform_device *dev);
+void cxl_guest_remove_adapter(struct cxl *adapter);
+int cxl_of_read_adapter_handle(struct cxl *adapter, struct device_node *np);
+int cxl_of_read_adapter_properties(struct cxl *adapter, struct device_node *np);
+ssize_t cxl_guest_read_adapter_vpd(struct cxl *adapter, void *buf, size_t len);
+ssize_t cxl_guest_read_afu_vpd(struct cxl_afu *afu, void *buf, size_t len);
+int cxl_guest_init_afu(struct cxl *adapter, int slice, struct device_node *afu_np);
+void cxl_guest_remove_afu(struct cxl_afu *afu);
+int cxl_of_read_afu_handle(struct cxl_afu *afu, struct device_node *afu_np);
+int cxl_of_read_afu_properties(struct cxl_afu *afu, struct device_node *afu_np);
+int cxl_guest_add_chardev(struct cxl *adapter);
+void cxl_guest_remove_chardev(struct cxl *adapter);
+void cxl_guest_reload_module(struct cxl *adapter);
+int cxl_of_probe(struct platform_device *pdev);
+
 struct cxl_backend_ops {
        struct module *module;
        int (*adapter_reset)(struct cxl *adapter);
@@ -824,6 +858,7 @@ struct cxl_backend_ops {
        int (*afu_cr_read64)(struct cxl_afu *afu, int cr_idx, u64 offset, u64 *val);
 };
 extern const struct cxl_backend_ops cxl_native_ops;
+extern const struct cxl_backend_ops cxl_guest_ops;
 extern const struct cxl_backend_ops *cxl_ops;
 
 #endif
index b8ce29bc52d5ae547379bcf33f8217b712f19d3a..df4d49a6c67ae3a67f7d19abe113fd65944c740d 100644 (file)
@@ -228,7 +228,7 @@ static long afu_ioctl_process_element(struct cxl_context *ctx,
 {
        pr_devel("%s: pe: %i\n", __func__, ctx->pe);
 
-       if (copy_to_user(upe, &ctx->pe, sizeof(__u32)))
+       if (copy_to_user(upe, &ctx->external_pe, sizeof(__u32)))
                return -EFAULT;
 
        return 0;
diff --git a/drivers/misc/cxl/guest.c b/drivers/misc/cxl/guest.c
new file mode 100644 (file)
index 0000000..d7d53d8
--- /dev/null
@@ -0,0 +1,950 @@
+/*
+ * Copyright 2015 IBM Corp.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/spinlock.h>
+#include <linux/uaccess.h>
+#include <linux/delay.h>
+
+#include "cxl.h"
+#include "hcalls.h"
+#include "trace.h"
+
+
+static irqreturn_t guest_handle_psl_slice_error(struct cxl_context *ctx, u64 dsisr,
+                                       u64 errstat)
+{
+       pr_devel("in %s\n", __func__);
+       dev_crit(&ctx->afu->dev, "PSL ERROR STATUS: 0x%.16llx\n", errstat);
+
+       return cxl_ops->ack_irq(ctx, 0, errstat);
+}
+
+static ssize_t guest_collect_vpd(struct cxl *adapter, struct cxl_afu *afu,
+                       void *buf, size_t len)
+{
+       unsigned int entries, mod;
+       unsigned long **vpd_buf = NULL;
+       struct sg_list *le;
+       int rc = 0, i, tocopy;
+       u64 out = 0;
+
+       if (buf == NULL)
+               return -EINVAL;
+
+       /* number of entries in the list */
+       entries = len / SG_BUFFER_SIZE;
+       mod = len % SG_BUFFER_SIZE;
+       if (mod)
+               entries++;
+
+       if (entries > SG_MAX_ENTRIES) {
+               entries = SG_MAX_ENTRIES;
+               len = SG_MAX_ENTRIES * SG_BUFFER_SIZE;
+               mod = 0;
+       }
+
+       vpd_buf = kzalloc(entries * sizeof(unsigned long *), GFP_KERNEL);
+       if (!vpd_buf)
+               return -ENOMEM;
+
+       le = (struct sg_list *)get_zeroed_page(GFP_KERNEL);
+       if (!le) {
+               rc = -ENOMEM;
+               goto err1;
+       }
+
+       for (i = 0; i < entries; i++) {
+               vpd_buf[i] = (unsigned long *)get_zeroed_page(GFP_KERNEL);
+               if (!vpd_buf[i]) {
+                       rc = -ENOMEM;
+                       goto err2;
+               }
+               le[i].phys_addr = cpu_to_be64(virt_to_phys(vpd_buf[i]));
+               le[i].len = cpu_to_be64(SG_BUFFER_SIZE);
+               if ((i == (entries - 1)) && mod)
+                       le[i].len = cpu_to_be64(mod);
+       }
+
+       if (adapter)
+               rc = cxl_h_collect_vpd_adapter(adapter->guest->handle,
+                                       virt_to_phys(le), entries, &out);
+       else
+               rc = cxl_h_collect_vpd(afu->guest->handle, 0,
+                               virt_to_phys(le), entries, &out);
+       pr_devel("length of available (entries: %i), vpd: %#llx\n",
+               entries, out);
+
+       if (!rc) {
+               /*
+                * hcall returns in 'out' the size of available VPDs.
+                * It fills the buffer with as much data as possible.
+                */
+               if (out < len)
+                       len = out;
+               rc = len;
+               if (out) {
+                       for (i = 0; i < entries; i++) {
+                               if (len < SG_BUFFER_SIZE)
+                                       tocopy = len;
+                               else
+                                       tocopy = SG_BUFFER_SIZE;
+                               memcpy(buf, vpd_buf[i], tocopy);
+                               buf += tocopy;
+                               len -= tocopy;
+                       }
+               }
+       }
+err2:
+       for (i = 0; i < entries; i++) {
+               if (vpd_buf[i])
+                       free_page((unsigned long) vpd_buf[i]);
+       }
+       free_page((unsigned long) le);
+err1:
+       kfree(vpd_buf);
+       return rc;
+}
+
+static int guest_get_irq_info(struct cxl_context *ctx, struct cxl_irq_info *info)
+{
+       return cxl_h_collect_int_info(ctx->afu->guest->handle, ctx->process_token, info);
+}
+
+static irqreturn_t guest_psl_irq(int irq, void *data)
+{
+       struct cxl_context *ctx = data;
+       struct cxl_irq_info irq_info;
+       int rc;
+
+       pr_devel("%d: received PSL interrupt %i\n", ctx->pe, irq);
+       rc = guest_get_irq_info(ctx, &irq_info);
+       if (rc) {
+               WARN(1, "Unable to get IRQ info: %i\n", rc);
+               return IRQ_HANDLED;
+       }
+
+       rc = cxl_irq(irq, ctx, &irq_info);
+       return rc;
+}
+
+static irqreturn_t guest_slice_irq_err(int irq, void *data)
+{
+       struct cxl_afu *afu = data;
+       int rc;
+       u64 serr;
+
+       WARN(irq, "CXL SLICE ERROR interrupt %i\n", irq);
+       rc = cxl_h_get_fn_error_interrupt(afu->guest->handle, &serr);
+       if (rc) {
+               dev_crit(&afu->dev, "Couldn't read PSL_SERR_An: %d\n", rc);
+               return IRQ_HANDLED;
+       }
+       dev_crit(&afu->dev, "PSL_SERR_An: 0x%.16llx\n", serr);
+
+       rc = cxl_h_ack_fn_error_interrupt(afu->guest->handle, serr);
+       if (rc)
+               dev_crit(&afu->dev, "Couldn't ack slice error interrupt: %d\n",
+                       rc);
+
+       return IRQ_HANDLED;
+}
+
+
+static int irq_alloc_range(struct cxl *adapter, int len, int *irq)
+{
+       int i, n;
+       struct irq_avail *cur;
+
+       for (i = 0; i < adapter->guest->irq_nranges; i++) {
+               cur = &adapter->guest->irq_avail[i];
+               n = bitmap_find_next_zero_area(cur->bitmap, cur->range,
+                                       0, len, 0);
+               if (n < cur->range) {
+                       bitmap_set(cur->bitmap, n, len);
+                       *irq = cur->offset + n;
+                       pr_devel("guest: allocate IRQs %#x->%#x\n",
+                               *irq, *irq + len - 1);
+
+                       return 0;
+               }
+       }
+       return -ENOSPC;
+}
+
+static int irq_free_range(struct cxl *adapter, int irq, int len)
+{
+       int i, n;
+       struct irq_avail *cur;
+
+       if (len == 0)
+               return -ENOENT;
+
+       for (i = 0; i < adapter->guest->irq_nranges; i++) {
+               cur = &adapter->guest->irq_avail[i];
+               if (irq >= cur->offset &&
+                       (irq + len) <= (cur->offset + cur->range)) {
+                       n = irq - cur->offset;
+                       bitmap_clear(cur->bitmap, n, len);
+                       pr_devel("guest: release IRQs %#x->%#x\n",
+                               irq, irq + len - 1);
+                       return 0;
+               }
+       }
+       return -ENOENT;
+}
+
+static int guest_reset(struct cxl *adapter)
+{
+       int rc;
+
+       pr_devel("Adapter reset request\n");
+       rc = cxl_h_reset_adapter(adapter->guest->handle);
+       return rc;
+}
+
+static int guest_alloc_one_irq(struct cxl *adapter)
+{
+       int irq;
+
+       spin_lock(&adapter->guest->irq_alloc_lock);
+       if (irq_alloc_range(adapter, 1, &irq))
+               irq = -ENOSPC;
+       spin_unlock(&adapter->guest->irq_alloc_lock);
+       return irq;
+}
+
+static void guest_release_one_irq(struct cxl *adapter, int irq)
+{
+       spin_lock(&adapter->guest->irq_alloc_lock);
+       irq_free_range(adapter, irq, 1);
+       spin_unlock(&adapter->guest->irq_alloc_lock);
+}
+
+static int guest_alloc_irq_ranges(struct cxl_irq_ranges *irqs,
+                               struct cxl *adapter, unsigned int num)
+{
+       int i, try, irq;
+
+       memset(irqs, 0, sizeof(struct cxl_irq_ranges));
+
+       spin_lock(&adapter->guest->irq_alloc_lock);
+       for (i = 0; i < CXL_IRQ_RANGES && num; i++) {
+               try = num;
+               while (try) {
+                       if (irq_alloc_range(adapter, try, &irq) == 0)
+                               break;
+                       try /= 2;
+               }
+               if (!try)
+                       goto error;
+               irqs->offset[i] = irq;
+               irqs->range[i] = try;
+               num -= try;
+       }
+       if (num)
+               goto error;
+       spin_unlock(&adapter->guest->irq_alloc_lock);
+       return 0;
+
+error:
+       for (i = 0; i < CXL_IRQ_RANGES; i++)
+               irq_free_range(adapter, irqs->offset[i], irqs->range[i]);
+       spin_unlock(&adapter->guest->irq_alloc_lock);
+       return -ENOSPC;
+}
+
+static void guest_release_irq_ranges(struct cxl_irq_ranges *irqs,
+                               struct cxl *adapter)
+{
+       int i;
+
+       spin_lock(&adapter->guest->irq_alloc_lock);
+       for (i = 0; i < CXL_IRQ_RANGES; i++)
+               irq_free_range(adapter, irqs->offset[i], irqs->range[i]);
+       spin_unlock(&adapter->guest->irq_alloc_lock);
+}
+
+static int guest_register_serr_irq(struct cxl_afu *afu)
+{
+       afu->err_irq_name = kasprintf(GFP_KERNEL, "cxl-%s-err",
+                                     dev_name(&afu->dev));
+       if (!afu->err_irq_name)
+               return -ENOMEM;
+
+       if (!(afu->serr_virq = cxl_map_irq(afu->adapter, afu->serr_hwirq,
+                                guest_slice_irq_err, afu, afu->err_irq_name))) {
+               kfree(afu->err_irq_name);
+               afu->err_irq_name = NULL;
+               return -ENOMEM;
+       }
+
+       return 0;
+}
+
+static void guest_release_serr_irq(struct cxl_afu *afu)
+{
+       cxl_unmap_irq(afu->serr_virq, afu);
+       cxl_ops->release_one_irq(afu->adapter, afu->serr_hwirq);
+       kfree(afu->err_irq_name);
+}
+
+static int guest_ack_irq(struct cxl_context *ctx, u64 tfc, u64 psl_reset_mask)
+{
+       return cxl_h_control_faults(ctx->afu->guest->handle, ctx->process_token,
+                               tfc >> 32, (psl_reset_mask != 0));
+}
+
+static void disable_afu_irqs(struct cxl_context *ctx)
+{
+       irq_hw_number_t hwirq;
+       unsigned int virq;
+       int r, i;
+
+       pr_devel("Disabling AFU(%d) interrupts\n", ctx->afu->slice);
+       for (r = 0; r < CXL_IRQ_RANGES; r++) {
+               hwirq = ctx->irqs.offset[r];
+               for (i = 0; i < ctx->irqs.range[r]; hwirq++, i++) {
+                       virq = irq_find_mapping(NULL, hwirq);
+                       disable_irq(virq);
+               }
+       }
+}
+
+static void enable_afu_irqs(struct cxl_context *ctx)
+{
+       irq_hw_number_t hwirq;
+       unsigned int virq;
+       int r, i;
+
+       pr_devel("Enabling AFU(%d) interrupts\n", ctx->afu->slice);
+       for (r = 0; r < CXL_IRQ_RANGES; r++) {
+               hwirq = ctx->irqs.offset[r];
+               for (i = 0; i < ctx->irqs.range[r]; hwirq++, i++) {
+                       virq = irq_find_mapping(NULL, hwirq);
+                       enable_irq(virq);
+               }
+       }
+}
+
+static int _guest_afu_cr_readXX(int sz, struct cxl_afu *afu, int cr_idx,
+                       u64 offset, u64 *val)
+{
+       unsigned long cr;
+       char c;
+       int rc = 0;
+
+       if (afu->crs_len < sz)
+               return -ENOENT;
+
+       if (unlikely(offset >= afu->crs_len))
+               return -ERANGE;
+
+       cr = get_zeroed_page(GFP_KERNEL);
+       if (!cr)
+               return -ENOMEM;
+
+       rc = cxl_h_get_config(afu->guest->handle, cr_idx, offset,
+                       virt_to_phys((void *)cr), sz);
+       if (rc)
+               goto err;
+
+       switch (sz) {
+       case 1:
+               c = *((char *) cr);
+               *val = c;
+               break;
+       case 2:
+               *val = in_le16((u16 *)cr);
+               break;
+       case 4:
+               *val = in_le32((unsigned *)cr);
+               break;
+       case 8:
+               *val = in_le64((u64 *)cr);
+               break;
+       default:
+               WARN_ON(1);
+       }
+err:
+       free_page(cr);
+       return rc;
+}
+
+static int guest_afu_cr_read32(struct cxl_afu *afu, int cr_idx, u64 offset,
+                       u32 *out)
+{
+       int rc;
+       u64 val;
+
+       rc = _guest_afu_cr_readXX(4, afu, cr_idx, offset, &val);
+       if (!rc)
+               *out = (u32) val;
+       return rc;
+}
+
+static int guest_afu_cr_read16(struct cxl_afu *afu, int cr_idx, u64 offset,
+                       u16 *out)
+{
+       int rc;
+       u64 val;
+
+       rc = _guest_afu_cr_readXX(2, afu, cr_idx, offset, &val);
+       if (!rc)
+               *out = (u16) val;
+       return rc;
+}
+
+static int guest_afu_cr_read8(struct cxl_afu *afu, int cr_idx, u64 offset,
+                       u8 *out)
+{
+       int rc;
+       u64 val;
+
+       rc = _guest_afu_cr_readXX(1, afu, cr_idx, offset, &val);
+       if (!rc)
+               *out = (u8) val;
+       return rc;
+}
+
+static int guest_afu_cr_read64(struct cxl_afu *afu, int cr_idx, u64 offset,
+                       u64 *out)
+{
+       return _guest_afu_cr_readXX(8, afu, cr_idx, offset, out);
+}
+
+static int attach_afu_directed(struct cxl_context *ctx, u64 wed, u64 amr)
+{
+       struct cxl_process_element_hcall *elem;
+       struct cxl *adapter = ctx->afu->adapter;
+       const struct cred *cred;
+       u32 pid, idx;
+       int rc, r, i;
+       u64 mmio_addr, mmio_size;
+       __be64 flags = 0;
+
+       /* Must be 8 byte aligned and cannot cross a 4096 byte boundary */
+       if (!(elem = (struct cxl_process_element_hcall *)
+                       get_zeroed_page(GFP_KERNEL)))
+               return -ENOMEM;
+
+       elem->version = cpu_to_be64(CXL_PROCESS_ELEMENT_VERSION);
+       if (ctx->kernel) {
+               pid = 0;
+               flags |= CXL_PE_TRANSLATION_ENABLED;
+               flags |= CXL_PE_PRIVILEGED_PROCESS;
+               if (mfmsr() & MSR_SF)
+                       flags |= CXL_PE_64_BIT;
+       } else {
+               pid = current->pid;
+               flags |= CXL_PE_PROBLEM_STATE;
+               flags |= CXL_PE_TRANSLATION_ENABLED;
+               if (!test_tsk_thread_flag(current, TIF_32BIT))
+                       flags |= CXL_PE_64_BIT;
+               cred = get_current_cred();
+               if (uid_eq(cred->euid, GLOBAL_ROOT_UID))
+                       flags |= CXL_PE_PRIVILEGED_PROCESS;
+               put_cred(cred);
+       }
+       elem->flags         = cpu_to_be64(flags);
+       elem->common.tid    = cpu_to_be32(0); /* Unused */
+       elem->common.pid    = cpu_to_be32(pid);
+       elem->common.csrp   = cpu_to_be64(0); /* disable */
+       elem->common.aurp0  = cpu_to_be64(0); /* disable */
+       elem->common.aurp1  = cpu_to_be64(0); /* disable */
+
+       cxl_prefault(ctx, wed);
+
+       elem->common.sstp0  = cpu_to_be64(ctx->sstp0);
+       elem->common.sstp1  = cpu_to_be64(ctx->sstp1);
+       for (r = 0; r < CXL_IRQ_RANGES; r++) {
+               for (i = 0; i < ctx->irqs.range[r]; i++) {
+                       if (r == 0 && i == 0) {
+                               elem->pslVirtualIsn = cpu_to_be32(ctx->irqs.offset[0]);
+                       } else {
+                               idx = ctx->irqs.offset[r] + i - adapter->guest->irq_base_offset;
+                               elem->applicationVirtualIsnBitmap[idx / 8] |= 0x80 >> (idx % 8);
+                       }
+               }
+       }
+       elem->common.amr = cpu_to_be64(amr);
+       elem->common.wed = cpu_to_be64(wed);
+
+       disable_afu_irqs(ctx);
+
+       rc = cxl_h_attach_process(ctx->afu->guest->handle, elem,
+                               &ctx->process_token, &mmio_addr, &mmio_size);
+       if (rc == H_SUCCESS) {
+               if (ctx->master || !ctx->afu->pp_psa) {
+                       ctx->psn_phys = ctx->afu->psn_phys;
+                       ctx->psn_size = ctx->afu->adapter->ps_size;
+               } else {
+                       ctx->psn_phys = mmio_addr;
+                       ctx->psn_size = mmio_size;
+               }
+               if (ctx->afu->pp_psa && mmio_size &&
+                       ctx->afu->pp_size == 0) {
+                       /*
+                        * There's no property in the device tree to read the
+                        * pp_size. We only find out at the 1st attach.
+                        * Compared to bare-metal, it is too late and we
+                        * should really lock here. However, on powerVM,
+                        * pp_size is really only used to display in /sys.
+                        * Being discussed with pHyp for their next release.
+                        */
+                       ctx->afu->pp_size = mmio_size;
+               }
+               /* from PAPR: process element is bytes 4-7 of process token */
+               ctx->external_pe = ctx->process_token & 0xFFFFFFFF;
+               pr_devel("CXL pe=%i is known as %i for pHyp, mmio_size=%#llx",
+                       ctx->pe, ctx->external_pe, ctx->psn_size);
+               ctx->pe_inserted = true;
+               enable_afu_irqs(ctx);
+       }
+
+       free_page((u64)elem);
+       return rc;
+}
+
+static int guest_attach_process(struct cxl_context *ctx, bool kernel, u64 wed, u64 amr)
+{
+       pr_devel("in %s\n", __func__);
+
+       ctx->kernel = kernel;
+       if (ctx->afu->current_mode == CXL_MODE_DIRECTED)
+               return attach_afu_directed(ctx, wed, amr);
+
+       /* dedicated mode not supported on FW840 */
+
+       return -EINVAL;
+}
+
+static int detach_afu_directed(struct cxl_context *ctx)
+{
+       if (!ctx->pe_inserted)
+               return 0;
+       if (cxl_h_detach_process(ctx->afu->guest->handle, ctx->process_token))
+               return -1;
+       return 0;
+}
+
+static int guest_detach_process(struct cxl_context *ctx)
+{
+       pr_devel("in %s\n", __func__);
+       trace_cxl_detach(ctx);
+
+       if (!cxl_ops->link_ok(ctx->afu->adapter))
+               return -EIO;
+
+       if (ctx->afu->current_mode == CXL_MODE_DIRECTED)
+               return detach_afu_directed(ctx);
+
+       return -EINVAL;
+}
+
+static void guest_release_afu(struct device *dev)
+{
+       struct cxl_afu *afu = to_cxl_afu(dev);
+
+       pr_devel("%s\n", __func__);
+
+       idr_destroy(&afu->contexts_idr);
+
+       kfree(afu->guest);
+       kfree(afu);
+}
+
+ssize_t cxl_guest_read_afu_vpd(struct cxl_afu *afu, void *buf, size_t len)
+{
+       return guest_collect_vpd(NULL, afu, buf, len);
+}
+
+#define ERR_BUFF_MAX_COPY_SIZE PAGE_SIZE
+static ssize_t guest_afu_read_err_buffer(struct cxl_afu *afu, char *buf,
+                                       loff_t off, size_t count)
+{
+       void *tbuf = NULL;
+       int rc = 0;
+
+       tbuf = (void *) get_zeroed_page(GFP_KERNEL);
+       if (!tbuf)
+               return -ENOMEM;
+
+       rc = cxl_h_get_afu_err(afu->guest->handle,
+                              off & 0x7,
+                              virt_to_phys(tbuf),
+                              count);
+       if (rc)
+               goto err;
+
+       if (count > ERR_BUFF_MAX_COPY_SIZE)
+               count = ERR_BUFF_MAX_COPY_SIZE - (off & 0x7);
+       memcpy(buf, tbuf, count);
+err:
+       free_page((u64)tbuf);
+
+       return rc;
+}
+
+static int guest_afu_check_and_enable(struct cxl_afu *afu)
+{
+       return 0;
+}
+
+static int activate_afu_directed(struct cxl_afu *afu)
+{
+       int rc;
+
+       dev_info(&afu->dev, "Activating AFU(%d) directed mode\n", afu->slice);
+
+       afu->current_mode = CXL_MODE_DIRECTED;
+
+       afu->num_procs = afu->max_procs_virtualised;
+
+       if ((rc = cxl_chardev_m_afu_add(afu)))
+               return rc;
+
+       if ((rc = cxl_sysfs_afu_m_add(afu)))
+               goto err;
+
+       if ((rc = cxl_chardev_s_afu_add(afu)))
+               goto err1;
+
+       return 0;
+err1:
+       cxl_sysfs_afu_m_remove(afu);
+err:
+       cxl_chardev_afu_remove(afu);
+       return rc;
+}
+
+static int guest_afu_activate_mode(struct cxl_afu *afu, int mode)
+{
+       if (!mode)
+               return 0;
+       if (!(mode & afu->modes_supported))
+               return -EINVAL;
+
+       if (mode == CXL_MODE_DIRECTED)
+               return activate_afu_directed(afu);
+
+       if (mode == CXL_MODE_DEDICATED)
+               dev_err(&afu->dev, "Dedicated mode not supported\n");
+
+       return -EINVAL;
+}
+
+static int deactivate_afu_directed(struct cxl_afu *afu)
+{
+       dev_info(&afu->dev, "Deactivating AFU(%d) directed mode\n", afu->slice);
+
+       afu->current_mode = 0;
+       afu->num_procs = 0;
+
+       cxl_sysfs_afu_m_remove(afu);
+       cxl_chardev_afu_remove(afu);
+
+       cxl_ops->afu_reset(afu);
+
+       return 0;
+}
+
+static int guest_afu_deactivate_mode(struct cxl_afu *afu, int mode)
+{
+       if (!mode)
+               return 0;
+       if (!(mode & afu->modes_supported))
+               return -EINVAL;
+
+       if (mode == CXL_MODE_DIRECTED)
+               return deactivate_afu_directed(afu);
+       return 0;
+}
+
+static int guest_afu_reset(struct cxl_afu *afu)
+{
+       pr_devel("AFU(%d) reset request\n", afu->slice);
+       return cxl_h_reset_afu(afu->guest->handle);
+}
+
+static int guest_map_slice_regs(struct cxl_afu *afu)
+{
+       if (!(afu->p2n_mmio = ioremap(afu->guest->p2n_phys, afu->guest->p2n_size))) {
+               dev_err(&afu->dev, "Error mapping AFU(%d) MMIO regions\n",
+                       afu->slice);
+               return -ENOMEM;
+       }
+       return 0;
+}
+
+static void guest_unmap_slice_regs(struct cxl_afu *afu)
+{
+       if (afu->p2n_mmio)
+               iounmap(afu->p2n_mmio);
+}
+
+static bool guest_link_ok(struct cxl *cxl)
+{
+       return true;
+}
+
+static int afu_properties_look_ok(struct cxl_afu *afu)
+{
+       if (afu->pp_irqs < 0) {
+               dev_err(&afu->dev, "Unexpected per-process minimum interrupt value\n");
+               return -EINVAL;
+       }
+
+       if (afu->max_procs_virtualised < 1) {
+               dev_err(&afu->dev, "Unexpected max number of processes virtualised value\n");
+               return -EINVAL;
+       }
+
+       if (afu->crs_len < 0) {
+               dev_err(&afu->dev, "Unexpected configuration record size value\n");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+int cxl_guest_init_afu(struct cxl *adapter, int slice, struct device_node *afu_np)
+{
+       struct cxl_afu *afu;
+       bool free = true;
+       int rc;
+
+       pr_devel("in %s - AFU(%d)\n", __func__, slice);
+       if (!(afu = cxl_alloc_afu(adapter, slice)))
+               return -ENOMEM;
+
+       if (!(afu->guest = kzalloc(sizeof(struct cxl_afu_guest), GFP_KERNEL))) {
+               kfree(afu);
+               return -ENOMEM;
+       }
+
+       if ((rc = dev_set_name(&afu->dev, "afu%i.%i",
+                                         adapter->adapter_num,
+                                         slice)))
+               goto err1;
+
+       adapter->slices++;
+
+       if ((rc = cxl_of_read_afu_handle(afu, afu_np)))
+               goto err1;
+
+       if ((rc = cxl_ops->afu_reset(afu)))
+               goto err1;
+
+       if ((rc = cxl_of_read_afu_properties(afu, afu_np)))
+               goto err1;
+
+       if ((rc = afu_properties_look_ok(afu)))
+               goto err1;
+
+       if ((rc = guest_map_slice_regs(afu)))
+               goto err1;
+
+       if ((rc = guest_register_serr_irq(afu)))
+               goto err2;
+
+       /*
+        * After we call this function we must not free the afu directly, even
+        * if it returns an error!
+        */
+       if ((rc = cxl_register_afu(afu)))
+               goto err_put1;
+
+       if ((rc = cxl_sysfs_afu_add(afu)))
+               goto err_put1;
+
+       /*
+        * pHyp doesn't expose the programming models supported by the
+        * AFU. pHyp currently only supports directed mode. If it adds
+        * dedicated mode later, this version of cxl has no way to
+        * detect it. So we'll initialize the driver, but the first
+        * attach will fail.
+        * Being discussed with pHyp to do better (likely new property)
+        */
+       if (afu->max_procs_virtualised == 1)
+               afu->modes_supported = CXL_MODE_DEDICATED;
+       else
+               afu->modes_supported = CXL_MODE_DIRECTED;
+
+       if ((rc = cxl_afu_select_best_mode(afu)))
+               goto err_put2;
+
+       adapter->afu[afu->slice] = afu;
+
+       afu->enabled = true;
+
+       return 0;
+
+err_put2:
+       cxl_sysfs_afu_remove(afu);
+err_put1:
+       device_unregister(&afu->dev);
+       free = false;
+       guest_release_serr_irq(afu);
+err2:
+       guest_unmap_slice_regs(afu);
+err1:
+       if (free) {
+               kfree(afu->guest);
+               kfree(afu);
+       }
+       return rc;
+}
+
+void cxl_guest_remove_afu(struct cxl_afu *afu)
+{
+       pr_devel("in %s - AFU(%d)\n", __func__, afu->slice);
+
+       if (!afu)
+               return;
+
+       cxl_sysfs_afu_remove(afu);
+
+       spin_lock(&afu->adapter->afu_list_lock);
+       afu->adapter->afu[afu->slice] = NULL;
+       spin_unlock(&afu->adapter->afu_list_lock);
+
+       cxl_context_detach_all(afu);
+       cxl_ops->afu_deactivate_mode(afu, afu->current_mode);
+       guest_release_serr_irq(afu);
+       guest_unmap_slice_regs(afu);
+
+       device_unregister(&afu->dev);
+}
+
+static void free_adapter(struct cxl *adapter)
+{
+       struct irq_avail *cur;
+       int i;
+
+       if (adapter->guest->irq_avail) {
+               for (i = 0; i < adapter->guest->irq_nranges; i++) {
+                       cur = &adapter->guest->irq_avail[i];
+                       kfree(cur->bitmap);
+               }
+               kfree(adapter->guest->irq_avail);
+       }
+       kfree(adapter->guest->status);
+       cxl_remove_adapter_nr(adapter);
+       kfree(adapter->guest);
+       kfree(adapter);
+}
+
+static int properties_look_ok(struct cxl *adapter)
+{
+       /* The absence of this property means that the operational
+        * status is unknown or okay
+        */
+       if (strlen(adapter->guest->status) &&
+           strcmp(adapter->guest->status, "okay")) {
+               pr_err("ABORTING:Bad operational status of the device\n");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+ssize_t cxl_guest_read_adapter_vpd(struct cxl *adapter, void *buf, size_t len)
+{
+       return guest_collect_vpd(adapter, NULL, buf, len);
+}
+
+void cxl_guest_remove_adapter(struct cxl *adapter)
+{
+       pr_devel("in %s\n", __func__);
+
+       cxl_sysfs_adapter_remove(adapter);
+
+       device_unregister(&adapter->dev);
+}
+
+static void release_adapter(struct device *dev)
+{
+       free_adapter(to_cxl_adapter(dev));
+}
+
+struct cxl *cxl_guest_init_adapter(struct device_node *np, struct platform_device *pdev)
+{
+       struct cxl *adapter;
+       bool free = true;
+       int rc;
+
+       if (!(adapter = cxl_alloc_adapter()))
+               return ERR_PTR(-ENOMEM);
+
+       if (!(adapter->guest = kzalloc(sizeof(struct cxl_guest), GFP_KERNEL))) {
+               free_adapter(adapter);
+               return ERR_PTR(-ENOMEM);
+       }
+
+       adapter->slices = 0;
+       adapter->guest->pdev = pdev;
+       adapter->dev.parent = &pdev->dev;
+       adapter->dev.release = release_adapter;
+       dev_set_drvdata(&pdev->dev, adapter);
+
+       if ((rc = cxl_of_read_adapter_handle(adapter, np)))
+               goto err1;
+
+       if ((rc = cxl_of_read_adapter_properties(adapter, np)))
+               goto err1;
+
+       if ((rc = properties_look_ok(adapter)))
+               goto err1;
+
+       /*
+        * After we call this function we must not free the adapter directly,
+        * even if it returns an error!
+        */
+       if ((rc = cxl_register_adapter(adapter)))
+               goto err_put1;
+
+       if ((rc = cxl_sysfs_adapter_add(adapter)))
+               goto err_put1;
+
+       return adapter;
+
+err_put1:
+       device_unregister(&adapter->dev);
+       free = false;
+err1:
+       if (free)
+               free_adapter(adapter);
+       return ERR_PTR(rc);
+}
+
+const struct cxl_backend_ops cxl_guest_ops = {
+       .module = THIS_MODULE,
+       .adapter_reset = guest_reset,
+       .alloc_one_irq = guest_alloc_one_irq,
+       .release_one_irq = guest_release_one_irq,
+       .alloc_irq_ranges = guest_alloc_irq_ranges,
+       .release_irq_ranges = guest_release_irq_ranges,
+       .setup_irq = NULL,
+       .handle_psl_slice_error = guest_handle_psl_slice_error,
+       .psl_interrupt = guest_psl_irq,
+       .ack_irq = guest_ack_irq,
+       .attach_process = guest_attach_process,
+       .detach_process = guest_detach_process,
+       .link_ok = guest_link_ok,
+       .release_afu = guest_release_afu,
+       .afu_read_err_buffer = guest_afu_read_err_buffer,
+       .afu_check_and_enable = guest_afu_check_and_enable,
+       .afu_activate_mode = guest_afu_activate_mode,
+       .afu_deactivate_mode = guest_afu_deactivate_mode,
+       .afu_reset = guest_afu_reset,
+       .afu_cr_read8 = guest_afu_cr_read8,
+       .afu_cr_read16 = guest_afu_cr_read16,
+       .afu_cr_read32 = guest_afu_cr_read32,
+       .afu_cr_read64 = guest_afu_cr_read64,
+};
index 7e4c517f928e0d4c9a392f938c9fd0f21c1d8dab..24131e2537c5576ceceb244eb0bfe2aae18c0527 100644 (file)
@@ -11,7 +11,6 @@
 #include <linux/compiler.h>
 #include <linux/types.h>
 #include <linux/delay.h>
-#include <asm/hvcall.h>
 #include <asm/byteorder.h>
 #include "hcalls.h"
 
index 6bfab323578d5bc468e7139a9da8e1446d74f7ba..3e25522a5df64e33b66481436d5461e4066de8e7 100644 (file)
@@ -12,6 +12,7 @@
 
 #include <linux/types.h>
 #include <asm/byteorder.h>
+#include <asm/hvcall.h>
 #include "cxl.h"
 
 #define SG_BUFFER_SIZE 4096
index 927ba5a954f6a8ced51edfd05ee63c49f4ae4d6a..14b15835dc4af322f83b1d4317ab9296e85f30b2 100644 (file)
@@ -285,9 +285,6 @@ static int __init init_cxl(void)
 {
        int rc = 0;
 
-       if (!cpu_has_feature(CPU_FTR_HVMODE))
-               return -EPERM;
-
        if ((rc = cxl_file_init()))
                return rc;
 
@@ -296,8 +293,17 @@ static int __init init_cxl(void)
        if ((rc = register_cxl_calls(&cxl_calls)))
                goto err;
 
-       cxl_ops = &cxl_native_ops;
-       if ((rc = pci_register_driver(&cxl_pci_driver)))
+       if (cpu_has_feature(CPU_FTR_HVMODE)) {
+               cxl_ops = &cxl_native_ops;
+               rc = pci_register_driver(&cxl_pci_driver);
+       }
+#ifdef CONFIG_PPC_PSERIES
+       else {
+               cxl_ops = &cxl_guest_ops;
+               rc = platform_driver_register(&cxl_of_driver);
+       }
+#endif
+       if (rc)
                goto err1;
 
        return 0;
@@ -312,7 +318,12 @@ err:
 
 static void exit_cxl(void)
 {
-       pci_unregister_driver(&cxl_pci_driver);
+       if (cpu_has_feature(CPU_FTR_HVMODE))
+               pci_unregister_driver(&cxl_pci_driver);
+#ifdef CONFIG_PPC_PSERIES
+       else
+               platform_driver_unregister(&cxl_of_driver);
+#endif
 
        cxl_debugfs_exit();
        cxl_file_exit();
diff --git a/drivers/misc/cxl/of.c b/drivers/misc/cxl/of.c
new file mode 100644 (file)
index 0000000..edc4583
--- /dev/null
@@ -0,0 +1,513 @@
+/*
+ * Copyright 2015 IBM Corp.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#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 = kzalloc(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 = kcalloc(BITS_TO_LONGS(cur->range),
+                               sizeof(*cur->bitmap), 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];
+               kfree(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, slice_ok;
+
+       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 */
+       slice_ok = 0;
+       for (afu_np = NULL, slice = 0; (afu_np = of_get_next_child(np, afu_np)); slice++) {
+               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++;
+       }
+
+       if (slice_ok == 0) {
+               dev_info(&pdev->dev, "No active AFU");
+               adapter->slices = 0;
+       }
+
+       if (afu_np)
+               of_node_put(afu_np);
+       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,
+};