s390/diag324: Retrieve power readings via diag 0x324
authorSumanth Korikkar <sumanthk@linux.ibm.com>
Thu, 12 Dec 2024 16:17:19 +0000 (17:17 +0100)
committerAlexander Gordeev <agordeev@linux.ibm.com>
Tue, 17 Dec 2024 11:46:14 +0000 (12:46 +0100)
Retrieve electrical power readings for resources in a computing
environment via diag 0x324. diag 0x324 stores the power readings in the
power information block (pib).

Provide power readings from pib via diag324 ioctl interface.  diag324
ioctl provides new pib to the user only if the threshold time has passed
since the last call. Otherwise, cache data is returned. When there are
no active readers, cleanup of pib buffer is performed.

Reviewed-by: Heiko Carstens <hca@linux.ibm.com>
Signed-off-by: Sumanth Korikkar <sumanthk@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 [new file with mode: 0644]
arch/s390/kernel/Makefile
arch/s390/kernel/diag.c
arch/s390/kernel/diag/Makefile
arch/s390/kernel/diag/diag324.c [new file with mode: 0644]
arch/s390/kernel/diag/diag_ioctl.h [new file with mode: 0644]
arch/s390/kernel/diag/diag_misc.c
drivers/s390/char/sclp.h
drivers/s390/char/sclp_early.c

