@@ -15,6 +15,8 @@ typedef enum i2c_type {
enum nt_port_type_e {
NT_PORT_TYPE_NOT_AVAILABLE = 0, /* The NIM/port type is not available (unknown) */
NT_PORT_TYPE_NOT_RECOGNISED, /* The NIM/port type not recognized */
+ NT_PORT_TYPE_QSFP_PLUS_NOT_PRESENT, /* QSFP type but slot is empty */
+ NT_PORT_TYPE_QSFP_PLUS, /* QSFP type */
};
typedef enum nt_port_type_e nt_port_type_t, *nt_port_type_p;
@@ -51,6 +53,14 @@ typedef struct nim_i2c_ctx {
bool tx_disable;
bool dmi_supp;
+ union {
+ struct {
+ bool rx_only;
+ union {
+ } specific_u;
+ } qsfp;
+
+ } specific_u;
} nim_i2c_ctx_t, *nim_i2c_ctx_p;
struct nim_sensor_group {
@@ -18,6 +18,7 @@ static int _create_nim(adapter_info_t *drv, int port)
int res = 0;
const uint8_t valid_nim_id = 17U;
nim_i2c_ctx_t *nim_ctx;
+ sfp_nim_state_t nim;
nt4ga_link_t *link_info = &drv->nt4ga_link;
assert(port >= 0 && port < NUM_ADAPTER_PORTS_MAX);
@@ -31,11 +32,20 @@ static int _create_nim(adapter_info_t *drv, int port)
*/
nt_os_wait_usec(1000000); /* pause 1.0s */
- res = construct_and_preinit_nim(nim_ctx);
+ res = construct_and_preinit_nim(nim_ctx, NULL);
if (res)
return res;
+ res = nim_state_build(nim_ctx, &nim);
+
+ if (res)
+ return res;
+
+ NT_LOG(DBG, NTHW, "%s: NIM id = %u (%s), br = %u, vendor = '%s', pn = '%s', sn='%s'\n",
+ drv->mp_port_id_str[port], nim_ctx->nim_id, nim_id_to_text(nim_ctx->nim_id), nim.br,
+ nim_ctx->vendor_name, nim_ctx->prod_no, nim_ctx->serial_no);
+
/*
* Does the driver support the NIM module type?
*/
@@ -10,6 +10,7 @@
#include "ntlog.h"
#include "nt_util.h"
#include "ntnic_mod_reg.h"
+#include "qsfp_registers.h"
#include "nim_defines.h"
#define NIM_READ false
@@ -17,6 +18,25 @@
#define NIM_PAGE_SEL_REGISTER 127
#define NIM_I2C_0XA0 0xA0 /* Basic I2C address */
+
+static bool page_addressing(nt_nim_identifier_t id)
+{
+ switch (id) {
+ case NT_NIM_QSFP:
+ case NT_NIM_QSFP_PLUS:
+ return true;
+
+ default:
+ NT_LOG(DBG, NTNIC, "Unknown NIM identifier %d\n", id);
+ return false;
+ }
+}
+
+static nt_nim_identifier_t translate_nimid(const nim_i2c_ctx_t *ctx)
+{
+ return (nt_nim_identifier_t)ctx->nim_id;
+}
+
static int nim_read_write_i2c_data(nim_i2c_ctx_p ctx, bool do_write, uint16_t lin_addr,
uint8_t i2c_addr, uint8_t a_reg_addr, uint8_t seq_cnt,
uint8_t *p_data)
@@ -158,6 +178,13 @@ static int nim_read_write_data_lin(nim_i2c_ctx_p ctx, bool m_page_addressing, ui
return 0;
}
+static int read_data_lin(nim_i2c_ctx_p ctx, uint16_t lin_addr, uint16_t length, void *data)
+{
+ /* Wrapper for using Mutex for QSFP TODO */
+ return nim_read_write_data_lin(ctx, page_addressing(ctx->nim_id), lin_addr, length, data,
+ NIM_READ);
+}
+
static int nim_read_id(nim_i2c_ctx_t *ctx)
{
/* We are only reading the first byte so we don't care about pages here. */
@@ -205,20 +232,301 @@ static int i2c_nim_common_construct(nim_i2c_ctx_p ctx)
return 0;
}
+/*
+ * Read vendor information at a certain address. Any trailing whitespace is
+ * removed and a missing string termination in the NIM data is handled.
+ */
+static int nim_read_vendor_info(nim_i2c_ctx_p ctx, uint16_t addr, uint8_t max_len, char *p_data)
+{
+ const bool pg_addr = page_addressing(ctx->nim_id);
+ int i;
+ /* Subtract "1" from max_len that includes a terminating "0" */
+
+ if (nim_read_write_data_lin(ctx, pg_addr, addr, (uint8_t)(max_len - 1), (uint8_t *)p_data,
+ NIM_READ) != 0) {
+ return -1;
+ }
+
+ /* Terminate at first found white space */
+ for (i = 0; i < max_len - 1; i++) {
+ if (*p_data == ' ' || *p_data == '\n' || *p_data == '\t' || *p_data == '\v' ||
+ *p_data == '\f' || *p_data == '\r') {
+ *p_data = '\0';
+ return 0;
+ }
+
+ p_data++;
+ }
+
+ /*
+ * Add line termination as the very last character, if it was missing in the
+ * NIM data
+ */
+ *p_data = '\0';
+ return 0;
+}
+
+static void qsfp_read_vendor_info(nim_i2c_ctx_t *ctx)
+{
+ nim_read_vendor_info(ctx, QSFP_VENDOR_NAME_LIN_ADDR, sizeof(ctx->vendor_name),
+ ctx->vendor_name);
+ nim_read_vendor_info(ctx, QSFP_VENDOR_PN_LIN_ADDR, sizeof(ctx->prod_no), ctx->prod_no);
+ nim_read_vendor_info(ctx, QSFP_VENDOR_SN_LIN_ADDR, sizeof(ctx->serial_no), ctx->serial_no);
+ nim_read_vendor_info(ctx, QSFP_VENDOR_DATE_LIN_ADDR, sizeof(ctx->date), ctx->date);
+ nim_read_vendor_info(ctx, QSFP_VENDOR_REV_LIN_ADDR, (uint8_t)(sizeof(ctx->rev) - 2),
+ ctx->rev); /*OBS Only two bytes*/
+}
+static int qsfp_nim_state_build(nim_i2c_ctx_t *ctx, sfp_nim_state_t *state)
+{
+ int res = 0; /* unused due to no readings from HW */
+
+ assert(ctx && state);
+ assert(ctx->nim_id != NT_NIM_UNKNOWN && "Nim is not initialized");
+
+ (void)memset(state, 0, sizeof(*state));
+
+ switch (ctx->nim_id) {
+ case 12U:
+ state->br = 10U;/* QSFP: 4 x 1G = 4G */
+ break;
+
+ case 13U:
+ state->br = 103U; /* QSFP+: 4 x 10G = 40G */
+ break;
+
+ default:
+ NT_LOG(INF, NIM, "nim_id = %u is not an QSFP/QSFP+ module\n", ctx->nim_id);
+ res = -1;
+ }
+
+ return res;
+}
+
+int nim_state_build(nim_i2c_ctx_t *ctx, sfp_nim_state_t *state)
+{
+ return qsfp_nim_state_build(ctx, state);
+}
+
const char *nim_id_to_text(uint8_t nim_id)
{
switch (nim_id) {
case 0x0:
return "UNKNOWN";
+ case 0x0C:
+ return "QSFP";
+
+ case 0x0D:
+ return "QSFP+";
+
default:
return "ILLEGAL!";
}
}
-int construct_and_preinit_nim(nim_i2c_ctx_p ctx)
+/*
+ * Disable laser for specific lane or all lanes
+ */
+int nim_qsfp_plus_nim_set_tx_laser_disable(nim_i2c_ctx_p ctx, bool disable, int lane_idx)
+{
+ uint8_t value;
+ uint8_t mask;
+ const bool pg_addr = page_addressing(ctx->nim_id);
+
+ if (lane_idx < 0) /* If no lane is specified then all lanes */
+ mask = QSFP_SOFT_TX_ALL_DISABLE_BITS;
+
+ else
+ mask = (uint8_t)(1U << lane_idx);
+
+ if (nim_read_write_data_lin(ctx, pg_addr, QSFP_CONTROL_STATUS_LIN_ADDR, sizeof(value),
+ &value, NIM_READ) != 0) {
+ return -1;
+ }
+
+ if (disable)
+ value |= mask;
+
+ else
+ value &= (uint8_t)(~mask);
+
+ if (nim_read_write_data_lin(ctx, pg_addr, QSFP_CONTROL_STATUS_LIN_ADDR, sizeof(value),
+ &value, NIM_WRITE) != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Import length info in various units from NIM module data and convert to meters
+ */
+static void nim_import_len_info(nim_i2c_ctx_p ctx, uint8_t *p_nim_len_info, uint16_t *p_nim_units)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(ctx->len_info); i++)
+ if (*(p_nim_len_info + i) == 255) {
+ ctx->len_info[i] = 65535;
+
+ } else {
+ uint32_t len = *(p_nim_len_info + i) * *(p_nim_units + i);
+
+ if (len > 65535)
+ ctx->len_info[i] = 65535;
+
+ else
+ ctx->len_info[i] = (uint16_t)len;
+ }
+}
+
+static int qsfpplus_read_basic_data(nim_i2c_ctx_t *ctx)
+{
+ const bool pg_addr = page_addressing(ctx->nim_id);
+ uint8_t options;
+ uint8_t value;
+ uint8_t nim_len_info[5];
+ uint16_t nim_units[5] = { 1000, 2, 1, 1, 1 }; /* QSFP MSA units in meters */
+ const char *yes_no[2] = { "No", "Yes" };
+ (void)yes_no;
+ NT_LOG(DBG, NTNIC, "Instance %d: NIM id: %s (%d)\n", ctx->instance,
+ nim_id_to_text(ctx->nim_id), ctx->nim_id);
+
+ /* Read DMI options */
+ if (nim_read_write_data_lin(ctx, pg_addr, QSFP_DMI_OPTION_LIN_ADDR, sizeof(options),
+ &options, NIM_READ) != 0) {
+ return -1;
+ }
+
+ ctx->avg_pwr = options & QSFP_DMI_AVG_PWR_BIT;
+ NT_LOG(DBG, NTNIC, "Instance %d: NIM options: (DMI: Yes, AvgPwr: %s)\n", ctx->instance,
+ yes_no[ctx->avg_pwr]);
+
+ qsfp_read_vendor_info(ctx);
+ NT_LOG(DBG, PMD,
+ "Instance %d: NIM info: (Vendor: %s, PN: %s, SN: %s, Date: %s, Rev: %s)\n",
+ ctx->instance, ctx->vendor_name, ctx->prod_no, ctx->serial_no, ctx->date, ctx->rev);
+
+ if (nim_read_write_data_lin(ctx, pg_addr, QSFP_SUP_LEN_INFO_LIN_ADDR, sizeof(nim_len_info),
+ nim_len_info, NIM_READ) != 0) {
+ return -1;
+ }
+
+ /*
+ * Returns supported length information in meters for various fibers as 5 indivi-
+ * dual values: [SM(9um), EBW(50um), MM(50um), MM(62.5um), Copper]
+ * If no length information is available for a certain entry, the returned value
+ * will be zero. This will be the case for SFP modules - EBW entry.
+ * If the MSBit is set the returned value in the lower 31 bits indicates that the
+ * supported length is greater than this.
+ */
+
+ nim_import_len_info(ctx, nim_len_info, nim_units);
+
+ /* Read required power level */
+ if (nim_read_write_data_lin(ctx, pg_addr, QSFP_EXTENDED_IDENTIFIER, sizeof(value), &value,
+ NIM_READ) != 0) {
+ return -1;
+ }
+
+ /*
+ * Get power class according to SFF-8636 Rev 2.7, Table 6-16, Page 43:
+ * If power class >= 5 setHighPower must be called for the module to be fully
+ * functional
+ */
+ if ((value & QSFP_POWER_CLASS_BITS_5_7) == 0) {
+ /* NIM in power class 1 - 4 */
+ ctx->pwr_level_req = (uint8_t)(((value & QSFP_POWER_CLASS_BITS_1_4) >> 6) + 1);
+
+ } else {
+ /* NIM in power class 5 - 7 */
+ ctx->pwr_level_req = (uint8_t)((value & QSFP_POWER_CLASS_BITS_5_7) + 4);
+ }
+
+ return 0;
+}
+
+static void qsfpplus_find_port_params(nim_i2c_ctx_p ctx)
+{
+ uint8_t device_tech;
+ read_data_lin(ctx, QSFP_TRANSMITTER_TYPE_LIN_ADDR, sizeof(device_tech), &device_tech);
+
+ switch (device_tech & 0xF0) {
+ case 0xA0: /* Copper cable unequalized */
+ break;
+
+ case 0xC0: /* Copper cable, near and far end limiting active equalizers */
+ case 0xD0: /* Copper cable, far end limiting active equalizers */
+ case 0xE0: /* Copper cable, near end limiting active equalizers */
+ break;
+
+ default:/* Optical */
+ ctx->port_type = NT_PORT_TYPE_QSFP_PLUS;
+ break;
+ }
+}
+
+static void qsfpplus_set_speed_mask(nim_i2c_ctx_p ctx)
+{
+ ctx->speed_mask = (ctx->lane_idx < 0) ? NT_LINK_SPEED_40G : (NT_LINK_SPEED_10G);
+}
+
+static void qsfpplus_construct(nim_i2c_ctx_p ctx, int8_t lane_idx)
+{
+ assert(lane_idx < 4);
+ ctx->lane_idx = lane_idx;
+ ctx->lane_count = 4;
+}
+
+static int qsfpplus_preinit(nim_i2c_ctx_p ctx, int8_t lane_idx)
+{
+ qsfpplus_construct(ctx, lane_idx);
+ int res = qsfpplus_read_basic_data(ctx);
+
+ if (!res) {
+ qsfpplus_find_port_params(ctx);
+
+ /*
+ * Read if TX_DISABLE has been implemented
+ * For passive optical modules this is required while it for copper and active
+ * optical modules is optional. Under all circumstances register 195.4 will
+ * indicate, if TX_DISABLE has been implemented in register 86.0-3
+ */
+ uint8_t value;
+ read_data_lin(ctx, QSFP_OPTION3_LIN_ADDR, sizeof(value), &value);
+
+ ctx->tx_disable = (value & QSFP_OPTION3_TX_DISABLE_BIT) != 0;
+
+ if (ctx->tx_disable)
+ ctx->options |= (1 << NIM_OPTION_TX_DISABLE);
+
+ /*
+ * Previously - considering AFBR-89BRDZ - code tried to establish if a module was
+ * RxOnly by testing the state of the lasers after reset. Lasers were for this
+ * module default disabled.
+ * However that code did not work for GigaLight, GQS-MPO400-SR4C so it was
+ * decided that this option should not be detected automatically but from PN
+ */
+ ctx->specific_u.qsfp.rx_only = (ctx->options & (1 << NIM_OPTION_RX_ONLY)) != 0;
+ qsfpplus_set_speed_mask(ctx);
+ }
+
+ return res;
+}
+
+int construct_and_preinit_nim(nim_i2c_ctx_p ctx, void *extra)
{
int res = i2c_nim_common_construct(ctx);
+ switch (translate_nimid(ctx)) {
+ case NT_NIM_QSFP_PLUS:
+ qsfpplus_preinit(ctx, extra ? *(int8_t *)extra : (int8_t)-1);
+ break;
+
+ default:
+ res = 1;
+ NT_LOG(ERR, NTHW, "NIM type %s is not supported.\n", nim_id_to_text(ctx->nim_id));
+ }
+
return res;
}
@@ -8,17 +8,29 @@
#include "ntnic_nim.h"
+typedef struct sfp_nim_state {
+ uint8_t br; /* bit rate, units of 100 MBits/sec */
+} sfp_nim_state_t, *sfp_nim_state_p;
+
+/*
+ * Builds an nim state for the port implied by `ctx`, returns zero
+ * if successful, and non-zero otherwise. SFP and QSFP nims are supported
+ */
+int nim_state_build(nim_i2c_ctx_t *ctx, sfp_nim_state_t *state);
+
/*
* Returns a type name such as "SFP/SFP+" for a given NIM type identifier,
* or the string "ILLEGAL!".
*/
const char *nim_id_to_text(uint8_t nim_id);
+int nim_qsfp_plus_nim_set_tx_laser_disable(nim_i2c_ctx_t *ctx, bool disable, int lane_idx);
+
/*
* This function tries to classify NIM based on it's ID and some register reads
* and collects information into ctx structure. The @extra parameter could contain
* the initialization argument for specific type of NIMS.
*/
-int construct_and_preinit_nim(nim_i2c_ctx_p ctx);
+int construct_and_preinit_nim(nim_i2c_ctx_p ctx, void *extra);
#endif /* I2C_NIM_H_ */
@@ -7,6 +7,7 @@
#define NIM_DEFINES_H_
#define NIM_IDENTIFIER_ADDR 0 /* 1 byte */
+#define QSFP_EXTENDED_IDENTIFIER 129
/* I2C addresses */
#define NIM_I2C_0XA0 0xA0 /* Basic I2C address */
@@ -23,6 +24,8 @@ typedef enum {
enum nt_nim_identifier_e {
NT_NIM_UNKNOWN = 0x00, /* Nim type is unknown */
+ NT_NIM_QSFP = 0x0C, /* Nim type = QSFP */
+ NT_NIM_QSFP_PLUS = 0x0D,/* Nim type = QSFP+ */
};
typedef enum nt_nim_identifier_e nt_nim_identifier_t;
new file mode 100644
@@ -0,0 +1,43 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef _QSFP_REGISTERS_H
+#define _QSFP_REGISTERS_H
+
+/*
+ * QSFP Registers
+ */
+#define QSFP_INT_STATUS_RX_LOS_ADDR 3
+#define QSFP_TEMP_LIN_ADDR 22
+#define QSFP_VOLT_LIN_ADDR 26
+#define QSFP_RX_PWR_LIN_ADDR 34 /* uint16_t [0..3] */
+#define QSFP_TX_BIAS_LIN_ADDR 42/* uint16_t [0..3] */
+#define QSFP_TX_PWR_LIN_ADDR 50 /* uint16_t [0..3] */
+
+#define QSFP_CONTROL_STATUS_LIN_ADDR 86
+#define QSFP_SOFT_TX_ALL_DISABLE_BITS 0x0F
+
+#define QSFP_POWER_CLASS_BITS_1_4 0xC0
+#define QSFP_POWER_CLASS_BITS_5_7 0x03
+
+#define QSFP_SUP_LEN_INFO_LIN_ADDR 142 /* 5bytes */
+#define QSFP_TRANSMITTER_TYPE_LIN_ADDR 147 /* 1byte */
+#define QSFP_VENDOR_NAME_LIN_ADDR 148 /* 16bytes */
+#define QSFP_VENDOR_PN_LIN_ADDR 168 /* 16bytes */
+#define QSFP_VENDOR_SN_LIN_ADDR 196 /* 16bytes */
+#define QSFP_VENDOR_DATE_LIN_ADDR 212 /* 8bytes */
+#define QSFP_VENDOR_REV_LIN_ADDR 184 /* 2bytes */
+
+#define QSFP_SPEC_COMPLIANCE_CODES_ADDR 131 /* 8 bytes */
+#define QSFP_EXT_SPEC_COMPLIANCE_CODES_ADDR 192 /* 1 byte */
+
+#define QSFP_OPTION3_LIN_ADDR 195
+#define QSFP_OPTION3_TX_DISABLE_BIT (1 << 4)
+
+#define QSFP_DMI_OPTION_LIN_ADDR 220
+#define QSFP_DMI_AVG_PWR_BIT (1 << 3)
+
+
+#endif /* _QSFP_REGISTERS_H */