blob: 57978800f6917e2fb65d649b1cb45e0bda7e348c [file] [log] [blame]
/*
* (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,
"simple send test for hdlc_enet",
"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,
"simple receive test for hdlc_enet",
"no arguments\n"
);
#endif
#endif /* CONFIG_KEYMILE_HDLC_ENET */