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