#ifndef _IPXE_FCELS_H
#define _IPXE_FCELS_H

/**
 * @file
 *
 * Fibre Channel Extended Link Services
 *
 */

FILE_LICENCE ( GPL2_OR_LATER );

#include <stdint.h>
#include <ipxe/fc.h>
#include <ipxe/tables.h>
#include <ipxe/refcnt.h>
#include <ipxe/list.h>
#include <ipxe/process.h>
#include <ipxe/interface.h>

/** Fibre Channel ELS frame common parameters */
struct fc_els_frame_common {
	/** ELS command code */
	uint8_t command;
	/** Reserved */
	uint8_t reserved[3];
} __attribute__ (( packed ));

/** Fibre Channel ELS command codes */
enum fc_els_command_code {
	FC_ELS_LS_RJT = 0x01,		/**< Link Service Reject */
	FC_ELS_LS_ACC = 0x02,		/**< Link Service Accept */
	FC_ELS_PLOGI = 0x03,		/**< Port Login */
	FC_ELS_FLOGI = 0x04,		/**< Fabric Login */
	FC_ELS_LOGO = 0x05,		/**< Logout */
	FC_ELS_RTV = 0x0e,		/**< Read Timeout Value */
	FC_ELS_ECHO = 0x10,		/**< Echo */
	FC_ELS_PRLI = 0x20,		/**< Process Login */
	FC_ELS_PRLO = 0x21,		/**< Process Logout */
};

/** A Fibre Channel LS_RJT frame */
struct fc_ls_rjt_frame {
	/** ELS command code */
	uint8_t command;
	/** Reserved */
	uint8_t reserved[4];
	/** Reason code */
	uint8_t reason;
	/** Reason code explanation */
	uint8_t explanation;
	/** Vendor unique */
	uint8_t vendor;
} __attribute__ (( packed ));

/** Fibre Channel ELS rejection reason codes */
enum fc_els_reject_reason {
	/** Invalid ELS command code */
	FC_ELS_RJT_INVALID_COMMAND = 0x01,
	/** Logical error */
	FC_ELS_RJT_ILLOGICAL = 0x03,
	/** Logical busy */
	FC_ELS_RJT_BUSY = 0x05,
	/** Protocol error */
	FC_ELS_RJT_PROTOCOL = 0x07,
	/** Unable to perform command request */
	FC_ELS_RJT_UNABLE = 0x09,
	/** Command not supported */
	FC_ELS_RJT_UNSUPPORTED = 0x0b,
	/** Command already in progress */
	FC_ELS_RJT_IN_PROGRESS = 0x0e,
};

/** Fibre Channel "common" service parameters */
struct fc_login_common {
	/** Login version */
	uint16_t version;
	/** Buffer-to-buffer credit */
	uint16_t credit;
	/** Flags */
	uint16_t flags;
	/** Receive size */
	uint16_t mtu;
	/** "Common"?! */
	union {
		struct {
			/** Maximum number of concurrent sequences */
			uint16_t max_seq;
			/** Relative offset by info category */
			uint16_t rel_offs;
		} plogi;
		struct {
			/** Resource allocation timeout value */
			uint32_t r_a_tov;
		} flogi;
	} u;
	/** Error detection timeout value */
	uint32_t e_d_tov;
} __attribute__ (( packed ));

/** Fibre Channel default login version */
#define FC_LOGIN_VERSION 0x2020

/** Fibre Channel default buffer-to-buffer credit */
#define FC_LOGIN_DEFAULT_B2B 10

/** Continuously increasing relative offset */
#define FC_LOGIN_CONTINUOUS_OFFSET 0x8000

/** Clean address */
#define FC_LOGIN_CLEAN 0x8000

/** Multiple N_Port_ID support */
#define FC_LOGIN_MULTI_N 0x8000

/** Random relative offset */
#define FC_LOGIN_RANDOM_OFFSET 0x4000

/** Virtual fabrics */
#define FC_LOGIN_VIRTUAL 0x4000

/** Vendor version level */
#define FC_LOGIN_VENDOR 0x2000

/** Multiple N_Port_ID support */
#define FC_LOGIN_MULTI_F 0x2000

/** Forwarder port */
#define FC_LOGIN_F_PORT 0x1000

/** Alternative credit management */
#define FC_LOGIN_ALT_CREDIT 0x0800

/** Name server session started */
#define FC_LOGIN_NSS_STARTED 0x0800

/** Begin name server session */
#define FC_LOGIN_NSS_BEGIN 0x0400

/** 1ns error detection timer resolution */
#define FC_LOGIN_HIRES_E_D_TOV 0x0400

/** Broadcast supported */
#define FC_LOGIN_BROADCAST 0x0100

/** Query buffer conditions */
#define FC_LOGIN_QUERY_BUF 0x0040

