mirror of https://github.com/torvalds/linux.git
225 lines
4.6 KiB
C
225 lines
4.6 KiB
C
// 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);
|