phy: Add support for the NC-SI protocol
This introduces support for the NC-SI protocol, modelled as a phy driver
for other ethernet drivers to consume.
NC-SI (Network Controller Sideband Interface) is a protocol to manage a
sideband connection to a proper network interface, for example a BMC
(Baseboard Management Controller) sharing the NIC of the host system.
Probing and configuration occurs by communicating with the "remote" NIC
via NC-SI control frames (Ethernet header 0x88f8).
This implementation is roughly based on the upstream Linux
implementation[0], with a reduced feature set and an emphasis on getting
a link up as fast as possible rather than probing the full possible
topology of the bus.
The current phy model relies on the network being "up", sending NC-SI
command frames via net_send_packet() and receiving them from the
net_loop() loop (added in a following patch).
The ncsi-pkt.h header[1] is copied from the Linux kernel for consistent
field definitions.
[0]: https://github.com/torvalds/linux/tree/master/net/ncsi
[1]: https://github.com/torvalds/linux/blob/master/net/ncsi/ncsi-pkt.h
Signed-off-by: Samuel Mendoza-Jonas <sam@mendozajonas.com>
Reviewed-by: Joel Stanley <joel@jms.id.au>
Acked-by: Joe Hershberger <joe.hershberger@ni.com>
diff --git a/drivers/net/phy/ncsi.c b/drivers/net/phy/ncsi.c
new file mode 100644
index 0000000..adc3ac0
--- /dev/null
+++ b/drivers/net/phy/ncsi.c
@@ -0,0 +1,897 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * NC-SI protocol configuration
+ *
+ * Copyright (C) 2019, IBM Corporation.
+ */
+
+#include <common.h>
+#include <malloc.h>
+#include <phy.h>
+#include <net/ncsi.h>
+#include <net/ncsi-pkt.h>
+#include <asm/unaligned.h>
+
+#define NCSI_PACKAGE_MAX 8
+#define NCSI_CHANNEL_MAX 31
+
+#define NCSI_PACKAGE_SHIFT 5
+#define NCSI_PACKAGE_INDEX(c) (((c) >> NCSI_PACKAGE_SHIFT) & 0x7)
+#define NCSI_RESERVED_CHANNEL 0x1f
+#define NCSI_CHANNEL_INDEX(c) ((c) & ((1 << NCSI_PACKAGE_SHIFT) - 1))
+#define NCSI_TO_CHANNEL(p, c) (((p) << NCSI_PACKAGE_SHIFT) | (c))
+
+#define NCSI_PKT_REVISION 0x01
+
+#define NCSI_CAP_GENERIC_MASK 0x7f
+#define NCSI_CAP_BC_MASK 0x0f
+#define NCSI_CAP_MC_MASK 0x3f
+#define NCSI_CAP_AEN_MASK 0x07
+#define NCSI_CAP_VLAN_MASK 0x07
+
+static void ncsi_send_ebf(unsigned int np, unsigned int nc);
+static void ncsi_send_ae(unsigned int np, unsigned int nc);
+static void ncsi_send_gls(unsigned int np, unsigned int nc);
+static int ncsi_send_command(unsigned int np, unsigned int nc, unsigned int cmd,
+ uchar *payload, int len, bool wait);
+
+struct ncsi_channel {
+ unsigned int id;
+ bool has_link;
+
+ /* capabilities */
+ u32 cap_generic;
+ u32 cap_bc;
+ u32 cap_mc;
+ u32 cap_buffer;
+ u32 cap_aen;
+ u32 cap_vlan;
+
+ /* version information */
+ struct {
+ u32 version; /* Supported BCD encoded NCSI version */
+ u32 alpha2; /* Supported BCD encoded NCSI version */
+ u8 fw_name[12]; /* Firmware name string */
+ u32 fw_version; /* Firmware version */
+ u16 pci_ids[4]; /* PCI identification */
+ u32 mf_id; /* Manufacture ID */
+ } version;
+
+};
+
+struct ncsi_package {
+ unsigned int id;
+ unsigned int n_channels;
+ struct ncsi_channel *channels;
+};
+
+struct ncsi {
+ enum {
+ NCSI_PROBE_PACKAGE_SP,
+ NCSI_PROBE_PACKAGE_DP,
+ NCSI_PROBE_CHANNEL_SP,
+ NCSI_PROBE_CHANNEL,
+ NCSI_CONFIG,
+ } state;
+
+ unsigned int pending_requests;
+ unsigned int requests[256];
+ unsigned int last_request;
+
+ unsigned int current_package;
+ unsigned int current_channel;
+
+ unsigned int n_packages;
+ struct ncsi_package *packages;
+};
+
+struct ncsi *ncsi_priv;
+
+bool ncsi_active(void)
+{
+ unsigned int np, nc;
+
+ if (!ncsi_priv)
+ return false;
+
+ np = ncsi_priv->current_package;
+ nc = ncsi_priv->current_channel;
+
+ if (ncsi_priv->state != NCSI_CONFIG)
+ return false;
+
+ return np < NCSI_PACKAGE_MAX && nc < NCSI_CHANNEL_MAX &&
+ ncsi_priv->packages[np].channels[nc].has_link;
+}
+
+static unsigned int cmd_payload(int cmd)
+{
+ switch (cmd) {
+ case NCSI_PKT_CMD_CIS:
+ return 0;
+ case NCSI_PKT_CMD_SP:
+ return 4;
+ case NCSI_PKT_CMD_DP:
+ return 0;
+ case NCSI_PKT_CMD_EC:
+ return 0;
+ case NCSI_PKT_CMD_DC:
+ return 4;
+ case NCSI_PKT_CMD_RC:
+ return 4;
+ case NCSI_PKT_CMD_ECNT:
+ return 0;
+ case NCSI_PKT_CMD_DCNT:
+ return 0;
+ case NCSI_PKT_CMD_AE:
+ return 8;
+ case NCSI_PKT_CMD_SL:
+ return 8;
+ case NCSI_PKT_CMD_GLS:
+ return 0;
+ case NCSI_PKT_CMD_SVF:
+ return 8;
+ case NCSI_PKT_CMD_EV:
+ return 4;
+ case NCSI_PKT_CMD_DV:
+ return 0;
+ case NCSI_PKT_CMD_SMA:
+ return 8;
+ case NCSI_PKT_CMD_EBF:
+ return 4;
+ case NCSI_PKT_CMD_DBF:
+ return 0;
+ case NCSI_PKT_CMD_EGMF:
+ return 4;
+ case NCSI_PKT_CMD_DGMF:
+ return 0;
+ case NCSI_PKT_CMD_SNFC:
+ return 4;
+ case NCSI_PKT_CMD_GVI:
+ return 0;
+ case NCSI_PKT_CMD_GC:
+ return 0;
+ case NCSI_PKT_CMD_GP:
+ return 0;
+ case NCSI_PKT_CMD_GCPS:
+ return 0;
+ case NCSI_PKT_CMD_GNS:
+ return 0;
+ case NCSI_PKT_CMD_GNPTS:
+ return 0;
+ case NCSI_PKT_CMD_GPS:
+ return 0;
+ default:
+ printf("NCSI: Unknown command 0x%02x\n", cmd);
+ return 0;
+ }
+}
+
+static u32 ncsi_calculate_checksum(unsigned char *data, int len)
+{
+ u32 checksum = 0;
+ int i;
+
+ for (i = 0; i < len; i += 2)
+ checksum += (((u32)data[i] << 8) | data[i + 1]);
+
+ checksum = (~checksum + 1);
+ return checksum;
+}
+
+static int ncsi_validate_rsp(struct ncsi_rsp_pkt *pkt, int payload)
+{
+ struct ncsi_rsp_pkt_hdr *hdr = &pkt->rsp;
+ u32 checksum, c_offset;
+ __be32 pchecksum;
+
+ if (hdr->common.revision != 1) {
+ printf("NCSI: 0x%02x response has unsupported revision 0x%x\n",
+ hdr->common.type, hdr->common.revision);
+ return -1;
+ }
+
+ if (hdr->code != 0) {
+ printf("NCSI: 0x%02x response returns error %d\n",
+ hdr->common.type, __be16_to_cpu(hdr->code));
+ if (ntohs(hdr->reason) == 0x05)
+ printf("(Invalid command length)\n");
+ return -1;
+ }
+
+ if (ntohs(hdr->common.length) != payload) {
+ printf("NCSI: 0x%02x response has incorrect length %d\n",
+ hdr->common.type, hdr->common.length);
+ return -1;
+ }
+
+ c_offset = sizeof(struct ncsi_rsp_pkt_hdr) + payload - sizeof(checksum);
+ pchecksum = get_unaligned_be32((void *)hdr + c_offset);
+ if (pchecksum != 0) {
+ checksum = ncsi_calculate_checksum((unsigned char *)hdr,
+ c_offset);
+ if (pchecksum != checksum) {
+ printf("NCSI: 0x%02x response has invalid checksum\n",
+ hdr->common.type);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void ncsi_rsp_ec(struct ncsi_rsp_pkt *pkt)
+{
+ struct ncsi_rsp_pkt_hdr *rsp = (struct ncsi_rsp_pkt_hdr *)&pkt->rsp;
+ unsigned int np, nc;
+
+ np = NCSI_PACKAGE_INDEX(rsp->common.channel);
+ nc = NCSI_CHANNEL_INDEX(rsp->common.channel);
+
+ if (ncsi_priv->packages[np].channels[nc].cap_aen != 0)
+ ncsi_send_ae(np, nc);
+ /* else, done */
+}
+
+static void ncsi_rsp_ecnt(struct ncsi_rsp_pkt *pkt)
+{
+ struct ncsi_rsp_pkt_hdr *rsp = (struct ncsi_rsp_pkt_hdr *)&pkt->rsp;
+ unsigned int np, nc;
+
+ np = NCSI_PACKAGE_INDEX(rsp->common.channel);
+ nc = NCSI_CHANNEL_INDEX(rsp->common.channel);
+
+ ncsi_send_command(np, nc, NCSI_PKT_CMD_EC, NULL, 0, true);
+}
+
+static void ncsi_rsp_ebf(struct ncsi_rsp_pkt *pkt)
+{
+ struct ncsi_rsp_pkt_hdr *rsp = (struct ncsi_rsp_pkt_hdr *)&pkt->rsp;
+ unsigned int np, nc;
+
+ np = NCSI_PACKAGE_INDEX(rsp->common.channel);
+ nc = NCSI_CHANNEL_INDEX(rsp->common.channel);
+
+ ncsi_send_command(np, nc, NCSI_PKT_CMD_ECNT, NULL, 0, true);
+}
+
+static void ncsi_rsp_sma(struct ncsi_rsp_pkt *pkt)
+{
+ struct ncsi_rsp_pkt_hdr *rsp = (struct ncsi_rsp_pkt_hdr *)&pkt->rsp;
+ unsigned int np, nc;
+
+ np = NCSI_PACKAGE_INDEX(rsp->common.channel);
+ nc = NCSI_CHANNEL_INDEX(rsp->common.channel);
+
+ ncsi_send_ebf(np, nc);
+}
+
+static void ncsi_rsp_gc(struct ncsi_rsp_pkt *pkt)
+{
+ struct ncsi_rsp_gc_pkt *gc = (struct ncsi_rsp_gc_pkt *)pkt;
+ struct ncsi_rsp_pkt_hdr *rsp = (struct ncsi_rsp_pkt_hdr *)&gc->rsp;
+ struct ncsi_channel *c;
+ unsigned int np, nc;
+
+ np = NCSI_PACKAGE_INDEX(rsp->common.channel);
+ nc = NCSI_CHANNEL_INDEX(rsp->common.channel);
+
+ if (np >= ncsi_priv->n_packages ||
+ nc >= ncsi_priv->packages[np].n_channels) {
+ printf("NCSI: Invalid package / channel (0x%02x, 0x%02x)\n",
+ np, nc);
+ return;
+ }
+
+ c = &ncsi_priv->packages[np].channels[nc];
+ c->cap_generic = ntohl(gc->cap) & NCSI_CAP_GENERIC_MASK;
+ c->cap_bc = ntohl(gc->bc_cap) & NCSI_CAP_BC_MASK;
+ c->cap_mc = ntohl(gc->mc_cap) & NCSI_CAP_MC_MASK;
+ c->cap_aen = ntohl(gc->aen_cap) & NCSI_CAP_AEN_MASK;
+ c->cap_vlan = ntohl(gc->vlan_mode) & NCSI_CAP_VLAN_MASK;
+
+ /* End of probe for this channel */
+}
+
+static void ncsi_rsp_gvi(struct ncsi_rsp_pkt *pkt)
+{
+ struct ncsi_rsp_gvi_pkt *gvi = (struct ncsi_rsp_gvi_pkt *)pkt;
+ struct ncsi_rsp_pkt_hdr *rsp = (struct ncsi_rsp_pkt_hdr *)&gvi->rsp;
+ struct ncsi_channel *c;
+ unsigned int np, nc, i;
+
+ np = NCSI_PACKAGE_INDEX(rsp->common.channel);
+ nc = NCSI_CHANNEL_INDEX(rsp->common.channel);
+
+ if (np >= ncsi_priv->n_packages ||
+ nc >= ncsi_priv->packages[np].n_channels) {
+ printf("NCSI: Invalid package / channel (0x%02x, 0x%02x)\n",
+ np, nc);
+ return;
+ }
+
+ c = &ncsi_priv->packages[np].channels[nc];
+ c->version.version = get_unaligned_be32(&gvi->ncsi_version);
+ c->version.alpha2 = gvi->alpha2;
+ memcpy(c->version.fw_name, gvi->fw_name, sizeof(c->version.fw_name));
+ c->version.fw_version = get_unaligned_be32(&gvi->fw_version);
+ for (i = 0; i < ARRAY_SIZE(c->version.pci_ids); i++)
+ c->version.pci_ids[i] = get_unaligned_be16(gvi->pci_ids + i);
+ c->version.mf_id = get_unaligned_be32(&gvi->mf_id);
+
+ if (ncsi_priv->state == NCSI_PROBE_CHANNEL)
+ ncsi_send_command(np, nc, NCSI_PKT_CMD_GC, NULL, 0, true);
+}
+
+static void ncsi_rsp_gls(struct ncsi_rsp_pkt *pkt)
+{
+ struct ncsi_rsp_gls_pkt *gls = (struct ncsi_rsp_gls_pkt *)pkt;
+ struct ncsi_rsp_pkt_hdr *rsp = (struct ncsi_rsp_pkt_hdr *)&gls->rsp;
+ unsigned int np, nc;
+
+ np = NCSI_PACKAGE_INDEX(rsp->common.channel);
+ nc = NCSI_CHANNEL_INDEX(rsp->common.channel);
+
+ if (np >= ncsi_priv->n_packages ||
+ nc >= ncsi_priv->packages[np].n_channels) {
+ printf("NCSI: Invalid package / channel (0x%02x, 0x%02x)\n",
+ np, nc);
+ return;
+ }
+
+ ncsi_priv->packages[np].channels[nc].has_link =
+ !!(get_unaligned_be32(&gls->status));
+
+ if (ncsi_priv->state == NCSI_PROBE_CHANNEL)
+ ncsi_send_command(np, nc, NCSI_PKT_CMD_GVI, NULL, 0, true);
+}
+
+static void ncsi_rsp_cis(struct ncsi_rsp_pkt *pkt)
+{
+ struct ncsi_rsp_pkt_hdr *rsp = (struct ncsi_rsp_pkt_hdr *)pkt;
+ struct ncsi_package *package;
+ unsigned int np, nc;
+
+ np = NCSI_PACKAGE_INDEX(rsp->common.channel);
+ nc = NCSI_CHANNEL_INDEX(rsp->common.channel);
+
+ if (np >= ncsi_priv->n_packages) {
+ printf("NCSI: Mystery package 0x%02x from CIS\n", np);
+ return;
+ }
+
+ package = &ncsi_priv->packages[np];
+
+ if (nc < package->n_channels) {
+ /*
+ * This is fine in general but in the current design we
+ * don't send CIS commands to known channels.
+ */
+ debug("NCSI: Duplicate channel 0x%02x\n", nc);
+ return;
+ }
+
+ package->channels = realloc(package->channels,
+ sizeof(struct ncsi_channel) *
+ (package->n_channels + 1));
+ if (!package->channels) {
+ printf("NCSI: Could not allocate memory for new channel\n");
+ return;
+ }
+
+ debug("NCSI: New channel 0x%02x\n", nc);
+
+ package->channels[nc].id = nc;
+ package->channels[nc].has_link = false;
+ package->n_channels++;
+
+ ncsi_send_gls(np, nc);
+}
+
+static void ncsi_rsp_dp(struct ncsi_rsp_pkt *pkt)
+{
+ struct ncsi_rsp_pkt_hdr *rsp = (struct ncsi_rsp_pkt_hdr *)pkt;
+ unsigned int np;
+
+ /* No action needed */
+
+ np = NCSI_PACKAGE_INDEX(rsp->common.channel);
+ if (np >= ncsi_priv->n_packages)
+ debug("NCSI: DP response from unknown package %d\n", np);
+}
+
+static void ncsi_rsp_sp(struct ncsi_rsp_pkt *pkt)
+{
+ struct ncsi_rsp_pkt_hdr *rsp = (struct ncsi_rsp_pkt_hdr *)pkt;
+ unsigned int np;
+
+ np = NCSI_PACKAGE_INDEX(rsp->common.channel);
+
+ if (np < ncsi_priv->n_packages) {
+ /* Already know about this package */
+ debug("NCSI: package 0x%02x selected\n", np);
+ return;
+ }
+
+ debug("NCSI: adding new package %d\n", np);
+
+ ncsi_priv->packages = realloc(ncsi_priv->packages,
+ sizeof(struct ncsi_package) *
+ (ncsi_priv->n_packages + 1));
+ if (!ncsi_priv->packages) {
+ printf("NCSI: could not allocate memory for new package\n");
+ return;
+ }
+
+ ncsi_priv->packages[np].id = np;
+ ncsi_priv->packages[np].n_channels = 0;
+ ncsi_priv->packages[np].channels = NULL;
+ ncsi_priv->n_packages++;
+}
+
+static void ncsi_update_state(struct ncsi_rsp_pkt_hdr *nh)
+{
+ bool timeout = !nh;
+ int np, nc;
+
+ switch (ncsi_priv->state) {
+ case NCSI_PROBE_PACKAGE_SP:
+ if (!timeout &&
+ ncsi_priv->current_package + 1 < NCSI_PACKAGE_MAX) {
+ ncsi_priv->current_package++;
+ } else {
+ ncsi_priv->state = NCSI_PROBE_PACKAGE_DP;
+ ncsi_priv->current_package = 0;
+ }
+ return ncsi_probe_packages();
+ case NCSI_PROBE_PACKAGE_DP:
+ if (ncsi_priv->current_package + 1 < ncsi_priv->n_packages &&
+ !timeout) {
+ ncsi_priv->current_package++;
+ } else {
+ if (!ncsi_priv->n_packages) {
+ printf("NCSI: no packages found\n");
+ net_set_state(NETLOOP_FAIL);
+ return;
+ }
+ printf("NCSI: probing channels\n");
+ ncsi_priv->state = NCSI_PROBE_CHANNEL_SP;
+ ncsi_priv->current_package = 0;
+ ncsi_priv->current_channel = 0;
+ }
+ return ncsi_probe_packages();
+ case NCSI_PROBE_CHANNEL_SP:
+ if (!timeout && nh->common.type == NCSI_PKT_RSP_SP) {
+ ncsi_priv->state = NCSI_PROBE_CHANNEL;
+ return ncsi_probe_packages();
+ }
+ printf("NCSI: failed to select package 0x%0x2 or timeout\n",
+ ncsi_priv->current_package);
+ net_set_state(NETLOOP_FAIL);
+ break;
+ case NCSI_PROBE_CHANNEL:
+ // TODO only does package 0 for now
+ if (ncsi_priv->pending_requests == 0) {
+ np = ncsi_priv->current_package;
+ nc = ncsi_priv->current_channel;
+
+ /* Configure first channel that has link */
+ if (ncsi_priv->packages[np].channels[nc].has_link) {
+ ncsi_priv->state = NCSI_CONFIG;
+ } else if (ncsi_priv->current_channel + 1 <
+ NCSI_CHANNEL_MAX) {
+ ncsi_priv->current_channel++;
+ } else {
+ // XXX As above only package 0
+ printf("NCSI: no channel found with link\n");
+ net_set_state(NETLOOP_FAIL);
+ return;
+ }
+ return ncsi_probe_packages();
+ }
+ break;
+ case NCSI_CONFIG:
+ if (ncsi_priv->pending_requests == 0) {
+ printf("NCSI: configuration done!\n");
+ net_set_state(NETLOOP_SUCCESS);
+ } else if (timeout) {
+ printf("NCSI: timeout during configure\n");
+ net_set_state(NETLOOP_FAIL);
+ }
+ break;
+ default:
+ printf("NCSI: something went very wrong, nevermind\n");
+ net_set_state(NETLOOP_FAIL);
+ break;
+ }
+}
+
+static void ncsi_timeout_handler(void)
+{
+ if (ncsi_priv->pending_requests)
+ ncsi_priv->pending_requests--;
+
+ ncsi_update_state(NULL);
+}
+
+static int ncsi_send_command(unsigned int np, unsigned int nc, unsigned int cmd,
+ uchar *payload, int len, bool wait)
+{
+ struct ncsi_pkt_hdr *hdr;
+ __be32 *pchecksum;
+ int eth_hdr_size;
+ u32 checksum;
+ uchar *pkt, *start;
+ int final_len;
+
+ pkt = calloc(1, PKTSIZE_ALIGN + PKTALIGN);
+ if (!pkt)
+ return -ENOMEM;
+ start = pkt;
+
+ eth_hdr_size = net_set_ether(pkt, net_bcast_ethaddr, PROT_NCSI);
+ pkt += eth_hdr_size;
+
+ /* Set NCSI command header fields */
+ hdr = (struct ncsi_pkt_hdr *)pkt;
+ hdr->mc_id = 0;
+ hdr->revision = NCSI_PKT_REVISION;
+ hdr->id = ++ncsi_priv->last_request;
+ ncsi_priv->requests[ncsi_priv->last_request] = 1;
+ hdr->type = cmd;
+ hdr->channel = NCSI_TO_CHANNEL(np, nc);
+ hdr->length = htons(len);
+
+ if (payload && len)
+ memcpy(pkt + sizeof(struct ncsi_pkt_hdr), payload, len);
+
+ /* Calculate checksum */
+ checksum = ncsi_calculate_checksum((unsigned char *)hdr,
+ sizeof(*hdr) + len);
+ pchecksum = (__be32 *)((void *)(hdr + 1) + len);
+ put_unaligned_be32(htonl(checksum), pchecksum);
+
+ if (wait) {
+ net_set_timeout_handler(1000UL, ncsi_timeout_handler);
+ ncsi_priv->pending_requests++;
+ }
+
+ if (len < 26)
+ len = 26;
+ /* frame header, packet header, payload, checksum */
+ final_len = eth_hdr_size + sizeof(struct ncsi_cmd_pkt_hdr) + len + 4;
+
+ net_send_packet(start, final_len);
+ free(start);
+ return 0;
+}
+
+static void ncsi_handle_aen(struct ip_udp_hdr *ip, unsigned int len)
+{
+ struct ncsi_aen_pkt_hdr *hdr = (struct ncsi_aen_pkt_hdr *)ip;
+ int payload, i;
+ __be32 pchecksum;
+ u32 checksum;
+
+ switch (hdr->type) {
+ case NCSI_PKT_AEN_LSC:
+ printf("NCSI: link state changed\n");
+ payload = 12;
+ break;
+ case NCSI_PKT_AEN_CR:
+ printf("NCSI: re-configuration required\n");
+ payload = 4;
+ break;
+ case NCSI_PKT_AEN_HNCDSC:
+ /* Host notifcation - N/A but weird */
+ debug("NCSI: HNCDSC AEN received\n");
+ return;
+ default:
+ printf("%s: Invalid type 0x%02x\n", __func__, hdr->type);
+ return;
+ }
+
+ /* Validate packet */
+ if (hdr->common.revision != 1) {
+ printf("NCSI: 0x%02x response has unsupported revision 0x%x\n",
+ hdr->common.type, hdr->common.revision);
+ return;
+ }
+
+ if (ntohs(hdr->common.length) != payload) {
+ printf("NCSI: 0x%02x response has incorrect length %d\n",
+ hdr->common.type, hdr->common.length);
+ return;
+ }
+
+ pchecksum = get_unaligned_be32((void *)(hdr + 1) + payload - 4);
+ if (pchecksum != 0) {
+ checksum = ncsi_calculate_checksum((unsigned char *)hdr,
+ sizeof(*hdr) + payload - 4);
+ if (pchecksum != checksum) {
+ printf("NCSI: 0x%02x response has invalid checksum\n",
+ hdr->common.type);
+ return;
+ }
+ }
+
+ /* Link or configuration lost - just redo the discovery process */
+ ncsi_priv->state = NCSI_PROBE_PACKAGE_SP;
+ for (i = 0; i < ncsi_priv->n_packages; i++)
+ free(ncsi_priv->packages[i].channels);
+ free(ncsi_priv->packages);
+ ncsi_priv->n_packages = 0;
+
+ ncsi_priv->current_package = NCSI_PACKAGE_MAX;
+ ncsi_priv->current_channel = NCSI_CHANNEL_MAX;
+
+ ncsi_probe_packages();
+}
+
+void ncsi_receive(struct ethernet_hdr *et, struct ip_udp_hdr *ip,
+ unsigned int len)
+{
+ struct ncsi_rsp_pkt *pkt = (struct ncsi_rsp_pkt *)ip;
+ struct ncsi_rsp_pkt_hdr *nh = (struct ncsi_rsp_pkt_hdr *)&pkt->rsp;
+ void (*handler)(struct ncsi_rsp_pkt *pkt) = NULL;
+ unsigned short payload;
+
+ if (ncsi_priv->pending_requests)
+ ncsi_priv->pending_requests--;
+
+ if (len < sizeof(struct ncsi_rsp_pkt_hdr)) {
+ printf("NCSI: undersized packet: %u bytes\n", len);
+ goto out;
+ }
+
+ if (nh->common.type == NCSI_PKT_AEN)
+ return ncsi_handle_aen(ip, len);
+
+ switch (nh->common.type) {
+ case NCSI_PKT_RSP_SP:
+ payload = 4;
+ handler = ncsi_rsp_sp;
+ break;
+ case NCSI_PKT_RSP_DP:
+ payload = 4;
+ handler = ncsi_rsp_dp;
+ break;
+ case NCSI_PKT_RSP_CIS:
+ payload = 4;
+ handler = ncsi_rsp_cis;
+ break;
+ case NCSI_PKT_RSP_GLS:
+ payload = 16;
+ handler = ncsi_rsp_gls;
+ break;
+ case NCSI_PKT_RSP_GVI:
+ payload = 40;
+ handler = ncsi_rsp_gvi;
+ break;
+ case NCSI_PKT_RSP_GC:
+ payload = 32;
+ handler = ncsi_rsp_gc;
+ break;
+ case NCSI_PKT_RSP_SMA:
+ payload = 4;
+ handler = ncsi_rsp_sma;
+ break;
+ case NCSI_PKT_RSP_EBF:
+ payload = 4;
+ handler = ncsi_rsp_ebf;
+ break;
+ case NCSI_PKT_RSP_ECNT:
+ payload = 4;
+ handler = ncsi_rsp_ecnt;
+ break;
+ case NCSI_PKT_RSP_EC:
+ payload = 4;
+ handler = ncsi_rsp_ec;
+ break;
+ case NCSI_PKT_RSP_AE:
+ payload = 4;
+ handler = NULL;
+ break;
+ default:
+ printf("NCSI: unsupported packet type 0x%02x\n",
+ nh->common.type);
+ goto out;
+ }
+
+ if (ncsi_validate_rsp(pkt, payload) != 0) {
+ printf("NCSI: discarding invalid packet of type 0x%02x\n",
+ nh->common.type);
+ goto out;
+ }
+
+ if (handler)
+ handler(pkt);
+out:
+ ncsi_update_state(nh);
+}
+
+static void ncsi_send_sp(unsigned int np)
+{
+ uchar payload[4] = {0};
+
+ ncsi_send_command(np, NCSI_RESERVED_CHANNEL, NCSI_PKT_CMD_SP,
+ (unsigned char *)&payload,
+ cmd_payload(NCSI_PKT_CMD_SP), true);
+}
+
+static void ncsi_send_dp(unsigned int np)
+{
+ ncsi_send_command(np, NCSI_RESERVED_CHANNEL, NCSI_PKT_CMD_DP, NULL, 0,
+ true);
+}
+
+static void ncsi_send_gls(unsigned int np, unsigned int nc)
+{
+ ncsi_send_command(np, nc, NCSI_PKT_CMD_GLS, NULL, 0, true);
+}
+
+static void ncsi_send_cis(unsigned int np, unsigned int nc)
+{
+ ncsi_send_command(np, nc, NCSI_PKT_CMD_CIS, NULL, 0, true);
+}
+
+static void ncsi_send_ae(unsigned int np, unsigned int nc)
+{
+ struct ncsi_cmd_ae_pkt cmd;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.mode = htonl(ncsi_priv->packages[np].channels[nc].cap_aen);
+
+ ncsi_send_command(np, nc, NCSI_PKT_CMD_AE,
+ ((unsigned char *)&cmd)
+ + sizeof(struct ncsi_cmd_pkt_hdr),
+ cmd_payload(NCSI_PKT_CMD_AE), true);
+}
+
+static void ncsi_send_ebf(unsigned int np, unsigned int nc)
+{
+ struct ncsi_cmd_ebf_pkt cmd;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.mode = htonl(ncsi_priv->packages[np].channels[nc].cap_bc);
+
+ ncsi_send_command(np, nc, NCSI_PKT_CMD_EBF,
+ ((unsigned char *)&cmd)
+ + sizeof(struct ncsi_cmd_pkt_hdr),
+ cmd_payload(NCSI_PKT_CMD_EBF), true);
+}
+
+static void ncsi_send_sma(unsigned int np, unsigned int nc)
+{
+ struct ncsi_cmd_sma_pkt cmd;
+ unsigned char *addr, i;
+
+ addr = eth_get_ethaddr();
+ if (!addr) {
+ printf("NCSI: no MAC address configured\n");
+ return;
+ }
+
+ memset(&cmd, 0, sizeof(cmd));
+ for (i = 0; i < ARP_HLEN; i++)
+ cmd.mac[i] = addr[i];
+ cmd.index = 1;
+ cmd.at_e = 1;
+
+ ncsi_send_command(np, nc, NCSI_PKT_CMD_SMA,
+ ((unsigned char *)&cmd)
+ + sizeof(struct ncsi_cmd_pkt_hdr),
+ cmd_payload(NCSI_PKT_CMD_SMA), true);
+}
+
+void ncsi_probe_packages(void)
+{
+ struct ncsi_package *package;
+ unsigned int np, nc;
+
+ switch (ncsi_priv->state) {
+ case NCSI_PROBE_PACKAGE_SP:
+ if (ncsi_priv->current_package == NCSI_PACKAGE_MAX)
+ ncsi_priv->current_package = 0;
+ ncsi_send_sp(ncsi_priv->current_package);
+ break;
+ case NCSI_PROBE_PACKAGE_DP:
+ ncsi_send_dp(ncsi_priv->current_package);
+ break;
+ case NCSI_PROBE_CHANNEL_SP:
+ if (ncsi_priv->n_packages > 0)
+ ncsi_send_sp(ncsi_priv->current_package);
+ else
+ printf("NCSI: no packages discovered, configuration not possible\n");
+ break;
+ case NCSI_PROBE_CHANNEL:
+ /* Kicks off chain of channel discovery */
+ ncsi_send_cis(ncsi_priv->current_package,
+ ncsi_priv->current_channel);
+ break;
+ case NCSI_CONFIG:
+ for (np = 0; np < ncsi_priv->n_packages; np++) {
+ package = &ncsi_priv->packages[np];
+ for (nc = 0; nc < package->n_channels; nc++)
+ if (package->channels[nc].has_link)
+ break;
+ if (nc < package->n_channels)
+ break;
+ }
+ if (np == ncsi_priv->n_packages) {
+ printf("NCSI: no link available\n");
+ return;
+ }
+
+ printf("NCSI: configuring channel %d\n", nc);
+ ncsi_priv->current_package = np;
+ ncsi_priv->current_channel = nc;
+ /* Kicks off rest of configure chain */
+ ncsi_send_sma(np, nc);
+ break;
+ default:
+ printf("NCSI: unknown state 0x%x\n", ncsi_priv->state);
+ }
+}
+
+int ncsi_probe(struct phy_device *phydev)
+{
+ if (!phydev->priv) {
+ phydev->priv = malloc(sizeof(struct ncsi));
+ if (!phydev->priv)
+ return -ENOMEM;
+ memset(phydev->priv, 0, sizeof(struct ncsi));
+ }
+
+ ncsi_priv = phydev->priv;
+
+ return 0;
+}
+
+int ncsi_startup(struct phy_device *phydev)
+{
+ /* Set phydev parameters */
+ phydev->speed = SPEED_100;
+ phydev->duplex = DUPLEX_FULL;
+ /* Normal phy reset is N/A */
+ phydev->flags |= PHY_FLAG_BROKEN_RESET;
+
+ /* Set initial probe state */
+ ncsi_priv->state = NCSI_PROBE_PACKAGE_SP;
+
+ /* No active package/channel yet */
+ ncsi_priv->current_package = NCSI_PACKAGE_MAX;
+ ncsi_priv->current_channel = NCSI_CHANNEL_MAX;
+
+ /* Pretend link works so the MAC driver sets final bits up */
+ phydev->link = true;
+
+ /* Set ncsi_priv so we can use it when called from net_loop() */
+ ncsi_priv = phydev->priv;
+
+ return 0;
+}
+
+int ncsi_shutdown(struct phy_device *phydev)
+{
+ printf("NCSI: Disabling package %d\n", ncsi_priv->current_package);
+ ncsi_send_dp(ncsi_priv->current_package);
+ return 0;
+}
+
+static struct phy_driver ncsi_driver = {
+ .uid = PHY_NCSI_ID,
+ .mask = 0xffffffff,
+ .name = "NC-SI",
+ .features = PHY_100BT_FEATURES | PHY_DEFAULT_FEATURES |
+ SUPPORTED_100baseT_Full | SUPPORTED_MII,
+ .probe = ncsi_probe,
+ .startup = ncsi_startup,
+ .shutdown = ncsi_shutdown,
+};
+
+int phy_ncsi_init(void)
+{
+ phy_register(&ncsi_driver);
+ return 0;
+}