/** Security */
#define FC_LOGIN_SECURITY 0x0020

/** Clock sync primitive capable */
#define FC_LOGIN_CLOCK_SYNC 0x0010

/** Short R_T timeout */
#define FC_LOGIN_SHORT_R_T_TOV 0x0008

/** Dynamic half duplex */
#define FC_LOGIN_DHD 0x0004

/** Continuously increasing sequence count */
#define FC_LOGIN_CONTINUOUS_SEQ 0x0002

/** Payload */
#define FC_LOGIN_PAYLOAD 0x0001

/** Fibre Channel default MTU */
#define FC_LOGIN_DEFAULT_MTU 1452

/** Default maximum number of concurrent sequences */
#define FC_LOGIN_DEFAULT_MAX_SEQ 255

/** Default relative offset by info category */
#define FC_LOGIN_DEFAULT_REL_OFFS 0x1f

/** Default E_D timeout value */
#define FC_LOGIN_DEFAULT_E_D_TOV 2000

/** Fibre Channel class-specific login parameters */
struct fc_login_class {
	/** Flags */
	uint16_t flags;
	/** Initiator flags */
	uint16_t init_flags;
	/** Recipient flags */
	uint16_t recip_flags;
	/** Receive data field size */
	uint16_t mtu;
	/** Maximum number of concurrent sequences */
	uint16_t max_seq;
	/** End-to-end credit */
	uint16_t credit;
	/** Reserved */
	uint8_t reserved0;
	/** Maximum number of open sequences per exchange */
	uint8_t max_seq_per_xchg;
	/** Reserved */
	uint8_t reserved1[2];
} __attribute__ (( packed ));

/** Class valid */
#define FC_LOGIN_CLASS_VALID 0x8000

/** Sequential delivery requested */
#define FC_LOGIN_CLASS_SEQUENTIAL 0x0800

/** A Fibre Channel FLOGI/PLOGI frame */
struct fc_login_frame {
	/** ELS command code */
	uint8_t command;
	/** Reserved */
	uint8_t reserved[3];
	/** Common service parameters */
	struct fc_login_common common;
	/** Port name */
	struct fc_name port_wwn;
	/** Node name */
	struct fc_name node_wwn;
	/** Class 1 service parameters */
	struct fc_login_class class1;
	/** Class 2 service parameters */
	struct fc_login_class class2;
	/** Class 3 service parameters */
	struct fc_login_class class3;
	/** Class 4 service parameters */
	struct fc_login_class class4;
	/** Vendor version level */
	uint8_t vendor_version[16];
} __attribute__ (( packed ));

/** A Fibre Channel LOGO request frame */
struct fc_logout_request_frame {
	/** ELS command code */
	uint8_t command;
	/** Reserved */
	uint8_t reserved[4];
	/** Port ID */
	struct fc_port_id port_id;
	/** Port name */
	struct fc_name port_wwn;
} __attribute__ (( packed ));

/** A Fibre Channel LOGO response frame */
struct fc_logout_response_frame {
	/** ELS command code */
	uint8_t command;
	/** Reserved */
	uint8_t reserved[3];
} __attribute__ (( packed ));

/** A Fibre Channel PRLI service parameter page */
struct fc_prli_page {
	/** Type code */
	uint8_t type;
	/** Type code extension */
	uint8_t type_ext;
	/** Flags and response code */
	uint16_t flags;
	/** Reserved */
	uint32_t reserved[2];
} __attribute__ (( packed ));

/** Establish image pair */
#define FC_PRLI_ESTABLISH 0x2000

/** Response code mask */
#define FC_PRLI_RESPONSE_MASK 0x0f00

/** Request was executed successfully */
#define FC_PRLI_RESPONSE_SUCCESS 0x0100

/** A Fibre Channel PRLI frame */
struct fc_prli_frame {
	/** ELS command code */
	uint8_t command;
	/** Page length */
	uint8_t page_len;
	/** Payload length */
	uint16_t len;
	/** Service parameter page */
	struct fc_prli_page page;
} __attribute__ (( packed ));

/** A Fibre Channel RTV request frame */
struct fc_rtv_request_frame {
	/** ELS command code */
	uint8_t command;
	/** Reserved */
	uint8_t reserved[3];
} __attribute__ (( packed ));

/** A Fibre Channel RTV response frame */
struct fc_rtv_response_frame {
	/** ELS command code */
	uint8_t command;
	/** Reserved */
	uint8_t reserved0[3];
	/** Resource allocation timeout value */
	uint32_t r_a_tov;
	/** Error detection timeout value */
	uint32_t e_d_tov;
	/** Timeout qualifier */
	uint16_t flags;
	/** Reserved */
	uint16_t reserved1;
} __attribute__ (( packed ));

/** 1ns error detection timer resolution */
#define FC_RTV_HIRES_E_D_TOV 0x0400

