mirror of
				https://github.com/smaeul/u-boot.git
				synced 2025-10-26 01:28:14 +00:00 
			
		
		
		
	As part of bringing the master branch back in to next, we need to allow for all of these changes to exist here. Reported-by: Jonas Karlman <jonas@kwiboo.se> Signed-off-by: Tom Rini <trini@konsulko.com>
		
			
				
	
	
		
			896 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			896 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // 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,
 | |
| };
 |