/* SPDX-License-Identifier: GPL-2.0-only */
/*
 * CSS definitions
 *
 * Copyright IBM Corp. 2020
 * Author: Pierre Morel <pmorel@linux.ibm.com>
 */

#ifndef _S390X_CSS_H_
#define _S390X_CSS_H_

/* subchannel ID bit 16 must always be one */
#define SCHID_ONE	0x00010000

#define CCW_F_CD	0x80
#define CCW_F_CC	0x40
#define CCW_F_SLI	0x20
#define CCW_F_SKP	0x10
#define CCW_F_PCI	0x08
#define CCW_F_IDA	0x04
#define CCW_F_S		0x02
#define CCW_F_MIDA	0x01

#define CCW_C_NOP	0x03
#define CCW_C_TIC	0x08

struct ccw1 {
	uint8_t code;
	uint8_t flags;
	uint16_t count;
	uint32_t data_address;
} __attribute__ ((aligned(8)));

#define ORB_CTRL_KEY	0xf0000000
#define ORB_CTRL_SPND	0x08000000
#define ORB_CTRL_STR	0x04000000
#define ORB_CTRL_MOD	0x02000000
#define ORB_CTRL_SYNC	0x01000000
#define ORB_CTRL_FMT	0x00800000
#define ORB_CTRL_PFCH	0x00400000
#define ORB_CTRL_ISIC	0x00200000
#define ORB_CTRL_ALCC	0x00100000
#define ORB_CTRL_SSIC	0x00080000
#define ORB_CTRL_CPTC	0x00040000
#define ORB_CTRL_C64	0x00020000
#define ORB_CTRL_I2K	0x00010000
#define ORB_CTRL_LPM	0x0000ff00
#define ORB_CTRL_ILS	0x00000080
#define ORB_CTRL_MIDAW	0x00000040
#define ORB_CTRL_ORBX	0x00000001

#define ORB_LPM_DFLT	0x00008000

struct orb {
	uint32_t intparm;
	uint32_t ctrl;
	uint32_t cpa;
	uint32_t prio;
	uint32_t reserved[4];
} __attribute__ ((aligned(4)));

struct scsw {
#define SCSW_SC_PENDING		0x00000001
#define SCSW_SC_SECONDARY	0x00000002
#define SCSW_SC_PRIMARY		0x00000004
#define SCSW_SC_INTERMEDIATE	0x00000008
#define SCSW_SC_ALERT		0x00000010
	uint32_t ctrl;
	uint32_t ccw_addr;
#define SCSW_DEVS_DEV_END	0x04
#define SCSW_DEVS_SCH_END	0x08
	uint8_t  dev_stat;
#define SCSW_SCHS_PCI	0x80
#define SCSW_SCHS_IL	0x40
	uint8_t  sch_stat;
	uint16_t count;
};

struct pmcw {
	uint32_t intparm;
#define PMCW_DNV	0x0001
#define PMCW_ENABLE	0x0080
#define PMCW_MBUE	0x0010
#define PMCW_DCTME	0x0008
#define PMCW_ISC_MASK	0x3800
#define PMCW_ISC_SHIFT	11
	uint16_t flags;
	uint16_t devnum;
	uint8_t  lpm;
	uint8_t  pnom;
	uint8_t  lpum;
	uint8_t  pim;
	uint16_t mbi;
	uint8_t  pom;
	uint8_t  pam;
	uint8_t  chpid[8];
#define PMCW_MBF1	0x0004
	uint32_t flags2;
};
#define PMCW_CHANNEL_TYPE(pmcw) (pmcw->flags2 >> 21)

struct schib {
	struct pmcw pmcw;
	struct scsw scsw;
	uint64_t mbo;
	uint8_t  md[4];
} __attribute__ ((aligned(4)));
extern struct schib schib;

struct irb {
	struct scsw scsw;
	uint32_t esw[5];
	uint32_t ecw[8];
	uint32_t emw[8];
} __attribute__ ((aligned(4)));

#define CCW_CMD_SENSE_ID	0xe4
#define CSS_SENSEID_COMMON_LEN	8
struct senseid {
	/* common part */
	uint8_t reserved;        /* always 0x'FF' */
	uint16_t cu_type;        /* control unit type */
	uint8_t cu_model;        /* control unit model */
	uint16_t dev_type;       /* device type */
	uint8_t dev_model;       /* device model */
	uint8_t unused;          /* padding byte */
	uint8_t padding[256 - 8]; /* Extended part */
} __attribute__ ((aligned(4))) __attribute__ ((packed));

