/*
 * Copyright (c) 2015-2021 Apple Inc. All rights reserved.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. The rights granted to you under the License
 * may not be used to create, or enable the creation or redistribution of,
 * unlawful or unlicensed copies of an Apple operating system, or to
 * circumvent, violate, or enable the circumvention or violation of, any
 * terms of an Apple operating system software license agreement.
 *
 * Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
 */

#ifndef _SKYWALK_NEXUS_COMMON_H_
#define _SKYWALK_NEXUS_COMMON_H_

#if defined(PRIVATE) || defined(BSD_KERNEL_PRIVATE)
/*
 * Routines common to kernel and userland.  This file is intended to be
 * included by code implementing the nexus controller logic, in particular,
 * the Skywalk kernel and libsyscall code.
 */

#include <skywalk/os_nexus_private.h>
#include <sys/errno.h>

#ifndef KERNEL
#if !defined(LIBSYSCALL_INTERFACE)
#error "LIBSYSCALL_INTERFACE not defined"
#endif /* !LIBSYSCALL_INTERFACE */
#endif /* !KERNEL */

__attribute__((always_inline))
static inline int
__nexus_attr_set(const nexus_attr_t nxa, const nexus_attr_type_t type,
    const uint64_t value)
{
	int err = 0;

	if (nxa == NULL) {
		return EINVAL;
	}

	switch (type) {
	case NEXUS_ATTR_TX_RINGS:
		nxa->nxa_requested |= NXA_REQ_TX_RINGS;
		nxa->nxa_tx_rings = value;
		break;

	case NEXUS_ATTR_RX_RINGS:
		nxa->nxa_requested |= NXA_REQ_RX_RINGS;
		nxa->nxa_rx_rings = value;
		break;

	case NEXUS_ATTR_TX_SLOTS:
		nxa->nxa_requested |= NXA_REQ_TX_SLOTS;
		nxa->nxa_tx_slots = value;
		break;

	case NEXUS_ATTR_RX_SLOTS:
		nxa->nxa_requested |= NXA_REQ_RX_SLOTS;
		nxa->nxa_rx_slots = value;
		break;

	case NEXUS_ATTR_SLOT_BUF_SIZE:
		nxa->nxa_requested |= NXA_REQ_BUF_SIZE;
		nxa->nxa_buf_size = value;
		break;

	case NEXUS_ATTR_ANONYMOUS:
		nxa->nxa_requested |= NXA_REQ_ANONYMOUS;
		nxa->nxa_anonymous = value;
		break;

	case NEXUS_ATTR_PIPES:
		nxa->nxa_requested |= NXA_REQ_PIPES;
		nxa->nxa_pipes = value;
		break;

	case NEXUS_ATTR_EXTENSIONS:
		nxa->nxa_requested |= NXA_REQ_EXTENSIONS;
		nxa->nxa_extensions = value;
		break;

	case NEXUS_ATTR_MHINTS:
		nxa->nxa_requested |= NXA_REQ_MHINTS;
		nxa->nxa_mhints = value;
		break;

	case NEXUS_ATTR_QMAP:
		nxa->nxa_requested |= NXA_REQ_QMAP;
		nxa->nxa_qmap = value;
		break;

	case NEXUS_ATTR_IFINDEX:
#if !defined(LIBSYSCALL_INTERFACE)
		nxa->nxa_requested |= NXA_REQ_IFINDEX;
		nxa->nxa_ifindex = value;
#else /* LIBSYSCALL_INTERFACE */
		err = ENOTSUP;
#endif /* LIBSYSCALL_INTERFACE */
		break;

	case NEXUS_ATTR_USER_CHANNEL:
		nxa->nxa_requested |= NXA_REQ_USER_CHANNEL;
		nxa->nxa_user_channel = value;
		break;

	case NEXUS_ATTR_MAX_FRAGS:
		nxa->nxa_requested |= NXA_REQ_MAX_FRAGS;
		nxa->nxa_max_frags = value;
		break;

	case NEXUS_ATTR_REJECT_ON_CLOSE:
		nxa->nxa_requested |= NXA_REQ_REJECT_ON_CLOSE;
		nxa->nxa_reject_on_close = (value != 0);
		break;

	case NEXUS_ATTR_LARGE_BUF_SIZE:
		nxa->nxa_requested |= NXA_REQ_LARGE_BUF_SIZE;
		nxa->nxa_large_buf_size = value;
		break;

	case NEXUS_ATTR_FLOWADV_MAX:
	case NEXUS_ATTR_STATS_SIZE:
	case NEXUS_ATTR_SLOT_META_SIZE:
	case NEXUS_ATTR_CHECKSUM_OFFLOAD:
	case NEXUS_ATTR_USER_PACKET_POOL:
	case NEXUS_ATTR_ADV_SIZE:
		err = ENOTSUP;
		break;

	default:
		err = EINVAL;
		break;
	}

	return err;
}

