162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
262306a36Sopenharmony_ci/* Copyright (c) 2021, Microsoft Corporation. */
362306a36Sopenharmony_ci
462306a36Sopenharmony_ci#include <linux/delay.h>
562306a36Sopenharmony_ci#include <linux/device.h>
662306a36Sopenharmony_ci#include <linux/io.h>
762306a36Sopenharmony_ci#include <linux/mm.h>
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <net/mana/shm_channel.h>
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#define PAGE_FRAME_L48_WIDTH_BYTES 6
1262306a36Sopenharmony_ci#define PAGE_FRAME_L48_WIDTH_BITS (PAGE_FRAME_L48_WIDTH_BYTES * 8)
1362306a36Sopenharmony_ci#define PAGE_FRAME_L48_MASK 0x0000FFFFFFFFFFFF
1462306a36Sopenharmony_ci#define PAGE_FRAME_H4_WIDTH_BITS 4
1562306a36Sopenharmony_ci#define VECTOR_MASK 0xFFFF
1662306a36Sopenharmony_ci#define SHMEM_VF_RESET_STATE ((u32)-1)
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#define SMC_MSG_TYPE_ESTABLISH_HWC 1
1962306a36Sopenharmony_ci#define SMC_MSG_TYPE_ESTABLISH_HWC_VERSION 0
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#define SMC_MSG_TYPE_DESTROY_HWC 2
2262306a36Sopenharmony_ci#define SMC_MSG_TYPE_DESTROY_HWC_VERSION 0
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#define SMC_MSG_DIRECTION_REQUEST 0
2562306a36Sopenharmony_ci#define SMC_MSG_DIRECTION_RESPONSE 1
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci/* Structures labeled with "HW DATA" are exchanged with the hardware. All of
2862306a36Sopenharmony_ci * them are naturally aligned and hence don't need __packed.
2962306a36Sopenharmony_ci */
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci/* Shared memory channel protocol header
3262306a36Sopenharmony_ci *
3362306a36Sopenharmony_ci * msg_type: set on request and response; response matches request.
3462306a36Sopenharmony_ci * msg_version: newer PF writes back older response (matching request)
3562306a36Sopenharmony_ci *  older PF acts on latest version known and sets that version in result
3662306a36Sopenharmony_ci *  (less than request).
3762306a36Sopenharmony_ci * direction: 0 for request, VF->PF; 1 for response, PF->VF.
3862306a36Sopenharmony_ci * status: 0 on request,
3962306a36Sopenharmony_ci *   operation result on response (success = 0, failure = 1 or greater).
4062306a36Sopenharmony_ci * reset_vf: If set on either establish or destroy request, indicates perform
4162306a36Sopenharmony_ci *  FLR before/after the operation.
4262306a36Sopenharmony_ci * owner_is_pf: 1 indicates PF owned, 0 indicates VF owned.
4362306a36Sopenharmony_ci */
4462306a36Sopenharmony_ciunion smc_proto_hdr {
4562306a36Sopenharmony_ci	u32 as_uint32;
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	struct {
4862306a36Sopenharmony_ci		u8 msg_type	: 3;
4962306a36Sopenharmony_ci		u8 msg_version	: 3;
5062306a36Sopenharmony_ci		u8 reserved_1	: 1;
5162306a36Sopenharmony_ci		u8 direction	: 1;
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci		u8 status;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci		u8 reserved_2;
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci		u8 reset_vf	: 1;
5862306a36Sopenharmony_ci		u8 reserved_3	: 6;
5962306a36Sopenharmony_ci		u8 owner_is_pf	: 1;
6062306a36Sopenharmony_ci	};
6162306a36Sopenharmony_ci}; /* HW DATA */
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci#define SMC_APERTURE_BITS 256
6462306a36Sopenharmony_ci#define SMC_BASIC_UNIT (sizeof(u32))
6562306a36Sopenharmony_ci#define SMC_APERTURE_DWORDS (SMC_APERTURE_BITS / (SMC_BASIC_UNIT * 8))
6662306a36Sopenharmony_ci#define SMC_LAST_DWORD (SMC_APERTURE_DWORDS - 1)
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_cistatic int mana_smc_poll_register(void __iomem *base, bool reset)
6962306a36Sopenharmony_ci{
7062306a36Sopenharmony_ci	void __iomem *ptr = base + SMC_LAST_DWORD * SMC_BASIC_UNIT;
7162306a36Sopenharmony_ci	u32 last_dword;
7262306a36Sopenharmony_ci	int i;
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci	/* Poll the hardware for the ownership bit. This should be pretty fast,
7562306a36Sopenharmony_ci	 * but let's do it in a loop just in case the hardware or the PF
7662306a36Sopenharmony_ci	 * driver are temporarily busy.
7762306a36Sopenharmony_ci	 */
7862306a36Sopenharmony_ci	for (i = 0; i < 20 * 1000; i++)  {
7962306a36Sopenharmony_ci		last_dword = readl(ptr);
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci		/* shmem reads as 0xFFFFFFFF in the reset case */
8262306a36Sopenharmony_ci		if (reset && last_dword == SHMEM_VF_RESET_STATE)
8362306a36Sopenharmony_ci			return 0;
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci		/* If bit_31 is set, the PF currently owns the SMC. */
8662306a36Sopenharmony_ci		if (!(last_dword & BIT(31)))
8762306a36Sopenharmony_ci			return 0;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci		usleep_range(1000, 2000);
9062306a36Sopenharmony_ci	}
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	return -ETIMEDOUT;
9362306a36Sopenharmony_ci}
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_cistatic int mana_smc_read_response(struct shm_channel *sc, u32 msg_type,
9662306a36Sopenharmony_ci				  u32 msg_version, bool reset_vf)
9762306a36Sopenharmony_ci{
9862306a36Sopenharmony_ci	void __iomem *base = sc->base;
9962306a36Sopenharmony_ci	union smc_proto_hdr hdr;
10062306a36Sopenharmony_ci	int err;
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	/* Wait for PF to respond. */
10362306a36Sopenharmony_ci	err = mana_smc_poll_register(base, reset_vf);
10462306a36Sopenharmony_ci	if (err)
10562306a36Sopenharmony_ci		return err;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	hdr.as_uint32 = readl(base + SMC_LAST_DWORD * SMC_BASIC_UNIT);
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	if (reset_vf && hdr.as_uint32 == SHMEM_VF_RESET_STATE)
11062306a36Sopenharmony_ci		return 0;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	/* Validate protocol fields from the PF driver */
11362306a36Sopenharmony_ci	if (hdr.msg_type != msg_type || hdr.msg_version > msg_version ||
11462306a36Sopenharmony_ci	    hdr.direction != SMC_MSG_DIRECTION_RESPONSE) {
11562306a36Sopenharmony_ci		dev_err(sc->dev, "Wrong SMC response 0x%x, type=%d, ver=%d\n",
11662306a36Sopenharmony_ci			hdr.as_uint32, msg_type, msg_version);
11762306a36Sopenharmony_ci		return -EPROTO;
11862306a36Sopenharmony_ci	}
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	/* Validate the operation result */
12162306a36Sopenharmony_ci	if (hdr.status != 0) {
12262306a36Sopenharmony_ci		dev_err(sc->dev, "SMC operation failed: 0x%x\n", hdr.status);
12362306a36Sopenharmony_ci		return -EPROTO;
12462306a36Sopenharmony_ci	}
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	return 0;
12762306a36Sopenharmony_ci}
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_civoid mana_smc_init(struct shm_channel *sc, struct device *dev,
13062306a36Sopenharmony_ci		   void __iomem *base)
13162306a36Sopenharmony_ci{
13262306a36Sopenharmony_ci	sc->dev = dev;
13362306a36Sopenharmony_ci	sc->base = base;
13462306a36Sopenharmony_ci}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ciint mana_smc_setup_hwc(struct shm_channel *sc, bool reset_vf, u64 eq_addr,
13762306a36Sopenharmony_ci		       u64 cq_addr, u64 rq_addr, u64 sq_addr,
13862306a36Sopenharmony_ci		       u32 eq_msix_index)
13962306a36Sopenharmony_ci{
14062306a36Sopenharmony_ci	union smc_proto_hdr *hdr;
14162306a36Sopenharmony_ci	u16 all_addr_h4bits = 0;
14262306a36Sopenharmony_ci	u16 frame_addr_seq = 0;
14362306a36Sopenharmony_ci	u64 frame_addr = 0;
14462306a36Sopenharmony_ci	u8 shm_buf[32];
14562306a36Sopenharmony_ci	u64 *shmem;
14662306a36Sopenharmony_ci	u32 *dword;
14762306a36Sopenharmony_ci	u8 *ptr;
14862306a36Sopenharmony_ci	int err;
14962306a36Sopenharmony_ci	int i;
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	/* Ensure VF already has possession of shared memory */
15262306a36Sopenharmony_ci	err = mana_smc_poll_register(sc->base, false);
15362306a36Sopenharmony_ci	if (err) {
15462306a36Sopenharmony_ci		dev_err(sc->dev, "Timeout when setting up HWC: %d\n", err);
15562306a36Sopenharmony_ci		return err;
15662306a36Sopenharmony_ci	}
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	if (!PAGE_ALIGNED(eq_addr) || !PAGE_ALIGNED(cq_addr) ||
15962306a36Sopenharmony_ci	    !PAGE_ALIGNED(rq_addr) || !PAGE_ALIGNED(sq_addr))
16062306a36Sopenharmony_ci		return -EINVAL;
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	if ((eq_msix_index & VECTOR_MASK) != eq_msix_index)
16362306a36Sopenharmony_ci		return -EINVAL;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	/* Scheme for packing four addresses and extra info into 256 bits.
16662306a36Sopenharmony_ci	 *
16762306a36Sopenharmony_ci	 * Addresses must be page frame aligned, so only frame address bits
16862306a36Sopenharmony_ci	 * are transferred.
16962306a36Sopenharmony_ci	 *
17062306a36Sopenharmony_ci	 * 52-bit frame addresses are split into the lower 48 bits and upper
17162306a36Sopenharmony_ci	 * 4 bits. Lower 48 bits of 4 address are written sequentially from
17262306a36Sopenharmony_ci	 * the start of the 256-bit shared memory region followed by 16 bits
17362306a36Sopenharmony_ci	 * containing the upper 4 bits of the 4 addresses in sequence.
17462306a36Sopenharmony_ci	 *
17562306a36Sopenharmony_ci	 * A 16 bit EQ vector number fills out the next-to-last 32-bit dword.
17662306a36Sopenharmony_ci	 *
17762306a36Sopenharmony_ci	 * The final 32-bit dword is used for protocol control information as
17862306a36Sopenharmony_ci	 * defined in smc_proto_hdr.
17962306a36Sopenharmony_ci	 */
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	memset(shm_buf, 0, sizeof(shm_buf));
18262306a36Sopenharmony_ci	ptr = shm_buf;
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	/* EQ addr: low 48 bits of frame address */
18562306a36Sopenharmony_ci	shmem = (u64 *)ptr;
18662306a36Sopenharmony_ci	frame_addr = PHYS_PFN(eq_addr);
18762306a36Sopenharmony_ci	*shmem = frame_addr & PAGE_FRAME_L48_MASK;
18862306a36Sopenharmony_ci	all_addr_h4bits |= (frame_addr >> PAGE_FRAME_L48_WIDTH_BITS) <<
18962306a36Sopenharmony_ci		(frame_addr_seq++ * PAGE_FRAME_H4_WIDTH_BITS);
19062306a36Sopenharmony_ci	ptr += PAGE_FRAME_L48_WIDTH_BYTES;
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	/* CQ addr: low 48 bits of frame address */
19362306a36Sopenharmony_ci	shmem = (u64 *)ptr;
19462306a36Sopenharmony_ci	frame_addr = PHYS_PFN(cq_addr);
19562306a36Sopenharmony_ci	*shmem = frame_addr & PAGE_FRAME_L48_MASK;
19662306a36Sopenharmony_ci	all_addr_h4bits |= (frame_addr >> PAGE_FRAME_L48_WIDTH_BITS) <<
19762306a36Sopenharmony_ci		(frame_addr_seq++ * PAGE_FRAME_H4_WIDTH_BITS);
19862306a36Sopenharmony_ci	ptr += PAGE_FRAME_L48_WIDTH_BYTES;
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	/* RQ addr: low 48 bits of frame address */
20162306a36Sopenharmony_ci	shmem = (u64 *)ptr;
20262306a36Sopenharmony_ci	frame_addr = PHYS_PFN(rq_addr);
20362306a36Sopenharmony_ci	*shmem = frame_addr & PAGE_FRAME_L48_MASK;
20462306a36Sopenharmony_ci	all_addr_h4bits |= (frame_addr >> PAGE_FRAME_L48_WIDTH_BITS) <<
20562306a36Sopenharmony_ci		(frame_addr_seq++ * PAGE_FRAME_H4_WIDTH_BITS);
20662306a36Sopenharmony_ci	ptr += PAGE_FRAME_L48_WIDTH_BYTES;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	/* SQ addr: low 48 bits of frame address */
20962306a36Sopenharmony_ci	shmem = (u64 *)ptr;
21062306a36Sopenharmony_ci	frame_addr = PHYS_PFN(sq_addr);
21162306a36Sopenharmony_ci	*shmem = frame_addr & PAGE_FRAME_L48_MASK;
21262306a36Sopenharmony_ci	all_addr_h4bits |= (frame_addr >> PAGE_FRAME_L48_WIDTH_BITS) <<
21362306a36Sopenharmony_ci		(frame_addr_seq++ * PAGE_FRAME_H4_WIDTH_BITS);
21462306a36Sopenharmony_ci	ptr += PAGE_FRAME_L48_WIDTH_BYTES;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	/* High 4 bits of the four frame addresses */
21762306a36Sopenharmony_ci	*((u16 *)ptr) = all_addr_h4bits;
21862306a36Sopenharmony_ci	ptr += sizeof(u16);
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	/* EQ MSIX vector number */
22162306a36Sopenharmony_ci	*((u16 *)ptr) = (u16)eq_msix_index;
22262306a36Sopenharmony_ci	ptr += sizeof(u16);
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	/* 32-bit protocol header in final dword */
22562306a36Sopenharmony_ci	*((u32 *)ptr) = 0;
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	hdr = (union smc_proto_hdr *)ptr;
22862306a36Sopenharmony_ci	hdr->msg_type = SMC_MSG_TYPE_ESTABLISH_HWC;
22962306a36Sopenharmony_ci	hdr->msg_version = SMC_MSG_TYPE_ESTABLISH_HWC_VERSION;
23062306a36Sopenharmony_ci	hdr->direction = SMC_MSG_DIRECTION_REQUEST;
23162306a36Sopenharmony_ci	hdr->reset_vf = reset_vf;
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	/* Write 256-message buffer to shared memory (final 32-bit write
23462306a36Sopenharmony_ci	 * triggers HW to set possession bit to PF).
23562306a36Sopenharmony_ci	 */
23662306a36Sopenharmony_ci	dword = (u32 *)shm_buf;
23762306a36Sopenharmony_ci	for (i = 0; i < SMC_APERTURE_DWORDS; i++)
23862306a36Sopenharmony_ci		writel(*dword++, sc->base + i * SMC_BASIC_UNIT);
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	/* Read shmem response (polling for VF possession) and validate.
24162306a36Sopenharmony_ci	 * For setup, waiting for response on shared memory is not strictly
24262306a36Sopenharmony_ci	 * necessary, since wait occurs later for results to appear in EQE's.
24362306a36Sopenharmony_ci	 */
24462306a36Sopenharmony_ci	err = mana_smc_read_response(sc, SMC_MSG_TYPE_ESTABLISH_HWC,
24562306a36Sopenharmony_ci				     SMC_MSG_TYPE_ESTABLISH_HWC_VERSION,
24662306a36Sopenharmony_ci				     reset_vf);
24762306a36Sopenharmony_ci	if (err) {
24862306a36Sopenharmony_ci		dev_err(sc->dev, "Error when setting up HWC: %d\n", err);
24962306a36Sopenharmony_ci		return err;
25062306a36Sopenharmony_ci	}
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	return 0;
25362306a36Sopenharmony_ci}
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ciint mana_smc_teardown_hwc(struct shm_channel *sc, bool reset_vf)
25662306a36Sopenharmony_ci{
25762306a36Sopenharmony_ci	union smc_proto_hdr hdr = {};
25862306a36Sopenharmony_ci	int err;
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	/* Ensure already has possession of shared memory */
26162306a36Sopenharmony_ci	err = mana_smc_poll_register(sc->base, false);
26262306a36Sopenharmony_ci	if (err) {
26362306a36Sopenharmony_ci		dev_err(sc->dev, "Timeout when tearing down HWC\n");
26462306a36Sopenharmony_ci		return err;
26562306a36Sopenharmony_ci	}
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	/* Set up protocol header for HWC destroy message */
26862306a36Sopenharmony_ci	hdr.msg_type = SMC_MSG_TYPE_DESTROY_HWC;
26962306a36Sopenharmony_ci	hdr.msg_version = SMC_MSG_TYPE_DESTROY_HWC_VERSION;
27062306a36Sopenharmony_ci	hdr.direction = SMC_MSG_DIRECTION_REQUEST;
27162306a36Sopenharmony_ci	hdr.reset_vf = reset_vf;
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	/* Write message in high 32 bits of 256-bit shared memory, causing HW
27462306a36Sopenharmony_ci	 * to set possession bit to PF.
27562306a36Sopenharmony_ci	 */
27662306a36Sopenharmony_ci	writel(hdr.as_uint32, sc->base + SMC_LAST_DWORD * SMC_BASIC_UNIT);
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci	/* Read shmem response (polling for VF possession) and validate.
27962306a36Sopenharmony_ci	 * For teardown, waiting for response is required to ensure hardware
28062306a36Sopenharmony_ci	 * invalidates MST entries before software frees memory.
28162306a36Sopenharmony_ci	 */
28262306a36Sopenharmony_ci	err = mana_smc_read_response(sc, SMC_MSG_TYPE_DESTROY_HWC,
28362306a36Sopenharmony_ci				     SMC_MSG_TYPE_DESTROY_HWC_VERSION,
28462306a36Sopenharmony_ci				     reset_vf);
28562306a36Sopenharmony_ci	if (err) {
28662306a36Sopenharmony_ci		dev_err(sc->dev, "Error when tearing down HWC: %d\n", err);
28762306a36Sopenharmony_ci		return err;
28862306a36Sopenharmony_ci	}
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	return 0;
29162306a36Sopenharmony_ci}
292