Contributors: 6
Author Tokens Token Proportion Commits Commit Proportion
Sumanth Korikkar 929 95.48% 3 23.08%
Michael Holzheu 18 1.85% 3 23.08%
Heiko Carstens 12 1.23% 3 23.08%
Martin Schwidefsky 8 0.82% 2 15.38%
Janosch Frank 5 0.51% 1 7.69%
Greg Kroah-Hartman 1 0.10% 1 7.69%
Total 973 13


// 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);