diff options
Diffstat (limited to 'board/keymile/common/keymile_hdlc_enet.c')
| -rw-r--r-- | board/keymile/common/keymile_hdlc_enet.c | 620 | 
1 files changed, 620 insertions, 0 deletions
| diff --git a/board/keymile/common/keymile_hdlc_enet.c b/board/keymile/common/keymile_hdlc_enet.c new file mode 100644 index 000000000..141371b8f --- /dev/null +++ b/board/keymile/common/keymile_hdlc_enet.c @@ -0,0 +1,620 @@ +/* + * (C) Copyright 2008 + * Gary Jennejohn, DENX Software Engineering GmbH, garyj@denx.de. + * + * Based in part on cpu/mpc8260/ether_scc.c. + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <common.h> +#include <malloc.h> +#include <net.h> + +#ifdef CONFIG_KEYMILE_HDLC_ENET +#ifdef TEST_IT +#include <command.h> +#endif + +#include "keymile_hdlc_enet.h" + +extern char keymile_slot;	/* our slot number in the backplane */ + +/* Allow up to about 50 ms for sending */ +#define TOUT_LOOP	50000 + +/* + * Since, except during initialization, ethact is always HDLC ETHERNET + * while we're in the driver, just use serial_printf() everywhere for + * output.  This avoids possible conflicts when netconsole is being + * used. + */ +#define dprintf(fmt, args...)	serial_printf(fmt, ##args) + +/* Cannot use the storage from net.c because we allocate larger buffers */ +static volatile uchar MyPktBuf[HDLC_PKTBUFSRX * PKT_MAXBLR_SIZE + PKTALIGN]; +static volatile uchar *MyRxPackets[HDLC_PKTBUFSRX]; /* Receive packet */ + +static unsigned int keymile_rxIdx;	/* index of the current RX buffer */ + +static IPaddr_t cachedNumbers[CACHEDNUMBERS]; /* 4 bytes per entry */ +void initCachedNumbers(int); + +/* +  * SCC Ethernet Tx and Rx buffer descriptors allocated at the +  *  immr->udata_bd address on Dual-Port RAM +  * Provide for Double Buffering +  */ +typedef volatile struct CommonBufferDescriptor { +    cbd_t txbd;			/* Tx BD */ +    cbd_t rxbd[HDLC_PKTBUFSRX];	/* Rx BD */ +} RTXBD; + +/* + * This must be extern because it is allocated in DPRAM using CPM-sepcific + * code. + */ +static RTXBD *rtx; + +static int keymile_hdlc_enet_send(struct eth_device *, volatile void *, int); +static int keymile_hdlc_enet_recv(struct eth_device *); +void keymile_hdlc_enet_init_bds(RTXBD *); +extern int keymile_hdlc_enet_init(struct eth_device *, bd_t *); +extern void keymile_hdlc_enet_halt(struct eth_device *); + +/* flags in the buffer descriptor not defined anywhere else */ +#define BD_SC_CT	BD_SC_CD +#define BD_SC_CR	0x04 +#define BD_SC_DE	0x80 +#ifndef BD_SC_TC +#define BD_SC_TC	((ushort)0x0400)	/* Transmit CRC */ +#endif +#define BD_SC_FIRST	BD_SC_TC +#define BD_SC_STATS (BD_SC_BR | BD_SC_FR | BD_SC_PR | BD_SC_CR | BD_SC_CD \ +		| BD_SC_OV | BD_SC_DE) + +#if defined(TEST_RX) || defined(TEST_TX) || defined(TEST_IT) +static void hexdump(unsigned char *buf, int len) +{ +	int i; +	const int bytesPerLine = 32; + +	if (len > 4 * bytesPerLine) +		len = 4 * bytesPerLine; +	dprintf("\t address: %08x\n", (unsigned int)buf); +	for (i = 0; i < len; i++) { +		if (i % bytesPerLine == 0) +			dprintf("%04x: ", (unsigned short)i); +		dprintf("%02x ", buf[i]); +		if ((i + 1) % bytesPerLine == 0) { +			dprintf("\n"); +			continue; +		} +		if ((i + 1) % 8 == 0) +			printf(" "); +	} +	if (len % bytesPerLine) +		dprintf("\n"); +} +#endif + +int keymile_hdlc_enet_initialize(bd_t *bis) +{ +	struct eth_device *dev; + +	dev = (struct eth_device *) malloc(sizeof *dev); +	memset(dev, 0, sizeof *dev); +#ifdef TEST_IT +	seth = dev; +#endif + +	sprintf(dev->name, "HDLC ETHERNET"); +	dev->init   = keymile_hdlc_enet_init; +	dev->halt   = keymile_hdlc_enet_halt; +	dev->send   = keymile_hdlc_enet_send; +	dev->recv   = keymile_hdlc_enet_recv; + +	eth_register(dev); + +	return 1; +} + +/* + * This is called from the board-specific driver after rtx is allocated. + */ +void keymile_hdlc_enet_init_bds(RTXBD *board_rtx) +{ +	volatile cbd_t *bdp; +	int i; + +	rtx = board_rtx; +	keymile_rxIdx = 0; +	/* +	 * Initialize the buffer descriptors. +	 */ +	bdp = &rtx->txbd; +	bdp->cbd_sc = 0; +	bdp->cbd_bufaddr = 0; +	bdp->cbd_sc = BD_SC_WRAP; + +	/* +	 *	Setup RX packet buffers, aligned correctly. +	 *	Borrowed from net/net.c. +	 */ +	MyRxPackets[0] = &MyPktBuf[0] + (PKTALIGN - 1); +	MyRxPackets[0] -= (ulong)MyRxPackets[0] % PKTALIGN; +	for (i = 1; i < HDLC_PKTBUFSRX; i++) +		MyRxPackets[i] = MyRxPackets[0] + i * PKT_MAXBLR_SIZE; + +	bdp = &rtx->rxbd[0]; +	for (i = 0; i < HDLC_PKTBUFSRX; i++) { +		bdp->cbd_sc = BD_SC_EMPTY; +		/* Leave space at the start for INET header. */ +		bdp->cbd_bufaddr = (unsigned int)(MyRxPackets[i] + +			INET_HDR_ALIGN); +		bdp++; +	} +	bdp--; +	bdp->cbd_sc |= BD_SC_WRAP; +} + +/* + * This returns the current port number for NETCONSOLE.  If nc_port + * in netconsole.c weren't declared static we wouldn't need this. + */ +static short get_netcons_port(void) +{ +	char *p; +	short nc_port; + +	nc_port = 6666; /* default */ + +	p = getenv("ncip"); +	if (p != NULL) { +		p = strchr(p, ':'); +		if (p != NULL) +			nc_port = simple_strtoul(p + 1, NULL, 10); +	} + +	return htons(nc_port); +} + +/* + * Read the port numbers from the variables + */ +void initCachedNumbers(int verbose) +{ +	char *str; +	ushort port; + +	/* already in network order */ +	cachedNumbers[IP_ADDR] = getenv_IPaddr("ipaddr"); +	/* already in network order */ +	cachedNumbers[IP_SERVER] = getenv_IPaddr("serverip"); +	str = getenv("tftpsrcp"); +	if (str != NULL) { +		/* avoid doing htons() again and again */ +		port = htons((ushort)simple_strtol(str, NULL, 10)); +		cachedNumbers[TFTP_SRC_PORT] = port; +	} else +		/* this can never be a valid port number */ +		cachedNumbers[TFTP_SRC_PORT] = (ulong)-1; +	str = getenv("tftpdstp"); +	if (str != NULL) { +		/* avoid doing htons() again and again */ +		port = htons((ushort)simple_strtol(str, NULL, 10)); +		cachedNumbers[TFTP_DST_PORT] = port; +	} else +		/* this is the default value */ +		cachedNumbers[TFTP_DST_PORT] = htons(WELL_KNOWN_PORT); +	/* already in network order */ +	cachedNumbers[NETCONS_PORT] = get_netcons_port(); +	if (verbose) { +		dprintf("\nIP Number Initialization:\n"); +		dprintf(" ip address          %08lx\n", cachedNumbers[IP_ADDR]); +		dprintf(" server ip address   %08lx\n", +			cachedNumbers[IP_SERVER]); +		dprintf(" tftp client port    %ld\n", +			cachedNumbers[TFTP_SRC_PORT]); +		dprintf(" tftp server port    %ld\n", +			cachedNumbers[TFTP_DST_PORT]); +		dprintf(" netcons port        %ld\n", +			cachedNumbers[NETCONS_PORT]); +		dprintf(" slot number (hex)   %02x\n", keymile_slot); +	} +} + +static void keymile_hdlc_enet_doarp(volatile void *packet, int len) +{ +	ARP_t *arp; +	IPaddr_t src_ip; /* U-Boot's IP */ +	IPaddr_t dest_ip; /* the mgcoge's IP */ +	unsigned char *packet_copy = malloc(len); + +	/* +	 * Handling an ARP request means that a new transfer has started. +	 * Update our cached parameters now. +	 */ +	initCachedNumbers(0); /* may reinit port numbers */ + +	/* special handling required for ARP */ +	arp = (ARP_t *)(packet + ETHER_HDR_SIZE); +	/* +	 *	XXXX +	 * This is pretty dirty!  NetReceive only uses +	 * a few fields when handling an ARP reply, so +	 * we only modify those here.  This could +	 * result in catastrophic failure at a later +	 * time if the handler is modified! +	 */ +	arp->ar_op = htons(ARPOP_REPLY); +	/* save his/our IP */ +	src_ip = NetReadIP(&arp->ar_data[6]); +	dest_ip = NetReadIP(&arp->ar_data[16]); +	/* copy target IP to source IP */ +	NetCopyIP(&arp->ar_data[6], &dest_ip); +	/* copy our IP to the right place */ +	NetCopyIP(&arp->ar_data[16], &src_ip); +	/* always use 0x7f as the MAC for the coge */ +	arp->ar_data[0] = HDLC_UACUA; +	/* +	 * copy the packet +	 * if NetReceive wants to write to stdout, it may overwrite packet +	 * especially if stdout is set to nc! +	 * +	 * However, if the malloc() above fails then we can still try the +	 * original packet, rather than causing the transfer to fail. +	 */ +	if (packet_copy != NULL) { +		memcpy(packet_copy, (char *)packet, len); +		NetReceive(packet_copy, len); +		free(packet_copy); +	} else +		NetReceive(packet, len); +} + +/* + * NOTE all callers ignore the returned value! + * At the moment this only handles ARP Requests, TFTP and NETCONSOLE. + */ +static int keymile_hdlc_enet_send(struct eth_device *dev, volatile void *packet, +	int len) +{ +	int j; +	uint data_addr; +	int data_len; +	struct icn_hdr header; +	struct icn_frame *frame; +	Ethernet_t *et; +	ARP_t *arp; +	IP_t *ip; + +	if (len > (MAX_FRAME_LENGTH - sizeof(header))) +		return -1; + +	frame = NULL; +	et = NULL; +	arp = NULL; +	ip = NULL; + +	j = 0; +	while ((rtx->txbd.cbd_sc & BD_SC_READY) && (j < TOUT_LOOP)) { +		/* will also trigger Wd if needed, but maybe too often  */ +		udelay(1); +		j++; +	} +	if (j >= TOUT_LOOP) { +		dprintf("TX not ready sc %x\n", rtx->txbd.cbd_sc); +		return -1; +	} + +	/* +	 * First check for an ARP Request since this requires special handling. +	 */ +	if (len >= (ARP_HDR_SIZE + ETHER_HDR_SIZE)) { +		et = (Ethernet_t *)packet; +		arp = (ARP_t *)(((char *)et) + ETHER_HDR_SIZE); +		/* ARP and REQUEST? */ +		if (et->et_protlen == PROT_ARP && +			arp->ar_op == htons(ARPOP_REQUEST)) { +			/* just short-circuit the request on the U-Boot side */ +			keymile_hdlc_enet_doarp(packet, len); +			return 0; +		} +	} + +	/* +	 * GJ - I suppose the assumption here that len will always be +	 * > INET_HDR_SIZE is alright as long as the network stack +	 * isn't changed. +	 * Do not send INET header. +	 */ +	data_len = len + sizeof(header) - INET_HDR_SIZE; +	frame = (struct icn_frame *) (((char *)packet) + INET_HDR_SIZE - +		sizeof(header)); + +#ifdef TEST_TX +	printf("frame: %08x, ", frame); +	hexdump((unsigned char *)packet, data_len + INET_HDR_SIZE); +#endif + +	data_addr = (uint)frame; +	if (len >= (IP_HDR_SIZE + ETHER_HDR_SIZE)) +		ip = (IP_t *)(packet + ETHER_HDR_SIZE); +	/* Is it TFTP? TFTP always uses UDP and the cached dport */ +	if (ip != NULL && ip->ip_p == IPPROTO_UDP && ip->udp_dst == +			(ushort)cachedNumbers[TFTP_DST_PORT]) { +		/* just in case the port wasn't set in the environment */ +		if (cachedNumbers[TFTP_SRC_PORT] == (ulong)-1) +			cachedNumbers[TFTP_SRC_PORT] = ip->udp_src; +		frame->hdr.application = MGS_TFTP; +	} +	/* +	 * Is it NETCONSOLE?  NETCONSOLE always uses UDP. +	 */ +	else if (ip != NULL && ip->ip_p == IPPROTO_UDP +		&& ip->udp_dst == (ushort)cachedNumbers[NETCONS_PORT]) { +			frame->hdr.application = MGS_NETCONS; +	} else { +		/* reject unknown packets */ +		/* may do some check on frame->hdr.application */ +		dprintf("Unknown packet type in %s, rejected\n", +			__func__); +		return -1; +	} +	/* +	 * Could extract the target's slot ID from its MAC here, +	 * but u-boot only wants to talk to the active server. +	 * +	 * avoid setting new source address when moving to another slot +	 */ +	frame->hdr.src_addr = keymile_slot; +	frame->hdr.dest_addr = HDLC_UACUA; +#ifdef TEST_TX +	{ +		dprintf("TX: "); +		hexdump((unsigned char *)data_addr, data_len); +	} +#endif + +	flush_cache(data_addr, data_len); +	rtx->txbd.cbd_bufaddr = data_addr; +	rtx->txbd.cbd_datlen = data_len; +	rtx->txbd.cbd_sc |= (BD_SC_READY | BD_SC_TC | BD_SC_LAST | BD_SC_WRAP); + +	while ((rtx->txbd.cbd_sc & BD_SC_READY) && (j < TOUT_LOOP)) { +		/* will also trigger Wd if needed, but maybe too often  */ +		udelay(1); +		j++; +	} +	if (j >= TOUT_LOOP) +		dprintf("TX timeout\n"); +#ifdef ET_DEBUG +	dprintf("cycles: %d    status: %x\n", j, rtx->txbd.cbd_sc); +#endif +	j = (rtx->txbd.cbd_sc & BD_SC_STATS); /* return only status bits */ +	return j; +} + +/* + * During a receive, the RxIdx points to the current incoming buffer. + * When we update through the ring, if the next incoming buffer has + * not been given to the system, we just set the empty indicator, + * effectively tossing the packet. + */ +static int keymile_hdlc_enet_recv(struct eth_device *dev) +{ +	int length; +	unsigned char app; +	struct icn_frame *fp; +	Ethernet_t *ep; +	IP_t *ip; + +	for (;;) { +		if (rtx->rxbd[keymile_rxIdx].cbd_sc & BD_SC_EMPTY) { +			length = -1; +			break;	/* nothing received - leave for() loop */ +		} + +		length = rtx->rxbd[keymile_rxIdx].cbd_datlen; +#ifdef TEST_RX +		dprintf("packet %d bytes long\n", length); +#endif + +		/* +		 * BD_SC_BR -> LG bit +		 * BD_SC_FR -> NO bit +		 * BD_SC_PR -> AB bit +		 * BD_SC_NAK -> CR bit +		 * 0x80 -> DE bit +		 */ +		if (rtx->rxbd[keymile_rxIdx].cbd_sc & BD_SC_STATS) { +#ifdef ET_DEBUG +			dprintf("err: %x\n", rtx->rxbd[keymile_rxIdx].cbd_sc); +#endif +		} else if (length > MAX_FRAME_LENGTH) { /* can't happen */ +#ifdef ET_DEBUG +			dprintf("err: packet too big\n"); +#endif +		} else { +			fp = (struct icn_frame *)(MyRxPackets[keymile_rxIdx] + +				INET_HDR_ALIGN - INET_HDR_SIZE); +#ifdef TEST_RX +			dprintf("RX %d: ", keymile_rxIdx); +			hexdump((unsigned char *)MyRxPackets[keymile_rxIdx], +				INET_HDR_ALIGN + INET_HDR_SIZE + 4); +#endif +			/* copy icn header to the beginning */ +			memcpy(fp, ((char *)fp + INET_HDR_SIZE), +				sizeof(struct icn_hdr)); +			app = fp->hdr.application; +			if (app == MGS_NETCONS || app == MGS_TFTP) { +				struct icn_hdr *ih = &fp->hdr; +				unsigned char icn_src_addr = ih->src_addr; +				unsigned char icn_dest_addr = ih->dest_addr; + +				/* +				 * expand header by INET_HDR_SIZE +				 */ +				length += INET_HDR_SIZE; +				/* initalize header */ +				memset((char *)fp->data, 0x00, INET_HDR_SIZE); +				ep = (Ethernet_t *)fp->data; +				/* set MACs */ +				ep->et_dest[0] = icn_dest_addr; +				ep->et_src[0] = icn_src_addr; +				ep->et_protlen = htons(PROT_IP); +				/* set ip stuff */ +				ip = (IP_t *)(fp->data + ETHER_HDR_SIZE); +				/* set ip addresses */ +				ip->ip_src = cachedNumbers[IP_SERVER]; +				ip->ip_dst = cachedNumbers[IP_ADDR]; +				/* ip length */ +				ip->ip_len = htons(length - ETHER_HDR_SIZE - +					REMOVE); +				/* ip proto */ +				ip->ip_p = IPPROTO_UDP; +				switch (app) { +				case MGS_TFTP: +					/* swap src/dst port numbers */ +					ip->udp_src = (ushort) +						cachedNumbers[TFTP_DST_PORT]; +					ip->udp_dst = (ushort) +						cachedNumbers[TFTP_SRC_PORT]; +					ip->udp_len = ip->ip_len - +						IP_HDR_SIZE_NO_UDP; +					ip->udp_xsum = 0; +					break; +				case MGS_NETCONS: +					ip->udp_src = (ushort) +						cachedNumbers[NETCONS_PORT]; +					/* +					 * in drivers/net/netconsole.c src port +					 * equals dest port +					 */ +					ip->udp_dst = ip->udp_src; +					ip->udp_len = ip->ip_len - +						IP_HDR_SIZE_NO_UDP; +					ip->udp_xsum = 0; +					break; +				} +				/* ip version */ +				ip->ip_hl_v = (0x40) | (0x0f & +					(IP_HDR_SIZE_NO_UDP / 4)); +				ip->ip_tos = 0; +				ip->ip_id = 0; +				/* flags, fragment offset */ +				ip->ip_off = htons(0x4000); +				ip->ip_ttl = 255; /* time to live */ +				/* have to fixup the checksum */ +				ip->ip_sum = ~NetCksum((uchar *)ip, +					IP_HDR_SIZE_NO_UDP / 2); +				/* +				 * Pass the packet up to the protocol layers +				 * but remove dest_addr, src_addr, application +				 * and the CRC. +				 */ +#ifdef TEST_RX +				hexdump((unsigned char *)fp->data, +					INET_HDR_SIZE + 4); +#endif +				NetReceive(fp->data, length - REMOVE); +			} else { +				/* +				 * the other application types are not yet +				 * supported by u-boot. +				 */ +				/* normally drop it */ +#ifdef TEST_NO +				/* send it anyway */ +				fp = (struct icn_frame *) +					(MyRxPackets[keymile_rxIdx] + +						INET_HDR_ALIGN); +				NetReceive(fp->data, length - REMOVE); +#endif +			} +		} + +		/* Give the buffer back to the SCC. */ +		rtx->rxbd[keymile_rxIdx].cbd_datlen = 0; + +		/* wrap around buffer index when necessary */ +		if ((keymile_rxIdx + 1) >= HDLC_PKTBUFSRX) { +			rtx->rxbd[HDLC_PKTBUFSRX - 1].cbd_sc = +				(BD_SC_WRAP | BD_SC_EMPTY); +			keymile_rxIdx = 0; +		} else { +			rtx->rxbd[keymile_rxIdx].cbd_sc = BD_SC_EMPTY; +			keymile_rxIdx++; +		} +	} +	return length; +} + +#ifdef TEST_IT +/* simple send test routine */ +int hdlc_enet_stest(struct cmd_tbl_s *a, int b, int c, char **d) +{ +	unsigned char pkt[2]; +	int ret; + +	dprintf("enter stest\n"); +	/* may have to initialize things */ +	if (seth->state != ETH_STATE_ACTIVE) { +		/* the bd_t* is not used */ +		if (seth->init(seth, NULL) >= 0) +			seth->state = ETH_STATE_ACTIVE; +	} +	pkt[0] = 0xea; +	pkt[1] = 0xae; +	ret = keymile_hdlc_enet_send(seth, pkt, 2); +	dprintf("return from send %x\n", ret); +	dprintf("exit stest\n"); +	return ret; +} +U_BOOT_CMD( +	stest, 1, 1, hdlc_enet_stest, +	"stest	- simple send test for hdlc_enet\n", +	"no arguments\n" +); +/* simple receive test routine */ +int hdlc_enet_rtest(struct cmd_tbl_s *a, int b, int c, char **d) +{ +	int ret; + +	dprintf("enter rtest\n"); +	/* may have to initialize things */ +	if (seth->state != ETH_STATE_ACTIVE) { +		/* the bd_t* is not used */ +		if (seth->init(seth, NULL) >= 0) +			seth->state = ETH_STATE_ACTIVE; +	} +	ret = keymile_hdlc_enet_recv(seth); +	dprintf("return from recv %x\n", ret); +	dprintf("exit rtest\n"); +	return ret; +} +U_BOOT_CMD( +	rtest, 1, 1, hdlc_enet_rtest, +	"rtest	- simple receive test for hdlc_enet\n", +	"no arguments\n" +); +#endif + +#endif /* CONFIG_KEYMILE_HDLC_ENET */ |