/* SPDX-License-Identifier: GPL-2.0 */
#ifndef S390_ISM_H
#define S390_ISM_H

#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/pci.h>
#include <net/smc.h>
#include <asm/pci_insn.h>

#define UTIL_STR_LEN	16

/*
 * Do not use the first word of the DMB bits to ensure 8 byte aligned access.
 */
#define ISM_DMB_WORD_OFFSET	1
#define ISM_DMB_BIT_OFFSET	(ISM_DMB_WORD_OFFSET * 32)
#define ISM_NR_DMBS		1920

#define ISM_REG_SBA	0x1
#define ISM_REG_IEQ	0x2
#define ISM_READ_GID	0x3
#define ISM_ADD_VLAN_ID	0x4
#define ISM_DEL_VLAN_ID	0x5
#define ISM_SET_VLAN	0x6
#define ISM_RESET_VLAN	0x7
#define ISM_QUERY_INFO	0x8
#define ISM_QUERY_RGID	0x9
#define ISM_REG_DMB	0xA
#define ISM_UNREG_DMB	0xB
#define ISM_SIGNAL_IEQ	0xE
#define ISM_UNREG_SBA	0x11
#define ISM_UNREG_IEQ	0x12

#define ISM_ERROR	0xFFFF

struct ism_req_hdr {
	u32 cmd;
	u16 : 16;
	u16 len;
};

struct ism_resp_hdr {
	u32 cmd;
	u16 ret;
	u16 len;
};

union ism_reg_sba {
	struct {
		struct ism_req_hdr hdr;
		u64 sba;
	} request;
	struct {
		struct ism_resp_hdr hdr;
	} response;
} __aligned(16);

union ism_reg_ieq {
	struct {
		struct ism_req_hdr hdr;
		u64 ieq;
		u64 len;
	} request;
	struct {
		struct ism_resp_hdr hdr;
	} response;
} __aligned(16);

union ism_read_gid {
	struct {
		struct ism_req_hdr hdr;
	} request;
	struct {
		struct ism_resp_hdr hdr;
		u64 gid;
	} response;
} __aligned(16);

union ism_qi {
	struct {
		struct ism_req_hdr hdr;
	} request;
	struct {
		struct ism_resp_hdr hdr;
		u32 version;
		u32 max_len;
		u64 ism_state;
		u64 my_gid;
		u64 sba;
		u64 ieq;
		u32 ieq_len;
		u32 : 32;
		u32 dmbs_owned;
		u32 dmbs_used;
		u32 vlan_required;
		u32 vlan_nr_ids;
		u16 vlan_id[64];
	} response;
} __aligned(64);

union ism_query_rgid {
	struct {
		struct ism_req_hdr hdr;
		u64 rgid;
		u32 vlan_valid;
		u32 vlan_id;
	} request;
	struct {
		struct ism_resp_hdr hdr;
	} response;
} __aligned(16);

union ism_reg_dmb {
	struct {
		struct ism_req_hdr hdr;
		u64 dmb;
		u32 dmb_len;
		u32 sba_idx;
		u32 vlan_valid;
		u32 vlan_id;
		u64 rgid;
	} request;
	struct {
		struct ism_resp_hdr hdr;
		u64 dmb_tok;
	} response;
} __aligned(32);

union ism_sig_ieq {
	struct {
		struct ism_req_hdr hdr;
		u64 rgid;
		u32 trigger_irq;
		u32 event_code;
		u64 info;
	} request;
	struct {
		struct ism_resp_hdr hdr;
	} response;
} __aligned(32);

union ism_unreg_dmb {
	struct {
		struct ism_req_hdr hdr;
		u64 dmb_tok;
	} request;
	struct {
		struct ism_resp_hdr hdr;
	} response;
} __aligned(16);

union ism_cmd_simple {
	struct {
		struct ism_req_hdr hdr;
	} request;
	struct {
		struct ism_resp_hdr hdr;
	} response;
} __aligned(8);

union ism_set_vlan_id {
	struct {
		struct ism_req_hdr hdr;
		u64 vlan_id;
	} request;
	struct {
		struct ism_resp_hdr hdr;
	} response;
} __aligned(16);

struct ism_eq_header {
	u64 idx;
	u64 ieq_len;
	u64 entry_len;
	u64 : 64;
};

struct ism_eq {
	struct ism_eq_header header;
	struct smcd_event entry[15];
};

struct ism_sba {
	u32 s : 1;	/* summary bit */
	u32 e : 1;	/* event bit */
	u32 : 30;
	u32 dmb_bits[ISM_NR_DMBS / 32];
	u32 reserved[3];
	u16 dmbe_mask[ISM_NR_DMBS];
};

struct ism_dev {
	spinlock_t lock;
	struct pci_dev *pdev;
	struct smcd_dev *smcd;

	struct ism_sba *sba;
	dma_addr_t sba_dma_addr;
	DECLARE_BITMAP(sba_bitmap, ISM_NR_DMBS);

	struct ism_eq *ieq;
	dma_addr_t ieq_dma_addr;

	int ieq_idx;
};

#define ISM_CREATE_REQ(dmb, idx, sf, offset)		\
	((dmb) | (idx) << 24 | (sf) << 23 | (offset))

static inline void __ism_read_cmd(struct ism_dev *ism, void *data,
				  unsigned long offset, unsigned long len)
{
	struct zpci_dev *zdev = to_zpci(ism->pdev);
	u64 req = ZPCI_CREATE_REQ(zdev->fh, 2, 8);

	while (len > 0) {
		__zpci_load(data, req, offset);
		offset += 8;
		data += 8;
		len -= 8;
	}
}

static inline void __ism_write_cmd(struct ism_dev *ism, void *data,
				   unsigned long offset, unsigned long len)
{
	struct zpci_dev *zdev = to_zpci(ism->pdev);
	u64 req = ZPCI_CREATE_REQ(zdev->fh, 2, len);

	if (len)
		__zpci_store_block(data, req, offset);
}

static inline int __ism_move(struct ism_dev *ism, u64 dmb_req, void *data,
			     unsigned int size)
{
	struct zpci_dev *zdev = to_zpci(ism->pdev);
	u64 req = ZPCI_CREATE_REQ(zdev->fh, 0, size);

	return __zpci_store_block(data, req, dmb_req);
}

#endif /* S390_ISM_H */