/** Short R_T timeout */
#define FC_RTV_SHORT_R_T_TOV 0x0008

/** A Fibre Channel ECHO frame */
struct fc_echo_frame_header {
	/** ELS command code */
	uint8_t command;
	/** Reserved */
	uint8_t reserved[3];
} __attribute__ (( packed ));

/** A Fibre Channel extended link services transaction */
struct fc_els {
	/** Reference count */
	struct refcnt refcnt;
	/** Job control interface */
	struct interface job;
	/** Fibre Channel exchange */
	struct interface xchg;
	/** Request sending process */
	struct process process;

	/** Fibre Channel port */
	struct fc_port *port;
	/** Local port ID */
	struct fc_port_id port_id;
	/** Peer port ID */
	struct fc_port_id peer_port_id;
	/** ELS handler, if known */
	struct fc_els_handler *handler;
	/** Flags */
	unsigned int flags;
};

/** Fibre Channel extended link services transaction flags */
enum fc_els_flags {
	/** ELS transaction is a request */
	FC_ELS_REQUEST = 0x0001,
};

/** A Fibre Channel extended link services handler */
struct fc_els_handler {
	/** Name */
	const char *name;
	/** Transmit ELS frame
	 *
	 * @v els		Fibre Channel ELS transaction
	 * @ret rc		Return status code
	 */
	int ( * tx ) ( struct fc_els *els );
	/** Receive ELS frame
	 *
	 * @v els		Fibre Channel ELS transaction
	 * @v data		ELS frame
	 * @v len		Length of ELS frame
	 * @ret rc		Return status code
	 */
	int ( * rx ) ( struct fc_els *els, void *data, size_t len );
	/** Detect ELS frame
	 *
	 * @v els		Fibre Channel ELS transaction
	 * @v data		ELS frame
	 * @v len		Length of ELS frame
	 * @ret rc		Return status code
	 */
	int ( * detect ) ( struct fc_els *els, const void *data, size_t len );
};

/** Fibre Channel ELS handler table */
#define FC_ELS_HANDLERS __table ( struct fc_els_handler, "fc_els_handlers" )

/** Declare a Fibre Channel ELS handler */
#define __fc_els_handler __table_entry ( FC_ELS_HANDLERS, 01 )

/** A Fibre Channel ELS PRLI descriptor */
struct fc_els_prli_descriptor {
	/** Upper-layer protocol type */
	unsigned int type;
	/** Service parameter length */
	size_t param_len;
	/** Fibre Channel ELS handler */
	struct fc_els_handler *handler;
};

/** Fibre Channel ELS PRLI descriptor table */
#define FC_ELS_PRLI_DESCRIPTORS \
	__table ( struct fc_els_prli_descriptor, "fc_els_prli_descriptors" )

/** Declare a Fibre Channel ELS PRLI descriptor */
#define __fc_els_prli_descriptor __table_entry ( FC_ELS_PRLI_DESCRIPTORS, 01 )

/**
 * Check if Fibre Channel ELS transaction is a request
 *
 * @v els		Fibre Channel ELS transaction
 * @ret is_request	ELS transaction is a request
 */
static inline int fc_els_is_request ( struct fc_els *els ) {
	return ( els->flags & FC_ELS_REQUEST );
}

/**
 * Calculate ELS command to transmit
 *
 * @v els		Fibre Channel ELS transaction
 * @v request_command	Command for requests
 * @v command		Command to transmit
 */
static inline unsigned int fc_els_tx_command ( struct fc_els *els,
					       unsigned int request_command ) {
	return ( fc_els_is_request ( els ) ? request_command : FC_ELS_LS_ACC );
}

extern int fc_els_tx ( struct fc_els *els, const void *data, size_t len );
extern int fc_els_request ( struct interface *job, struct fc_port *port,
			    struct fc_port_id *peer_port_id,
			    struct fc_els_handler *handler );
extern int fc_els_flogi ( struct interface *parent, struct fc_port *port );
extern int fc_els_plogi ( struct interface *parent, struct fc_port *port,
			  struct fc_port_id *peer_port_id );
extern int fc_els_logo ( struct interface *parent, struct fc_port *port,
			 struct fc_port_id *peer_port_id );
extern int fc_els_prli ( struct interface *parent, struct fc_port *port,
			 struct fc_port_id *peer_port_id, unsigned int type );
extern int fc_els_prli_tx ( struct fc_els *els,
			    struct fc_els_prli_descriptor *descriptor,
			    void *param );
extern int fc_els_prli_rx ( struct fc_els *els,
			    struct fc_els_prli_descriptor *descriptor,
			    void *data, size_t len );
extern int fc_els_prli_detect ( struct fc_els *els __unused,
				struct fc_els_prli_descriptor *descriptor,
				const void *data, size_t len );

#endif /* _IPXE_FCELS_H */
