s390/pci: add ioctl interface for CLP
authorMartin Schwidefsky <schwidefsky@de.ibm.com>
Wed, 13 Jan 2016 11:54:28 +0000 (12:54 +0100)
committerMartin Schwidefsky <schwidefsky@de.ibm.com>
Mon, 7 Mar 2016 15:54:32 +0000 (16:54 +0100)
Provide a user space interface to issue call logical-processor instructions.
Only selected CLP commands are allowed, enough to get the full overview of
the installed PCI functions.

Reviewed-by: Sebastian Ott <sebott@linux.vnet.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
arch/s390/include/asm/clp.h
arch/s390/include/asm/pci_clp.h
arch/s390/include/uapi/asm/clp.h [new file with mode: 0644]
arch/s390/pci/pci_clp.c

index a0e71a501f7cecbf41252c0c549864659bd0e5f4..5687d62fb0cb313e7562553da0ea320c0f8a0b22 100644 (file)
@@ -4,14 +4,23 @@
 /* CLP common request & response block size */
 #define CLP_BLK_SIZE                   PAGE_SIZE
 
+#define CLP_LPS_BASE   0
+#define CLP_LPS_PCI    2
+
 struct clp_req_hdr {
        u16 len;
        u16 cmd;
+       u32 fmt         : 4;
+       u32 reserved1   : 28;
+       u64 reserved2;
 } __packed;
 
 struct clp_rsp_hdr {
        u16 len;
        u16 rsp;
+       u32 fmt         : 4;
+       u32 reserved1   : 28;
+       u64 reserved2;
 } __packed;
 
 /* CLP Response Codes */
