/* * (C) Copyright 2008 * Gary Jennejohn, DENX Software Engineering GmbH, garyj@denx.de. * * Based in part on arch/ppc/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 #include #include #ifdef CONFIG_KEYMILE_HDLC_ENET #ifdef TEST_IT #include #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", "" ); /* 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", "" ); #endif #endif /* CONFIG_KEYMILE_HDLC_ENET */