]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
s390/diag: Add memory topology information via diag310
authorMete Durlu <meted@linux.ibm.com>
Tue, 14 Jan 2025 16:03:09 +0000 (17:03 +0100)
committerAlexander Gordeev <agordeev@linux.ibm.com>
Wed, 15 Jan 2025 12:59:08 +0000 (13:59 +0100)
Introduce diag310 and memory topology related subcodes.
Provide memory topology information obtanied from diag310 to userspace
via diag ioctl.

Signed-off-by: Mete Durlu <meted@linux.ibm.com>
Reviewed-by: Heiko Carstens <hca@linux.ibm.com>
Signed-off-by: Alexander Gordeev <agordeev@linux.ibm.com>
arch/s390/include/asm/diag.h
arch/s390/include/asm/sclp.h
arch/s390/include/uapi/asm/diag.h
arch/s390/kernel/diag/Makefile
arch/s390/kernel/diag/diag.c
arch/s390/kernel/diag/diag310.c [new file with mode: 0644]
arch/s390/kernel/diag/diag_ioctl.h
arch/s390/kernel/diag/diag_misc.c
drivers/s390/char/sclp_early.c

index c1050f4a71072cd720fc23016b11a96b6b3156ff..5790630e31f0212d8ee3a3557af9d0fb5ecb3540 100644 (file)
@@ -36,6 +36,7 @@ enum diag_stat_enum {
        DIAG_STAT_X2FC,
        DIAG_STAT_X304,
        DIAG_STAT_X308,
+       DIAG_STAT_X310,
        DIAG_STAT_X318,
        DIAG_STAT_X320,
        DIAG_STAT_X324,
index 8ec6e436bf4ca6793f8e2021f20d6a12f3610a41..4da3b29562854c5bf316e6c4984b0c66c1f56463 100644 (file)
@@ -92,6 +92,7 @@ struct sclp_info {
        unsigned char has_kss : 1;
        unsigned char has_diag204_bif : 1;
        unsigned char has_gisaf : 1;
+       unsigned char has_diag310 : 1;
        unsigned char has_diag318 : 1;
        unsigned char has_diag320 : 1;
        unsigned char has_diag324 : 1;
index 361c9ba9d4f5cae9b6c7856efdbdd9de3f07bb87..b7e6ccb4ff6e4bd0c96eaafb0873167aca3d74ea 100644 (file)
@@ -17,8 +17,16 @@ struct diag324_pib {
        __u64 sequence;
 };
 
+struct diag310_memtop {
+       __u64 address;
+       __u64 nesting_lvl;
+};
+
 /* Diag ioctl definitions */
 #define DIAG324_GET_PIBBUF     _IOWR(DIAG_MAGIC_STR, 0x77, struct diag324_pib)
 #define DIAG324_GET_PIBLEN     _IOR(DIAG_MAGIC_STR, 0x78, size_t)
+#define DIAG310_GET_STRIDE     _IOR(DIAG_MAGIC_STR, 0x79, size_t)
+#define DIAG310_GET_MEMTOPLEN  _IOWR(DIAG_MAGIC_STR, 0x7a, size_t)
+#define DIAG310_GET_MEMTOPBUF  _IOWR(DIAG_MAGIC_STR, 0x7b, struct diag310_memtop)
 
 #endif /* __S390_UAPI_ASM_DIAG_H */
index 44d3e0471151c449065adf33b77d192fd16be08f..956aee6c409085e27d48a7cd1455b274682d2bca 100644 (file)
@@ -1 +1 @@
-obj-y  := diag_misc.o diag324.o diag.o
+obj-y  := diag_misc.o diag324.o diag.o diag310.o
index 91161f09299fb4676f51bbd48c1bee8e64dc55a7..e15b8dee3228dbbe0ad6d8518f009cecfb78acc9 100644 (file)
@@ -51,6 +51,7 @@ static const struct diag_desc diag_map[NR_DIAG_STAT] = {
        [DIAG_STAT_X2FC] = { .code = 0x2fc, .name = "Guest Performance Data" },
        [DIAG_STAT_X304] = { .code = 0x304, .name = "Partition-Resource Service" },
        [DIAG_STAT_X308] = { .code = 0x308, .name = "List-Directed IPL" },
+       [DIAG_STAT_X310] = { .code = 0x310, .name = "Memory Topology Information" },
        [DIAG_STAT_X318] = { .code = 0x318, .name = "CP Name and Version Codes" },
        [DIAG_STAT_X320] = { .code = 0x320, .name = "Certificate Store" },
        [DIAG_STAT_X324] = { .code = 0x324, .name = "Power Information Block" },
diff --git a/arch/s390/kernel/diag/diag310.c b/arch/s390/kernel/diag/diag310.c
new file mode 100644 (file)
index 0000000..d6a3445
--- /dev/null
@@ -0,0 +1,276 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Request memory topology information via diag0x310.
+ *
+ * Copyright IBM Corp. 2025
+ */
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/uaccess.h>
+#include <linux/vmalloc.h>
+#include <asm/diag.h>
+#include <asm/sclp.h>
+#include <uapi/asm/diag.h>
+#include "diag_ioctl.h"
+
+#define DIAG310_LEVELMIN 1
+#define DIAG310_LEVELMAX 6
+
+enum diag310_sc {
+       DIAG310_SUBC_0 = 0,
+       DIAG310_SUBC_1 = 1,
+       DIAG310_SUBC_4 = 4,
+       DIAG310_SUBC_5 = 5
+};
+
+enum diag310_retcode {
+       DIAG310_RET_SUCCESS     = 0x0001,
+       DIAG310_RET_BUSY        = 0x0101,
+       DIAG310_RET_OPNOTSUPP   = 0x0102,
+       DIAG310_RET_SC4_INVAL   = 0x0401,
+       DIAG310_RET_SC4_NODATA  = 0x0402,
+       DIAG310_RET_SC5_INVAL   = 0x0501,
+       DIAG310_RET_SC5_NODATA  = 0x0502,
+       DIAG310_RET_SC5_ESIZE   = 0x0503
+};
+
+union diag310_response {
+       u64 response;
+       struct {
+               u64 result      : 32;
+               u64             : 16;
+               u64 rc          : 16;
+       };
+};
+
+union diag310_req_subcode {
+       u64 subcode;
+       struct {
+               u64             : 48;
+               u64 st          : 8;
+               u64 sc          : 8;
+       };
+};
+
+union diag310_req_size {
+       u64 size;
+       struct {
+               u64 page_count  : 32;
+               u64             : 32;
+       };
+};
+
+static inline unsigned long diag310(unsigned long subcode, unsigned long size, void *addr)
+{
+       union register_pair rp = { .even = (unsigned long)addr, .odd = size };
+
+       diag_stat_inc(DIAG_STAT_X310);
+       asm volatile("diag      %[rp],%[subcode],0x310\n"
+                    : [rp] "+d" (rp.pair)
+                    : [subcode] "d" (subcode)
+                    : "memory");
+       return rp.odd;
+}
+
+static int diag310_result_to_errno(unsigned int result)
+{
+       switch (result) {
+       case DIAG310_RET_BUSY:
+               return -EBUSY;
+       case DIAG310_RET_OPNOTSUPP:
+               return -EOPNOTSUPP;
+       default:
+               return -EINVAL;
+       }
+}
+
+static int diag310_get_subcode_mask(unsigned long *mask)
+{
+       union diag310_response res;
+
+       res.response = diag310(DIAG310_SUBC_0, 0, NULL);
+       if (res.rc != DIAG310_RET_SUCCESS)
+               return diag310_result_to_errno(res.rc);
+       *mask = res.response;
+       return 0;
+}
+
+static int diag310_get_memtop_stride(unsigned long *stride)
+{
+       union diag310_response res;
+
+       res.response = diag310(DIAG310_SUBC_1, 0, NULL);
+       if (res.rc != DIAG310_RET_SUCCESS)
+               return diag310_result_to_errno(res.rc);
+       *stride = res.result;
+       return 0;
+}
+
+static int diag310_get_memtop_size(unsigned long *pages, unsigned long level)
+{
+       union diag310_req_subcode req = { .sc = DIAG310_SUBC_4, .st = level };
+       union diag310_response res;
+
+       res.response = diag310(req.subcode, 0, NULL);
+       switch (res.rc) {
+       case DIAG310_RET_SUCCESS:
+               *pages = res.result;
+               return 0;
+       case DIAG310_RET_SC4_NODATA:
+               return -ENODATA;
+       case DIAG310_RET_SC4_INVAL:
+               return -EINVAL;
+       default:
+               return diag310_result_to_errno(res.rc);
+       }
+}
+
+static int diag310_store_topology_map(void *buf, unsigned long pages, unsigned long level)
+{
+       union diag310_req_subcode req_sc = { .sc = DIAG310_SUBC_5, .st = level };
+       union diag310_req_size req_size = { .page_count = pages };
+       union diag310_response res;
+
+       res.response = diag310(req_sc.subcode, req_size.size, buf);
+       switch (res.rc) {
+       case DIAG310_RET_SUCCESS:
+               return 0;
+       case DIAG310_RET_SC5_NODATA:
+               return -ENODATA;
+       case DIAG310_RET_SC5_ESIZE:
+               return -EOVERFLOW;
+       case DIAG310_RET_SC5_INVAL:
+               return -EINVAL;
+       default:
+               return diag310_result_to_errno(res.rc);
+       }
+}
+
+static int diag310_check_features(void)
+{
+       static int features_available;
+       unsigned long mask;
+       int rc;
+
+       if (READ_ONCE(features_available))
+               return 0;
+       if (!sclp.has_diag310)
+               return -EOPNOTSUPP;
+       rc = diag310_get_subcode_mask(&mask);
+       if (rc)
+               return rc;
+       if (!test_bit_inv(DIAG310_SUBC_1, &mask))
+               return -EOPNOTSUPP;
+       if (!test_bit_inv(DIAG310_SUBC_4, &mask))
+               return -EOPNOTSUPP;
+       if (!test_bit_inv(DIAG310_SUBC_5, &mask))
+               return -EOPNOTSUPP;
+       WRITE_ONCE(features_available, 1);
+       return 0;
+}
+
+static int memtop_get_stride_len(unsigned long *res)
+{
+       static unsigned long memtop_stride;
+       unsigned long stride;
+       int rc;
+
+       stride = READ_ONCE(memtop_stride);
+       if (!stride) {
+               rc = diag310_get_memtop_stride(&stride);
+               if (rc)
+                       return rc;
+               WRITE_ONCE(memtop_stride, stride);
+       }
+       *res = stride;
+       return 0;
+}
+
+static int memtop_get_page_count(unsigned long *res, unsigned long level)
+{
+       static unsigned long memtop_pages[DIAG310_LEVELMAX];
+       unsigned long pages;
+       int rc;
+
+       if (level > DIAG310_LEVELMAX || level < DIAG310_LEVELMIN)
+               return -EINVAL;
+       pages = READ_ONCE(memtop_pages[level - 1]);
+       if (!pages) {
+               rc = diag310_get_memtop_size(&pages, level);
+               if (rc)
+                       return rc;
+               WRITE_ONCE(memtop_pages[level - 1], pages);
+       }
+       *res = pages;
+       return 0;
+}
+
+long diag310_memtop_stride(unsigned long arg)
+{
+       size_t __user *argp = (void __user *)arg;
+       unsigned long stride;
+       int rc;
+
+       rc = diag310_check_features();
+       if (rc)
+               return rc;
+       rc = memtop_get_stride_len(&stride);
+       if (rc)
+               return rc;
+       if (put_user(stride, argp))
+               return -EFAULT;
+       return 0;
+}
+
+long diag310_memtop_len(unsigned long arg)
+{
+       size_t __user *argp = (void __user *)arg;
+       unsigned long pages, level;
+       int rc;
+
+       rc = diag310_check_features();
+       if (rc)
+               return rc;
+       if (get_user(level, argp))
+               return -EFAULT;
+       rc = memtop_get_page_count(&pages, level);
+       if (rc)
+               return rc;
+       if (put_user(pages * PAGE_SIZE, argp))
+               return -EFAULT;
+       return 0;
+}
+
+long diag310_memtop_buf(unsigned long arg)
+{
+       struct diag310_memtop __user *udata = (struct diag310_memtop __user *)arg;
+       unsigned long level, pages, data_size;
+       u64 address;
+       void *buf;
+       int rc;
+
+       rc = diag310_check_features();
+       if (rc)
+               return rc;
+       if (get_user(level, &udata->nesting_lvl))
+               return -EFAULT;
+       if (get_user(address, &udata->address))
+               return -EFAULT;
+       rc = memtop_get_page_count(&pages, level);
+       if (rc)
+               return rc;
+       data_size = pages * PAGE_SIZE;
+       buf = __vmalloc_node(data_size, PAGE_SIZE, GFP_KERNEL | __GFP_ZERO | __GFP_ACCOUNT,
+                            NUMA_NO_NODE, __builtin_return_address(0));
+       if (!buf)
+               return -ENOMEM;
+       rc = diag310_store_topology_map(buf, pages, level);
+       if (rc)
+               goto out;
+       if (copy_to_user((void __user *)address, buf, data_size))
+               rc = -EFAULT;
+out:
+       vfree(buf);
+       return rc;
+}
index de5365b23e1122f95bde1859d72e2dcc95d3722a..7080be946785a49bd0f093f89c296f9ce7ba67d7 100644 (file)
@@ -7,4 +7,8 @@
 long diag324_pibbuf(unsigned long arg);
 long diag324_piblen(unsigned long arg);
 
+long diag310_memtop_stride(unsigned long arg);
+long diag310_memtop_len(unsigned long arg);
+long diag310_memtop_buf(unsigned long arg);
+
 #endif /* _DIAG_IOCTL_H */
index 1f4f5c75732965d45f8fdf3785db9a3622df38f5..efffe02ea02eef3dfcd633c70188bf3332ce430d 100644 (file)
@@ -26,6 +26,15 @@ static long diag_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
        case DIAG324_GET_PIBBUF:
                rc = diag324_pibbuf(arg);
                break;
+       case DIAG310_GET_STRIDE:
+               rc = diag310_memtop_stride(arg);
+               break;
+       case DIAG310_GET_MEMTOPLEN:
+               rc = diag310_memtop_len(arg);
+               break;
+       case DIAG310_GET_MEMTOPBUF:
+               rc = diag310_memtop_buf(arg);
+               break;
        default:
                rc = -ENOIOCTLCMD;
                break;
index b7a55ea6c77a6379914d658a5029923abb59b39c..d9d6edaf8de828a51b30098c288e76b2a61749cd 100644 (file)
@@ -55,6 +55,7 @@ static void __init sclp_early_facilities_detect(void)
        if (sccb->fac91 & 0x40)
                get_lowcore()->machine_flags |= MACHINE_FLAG_TLB_GUEST;
        sclp.has_diag204_bif = !!(sccb->fac98 & 0x80);
+       sclp.has_diag310 = !!(sccb->fac91 & 0x80);
        if (sccb->cpuoff > 134) {
                sclp.has_diag318 = !!(sccb->byte_134 & 0x80);
                sclp.has_diag320 = !!(sccb->byte_134 & 0x04);