| // SPDX-License-Identifier: GPL-2.0+ | |
| /* | |
| * NC-SI protocol configuration | |
| * | |
| * Copyright (C) 2019, IBM Corporation. | |
| */ | |
| #include <log.h> | |
| #include <malloc.h> | |
| #include <phy.h> | |
| #include <net.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 = get_unaligned_be32(&gc->cap) & NCSI_CAP_GENERIC_MASK; | |
| c->cap_bc = get_unaligned_be32(&gc->bc_cap) & NCSI_CAP_BC_MASK; | |
| c->cap_mc = get_unaligned_be32(&gc->mc_cap) & NCSI_CAP_MC_MASK; | |
| c->cap_aen = get_unaligned_be32(&gc->aen_cap) & NCSI_CAP_AEN_MASK; | |
| c->cap_vlan = 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(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); | |
| ncsi_priv->packages[i].channels = NULL; | |
| } | |
| free(ncsi_priv->packages); | |
| ncsi_priv->packages = NULL; | |
| 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; | |
| } | |
| U_BOOT_PHY_DRIVER(ncsi) = { | |
| .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, | |
| }; |