__attribute__((always_inline))
static inline int
__nexus_attr_get(const nexus_attr_t nxa, const nexus_attr_type_t type,
    uint64_t *value)
{
	int err = 0;

	if (nxa == NULL || value == NULL) {
		return EINVAL;
	}

	switch (type) {
	case NEXUS_ATTR_TX_RINGS:
		*value = nxa->nxa_tx_rings;
		break;

	case NEXUS_ATTR_RX_RINGS:
		*value = nxa->nxa_rx_rings;
		break;

	case NEXUS_ATTR_TX_SLOTS:
		*value = nxa->nxa_tx_slots;
		break;

	case NEXUS_ATTR_RX_SLOTS:
		*value = nxa->nxa_rx_slots;
		break;

	case NEXUS_ATTR_SLOT_BUF_SIZE:
		*value = nxa->nxa_buf_size;
		break;

	case NEXUS_ATTR_SLOT_META_SIZE:
		*value = nxa->nxa_meta_size;
		break;

	case NEXUS_ATTR_STATS_SIZE:
		*value = nxa->nxa_stats_size;
		break;

	case NEXUS_ATTR_FLOWADV_MAX:
		*value = nxa->nxa_flowadv_max;
		break;

	case NEXUS_ATTR_ANONYMOUS:
		*value = nxa->nxa_anonymous;
		break;

	case NEXUS_ATTR_PIPES:
		*value = nxa->nxa_pipes;
		break;

	case NEXUS_ATTR_EXTENSIONS:
		*value = nxa->nxa_extensions;
		break;

	case NEXUS_ATTR_MHINTS:
		*value = nxa->nxa_mhints;
		break;

	case NEXUS_ATTR_IFINDEX:
		*value = nxa->nxa_ifindex;
		break;

	case NEXUS_ATTR_QMAP:
		*value = nxa->nxa_qmap;
		break;

	case NEXUS_ATTR_CHECKSUM_OFFLOAD:
		*value = nxa->nxa_checksum_offload;
		break;

	case NEXUS_ATTR_USER_PACKET_POOL:
		*value = nxa->nxa_user_packet_pool;
		break;

	case NEXUS_ATTR_ADV_SIZE:
		*value = nxa->nxa_nexusadv_size;
		break;

	case NEXUS_ATTR_USER_CHANNEL:
		*value = nxa->nxa_user_channel;
		break;

	case NEXUS_ATTR_MAX_FRAGS:
		*value = nxa->nxa_max_frags;
		break;

	case NEXUS_ATTR_REJECT_ON_CLOSE:
		*value = nxa->nxa_reject_on_close;
		break;

	case NEXUS_ATTR_LARGE_BUF_SIZE:
		*value = nxa->nxa_large_buf_size;
		break;

	default:
		err = EINVAL;
		break;
	}

	return err;
}

__attribute__((always_inline))
static inline void
__nexus_attr_from_params(nexus_attr_t nxa, const struct nxprov_params *p)
{
	bzero(nxa, sizeof(*nxa));
	nxa->nxa_tx_rings = p->nxp_tx_rings;
	nxa->nxa_rx_rings = p->nxp_rx_rings;
	nxa->nxa_tx_slots = p->nxp_tx_slots;
	nxa->nxa_rx_slots = p->nxp_rx_slots;
	nxa->nxa_buf_size = p->nxp_buf_size;
	nxa->nxa_meta_size = p->nxp_meta_size;
	nxa->nxa_stats_size = p->nxp_stats_size;
	nxa->nxa_flowadv_max = p->nxp_flowadv_max;
	nxa->nxa_anonymous = !!(p->nxp_flags & NXPF_ANONYMOUS);
	nxa->nxa_pipes = p->nxp_pipes;
	nxa->nxa_extensions = p->nxp_extensions;
	nxa->nxa_mhints = p->nxp_mhints;
	nxa->nxa_ifindex = p->nxp_ifindex;
	nxa->nxa_qmap = p->nxp_qmap;
	nxa->nxa_checksum_offload = (p->nxp_capabilities &
	    NXPCAP_CHECKSUM_PARTIAL) ? 1 : 0;
	nxa->nxa_user_packet_pool = (p->nxp_capabilities &
	    NXPCAP_USER_PACKET_POOL) ? 1 : 0;
	nxa->nxa_nexusadv_size = p->nxp_nexusadv_size;
	nxa->nxa_user_channel = !!(p->nxp_flags & NXPF_USER_CHANNEL);
	nxa->nxa_max_frags = p->nxp_max_frags;
	nxa->nxa_reject_on_close = (p->nxp_reject_on_close != 0);
	nxa->nxa_large_buf_size = p->nxp_large_buf_size;
}

