diff options
author | Samuel Mendoza-Jonas <sam@mendozajonas.com> | 2019-06-18 11:37:17 +1000 |
---|---|---|
committer | Joe Hershberger <joe.hershberger@ni.com> | 2020-03-09 18:11:23 -0500 |
commit | f641a8ac93e0c0179fe6f73429812af228109ff2 (patch) | |
tree | 68a93215ff4583635f66615c090823488bedeb54 /drivers/net/phy | |
parent | 4b7f5f3e75841b115caacef5983a9cc681521ec7 (diff) | |
download | u-boot-f641a8ac93e0c0179fe6f73429812af228109ff2.zip u-boot-f641a8ac93e0c0179fe6f73429812af228109ff2.tar.gz u-boot-f641a8ac93e0c0179fe6f73429812af228109ff2.tar.bz2 |
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>
Diffstat (limited to 'drivers/net/phy')
-rw-r--r-- | drivers/net/phy/Kconfig | 4 | ||||
-rw-r--r-- | drivers/net/phy/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/phy/ncsi.c | 897 |
3 files changed, 902 insertions, 0 deletions
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index dceea15..d1f049e 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -267,4 +267,8 @@ config PHY_FIXED on, the link is always up with fixed speed and fixed duplex-setting. More information: doc/device-tree-bindings/net/fixed-link.txt +config PHY_NCSI + bool "NC-SI based PHY" + depends on DM_ETH + endif #PHYLIB diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index 78955c5..1d81516 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -31,3 +31,4 @@ obj-$(CONFIG_PHY_XILINX_GMII2RGMII) += xilinx_gmii2rgmii.o obj-$(CONFIG_PHY_VITESSE) += vitesse.o obj-$(CONFIG_PHY_MSCC) += mscc.o obj-$(CONFIG_PHY_FIXED) += fixed.o +obj-$(CONFIG_PHY_NCSI) += ncsi.o 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; +} |