@@ -25,4 +34,22 @@ struct clp_rsp_hdr {
 #define CLP_RC_NODATA                  0x0080  /* No data available */
 #define CLP_RC_FC_UNKNOWN              0x0100  /* Function code not recognized */
 
+/* Store logical-processor characteristics request */
+struct clp_req_slpc {
+       struct clp_req_hdr hdr;
+} __packed;
+
+struct clp_rsp_slpc {
+       struct clp_rsp_hdr hdr;
+       u32 reserved2[4];
+       u32 lpif[8];
+       u32 reserved3[8];
+       u32 lpic[8];
+} __packed;
+
+struct clp_req_rsp_slpc {
+       struct clp_req_slpc request;
+       struct clp_rsp_slpc response;
+} __packed;
+
 #endif
index dd78f92f1cce5fc419e795e325800d1c78a155ac..e75c64cbcf080a260f5578d9b8383ed73d9617ba 100644 (file)
@@ -49,9 +49,6 @@ struct clp_fh_list_entry {
 /* List PCI functions request */
 struct clp_req_list_pci {
        struct clp_req_hdr hdr;
-       u32 fmt                 :  4;   /* cmd request block format */
-       u32                     : 28;
-       u64 reserved1;
        u64 resume_token;
        u64 reserved2;
 } __packed;
@@ -59,9 +56,6 @@ struct clp_req_list_pci {
 /* List PCI functions response */
 struct clp_rsp_list_pci {
        struct clp_rsp_hdr hdr;
-       u32 fmt                 :  4;   /* cmd request block format */
-       u32                     : 28;
-       u64 reserved1;
        u64 resume_token;
        u32 reserved2;
        u16 max_fn;
@@ -73,9 +67,6 @@ struct clp_rsp_list_pci {
 /* Query PCI function request */
 struct clp_req_query_pci {
        struct clp_req_hdr hdr;
-       u32 fmt                 :  4;   /* cmd request block format */
-       u32                     : 28;
-       u64 reserved1;
        u32 fh;                         /* function handle */
        u32 reserved2;
        u64 reserved3;
@@ -84,9 +75,6 @@ struct clp_req_query_pci {
 /* Query PCI function response */
 struct clp_rsp_query_pci {
        struct clp_rsp_hdr hdr;
-       u32 fmt                 :  4;   /* cmd request block format */
-       u32                     : 28;
-       u64                     : 64;
        u16 vfn;                        /* virtual fn number */
        u16                     :  7;
        u16 util_str_avail      :  1;   /* utility string available? */
@@ -108,21 +96,15 @@ struct clp_rsp_query_pci {
 /* Query PCI function group request */
 struct clp_req_query_pci_grp {
        struct clp_req_hdr hdr;
-       u32 fmt                 :  4;   /* cmd request block format */
-       u32                     : 28;
-       u64 reserved1;
-       u32                     : 24;
+       u32 reserved2           : 24;
        u32 pfgid               :  8;   /* function group id */
-       u32 reserved2;
-       u64 reserved3;
+       u32 reserved3;
+       u64 reserved4;
 } __packed;
 
 /* Query PCI function group response */
 struct clp_rsp_query_pci_grp {
        struct clp_rsp_hdr hdr;
-       u32 fmt                 :  4;   /* cmd request block format */
-       u32                     : 28;
-       u64 reserved1;
        u16                     :  4;
        u16 noi                 : 12;   /* number of interrupts */
        u8 version;
@@ -141,9 +123,6 @@ struct clp_rsp_query_pci_grp {
 /* Set PCI function request */
 struct clp_req_set_pci {
        struct clp_req_hdr hdr;
-       u32 fmt                 :  4;   /* cmd request block format */
-       u32                     : 28;
-       u64 reserved1;
        u32 fh;                         /* function handle */
        u16 reserved2;
        u8 oc;                          /* operation controls */
@@ -154,9 +133,6 @@ struct clp_req_set_pci {
 /* Set PCI function response */
 struct clp_rsp_set_pci {
        struct clp_rsp_hdr hdr;
-       u32 fmt                 :  4;   /* cmd request block format */
-       u32                     : 28;
-       u64 reserved1;
        u32 fh;                         /* function handle */
        u32 reserved3;
        u64 reserved4;
diff --git a/arch/s390/include/uapi/asm/clp.h b/arch/s390/include/uapi/asm/clp.h
new file mode 100644 (file)
index 0000000..ab72d9d
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * ioctl interface for /dev/clp
+ *
+ * Copyright IBM Corp. 2016
+ * Author(s): Martin Schwidefsky <schwidefsky@de.ibm.com>
+ */
+
+#ifndef _ASM_CLP_H
+#define _ASM_CLP_H
+
+#include <linux/types.h>
+#include <linux/ioctl.h>
+
+struct clp_req {
+       unsigned int c : 1;
+       unsigned int r : 1;
+       unsigned int lps : 6;
+       unsigned int cmd : 8;
+       unsigned int : 16;
+       unsigned int reserved;
+       __u64 data_p;
+};
+
+#define CLP_IOCTL_MAGIC 'c'
+
+#define CLP_SYNC _IOWR(CLP_IOCTL_MAGIC, 0xC1, struct clp_req)
+
+#endif
index d6e411ed8b1f9017a62b68b6c3fbaab4ce4a0f18..21591ddb4c1fddcd59aeb1d990f484040e57aeae 100644 (file)
@@ -8,13 +8,19 @@
 #define KMSG_COMPONENT "zpci"
 #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
 
+#include <linux/compat.h>
 #include <linux/kernel.h>
+#include <linux/miscdevice.h>
 #include <linux/slab.h>
 #include <linux/err.h>
 #include <linux/delay.h>
 #include <linux/pci.h>
+#include <linux/uaccess.h>
 #include <asm/pci_debug.h>
 #include <asm/pci_clp.h>
+#include <asm/compat.h>
+#include <asm/clp.h>
+#include <uapi/asm/clp.h>
 
 static inline void zpci_err_clp(unsigned int rsp, int rc)
 {
@@ -27,21 +33,43 @@ static inline void zpci_err_clp(unsigned int rsp, int rc)
 }
 
 /*
- * Call Logical Processor
- * Retry logic is handled by the caller.
+ * Call Logical Processor with c=1, lps=0 and command 1
+ * to get the bit mask of installed logical processors
  */
-static inline u8 clp_instr(void *data)
+static inline int clp_get_ilp(unsigned long *ilp)
+{
+       unsigned long mask;
+       int cc = 3;
+
+       asm volatile (
+               "       .insn   rrf,0xb9a00000,%[mask],%[cmd],8,0\n"
+               "0:     ipm     %[cc]\n"
+               "       srl     %[cc],28\n"
+               "1:\n"
+               EX_TABLE(0b, 1b)
+               : [cc] "+d" (cc), [mask] "=d" (mask) : [cmd] "a" (1)
+               : "cc");
+       *ilp = mask;
+       return cc;
+}
+
+/*
+ * Call Logical Processor with c=0, the give constant lps and an lpcb request.
+ */
+static inline int clp_req(void *data, unsigned int lps)
 {
        struct { u8 _[CLP_BLK_SIZE]; } *req = data;
        u64 ignored;
-       u8 cc;
+       int cc = 3;
 
        asm volatile (
-               "       .insn   rrf,0xb9a00000,%[ign],%[req],0x0,0x2\n"
-               "       ipm     %[cc]\n"
+               "       .insn   rrf,0xb9a00000,%[ign],%[req],0,%[lps]\n"
+               "0:     ipm     %[cc]\n"
                "       srl     %[cc],28\n"
-               : [cc] "=d" (cc), [ign] "=d" (ignored), "+m" (*req)
-               : [req] "a" (req)
+               "1:\n"
+               EX_TABLE(0b, 1b)
+               : [cc] "+d" (cc), [ign] "=d" (ignored), "+m" (*req)
+               : [req] "a" (req), [lps] "i" (lps)
                : "cc");
        return cc;
 }
@@ -90,7 +118,7 @@ static int clp_query_pci_fngrp(struct zpci_dev *zdev, u8 pfgid)
        rrb->response.hdr.len = sizeof(rrb->response);
        rrb->request.pfgid = pfgid;
 
-       rc = clp_instr(rrb);
+       rc = clp_req(rrb, CLP_LPS_PCI);
        if (!rc && rrb->response.hdr.rsp == CLP_RC_OK)
                clp_store_query_pci_fngrp(zdev, &rrb->response);
        else {
@@ -143,7 +171,7 @@ static int clp_query_pci_fn(struct zpci_dev *zdev, u32 fh)
        rrb->response.hdr.len = sizeof(rrb->response);
        rrb->request.fh = fh;
 
-       rc = clp_instr(rrb);
+       rc = clp_req(rrb, CLP_LPS_PCI);
        if (!rc && rrb->response.hdr.rsp == CLP_RC_OK) {
                rc = clp_store_query_pci_fn(zdev, &rrb->response);
                if (rc)
@@ -214,7 +242,7 @@ static int clp_set_pci_fn(u32 *fh, u8 nr_dma_as, u8 command)
                rrb->request.oc = command;
                rrb->request.ndas = nr_dma_as;
 
-               rc = clp_instr(rrb);
+               rc = clp_req(rrb, CLP_LPS_PCI);
                if (rrb->response.hdr.rsp == CLP_RC_SETPCIFN_BUSY) {
                        retries--;
                        if (retries < 0)
@@ -280,7 +308,7 @@ static int clp_list_pci(struct clp_req_rsp_list_pci *rrb,
                rrb->request.resume_token = resume_token;
 
                /* Get PCI function handle list */
-               rc = clp_instr(rrb);
+               rc = clp_req(rrb, CLP_LPS_PCI);
                if (rc || rrb->response.hdr.rsp != CLP_RC_OK) {
                        zpci_err("List PCI FN:\n");
                        zpci_err_clp(rrb->response.hdr.rsp, rc);
@@ -391,3 +419,198 @@ int clp_rescan_pci_devices_simple(void)
        clp_free_block(rrb);
        return rc;
 }
+
+static int clp_base_slpc(struct clp_req *req, struct clp_req_rsp_slpc *lpcb)
+{
+       unsigned long limit = PAGE_SIZE - sizeof(lpcb->request);
+
+       if (lpcb->request.hdr.len != sizeof(lpcb->request) ||
+           lpcb->response.hdr.len > limit)
+               return -EINVAL;
+       return clp_req(lpcb, CLP_LPS_BASE) ? -EOPNOTSUPP : 0;
+}
+
+static int clp_base_command(struct clp_req *req, struct clp_req_hdr *lpcb)
+{
+       switch (lpcb->cmd) {
+       case 0x0001: /* store logical-processor characteristics */
+               return clp_base_slpc(req, (void *) lpcb);
+       default:
+               return -EINVAL;
+       }
+}
+
+static int clp_pci_slpc(struct clp_req *req, struct clp_req_rsp_slpc *lpcb)
+{
+       unsigned long limit = PAGE_SIZE - sizeof(lpcb->request);
+
+       if (lpcb->request.hdr.len != sizeof(lpcb->request) ||
+           lpcb->response.hdr.len > limit)
+               return -EINVAL;
+       return clp_req(lpcb, CLP_LPS_PCI) ? -EOPNOTSUPP : 0;
+}
+
+static int clp_pci_list(struct clp_req *req, struct clp_req_rsp_list_pci *lpcb)
+{
+       unsigned long limit = PAGE_SIZE - sizeof(lpcb->request);
+
+       if (lpcb->request.hdr.len != sizeof(lpcb->request) ||
+           lpcb->response.hdr.len > limit)
+               return -EINVAL;
+       if (lpcb->request.reserved2 != 0)
+               return -EINVAL;
+       return clp_req(lpcb, CLP_LPS_PCI) ? -EOPNOTSUPP : 0;
+}
+
+static int clp_pci_query(struct clp_req *req,
+                        struct clp_req_rsp_query_pci *lpcb)
+{
+       unsigned long limit = PAGE_SIZE - sizeof(lpcb->request);
+
+       if (lpcb->request.hdr.len != sizeof(lpcb->request) ||
+           lpcb->response.hdr.len > limit)
+               return -EINVAL;
+       if (lpcb->request.reserved2 != 0 || lpcb->request.reserved3 != 0)
+               return -EINVAL;
+       return clp_req(lpcb, CLP_LPS_PCI) ? -EOPNOTSUPP : 0;
+}
+
+static int clp_pci_query_grp(struct clp_req *req,
+                            struct clp_req_rsp_query_pci_grp *lpcb)
+{
+       unsigned long limit = PAGE_SIZE - sizeof(lpcb->request);
+
+       if (lpcb->request.hdr.len != sizeof(lpcb->request) ||
+           lpcb->response.hdr.len > limit)
+               return -EINVAL;
+       if (lpcb->request.reserved2 != 0 || lpcb->request.reserved3 != 0 ||
+           lpcb->request.reserved4 != 0)
+               return -EINVAL;
+       return clp_req(lpcb, CLP_LPS_PCI) ? -EOPNOTSUPP : 0;
+}
+
+static int clp_pci_command(struct clp_req *req, struct clp_req_hdr *lpcb)
+{
+       switch (lpcb->cmd) {
+       case 0x0001: /* store logical-processor characteristics */
+               return clp_pci_slpc(req, (void *) lpcb);
+       case 0x0002: /* list PCI functions */
+               return clp_pci_list(req, (void *) lpcb);
+       case 0x0003: /* query PCI function */
+               return clp_pci_query(req, (void *) lpcb);
+       case 0x0004: /* query PCI function group */
+               return clp_pci_query_grp(req, (void *) lpcb);
+       default:
+               return -EINVAL;
+       }
+}
+
+static int clp_normal_command(struct clp_req *req)
+{
+       struct clp_req_hdr *lpcb;
+       void __user *uptr;
+       int rc;
+
+       rc = -EINVAL;
+       if (req->lps != 0 && req->lps != 2)
+               goto out;
+
+       rc = -ENOMEM;
+       lpcb = clp_alloc_block(GFP_KERNEL);
+       if (!lpcb)
+               goto out;
+
+       rc = -EFAULT;
+       uptr = (void __force __user *)(unsigned long) req->data_p;
+       if (copy_from_user(lpcb, uptr, PAGE_SIZE) != 0)
+               goto out_free;
+
+       rc = -EINVAL;
+       if (lpcb->fmt != 0 || lpcb->reserved1 != 0 || lpcb->reserved2 != 0)
+               goto out_free;
+
+       switch (req->lps) {
+       case 0:
+               rc = clp_base_command(req, lpcb);
+               break;
+       case 2:
+               rc = clp_pci_command(req, lpcb);
+               break;
+       }
+       if (rc)
+               goto out_free;
+
+       rc = -EFAULT;
+       if (copy_to_user(uptr, lpcb, PAGE_SIZE) != 0)
+               goto out_free;
+
+       rc = 0;
+
+out_free:
+       clp_free_block(lpcb);
+out:
+       return rc;
+}
+
+static int clp_immediate_command(struct clp_req *req)
+{
+       void __user *uptr;
+       unsigned long ilp;
+       int exists;
+
+       if (req->cmd > 1 || clp_get_ilp(&ilp) != 0)
+               return -EINVAL;
+
+       uptr = (void __force __user *)(unsigned long) req->data_p;
+       if (req->cmd == 0) {
+               /* Command code 0: test for a specific processor */
+               exists = test_bit_inv(req->lps, &ilp);
+               return put_user(exists, (int __user *) uptr);
+       }
+       /* Command code 1: return bit mask of installed processors */
+       return put_user(ilp, (unsigned long __user *) uptr);
+}
+
+static long clp_misc_ioctl(struct file *filp, unsigned int cmd,
+                          unsigned long arg)
+{
+       struct clp_req req;
+       void __user *argp;
+
+       if (cmd != CLP_SYNC)
+               return -EINVAL;
+
+       argp = is_compat_task() ? compat_ptr(arg) : (void __user *) arg;
+       if (copy_from_user(&req, argp, sizeof(req)))
+               return -EFAULT;
+       if (req.r != 0)
+               return -EINVAL;
+       return req.c ? clp_immediate_command(&req) : clp_normal_command(&req);
+}
+
+static int clp_misc_release(struct inode *inode, struct file *filp)
+{
+       return 0;
+}
+
+static const struct file_operations clp_misc_fops = {
+       .owner = THIS_MODULE,
+       .open = nonseekable_open,
+       .release = clp_misc_release,
+       .unlocked_ioctl = clp_misc_ioctl,
+       .compat_ioctl = clp_misc_ioctl,
+       .llseek = no_llseek,
+};
+
+static struct miscdevice clp_misc_device = {
+       .minor = MISC_DYNAMIC_MINOR,
+       .name = "clp",
+       .fops = &clp_misc_fops,
+};
+
+static int __init clp_misc_init(void)
+{
+       return misc_register(&clp_misc_device);
+}
+
+device_initcall(clp_misc_init);