__attribute__((always_inline))
static inline int
__nexus_provider_reg_prepare(struct nxprov_reg *reg, const uint8_t *__null_terminated name,
    const nexus_type_t type, const nexus_attr_t nxa)
{
	struct nxprov_params *p = &reg->nxpreg_params;
	int err = 0;

	bzero(reg, sizeof(*reg));
	reg->nxpreg_version = NXPROV_REG_CURRENT_VERSION;
	p->nxp_namelen = (uint32_t)strlcpy((char *)p->nxp_name,
	    (const char *__null_terminated)name, sizeof(nexus_name_t));
	if (p->nxp_namelen == 0) {
		err = EINVAL;
		goto done;
	}
	p->nxp_type = type;
	if (nxa != NULL) {
		if (nxa->nxa_requested & NXA_REQ_TX_RINGS) {
			reg->nxpreg_requested |= NXPREQ_TX_RINGS;
			p->nxp_tx_rings = (uint32_t)nxa->nxa_tx_rings;
		}
		if (nxa->nxa_requested & NXA_REQ_RX_RINGS) {
			reg->nxpreg_requested |= NXPREQ_RX_RINGS;
			p->nxp_rx_rings = (uint32_t)nxa->nxa_rx_rings;
		}
		if (nxa->nxa_requested & NXA_REQ_TX_SLOTS) {
			reg->nxpreg_requested |= NXPREQ_TX_SLOTS;
			p->nxp_tx_slots = (uint32_t)nxa->nxa_tx_slots;
		}
		if (nxa->nxa_requested & NXA_REQ_RX_SLOTS) {
			reg->nxpreg_requested |= NXPREQ_RX_SLOTS;
			p->nxp_rx_slots = (uint32_t)nxa->nxa_rx_slots;
		}
		if (nxa->nxa_requested & NXA_REQ_BUF_SIZE) {
			reg->nxpreg_requested |= NXPREQ_BUF_SIZE;
			p->nxp_buf_size = (uint32_t)nxa->nxa_buf_size;
		}
		if (nxa->nxa_requested & NXA_REQ_ANONYMOUS) {
			reg->nxpreg_requested |= NXPREQ_ANONYMOUS;
			if (nxa->nxa_anonymous != 0) {
				p->nxp_flags |= NXPF_ANONYMOUS;
			} else {
				p->nxp_flags &= (uint32_t)~NXPF_ANONYMOUS;
			}
		}
		if (nxa->nxa_requested & NXA_REQ_PIPES) {
			reg->nxpreg_requested |= NXPREQ_PIPES;
			p->nxp_pipes = (uint32_t)nxa->nxa_pipes;
		}
		if (nxa->nxa_requested & NXA_REQ_EXTENSIONS) {
			reg->nxpreg_requested |= NXPREQ_EXTENSIONS;
			p->nxp_extensions = (uint32_t)nxa->nxa_extensions;
		}
		if (nxa->nxa_requested & NXA_REQ_MHINTS) {
			reg->nxpreg_requested |= NXPREQ_MHINTS;
			p->nxp_mhints = (uint32_t)nxa->nxa_mhints;
		}
		if (nxa->nxa_requested & NXA_REQ_QMAP) {
			if (type != NEXUS_TYPE_NET_IF) {
				err = EINVAL;
				goto done;
			}
			if ((nxa->nxa_qmap == NEXUS_QMAP_TYPE_WMM) &&
			    (reg->nxpreg_params.nxp_tx_rings !=
			    NEXUS_NUM_WMM_QUEUES)) {
				err = EINVAL;
				goto done;
			}
			reg->nxpreg_requested |= NXPREQ_QMAP;
			p->nxp_qmap = (uint32_t)nxa->nxa_qmap;
		}
		if (nxa->nxa_requested & NXA_REQ_IFINDEX) {
			if (type != NEXUS_TYPE_NET_IF) {
				err = EINVAL;
				goto done;
			}
			reg->nxpreg_requested |= NXPREQ_IFINDEX;
			p->nxp_ifindex = (uint32_t)nxa->nxa_ifindex;
		}
		if (nxa->nxa_requested & NXA_REQ_USER_CHANNEL) {
			reg->nxpreg_requested |= NXPREQ_USER_CHANNEL;
			if (nxa->nxa_user_channel != 0) {
				p->nxp_flags |= NXPF_USER_CHANNEL;
			} else {
				p->nxp_flags &= (uint32_t)~NXPF_USER_CHANNEL;
			}
		}
		if (nxa->nxa_requested & NXA_REQ_MAX_FRAGS) {
			if ((type != NEXUS_TYPE_NET_IF) &&
			    (type != NEXUS_TYPE_FLOW_SWITCH)) {
				err = EINVAL;
				goto done;
			}
			reg->nxpreg_requested |= NXPREQ_MAX_FRAGS;
			p->nxp_max_frags = (uint32_t)nxa->nxa_max_frags;
		}
		if (nxa->nxa_requested & NXA_REQ_REJECT_ON_CLOSE) {
			if (type != NEXUS_TYPE_USER_PIPE) {
				err = EINVAL;
				goto done;
			}
			reg->nxpreg_requested |= NXPREQ_REJECT_ON_CLOSE;
			p->nxp_reject_on_close =
			    (nxa->nxa_reject_on_close != 0);
		}
		if (nxa->nxa_requested & NXA_REQ_LARGE_BUF_SIZE) {
			reg->nxpreg_requested |= NXPREQ_LARGE_BUF_SIZE;
			p->nxp_large_buf_size =
			    (uint32_t)nxa->nxa_large_buf_size;
		}
	}
done:
	return err;
}

