forked from Minki/linux
s390/pci: add ioctl interface for CLP
Provide a user space interface to issue call logical-processor instructions. Only selected CLP commands are allowed, enough to get the full overview of the installed PCI functions. Reviewed-by: Sebastian Ott <sebott@linux.vnet.ibm.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
This commit is contained in:
parent
baebc70a4d
commit
988b86e69d
@ -4,14 +4,23 @@
|
||||
/* CLP common request & response block size */
|
||||
#define CLP_BLK_SIZE PAGE_SIZE
|
||||
|
||||
#define CLP_LPS_BASE 0
|
||||
#define CLP_LPS_PCI 2
|
||||
|
||||
struct clp_req_hdr {
|
||||
u16 len;
|
||||
u16 cmd;
|
||||
u32 fmt : 4;
|
||||
u32 reserved1 : 28;
|
||||
u64 reserved2;
|
||||
} __packed;
|
||||
|
||||
struct clp_rsp_hdr {
|
||||
u16 len;
|
||||
u16 rsp;
|
||||
u32 fmt : 4;
|
||||
u32 reserved1 : 28;
|
||||
u64 reserved2;
|
||||
} __packed;
|
||||
|
||||
/* CLP Response Codes */
|
||||
@ -25,4 +34,22 @@ struct clp_rsp_hdr {
|
||||
#define CLP_RC_NODATA 0x0080 /* No data available */
|
||||
#define CLP_RC_FC_UNKNOWN 0x0100 /* Function code not recognized */
|
||||
|
||||
/* Store logical-processor characteristics request */
|
||||
struct clp_req_slpc {
|
||||
struct clp_req_hdr hdr;
|
||||
} __packed;
|
||||
|
||||
struct clp_rsp_slpc {
|
||||
struct clp_rsp_hdr hdr;
|
||||
u32 reserved2[4];
|
||||
u32 lpif[8];
|
||||
u32 reserved3[8];
|
||||
u32 lpic[8];
|
||||
} __packed;
|
||||
|
||||
struct clp_req_rsp_slpc {
|
||||
struct clp_req_slpc request;
|
||||
struct clp_rsp_slpc response;
|
||||
} __packed;
|
||||
|
||||
#endif
|
||||
|
@ -49,9 +49,6 @@ struct clp_fh_list_entry {
|
||||
/* List PCI functions request */
|
||||
struct clp_req_list_pci {
|
||||
struct clp_req_hdr hdr;
|
||||
u32 fmt : 4; /* cmd request block format */
|
||||
u32 : 28;
|
||||
u64 reserved1;
|
||||
u64 resume_token;
|
||||
u64 reserved2;
|
||||
} __packed;
|
||||
@ -59,9 +56,6 @@ struct clp_req_list_pci {
|
||||
/* List PCI functions response */
|
||||
struct clp_rsp_list_pci {
|
||||
struct clp_rsp_hdr hdr;
|
||||
u32 fmt : 4; /* cmd request block format */
|
||||
u32 : 28;
|
||||
u64 reserved1;
|
||||
u64 resume_token;
|
||||
u32 reserved2;
|
||||
u16 max_fn;
|
||||
@ -73,9 +67,6 @@ struct clp_rsp_list_pci {
|
||||
/* Query PCI function request */
|
||||
struct clp_req_query_pci {
|
||||
struct clp_req_hdr hdr;
|
||||
u32 fmt : 4; /* cmd request block format */
|
||||
u32 : 28;
|
||||
u64 reserved1;
|
||||
u32 fh; /* function handle */
|
||||
u32 reserved2;
|
||||
u64 reserved3;
|
||||
@ -84,9 +75,6 @@ struct clp_req_query_pci {
|
||||
/* Query PCI function response */
|
||||
struct clp_rsp_query_pci {
|
||||
struct clp_rsp_hdr hdr;
|
||||
u32 fmt : 4; /* cmd request block format */
|
||||
u32 : 28;
|
||||
u64 : 64;
|
||||
u16 vfn; /* virtual fn number */
|
||||
u16 : 7;
|
||||
u16 util_str_avail : 1; /* utility string available? */
|
||||
@ -108,21 +96,15 @@ struct clp_rsp_query_pci {
|
||||
/* Query PCI function group request */
|
||||
struct clp_req_query_pci_grp {
|
||||
struct clp_req_hdr hdr;
|
||||
u32 fmt : 4; /* cmd request block format */
|
||||
u32 : 28;
|
||||
u64 reserved1;
|
||||
u32 : 24;
|
||||
u32 reserved2 : 24;
|
||||
u32 pfgid : 8; /* function group id */
|
||||
u32 reserved2;
|
||||
u64 reserved3;
|
||||
u32 reserved3;
|
||||
u64 reserved4;
|
||||
} __packed;
|
||||
|
||||
/* Query PCI function group response */
|
||||
struct clp_rsp_query_pci_grp {
|
||||
struct clp_rsp_hdr hdr;
|
||||
u32 fmt : 4; /* cmd request block format */
|
||||
u32 : 28;
|
||||
u64 reserved1;
|
||||
u16 : 4;
|
||||
u16 noi : 12; /* number of interrupts */
|
||||
u8 version;
|
||||
@ -141,9 +123,6 @@ struct clp_rsp_query_pci_grp {
|
||||
/* Set PCI function request */
|
||||
struct clp_req_set_pci {
|
||||
struct clp_req_hdr hdr;
|
||||
u32 fmt : 4; /* cmd request block format */
|
||||
u32 : 28;
|
||||
u64 reserved1;
|
||||
u32 fh; /* function handle */
|
||||
u16 reserved2;
|
||||
u8 oc; /* operation controls */
|
||||
@ -154,9 +133,6 @@ struct clp_req_set_pci {
|
||||
/* Set PCI function response */
|
||||
struct clp_rsp_set_pci {
|
||||
struct clp_rsp_hdr hdr;
|
||||
u32 fmt : 4; /* cmd request block format */
|
||||
u32 : 28;
|
||||
u64 reserved1;
|
||||
u32 fh; /* function handle */
|
||||
u32 reserved3;
|
||||
u64 reserved4;
|
||||
|
28
arch/s390/include/uapi/asm/clp.h
Normal file
28
arch/s390/include/uapi/asm/clp.h
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* ioctl interface for /dev/clp
|
||||
*
|
||||
* Copyright IBM Corp. 2016
|
||||
* Author(s): Martin Schwidefsky <schwidefsky@de.ibm.com>
|
||||
*/
|
||||
|
||||
#ifndef _ASM_CLP_H
|
||||
#define _ASM_CLP_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/ioctl.h>
|
||||
|
||||
struct clp_req {
|
||||
unsigned int c : 1;
|
||||
unsigned int r : 1;
|
||||
unsigned int lps : 6;
|
||||
unsigned int cmd : 8;
|
||||
unsigned int : 16;
|
||||
unsigned int reserved;
|
||||
__u64 data_p;
|
||||
};
|
||||
|
||||
#define CLP_IOCTL_MAGIC 'c'
|
||||
|
||||
#define CLP_SYNC _IOWR(CLP_IOCTL_MAGIC, 0xC1, struct clp_req)
|
||||
|
||||
#endif
|
@ -8,13 +8,19 @@
|
||||
#define KMSG_COMPONENT "zpci"
|
||||
#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
|
||||
|
||||
#include <linux/compat.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <asm/pci_debug.h>
|
||||
#include <asm/pci_clp.h>
|
||||
#include <asm/compat.h>
|
||||
#include <asm/clp.h>
|
||||
#include <uapi/asm/clp.h>
|
||||
|
||||
static inline void zpci_err_clp(unsigned int rsp, int rc)
|
||||
{
|
||||
@ -27,21 +33,43 @@ static inline void zpci_err_clp(unsigned int rsp, int rc)
|
||||
}
|
||||
|
||||
/*
|
||||
* Call Logical Processor
|
||||
* Retry logic is handled by the caller.
|
||||
* Call Logical Processor with c=1, lps=0 and command 1
|
||||
* to get the bit mask of installed logical processors
|
||||
*/
|
||||
static inline u8 clp_instr(void *data)
|
||||
static inline int clp_get_ilp(unsigned long *ilp)
|
||||
{
|
||||
unsigned long mask;
|
||||
int cc = 3;
|
||||
|
||||
asm volatile (
|
||||
" .insn rrf,0xb9a00000,%[mask],%[cmd],8,0\n"
|
||||
"0: ipm %[cc]\n"
|
||||
" srl %[cc],28\n"
|
||||
"1:\n"
|
||||
EX_TABLE(0b, 1b)
|
||||
: [cc] "+d" (cc), [mask] "=d" (mask) : [cmd] "a" (1)
|
||||
: "cc");
|
||||
*ilp = mask;
|
||||
return cc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Call Logical Processor with c=0, the give constant lps and an lpcb request.
|
||||
*/
|
||||
static inline int clp_req(void *data, unsigned int lps)
|
||||
{
|
||||
struct { u8 _[CLP_BLK_SIZE]; } *req = data;
|
||||
u64 ignored;
|
||||
u8 cc;
|
||||
int cc = 3;
|
||||
|
||||
asm volatile (
|
||||
" .insn rrf,0xb9a00000,%[ign],%[req],0x0,0x2\n"
|
||||
" ipm %[cc]\n"
|
||||
" .insn rrf,0xb9a00000,%[ign],%[req],0,%[lps]\n"
|
||||
"0: ipm %[cc]\n"
|
||||
" srl %[cc],28\n"
|
||||
: [cc] "=d" (cc), [ign] "=d" (ignored), "+m" (*req)
|
||||
: [req] "a" (req)
|
||||
"1:\n"
|
||||
EX_TABLE(0b, 1b)
|
||||
: [cc] "+d" (cc), [ign] "=d" (ignored), "+m" (*req)
|
||||
: [req] "a" (req), [lps] "i" (lps)
|
||||
: "cc");
|
||||
return cc;
|
||||
}
|
||||
@ -90,7 +118,7 @@ static int clp_query_pci_fngrp(struct zpci_dev *zdev, u8 pfgid)
|
||||
rrb->response.hdr.len = sizeof(rrb->response);
|
||||
rrb->request.pfgid = pfgid;
|
||||
|
||||
rc = clp_instr(rrb);
|
||||
rc = clp_req(rrb, CLP_LPS_PCI);
|
||||
if (!rc && rrb->response.hdr.rsp == CLP_RC_OK)
|
||||
clp_store_query_pci_fngrp(zdev, &rrb->response);
|
||||
else {
|
||||
@ -143,7 +171,7 @@ static int clp_query_pci_fn(struct zpci_dev *zdev, u32 fh)
|
||||
rrb->response.hdr.len = sizeof(rrb->response);
|
||||
rrb->request.fh = fh;
|
||||
|
||||
rc = clp_instr(rrb);
|
||||
rc = clp_req(rrb, CLP_LPS_PCI);
|
||||
if (!rc && rrb->response.hdr.rsp == CLP_RC_OK) {
|
||||
rc = clp_store_query_pci_fn(zdev, &rrb->response);
|
||||
if (rc)
|
||||
@ -214,7 +242,7 @@ static int clp_set_pci_fn(u32 *fh, u8 nr_dma_as, u8 command)
|
||||
rrb->request.oc = command;
|
||||
rrb->request.ndas = nr_dma_as;
|
||||
|
||||
rc = clp_instr(rrb);
|
||||
rc = clp_req(rrb, CLP_LPS_PCI);
|
||||
if (rrb->response.hdr.rsp == CLP_RC_SETPCIFN_BUSY) {
|
||||
retries--;
|
||||
if (retries < 0)
|
||||
@ -280,7 +308,7 @@ static int clp_list_pci(struct clp_req_rsp_list_pci *rrb,
|
||||
rrb->request.resume_token = resume_token;
|
||||
|
||||
/* Get PCI function handle list */
|
||||
rc = clp_instr(rrb);
|
||||
rc = clp_req(rrb, CLP_LPS_PCI);
|
||||
if (rc || rrb->response.hdr.rsp != CLP_RC_OK) {
|
||||
zpci_err("List PCI FN:\n");
|
||||
zpci_err_clp(rrb->response.hdr.rsp, rc);
|
||||
@ -391,3 +419,198 @@ int clp_rescan_pci_devices_simple(void)
|
||||
clp_free_block(rrb);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int clp_base_slpc(struct clp_req *req, struct clp_req_rsp_slpc *lpcb)
|
||||
{
|
||||
unsigned long limit = PAGE_SIZE - sizeof(lpcb->request);
|
||||
|
||||
if (lpcb->request.hdr.len != sizeof(lpcb->request) ||
|
||||
lpcb->response.hdr.len > limit)
|
||||
return -EINVAL;
|
||||
return clp_req(lpcb, CLP_LPS_BASE) ? -EOPNOTSUPP : 0;
|
||||
}
|
||||
|
||||
static int clp_base_command(struct clp_req *req, struct clp_req_hdr *lpcb)
|
||||
{
|
||||
switch (lpcb->cmd) {
|
||||
case 0x0001: /* store logical-processor characteristics */
|
||||
return clp_base_slpc(req, (void *) lpcb);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int clp_pci_slpc(struct clp_req *req, struct clp_req_rsp_slpc *lpcb)
|
||||
{
|
||||
unsigned long limit = PAGE_SIZE - sizeof(lpcb->request);
|
||||
|
||||
if (lpcb->request.hdr.len != sizeof(lpcb->request) ||
|
||||
lpcb->response.hdr.len > limit)
|
||||
return -EINVAL;
|
||||
return clp_req(lpcb, CLP_LPS_PCI) ? -EOPNOTSUPP : 0;
|
||||
}
|
||||
|
||||
static int clp_pci_list(struct clp_req *req, struct clp_req_rsp_list_pci *lpcb)
|
||||
{
|
||||
unsigned long limit = PAGE_SIZE - sizeof(lpcb->request);
|
||||
|
||||
if (lpcb->request.hdr.len != sizeof(lpcb->request) ||
|
||||
lpcb->response.hdr.len > limit)
|
||||
return -EINVAL;
|
||||
if (lpcb->request.reserved2 != 0)
|
||||
return -EINVAL;
|
||||
return clp_req(lpcb, CLP_LPS_PCI) ? -EOPNOTSUPP : 0;
|
||||
}
|
||||
|
||||
static int clp_pci_query(struct clp_req *req,
|
||||
struct clp_req_rsp_query_pci *lpcb)
|
||||
{
|
||||
unsigned long limit = PAGE_SIZE - sizeof(lpcb->request);
|
||||
|
||||
if (lpcb->request.hdr.len != sizeof(lpcb->request) ||
|
||||
lpcb->response.hdr.len > limit)
|
||||
return -EINVAL;
|
||||
if (lpcb->request.reserved2 != 0 || lpcb->request.reserved3 != 0)
|
||||
return -EINVAL;
|
||||
return clp_req(lpcb, CLP_LPS_PCI) ? -EOPNOTSUPP : 0;
|
||||
}
|
||||
|
||||
static int clp_pci_query_grp(struct clp_req *req,
|
||||
struct clp_req_rsp_query_pci_grp *lpcb)
|
||||
{
|
||||
unsigned long limit = PAGE_SIZE - sizeof(lpcb->request);
|
||||
|
||||
if (lpcb->request.hdr.len != sizeof(lpcb->request) ||
|
||||
lpcb->response.hdr.len > limit)
|
||||
return -EINVAL;
|
||||
if (lpcb->request.reserved2 != 0 || lpcb->request.reserved3 != 0 ||
|
||||
lpcb->request.reserved4 != 0)
|
||||
return -EINVAL;
|
||||
return clp_req(lpcb, CLP_LPS_PCI) ? -EOPNOTSUPP : 0;
|
||||
}
|
||||
|
||||
static int clp_pci_command(struct clp_req *req, struct clp_req_hdr *lpcb)
|
||||
{
|
||||
switch (lpcb->cmd) {
|
||||
case 0x0001: /* store logical-processor characteristics */
|
||||
return clp_pci_slpc(req, (void *) lpcb);
|
||||
case 0x0002: /* list PCI functions */
|
||||
return clp_pci_list(req, (void *) lpcb);
|
||||
case 0x0003: /* query PCI function */
|
||||
return clp_pci_query(req, (void *) lpcb);
|
||||
case 0x0004: /* query PCI function group */
|
||||
return clp_pci_query_grp(req, (void *) lpcb);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int clp_normal_command(struct clp_req *req)
|
||||
{
|
||||
struct clp_req_hdr *lpcb;
|
||||
void __user *uptr;
|
||||
int rc;
|
||||
|
||||
rc = -EINVAL;
|
||||
if (req->lps != 0 && req->lps != 2)
|
||||
goto out;
|
||||
|
||||
rc = -ENOMEM;
|
||||
lpcb = clp_alloc_block(GFP_KERNEL);
|
||||
if (!lpcb)
|
||||
goto out;
|
||||
|
||||
rc = -EFAULT;
|
||||
uptr = (void __force __user *)(unsigned long) req->data_p;
|
||||
if (copy_from_user(lpcb, uptr, PAGE_SIZE) != 0)
|
||||
goto out_free;
|
||||
|
||||
rc = -EINVAL;
|
||||
if (lpcb->fmt != 0 || lpcb->reserved1 != 0 || lpcb->reserved2 != 0)
|
||||
goto out_free;
|
||||
|
||||
switch (req->lps) {
|
||||
case 0:
|
||||
rc = clp_base_command(req, lpcb);
|
||||
break;
|
||||
case 2:
|
||||
rc = clp_pci_command(req, lpcb);
|
||||
break;
|
||||
}
|
||||
if (rc)
|
||||
goto out_free;
|
||||
|
||||
rc = -EFAULT;
|
||||
if (copy_to_user(uptr, lpcb, PAGE_SIZE) != 0)
|
||||
goto out_free;
|
||||
|
||||
rc = 0;
|
||||
|
||||
out_free:
|
||||
clp_free_block(lpcb);
|
||||
out:
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int clp_immediate_command(struct clp_req *req)
|
||||
{
|
||||
void __user *uptr;
|
||||
unsigned long ilp;
|
||||
int exists;
|
||||
|
||||
if (req->cmd > 1 || clp_get_ilp(&ilp) != 0)
|
||||
return -EINVAL;
|
||||
|
||||
uptr = (void __force __user *)(unsigned long) req->data_p;
|
||||
if (req->cmd == 0) {
|
||||
/* Command code 0: test for a specific processor */
|
||||
exists = test_bit_inv(req->lps, &ilp);
|
||||
return put_user(exists, (int __user *) uptr);
|
||||
}
|
||||
/* Command code 1: return bit mask of installed processors */
|
||||
return put_user(ilp, (unsigned long __user *) uptr);
|
||||
}
|
||||
|
||||
static long clp_misc_ioctl(struct file *filp, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct clp_req req;
|
||||
void __user *argp;
|
||||
|
||||
if (cmd != CLP_SYNC)
|
||||
return -EINVAL;
|
||||
|
||||
argp = is_compat_task() ? compat_ptr(arg) : (void __user *) arg;
|
||||
if (copy_from_user(&req, argp, sizeof(req)))
|
||||
return -EFAULT;
|
||||
if (req.r != 0)
|
||||
return -EINVAL;
|
||||
return req.c ? clp_immediate_command(&req) : clp_normal_command(&req);
|
||||
}
|
||||
|
||||
static int clp_misc_release(struct inode *inode, struct file *filp)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations clp_misc_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = nonseekable_open,
|
||||
.release = clp_misc_release,
|
||||
.unlocked_ioctl = clp_misc_ioctl,
|
||||
.compat_ioctl = clp_misc_ioctl,
|
||||
.llseek = no_llseek,
|
||||
};
|
||||
|
||||
static struct miscdevice clp_misc_device = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "clp",
|
||||
.fops = &clp_misc_fops,
|
||||
};
|
||||
|
||||
static int __init clp_misc_init(void)
|
||||
{
|
||||
return misc_register(&clp_misc_device);
|
||||
}
|
||||
|
||||
device_initcall(clp_misc_init);
|
||||
|
Loading…
Reference in New Issue
Block a user