#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;
}