__attribute__((always_inline))
static inline void
__nexus_bind_req_prepare(struct nx_bind_req *nbr, const uuid_t nx_uuid,
    const nexus_port_t port, const pid_t pid, const uuid_t exec_uuid,
    const void *key, const uint32_t key_len, const uint32_t bind_flags)
{
	uuid_t uuid_null = {0};

	bzero(nbr, sizeof(*nbr));
	if (nx_uuid != NULL) {
		bcopy(nx_uuid, nbr->nb_nx_uuid, sizeof(uuid_t));
	}
	if (exec_uuid != NULL && memcmp(exec_uuid, uuid_null, sizeof(uuid_t))) {
		bcopy(exec_uuid, nbr->nb_exec_uuid, sizeof(uuid_t));
	}
	nbr->nb_port = port;
	nbr->nb_pid = pid;
	if (bind_flags & NEXUS_BIND_PID) {
		nbr->nb_flags |= NBR_MATCH_PID;
	}
	if (bind_flags & NEXUS_BIND_EXEC_UUID) {
		nbr->nb_flags |= NBR_MATCH_EXEC_UUID;
	}
	if (bind_flags & NEXUS_BIND_KEY) {
		nbr->nb_flags |= NBR_MATCH_KEY;
		nbr->nb_key = (user_addr_t)key;
		nbr->nb_key_len = key_len;
	}
}

__attribute__((always_inline))
static inline void
__nexus_unbind_req_prepare(struct nx_unbind_req *nbu, const uuid_t nx_uuid,
    const nexus_port_t port)
{
	bzero(nbu, sizeof(*nbu));
	if (nx_uuid != NULL) {
		bcopy(nx_uuid, nbu->nu_nx_uuid, sizeof(uuid_t));
	}
	nbu->nu_port = port;
}

__attribute__((always_inline))
static inline void
__nexus_config_req_prepare(struct nx_cfg_req *ncr, const uuid_t nx_uuid,
    const nxcfg_cmd_t cmd, const void *arg, const size_t arg_len)
{
	VERIFY(arg_len <= UINT32_MAX);
	bzero(ncr, sizeof(*ncr));
	if (nx_uuid != NULL) {
		bcopy(nx_uuid, ncr->nc_nx_uuid, sizeof(uuid_t));
	}
	ncr->nc_cmd = cmd;
	ncr->nc_req_len = (uint32_t)arg_len;
	ncr->nc_req = (user_addr_t)arg;
}

#endif /* PRIVATE || BSD_KERNEL_PRIVATE */
#endif /* !_SKYWALK_NEXUS_COMMON_H_ */