/* CSS low level access functions */

static inline int ssch(unsigned long schid, struct orb *addr)
{
	register long long reg1 asm("1") = schid;
	int cc;

	asm volatile(
		"	ssch	0(%2)\n"
		"	ipm	%0\n"
		"	srl	%0,28\n"
		: "=d" (cc)
		: "d" (reg1), "a" (addr), "m" (*addr)
		: "cc", "memory");
	return cc;
}

static inline int stsch(unsigned long schid, struct schib *addr)
{
	register unsigned long reg1 asm ("1") = schid;
	uint64_t bogus_cc = 1;
	int cc;

	asm volatile(
		"       tmll    %[bogus_cc],3\n"
		"	stsch	0(%3)\n"
		"	ipm	%0\n"
		"	srl	%0,28"
		: "=d" (cc), "=m" (*addr)
		: "d" (reg1), "a" (addr), [bogus_cc] "d" (bogus_cc)
		: "cc");
	return cc;
}

static inline int msch(unsigned long schid, struct schib *addr)
{
	register unsigned long reg1 asm ("1") = schid;
	int cc;

	asm volatile(
		"	msch	0(%3)\n"
		"	ipm	%0\n"
		"	srl	%0,28"
		: "=d" (cc)
		: "d" (reg1), "m" (*addr), "a" (addr)
		: "cc");
	return cc;
}

static inline int tsch(unsigned long schid, struct irb *addr)
{
	register unsigned long reg1 asm ("1") = schid;
	uint64_t bogus_cc = 2;
	int cc;

	asm volatile(
		"       tmll    %[bogus_cc],3\n"
		"	tsch	0(%3)\n"
		"	ipm	%0\n"
		"	srl	%0,28"
		: "=d" (cc), "=m" (*addr)
		: "d" (reg1), "a" (addr), [bogus_cc] "d" (bogus_cc)
		: "cc");
	return cc;
}

static inline int hsch(unsigned long schid)
{
	register unsigned long reg1 asm("1") = schid;
	int cc;

	asm volatile(
		"	hsch\n"
		"	ipm	%0\n"
		"	srl	%0,28"
		: "=d" (cc)
		: "d" (reg1)
		: "cc");
	return cc;
}

static inline int xsch(unsigned long schid)
{
	register unsigned long reg1 asm("1") = schid;
	int cc;

	asm volatile(
		"	xsch\n"
		"	ipm	%0\n"
		"	srl	%0,28"
		: "=d" (cc)
		: "d" (reg1)
		: "cc");
	return cc;
}

static inline int csch(unsigned long schid)
{
	register unsigned long reg1 asm("1") = schid;
	int cc;

	asm volatile(
		"	csch\n"
		"	ipm	%0\n"
		"	srl	%0,28"
		: "=d" (cc)
		: "d" (reg1)
		: "cc");
	return cc;
}

static inline int rsch(unsigned long schid)
{
	register unsigned long reg1 asm("1") = schid;
	int cc;

	asm volatile(
		"	rsch\n"
		"	ipm	%0\n"
		"	srl	%0,28"
		: "=d" (cc)
		: "d" (reg1)
		: "cc");
	return cc;
}

static inline int rchp(unsigned long chpid)
{
	register unsigned long reg1 asm("1") = chpid;
	uint64_t bogus_cc = 1;
	int cc;

	asm volatile(
		"       tmll    %[bogus_cc],3\n"
		"	rchp\n"
		"	ipm	%0\n"
		"	srl	%0,28"
		: "=d" (cc)
		: "d" (reg1), [bogus_cc] "d" (bogus_cc)
		: "cc");
	return cc;
}

static inline int stcrw(uint32_t *crw)
{
	uint64_t bogus_cc = 1;
	int cc;

	asm volatile(
		"       tmll    %[bogus_cc],3\n"
		"	stcrw	%[crw]\n"
		"	ipm	%[cc]\n"
		"	srl	%[cc],28"
		: [cc] "=d" (cc)
		: [crw] "Q" (*crw), [bogus_cc] "d" (bogus_cc)
		: "cc", "memory");
	return cc;
}

/* Debug functions */
char *dump_pmcw_flags(uint16_t f);
char *dump_scsw_flags(uint32_t f);

void dump_scsw(struct scsw *scsw);
void dump_irb(struct irb *irbp);
void dump_schib(struct schib *sch);
struct ccw1 *dump_ccw(struct ccw1 *cp);
void dump_irb(struct irb *irbp);
void dump_pmcw(struct pmcw *p);
void dump_orb(struct orb *op);