index e1316e181230197b11939b961ffde78927271ff0..c1050f4a71072cd720fc23016b11a96b6b3156ff 100644 (file)
@@ -38,6 +38,7 @@ enum diag_stat_enum {
        DIAG_STAT_X308,
        DIAG_STAT_X318,
        DIAG_STAT_X320,
+       DIAG_STAT_X324,
        DIAG_STAT_X49C,
        DIAG_STAT_X500,
        NR_DIAG_STAT
index eb00fa1771da07a564dbc35bb9baba24c3ecdd95..de369a062545668609b43bfc5240aad44bac500b 100644 (file)
@@ -89,6 +89,7 @@ struct sclp_info {
        unsigned char has_gisaf : 1;
        unsigned char has_diag318 : 1;
        unsigned char has_diag320 : 1;
+       unsigned char has_diag324 : 1;
        unsigned char has_sipl : 1;
        unsigned char has_sipl_eckd : 1;
        unsigned char has_dirq : 1;
diff --git a/arch/s390/include/uapi/asm/diag.h b/arch/s390/include/uapi/asm/diag.h
new file mode 100644 (file)
index 0000000..361c9ba
--- /dev/null
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Diag ioctls and its associated structures definitions.
+ *
+ * Copyright IBM Corp. 2024
+ */
+
+#ifndef __S390_UAPI_ASM_DIAG_H
+#define __S390_UAPI_ASM_DIAG_H
+
+#include <linux/types.h>
+
+#define DIAG_MAGIC_STR 'D'
+
+struct diag324_pib {
+       __u64 address;
+       __u64 sequence;
+};
+
+/* 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)
+
+#endif /* __S390_UAPI_ASM_DIAG_H */
index 48caae8c7e104b25331d4476bc5a843b346aa5a6..887450df44bd4cb2fd2013fd5db704a199e0b9e5 100644 (file)
@@ -44,6 +44,7 @@ obj-y += runtime_instr.o cache.o fpu.o dumpstack.o guarded_storage.o sthyi.o
 obj-y  += entry.o reipl.o kdebugfs.o alternative.o
 obj-y  += nospec-branch.o ipl_vmparm.o machine_kexec_reloc.o unwind_bc.o
 obj-y  += smp.o text_amode31.o stacktrace.o abs_lowcore.o facility.o uv.o wti.o
+obj-y  += diag/
 
 extra-y                                += vmlinux.lds
 
index cdd6e31344fa6b1959d5a5f916a35ea9ac6807f6..d786b9e58a822ad1ba3b3df645f4c83103866093 100644 (file)
@@ -53,6 +53,7 @@ static const struct diag_desc diag_map[NR_DIAG_STAT] = {
        [DIAG_STAT_X308] = { .code = 0x308, .name = "List-Directed IPL" },
        [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" },
        [DIAG_STAT_X49C] = { .code = 0x49c, .name = "Warning-Track Interruption" },
        [DIAG_STAT_X500] = { .code = 0x500, .name = "Virtio Service" },
 };
index 15bfa866c44d9a2019202d125dbb8bcc84208c39..6083c1fd2f4bb746da5f336158f20c3f0ec0c6e0 100644 (file)
@@ -1 +1 @@
-obj-y  := diag_misc.o
+obj-y  := diag_misc.o diag324.o
diff --git a/arch/s390/kernel/diag/diag324.c b/arch/s390/kernel/diag/diag324.c
new file mode 100644 (file)
index 0000000..7fa4c0b
--- /dev/null
@@ -0,0 +1,224 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Request power readings for resources in a computing environment via
+ * diag 0x324. diag 0x324 stores the power readings in the power information
+ * block (pib).
+ *
+ * Copyright IBM Corp. 2024
+ */
+
+#define pr_fmt(fmt)    "diag324: " fmt
+#include <linux/fs.h>
+#include <linux/gfp.h>
+#include <linux/ioctl.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/ktime.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/types.h>
+#include <linux/uaccess.h>
+#include <linux/vmalloc.h>
+
+#include <asm/diag.h>
+#include <asm/sclp.h>
+#include <asm/timex.h>
+#include <uapi/asm/diag.h>
+#include "diag_ioctl.h"
+
+enum subcode {
+       DIAG324_SUBC_0 = 0,
+       DIAG324_SUBC_1 = 1,
+       DIAG324_SUBC_2 = 2,
+};
+
+enum retcode {
+       DIAG324_RET_SUCCESS             = 0x0001,
+       DIAG324_RET_SUBC_NOTAVAIL       = 0x0103,
+       DIAG324_RET_INSUFFICIENT_SIZE   = 0x0104,
+       DIAG324_RET_READING_UNAVAILABLE = 0x0105,
+};
+
+union diag324_response {
+       u64 response;
+       struct {
+               u64 installed   : 32;
+               u64             : 16;
+               u64 rc          : 16;
+       } sc0;
+       struct {
+               u64 format      : 16;
+               u64             : 16;
+               u64 pib_len     : 16;
+               u64 rc          : 16;
+       } sc1;
+       struct {
+               u64             : 48;
+               u64 rc          : 16;
+       } sc2;
+};
+
+union diag324_request {
+       u64 request;
+       struct {
+               u64             : 32;
+               u64 allocated   : 16;
+               u64             : 12;
+               u64 sc          : 4;
+       } sc2;
+};
+
+struct pib {
+       u32             : 8;
+       u32 num         : 8;
+       u32 len         : 16;
+       u32             : 24;
+       u32 hlen        : 8;
+       u64             : 64;
+       u64 intv;
+       u8  r[];
+} __packed;
+
+struct pibdata {
+       struct pib *pib;
+       ktime_t expire;
+       u64 sequence;
+       size_t len;
+       int rc;
+};
+
+static DEFINE_MUTEX(pibmutex);
+static struct pibdata pibdata;
+
+#define PIBWORK_DELAY (5 * NSEC_PER_SEC)
+
+static void pibwork_handler(struct work_struct *work);
+static DECLARE_DELAYED_WORK(pibwork, pibwork_handler);
+
+static unsigned long diag324(unsigned long subcode, void *addr)
+{
+       union register_pair rp = { .even = (unsigned long)addr };
+
+       diag_stat_inc(DIAG_STAT_X324);
+       asm volatile("diag      %[rp],%[subcode],0x324\n"
+                    : [rp] "+d" (rp.pair)
+                    : [subcode] "d" (subcode)
+                    : "memory");
+       return rp.odd;
+}
+
+static void pibwork_handler(struct work_struct *work)
+{
+       struct pibdata *data = &pibdata;
+       ktime_t timedout;
+
+       mutex_lock(&pibmutex);
+       timedout = ktime_add_ns(data->expire, PIBWORK_DELAY);
+       if (ktime_before(ktime_get(), timedout)) {
+               mod_delayed_work(system_wq, &pibwork, nsecs_to_jiffies(PIBWORK_DELAY));
+               goto out;
+       }
+       vfree(data->pib);
+       data->pib = NULL;
+out:
+       mutex_unlock(&pibmutex);
+}
+
+static void pib_update(struct pibdata *data)
+{
+       union diag324_request req = { .sc2.sc = DIAG324_SUBC_2, .sc2.allocated = data->len };
+       union diag324_response res;
+       int rc;
+
+       memset(data->pib, 0, data->len);
+       res.response = diag324(req.request, data->pib);
+       switch (res.sc2.rc) {
+       case DIAG324_RET_SUCCESS:
+               rc = 0;
+               break;
+       case DIAG324_RET_SUBC_NOTAVAIL:
+               rc = -ENOENT;
+               break;
+       case DIAG324_RET_INSUFFICIENT_SIZE:
+               rc = -EMSGSIZE;
+               break;
+       case DIAG324_RET_READING_UNAVAILABLE:
+               rc = -EBUSY;
+               break;
+       default:
+               rc = -EINVAL;
+       }
+       data->rc = rc;
+}
+
+long diag324_pibbuf(unsigned long arg)
+{
+       struct diag324_pib __user *udata = (struct diag324_pib __user *)arg;
+       struct pibdata *data = &pibdata;
+       static bool first = true;
+       u64 address;
+       int rc;
+
+       if (!data->len)
+               return -EOPNOTSUPP;
+       if (get_user(address, &udata->address))
+               return -EFAULT;
+       mutex_lock(&pibmutex);
+       rc = -ENOMEM;
+       if (!data->pib)
+               data->pib = vmalloc(data->len);
+       if (!data->pib)
+               goto out;
+       if (first || ktime_after(ktime_get(), data->expire)) {
+               pib_update(data);
+               data->sequence++;
+               data->expire = ktime_add_ns(ktime_get(), tod_to_ns(data->pib->intv));
+               mod_delayed_work(system_wq, &pibwork, nsecs_to_jiffies(PIBWORK_DELAY));
+               first = false;
+       }
+       rc = data->rc;
+       if (rc != 0 && rc != -EBUSY)
+               goto out;
+       rc = copy_to_user((void __user *)address, data->pib, data->pib->len);
+       rc |= put_user(data->sequence, &udata->sequence);
+       if (rc)
+               rc = -EFAULT;
+out:
+       mutex_unlock(&pibmutex);
+       return rc;
+}
+
+long diag324_piblen(unsigned long arg)
+{
+       struct pibdata *data = &pibdata;
+
+       if (!data->len)
+               return -EOPNOTSUPP;
+       if (put_user(data->len, (size_t __user *)arg))
+               return -EFAULT;
+       return 0;
+}
+
+static int __init diag324_init(void)
+{
+       union diag324_response res;
+       unsigned long installed;
+
+       if (!sclp.has_diag324)
+               return -EOPNOTSUPP;
+       res.response = diag324(DIAG324_SUBC_0, NULL);
+       if (res.sc0.rc != DIAG324_RET_SUCCESS)
+               return -EOPNOTSUPP;
+       installed = res.response;
+       if (!test_bit_inv(DIAG324_SUBC_1, &installed))
+               return -EOPNOTSUPP;
+       if (!test_bit_inv(DIAG324_SUBC_2, &installed))
+               return -EOPNOTSUPP;
+       res.response = diag324(DIAG324_SUBC_1, NULL);
+       if (res.sc1.rc != DIAG324_RET_SUCCESS)
+               return -EOPNOTSUPP;
+       pibdata.len = res.sc1.pib_len;
+       return 0;
+}
+device_initcall(diag324_init);
diff --git a/arch/s390/kernel/diag/diag_ioctl.h b/arch/s390/kernel/diag/diag_ioctl.h
new file mode 100644 (file)
index 0000000..de5365b
--- /dev/null
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _DIAG_IOCTL_H
+#define _DIAG_IOCTL_H
+
+#include <linux/types.h>
+
+long diag324_pibbuf(unsigned long arg);
+long diag324_piblen(unsigned long arg);
+
+#endif /* _DIAG_IOCTL_H */
index cd5b78880d7d337b26b57f9e981a2a64a6eae1d2..1f4f5c75732965d45f8fdf3785db9a3622df38f5 100644 (file)
 #include <linux/miscdevice.h>
 #include <linux/types.h>
 
+#include <uapi/asm/diag.h>
+#include "diag_ioctl.h"
+
 static long diag_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
 {
        long rc;
 
        switch (cmd) {
+       case DIAG324_GET_PIBLEN:
+               rc = diag324_piblen(arg);
+               break;
+       case DIAG324_GET_PIBBUF:
+               rc = diag324_pibbuf(arg);
+               break;
        default:
                rc = -ENOIOCTLCMD;
                break;
index 6c91e422927ff5e81694cf15586dcd3a1c4ea83f..847674ca94c392f4f89763f8e04204b2221ad529 100644 (file)
@@ -196,7 +196,9 @@ struct read_info_sccb {
        u8      byte_134;                       /* 134 */
        u8      cpudirq;                /* 135 */
        u16     cbl;                    /* 136-137 */
-       u8      _pad_138[EXT_SCCB_READ_SCP - 138];
+       u8      byte_138;               /* 138 */
+       u8      byte_139;               /* 139 */
+       u8      _pad_140[EXT_SCCB_READ_SCP - 140];
 } __packed __aligned(PAGE_SIZE);
 
 struct read_storage_sccb {
index 29156455970e99c3f42b6c14bf213548308032e7..b7a55ea6c77a6379914d658a5029923abb59b39c 100644 (file)
@@ -64,6 +64,8 @@ static void __init sclp_early_facilities_detect(void)
                sclp.has_sipl = !!(sccb->cbl & 0x4000);
                sclp.has_sipl_eckd = !!(sccb->cbl & 0x2000);
        }
+       if (sccb->cpuoff > 139)
+               sclp.has_diag324 = !!(sccb->byte_139 & 0x80);
        sclp.rnmax = sccb->rnmax ? sccb->rnmax : sccb->rnmax2;
        sclp.rzm = sccb->rnsize ? sccb->rnsize : sccb->rnsize2;
        sclp.rzm <<= 20;