int css_enumerate(void);
#define MAX_ENABLE_RETRIES      5

#define IO_SCH_ISC      3
int css_enable(int schid, int isc);
bool css_enabled(int schid);

/* Library functions */
int start_ccw1_chain(unsigned int sid, struct ccw1 *ccw);
struct ccw1 *ccw_alloc(int code, void *data, int count, unsigned char flags);
void css_irq_io(void);
int css_residual_count(unsigned int schid);

void enable_io_isc(uint8_t isc);
int wait_and_check_io_completion(int schid);

int css_find_installed_chpid(int sid, uint8_t *chpid_out);
int css_generate_crw(int sid);

/*
 * CHSC definitions
 */
struct chsc_header {
	uint16_t len;
	uint16_t code;
};

/* Store Channel Subsystem Characteristics */
struct chsc_scsc {
	struct chsc_header req;
	uint16_t req_fmt;
	uint8_t cssid;
	uint8_t reserved[9];
	struct chsc_header res;
	uint32_t res_fmt;
#define CSSC_EXTENDED_MEASUREMENT_BLOCK 48
	uint64_t general_char[255];
	uint64_t chsc_char[254];
};

extern struct chsc_scsc *chsc_scsc;
#define CHSC_SCSC	0x0010
#define CHSC_SCSC_LEN	0x0010

bool get_chsc_scsc(void);

#define CSS_GENERAL_FEAT_BITLEN	(255 * 64)
#define CSS_CHSC_FEAT_BITLEN	(254 * 64)

#define CHSC_SCSC	0x0010
#define CHSC_SCSC_LEN	0x0010

#define CHSC_ERROR	0x0000
#define CHSC_RSP_OK	0x0001
#define CHSC_RSP_INVAL	0x0002
#define CHSC_RSP_REQERR	0x0003
#define CHSC_RSP_ENOCMD	0x0004
#define CHSC_RSP_NODATA	0x0005
#define CHSC_RSP_SUP31B	0x0006
#define CHSC_RSP_EFRMT	0x0007
#define CHSC_RSP_ECSSID	0x0008
#define CHSC_RSP_ERFRMT	0x0009
#define CHSC_RSP_ESSID	0x000A
#define CHSC_RSP_EBUSY	0x000B
#define CHSC_RSP_MAX	0x000B

static inline int _chsc(void *p)
{
	int cc;

	asm volatile(" .insn   rre,0xb25f0000,%2,0\n"
		     " ipm     %0\n"
		     " srl     %0,28\n"
		     : "=d" (cc), "=m" (p)
		     : "d" (p), "m" (p)
		     : "cc");

	return cc;
}

bool chsc(void *p, uint16_t code, uint16_t len);

#include <bitops.h>
#define css_test_general_feature(bit) test_bit_inv(bit, chsc_scsc->general_char)
#define css_test_chsc_feature(bit) test_bit_inv(bit, chsc_scsc->chsc_char)

#define SCHM_DCTM	1 /* activate Device Connection TiMe */
#define SCHM_MBU	2 /* activate Measurement Block Update */

static inline void schm(void *mbo, unsigned int flags)
{
	register void *__gpr2 asm("2") = mbo;
	register long __gpr1 asm("1") = flags;

	asm("schm" : : "d" (__gpr2), "d" (__gpr1));
}

bool css_enable_mb(int sid, uint64_t mb, uint16_t mbi, uint16_t flg, bool fmt1);
bool css_disable_mb(int schid);

struct measurement_block_format0 {
	uint16_t ssch_rsch_count;
	uint16_t sample_count;
	uint32_t device_connect_time;
	uint32_t function_pending_time;
	uint32_t device_disconnect_time;
	uint32_t cu_queuing_time;
	uint32_t device_active_only_time;
	uint32_t device_busy_time;
	uint32_t initial_cmd_resp_time;
};

struct measurement_block_format1 {
	uint32_t ssch_rsch_count;
	uint32_t sample_count;
	uint32_t device_connect_time;
	uint32_t function_pending_time;
	uint32_t device_disconnect_time;
	uint32_t cu_queuing_time;
	uint32_t device_active_only_time;
	uint32_t device_busy_time;
	uint32_t initial_cmd_resp_time;
	uint32_t irq_delay_time;
	uint32_t irq_prio_delay_time;
};

#endif