bios: serial, network and flash boot support
authorSebastien Bourdeauducq <sebastien@milkymist.org>
Mon, 21 May 2012 20:57:12 +0000 (22:57 +0200)
committerSebastien Bourdeauducq <sebastien@milkymist.org>
Mon, 21 May 2012 20:57:12 +0000 (22:57 +0200)
software/bios/Makefile
software/bios/boot-helper.S [new file with mode: 0644]
software/bios/boot.c [new file with mode: 0644]
software/bios/boot.h [new file with mode: 0644]
software/bios/main.c
software/bios/microudp.c [new file with mode: 0644]
software/bios/microudp.h [new file with mode: 0644]
software/bios/tftp.c [new file with mode: 0644]
software/bios/tftp.h [new file with mode: 0644]
software/include/hw/minimac.h [new file with mode: 0644]

index e7f001544cbf6d2b4bb5300349f66e1563120aa2..1410b63d6f8e6f13a31577dab66b27f58a5d15c9 100644 (file)
@@ -1,7 +1,7 @@
 M2DIR=../..
 include $(M2DIR)/software/include.mak
 
-OBJECTS=crt0.o isr.o ddrinit.o main.o
+OBJECTS=crt0.o isr.o ddrinit.o timer.o main.o microudp.o tftp.o boot-helper.o boot.o
 
 all: bios.bin
 
diff --git a/software/bios/boot-helper.S b/software/bios/boot-helper.S
new file mode 100644 (file)
index 0000000..da99182
--- /dev/null
@@ -0,0 +1,10 @@
+.section    .text, "ax", @progbits
+.global     boot_helper
+boot_helper:
+       /* Invalidate instruction cache */
+       wcsr ICC, r0
+       nop
+       nop
+       nop
+       nop
+       call r5
diff --git a/software/bios/boot.c b/software/bios/boot.c
new file mode 100644 (file)
index 0000000..f4227e3
--- /dev/null
@@ -0,0 +1,284 @@
+/*
+ * Milkymist SoC (Software)
+ * Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 Sebastien Bourdeauducq
+ *
+ * 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, version 3 of the License.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <console.h>
+#include <uart.h>
+#include <system.h>
+#include <board.h>
+#include <extra/crc.h>
+#include <sfl.h>
+#include <string.h>
+#include <irq.h>
+
+#include <hw/flash.h>
+#include <hw/mem.h>
+
+#include "timer.h"
+#include "microudp.h"
+#include "tftp.h"
+#include "boot.h"
+
+extern int rescue;
+extern void boot_helper(unsigned int r1, unsigned int r2, unsigned int r3, unsigned int r4, unsigned int addr);
+
+static void __attribute__((noreturn)) boot(unsigned int r1, unsigned int r2, unsigned int r3, unsigned int r4, unsigned int addr)
+{
+       printf("Executing booted program.\n");
+       uart_sync();
+       irq_setmask(0);
+       irq_setie(0);
+       boot_helper(r1, r2, r3, r4, addr);
+       while(1);
+}
+
+static int check_ack(void)
+{
+       int recognized;
+       static const char str[SFL_MAGIC_LEN] = SFL_MAGIC_ACK;
+
+       timer_enable(0);
+       timer_set_reload(0);
+       timer_set_counter(get_system_frequency()/4);
+       timer_enable(1);
+       recognized = 0;
+       while(timer_get()) {
+               if(uart_read_nonblock()) {
+                       char c;
+                       c = uart_read();
+                       if(c == str[recognized]) {
+                               recognized++;
+                               if(recognized == SFL_MAGIC_LEN)
+                                       return 1;
+                       } else {
+                               if(c == str[0])
+                                       recognized = 1;
+                               else
+                                       recognized = 0;
+                       }
+               }
+       }
+       return 0;
+}
+
+#define MAX_FAILED 5
+
+void serialboot(void)
+{
+       struct sfl_frame frame;
+       int failed;
+       unsigned int cmdline_adr, initrdstart_adr, initrdend_adr;
+       static const char str[SFL_MAGIC_LEN+1] = SFL_MAGIC_REQ;
+       const char *c;
+
+       printf("Booting from serial...\n");
+
+       c = str;
+       while(*c) {
+               uart_write(*c);
+               c++;
+       }
+       if(!check_ack()) {
+               printf("Timeout\n");
+               return;
+       }
+
+       failed = 0;
+       cmdline_adr = initrdstart_adr = initrdend_adr = 0;
+       while(1) {
+               int i;
+               int actualcrc;
+               int goodcrc;
+
+               /* Grab one frame */
+               frame.length = uart_read();
+               frame.crc[0] = uart_read();
+               frame.crc[1] = uart_read();
+               frame.cmd = uart_read();
+               for(i=0;i<frame.length;i++)
+                       frame.payload[i] = uart_read();
+
+               /* Check CRC */
+               actualcrc = ((int)frame.crc[0] << 8)|(int)frame.crc[1];
+               goodcrc = crc16(&frame.cmd, frame.length+1);
+               if(actualcrc != goodcrc) {
+                       failed++;
+                       if(failed == MAX_FAILED) {
+                               printf("Too many consecutive errors, aborting");
+                               return;
+                       }
+                       uart_write(SFL_ACK_CRCERROR);
+                       continue;
+               }
+
+               /* CRC OK */
+               switch(frame.cmd) {
+                       case SFL_CMD_ABORT:
+                               failed = 0;
+                               uart_write(SFL_ACK_SUCCESS);
+                               return;
+                       case SFL_CMD_LOAD: {
+                               char *writepointer;
+
+                               failed = 0;
+                               writepointer = (char *)(
+                                        ((unsigned int)frame.payload[0] << 24)
+                                       |((unsigned int)frame.payload[1] << 16)
+                                       |((unsigned int)frame.payload[2] << 8)
+                                       |((unsigned int)frame.payload[3] << 0));
+                               for(i=4;i<frame.length;i++)
+                                       *(writepointer++) = frame.payload[i];
+                               uart_write(SFL_ACK_SUCCESS);
+                               break;
+                       }
+                       case SFL_CMD_JUMP: {
+                               unsigned int addr;
+
+                               failed = 0;
+                               addr =  ((unsigned int)frame.payload[0] << 24)
+                                       |((unsigned int)frame.payload[1] << 16)
+                                       |((unsigned int)frame.payload[2] << 8)
+                                       |((unsigned int)frame.payload[3] << 0);
+                               uart_write(SFL_ACK_SUCCESS);
+                               boot(cmdline_adr, initrdstart_adr, initrdend_adr, rescue, addr);
+                               break;
+                       }
+                       case SFL_CMD_CMDLINE:
+                               failed = 0;
+                               cmdline_adr =  ((unsigned int)frame.payload[0] << 24)
+                                             |((unsigned int)frame.payload[1] << 16)
+                                             |((unsigned int)frame.payload[2] << 8)
+                                             |((unsigned int)frame.payload[3] << 0);
+                               uart_write(SFL_ACK_SUCCESS);
+                               break;
+                       case SFL_CMD_INITRDSTART:
+                               failed = 0;
+                               initrdstart_adr =  ((unsigned int)frame.payload[0] << 24)
+                                                 |((unsigned int)frame.payload[1] << 16)
+                                                 |((unsigned int)frame.payload[2] << 8)
+                                                 |((unsigned int)frame.payload[3] << 0);
+                               uart_write(SFL_ACK_SUCCESS);
+                               break;
+                       case SFL_CMD_INITRDEND:
+                               failed = 0;
+                               initrdend_adr =  ((unsigned int)frame.payload[0] << 24)
+                                               |((unsigned int)frame.payload[1] << 16)
+                                               |((unsigned int)frame.payload[2] << 8)
+                                               |((unsigned int)frame.payload[3] << 0);
+                               uart_write(SFL_ACK_SUCCESS);
+                               break;
+                       default:
+                               failed++;
+                               if(failed == MAX_FAILED) {
+                                       printf("Too many consecutive errors, aborting");
+                                       return;
+                               }
+                               uart_write(SFL_ACK_UNKNOWN);
+                               break;
+               }
+       }
+}
+
+#define LOCALIP1 192
+#define LOCALIP2 168
+#define LOCALIP3 0
+#define LOCALIP4 42
+#define REMOTEIP1 192
+#define REMOTEIP2 168
+#define REMOTEIP3 0
+#define REMOTEIP4 14
+
+static int tftp_get_v(unsigned int ip, const char *filename, char *buffer)
+{
+       int r;
+
+       r = tftp_get(ip, filename, buffer);
+       if(r > 0)
+               printf("Successfully downloaded %d bytes from %s over TFTP\n", r, filename);
+       else
+               printf("Unable to download %s over TFTP\n", filename);
+       return r;
+}
+
+void netboot(void)
+{
+       int size;
+       unsigned int cmdline_adr, initrdstart_adr, initrdend_adr;
+       unsigned int ip;
+       unsigned char *macadr = (unsigned char *)FLASH_OFFSET_MAC_ADDRESS;
+
+       printf("Booting from network...\n");
+       printf("Local IP : %d.%d.%d.%d\n", LOCALIP1, LOCALIP2, LOCALIP3, LOCALIP4);
+       printf("Remote IP: %d.%d.%d.%d\n", REMOTEIP1, REMOTEIP2, REMOTEIP3, REMOTEIP4);
+
+       ip = IPTOINT(REMOTEIP1, REMOTEIP2, REMOTEIP3, REMOTEIP4);
+
+       microudp_start(macadr, IPTOINT(LOCALIP1, LOCALIP2, LOCALIP3, LOCALIP4));
+
+       if(tftp_get_v(ip, "boot.bin", (void *)SDRAM_BASE) <= 0) {
+               printf("Network boot failed\n");
+               return;
+       }
+
+       cmdline_adr = SDRAM_BASE+0x1000000;
+       size = tftp_get_v(ip, "cmdline.txt", (void *)cmdline_adr);
+       if(size <= 0) {
+               printf("No command line parameters found\n");
+               cmdline_adr = 0;
+       } else
+               *((char *)(cmdline_adr+size)) = 0x00;
+
+       initrdstart_adr = SDRAM_BASE+0x1002000;
+       size = tftp_get_v(ip, "initrd.bin", (void *)initrdstart_adr);
+       if(size <= 0) {
+               printf("No initial ramdisk found\n");
+               initrdstart_adr = 0;
+               initrdend_adr = 0;
+       } else
+               initrdend_adr = initrdstart_adr + size;
+
+       boot(cmdline_adr, initrdstart_adr, initrdend_adr, rescue, SDRAM_BASE);
+}
+
+void flashboot(void)
+{
+       unsigned int *flashbase;
+       unsigned int length;
+       unsigned int crc;
+       unsigned int got_crc;
+
+       printf("Booting from flash...\n");
+       if(rescue)
+               flashbase = (unsigned int *)FLASH_OFFSET_RESCUE_APP;
+       else
+               flashbase = (unsigned int *)FLASH_OFFSET_REGULAR_APP;
+       length = *flashbase++;
+       crc = *flashbase++;
+       if((length < 32) || (length > 4*1024*1024)) {
+               printf("Error: Invalid flash boot image length\n");
+               return;
+       }
+       
+       printf("Loading %d bytes from flash...\n", length);
+       memcpy((void *)SDRAM_BASE, flashbase, length);
+       got_crc = crc32((unsigned char *)SDRAM_BASE, length);
+       if(crc != got_crc) {
+               printf("CRC failed (expected %08x, got %08x)\n", crc, got_crc);
+               return;
+       }
+       boot(0, 0, 0, rescue, SDRAM_BASE);
+}
diff --git a/software/bios/boot.h b/software/bios/boot.h
new file mode 100644 (file)
index 0000000..58a33f5
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Milkymist SoC (Software)
+ * Copyright (C) 2007, 2008, 2009, 2010, 2012 Sebastien Bourdeauducq
+ *
+ * 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, version 3 of the License.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __BOOT_H
+#define __BOOT_H
+
+void serialboot(void);
+void netboot(void);
+void flashboot(void);
+
+#endif /* __BOOT_H */
index 4d1eaafe71e1999ae18acee7e457beb3971f421e..dc5a3e2039c7aa786c4f1b29c860bd0067fd1b9d 100644 (file)
 #include <extra/crc.h>
 
 #include <hw/flash.h>
+#include <hw/minimac.h>
 
 #include "ddrinit.h"
+#include "timer.h"
+#include "boot.h"
 
 enum {
        CSR_IE = 1, CSR_IM, CSR_IP, CSR_ICC, CSR_DCC, CSR_CC, CSR_CFG, CSR_EBA,
@@ -311,9 +314,10 @@ static void help(void)
        puts("crc        - compute CRC32 of a part of the address space");
        puts("rcsr       - read processor CSR");
        puts("wcsr       - write processor CSR");
+       puts("netboot    - boot via TFTP");
+       puts("serialboot - boot via SFL");
+       puts("flashboot  - boot from flash");
        puts("version    - display version");
-       puts("reboot     - system reset");
-       puts("reconf     - reload FPGA configuration");
 }
 
 static char *get_token(char **str)
@@ -343,6 +347,10 @@ static void do_command(char *c)
        else if(strcmp(token, "mc") == 0) mc(get_token(&c), get_token(&c), get_token(&c));
        else if(strcmp(token, "crc") == 0) crc(get_token(&c), get_token(&c));
 
+       else if(strcmp(token, "flashboot") == 0) flashboot();
+       else if(strcmp(token, "serialboot") == 0) serialboot();
+       else if(strcmp(token, "netboot") == 0) netboot();
+       
        else if(strcmp(token, "version") == 0) puts(VERSION);
 
        else if(strcmp(token, "help") == 0) help();
@@ -390,6 +398,17 @@ static void crcbios(void)
        }
 }
 
+static void ethreset(void)
+{
+       CSR_MINIMAC_PHYRST = 0;
+       busy_wait(2);
+       /* that pesky ethernet PHY needs two resets at times... */
+       CSR_MINIMAC_PHYRST = 1;
+       busy_wait(2);
+       CSR_MINIMAC_PHYRST = 0;
+       busy_wait(2);
+}
+
 static void print_mac(void)
 {
        unsigned char *macadr = (unsigned char *)FLASH_OFFSET_MAC_ADDRESS;
@@ -437,6 +456,54 @@ static void readstr(char *s, int size)
        }
 }
 
+static int test_user_abort(void)
+{
+       char c;
+
+       printf("Automatic boot in 2 seconds...\n");
+       printf("Q/ESC: abort boot\n");
+       printf("F7:    boot from serial\n");
+       printf("F8:    boot from network\n");
+       timer_enable(0);
+       timer_set_reload(0);
+       timer_set_counter(get_system_frequency()*2);
+       timer_enable(1);
+       while(timer_get()) {
+               if(readchar_nonblock()) {
+                       c = readchar();
+                       if((c == 'Q')||(c == '\e')) {
+                               puts("Aborted");
+                               return 0;
+                       }
+                       if(c == 0x06) {
+                               serialboot();
+                               return 0;
+                       }
+                       if(c == 0x07) {
+                               netboot();
+                               return 0;
+                       }
+               }
+       }
+       return 1;
+}
+
+static void boot_sequence(void)
+{
+       if(test_user_abort()) {
+               if(rescue) {
+                       netboot();
+                       serialboot();
+                       flashboot();
+               } else {
+                       flashboot();
+                       netboot();
+                       serialboot();
+               }
+               printf("No boot medium found\n");
+       }
+}
+
 int main(int i, char **c)
 {
        char buffer[64];
@@ -452,14 +519,13 @@ int main(int i, char **c)
        if(rescue)
                printf("Rescue mode\n");
        board_init();
+       ethreset();
        print_mac();
        ddr_ok = ddrinit();
-       if(ddr_ok) {
-               printf("Booting...\n");
-       } else {
+       if(ddr_ok)
+               boot_sequence();
+       else
                printf("Memory initialization failed\n");
-       }
-       
        
        while(1) {
                putsnonl("\e[1mBIOS>\e[0m ");
diff --git a/software/bios/microudp.c b/software/bios/microudp.c
new file mode 100644 (file)
index 0000000..c80c60b
--- /dev/null
@@ -0,0 +1,400 @@
+/*
+ * Milkymist SoC (Software)
+ * Copyright (C) 2007, 2008, 2009, 2010, 2011 Sebastien Bourdeauducq
+ *
+ * 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, version 3 of the License.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <system.h>
+#include <extra/crc.h>
+#include <hw/minimac.h>
+
+#include "microudp.h"
+
+#define ETHERTYPE_ARP 0x0806
+#define ETHERTYPE_IP  0x0800
+
+struct ethernet_header {
+       unsigned char preamble[8];
+       unsigned char destmac[6];
+       unsigned char srcmac[6];
+       unsigned short ethertype;
+} __attribute__((packed));
+
+static void fill_eth_header(struct ethernet_header *h, const unsigned char *destmac, const unsigned char *srcmac, unsigned short ethertype)
+{
+       int i;
+
+       for(i=0;i<7;i++)
+               h->preamble[i] = 0x55;
+       h->preamble[7] = 0xd5;
+       for(i=0;i<6;i++)
+               h->destmac[i] = destmac[i];
+       for(i=0;i<6;i++)
+               h->srcmac[i] = srcmac[i];
+       h->ethertype = ethertype;
+}
+
+#define ARP_HWTYPE_ETHERNET 0x0001
+#define ARP_PROTO_IP        0x0800
+
+#define ARP_OPCODE_REQUEST  0x0001
+#define ARP_OPCODE_REPLY    0x0002
+
+struct arp_frame {
+       unsigned short hwtype;
+       unsigned short proto;
+       unsigned char hwsize;
+       unsigned char protosize;
+       unsigned short opcode;
+       unsigned char sender_mac[6];
+       unsigned int sender_ip;
+       unsigned char target_mac[6];
+       unsigned int target_ip;
+       unsigned char padding[18];
+} __attribute__((packed));
+
+#define IP_IPV4                        0x45
+#define IP_DONT_FRAGMENT       0x4000
+#define IP_TTL                 64
+#define IP_PROTO_UDP           0x11
+
+struct ip_header {
+       unsigned char version;
+       unsigned char diff_services;
+       unsigned short total_length;
+       unsigned short identification;
+       unsigned short fragment_offset;
+       unsigned char ttl;
+       unsigned char proto;
+       unsigned short checksum;
+       unsigned int src_ip;
+       unsigned int dst_ip;
+} __attribute__((packed));
+
+struct udp_header {
+       unsigned short src_port;
+       unsigned short dst_port;
+       unsigned short length;
+       unsigned short checksum;
+} __attribute__((packed));
+
+struct udp_frame {
+       struct ip_header ip;
+       struct udp_header udp;
+       char payload[];
+} __attribute__((packed));
+
+struct ethernet_frame {
+       struct ethernet_header eth_header;
+       union {
+               struct arp_frame arp;
+               struct udp_frame udp;
+       } contents;
+} __attribute__((packed));
+
+typedef union {
+       struct ethernet_frame frame;
+       unsigned char raw[1532];
+} ethernet_buffer;
+
+
+static int rxlen;
+static ethernet_buffer *rxbuffer;
+static ethernet_buffer *rxbuffer0;
+static ethernet_buffer *rxbuffer1;
+static int txlen;
+static ethernet_buffer *txbuffer;
+
+static void send_packet(void)
+{
+       unsigned int crc;
+       
+       crc = crc32(&txbuffer->raw[8], txlen-8);
+       txbuffer->raw[txlen  ] = (crc & 0xff);
+       txbuffer->raw[txlen+1] = (crc & 0xff00) >> 8;
+       txbuffer->raw[txlen+2] = (crc & 0xff0000) >> 16;
+       txbuffer->raw[txlen+3] = (crc & 0xff000000) >> 24;
+       txlen += 4;
+       CSR_MINIMAC_TXCOUNTH = (txlen & 0xff00) >> 8;
+       CSR_MINIMAC_TXCOUNTL = txlen & 0x00ff;
+       CSR_MINIMAC_TXSTART = 1;
+       while(!(CSR_MINIMAC_EV_PENDING & MINIMAC_EV_TX));
+       CSR_MINIMAC_EV_PENDING = MINIMAC_EV_TX;
+}
+
+static unsigned char my_mac[6];
+static unsigned int my_ip;
+
+/* ARP cache - one entry only */
+static unsigned char cached_mac[6];
+static unsigned int cached_ip;
+
+static void process_arp(void)
+{
+       if(rxlen < 68) return;
+       if(rxbuffer->frame.contents.arp.hwtype != ARP_HWTYPE_ETHERNET) return;
+       if(rxbuffer->frame.contents.arp.proto != ARP_PROTO_IP) return;
+       if(rxbuffer->frame.contents.arp.hwsize != 6) return;
+       if(rxbuffer->frame.contents.arp.protosize != 4) return;
+       if(rxbuffer->frame.contents.arp.opcode == ARP_OPCODE_REPLY) {
+               if(rxbuffer->frame.contents.arp.sender_ip == cached_ip) {
+                       int i;
+                       for(i=0;i<6;i++)
+                               cached_mac[i] = rxbuffer->frame.contents.arp.sender_mac[i];
+               }
+               return;
+       }
+       if(rxbuffer->frame.contents.arp.opcode == ARP_OPCODE_REQUEST) {
+               if(rxbuffer->frame.contents.arp.target_ip == my_ip) {
+                       int i;
+                       
+                       fill_eth_header(&txbuffer->frame.eth_header,
+                               rxbuffer->frame.contents.arp.sender_mac,
+                               my_mac,
+                               ETHERTYPE_ARP);
+                       txlen = 68;
+                       txbuffer->frame.contents.arp.hwtype = ARP_HWTYPE_ETHERNET;
+                       txbuffer->frame.contents.arp.proto = ARP_PROTO_IP;
+                       txbuffer->frame.contents.arp.hwsize = 6;
+                       txbuffer->frame.contents.arp.protosize = 4;
+                       txbuffer->frame.contents.arp.opcode = ARP_OPCODE_REPLY;
+                       txbuffer->frame.contents.arp.sender_ip = my_ip;
+                       for(i=0;i<6;i++)
+                               txbuffer->frame.contents.arp.sender_mac[i] = my_mac[i];
+                       txbuffer->frame.contents.arp.target_ip = rxbuffer->frame.contents.arp.sender_ip;
+                       for(i=0;i<6;i++)
+                               txbuffer->frame.contents.arp.target_mac[i] = rxbuffer->frame.contents.arp.sender_mac[i];
+                       send_packet();
+               }
+               return;
+       }
+}
+
+static const unsigned char broadcast[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+int microudp_arp_resolve(unsigned int ip)
+{
+       int i;
+       int tries;
+       int timeout;
+
+       if(cached_ip == ip) {
+               for(i=0;i<6;i++)
+                       if(cached_mac[i]) return 1;
+       }
+       cached_ip = ip;
+       for(i=0;i<6;i++)
+               cached_mac[i] = 0;
+
+       for(tries=0;tries<5;tries++) {
+               /* Send an ARP request */
+               fill_eth_header(&txbuffer->frame.eth_header,
+                               broadcast,
+                               my_mac,
+                               ETHERTYPE_ARP);
+               txlen = 68;
+               txbuffer->frame.contents.arp.hwtype = ARP_HWTYPE_ETHERNET;
+               txbuffer->frame.contents.arp.proto = ARP_PROTO_IP;
+               txbuffer->frame.contents.arp.hwsize = 6;
+               txbuffer->frame.contents.arp.protosize = 4;
+               txbuffer->frame.contents.arp.opcode = ARP_OPCODE_REQUEST;
+               txbuffer->frame.contents.arp.sender_ip = my_ip;
+               for(i=0;i<6;i++)
+                       txbuffer->frame.contents.arp.sender_mac[i] = my_mac[i];
+               txbuffer->frame.contents.arp.target_ip = ip;
+               for(i=0;i<6;i++)
+                       txbuffer->frame.contents.arp.target_mac[i] = 0;
+               send_packet();
+
+               /* Do we get a reply ? */
+               for(timeout=0;timeout<2000000;timeout++) {
+                       microudp_service();
+                       for(i=0;i<6;i++)
+                               if(cached_mac[i]) return 1;
+               }
+       }
+
+       return 0;
+}
+
+static unsigned short ip_checksum(unsigned int r, void *buffer, unsigned int length, int complete)
+{
+       unsigned char *ptr;
+       int i;
+
+       ptr = (unsigned char *)buffer;
+       length >>= 1;
+
+       for(i=0;i<length;i++)
+               r += ((unsigned int)(ptr[2*i]) << 8)|(unsigned int)(ptr[2*i+1]) ;
+
+       /* Add overflows */
+       while(r >> 16)
+               r = (r & 0xffff) + (r >> 16);
+
+       if(complete) {
+               r = ~r;
+               r &= 0xffff;
+               if(r == 0) r = 0xffff;
+       }
+       return r;
+}
+
+void *microudp_get_tx_buffer(void)
+{
+       return txbuffer->frame.contents.udp.payload;
+}
+
+struct pseudo_header {
+       unsigned int src_ip;
+       unsigned int dst_ip;
+       unsigned char zero;
+       unsigned char proto;
+       unsigned short length;
+} __attribute__((packed));
+
+int microudp_send(unsigned short src_port, unsigned short dst_port, unsigned int length)
+{
+       struct pseudo_header h;
+       unsigned int r;
+       
+       if((cached_mac[0] == 0) && (cached_mac[1] == 0) && (cached_mac[2] == 0)
+               && (cached_mac[3] == 0) && (cached_mac[4] == 0) && (cached_mac[5] == 0))
+               return 0;
+
+       txlen = length + sizeof(struct ethernet_header) + sizeof(struct udp_frame) + 8;
+       if(txlen < 72) txlen = 72;
+       
+       fill_eth_header(&txbuffer->frame.eth_header,
+               cached_mac,
+               my_mac,
+               ETHERTYPE_IP);
+       
+       txbuffer->frame.contents.udp.ip.version = IP_IPV4;
+       txbuffer->frame.contents.udp.ip.diff_services = 0;
+       txbuffer->frame.contents.udp.ip.total_length = length + sizeof(struct udp_frame);
+       txbuffer->frame.contents.udp.ip.identification = 0;
+       txbuffer->frame.contents.udp.ip.fragment_offset = IP_DONT_FRAGMENT;
+       txbuffer->frame.contents.udp.ip.ttl = IP_TTL;
+       h.proto = txbuffer->frame.contents.udp.ip.proto = IP_PROTO_UDP;
+       txbuffer->frame.contents.udp.ip.checksum = 0;
+       h.src_ip = txbuffer->frame.contents.udp.ip.src_ip = my_ip;
+       h.dst_ip = txbuffer->frame.contents.udp.ip.dst_ip = cached_ip;
+       txbuffer->frame.contents.udp.ip.checksum = ip_checksum(0, &txbuffer->frame.contents.udp.ip,
+               sizeof(struct ip_header), 1);
+
+       txbuffer->frame.contents.udp.udp.src_port = src_port;
+       txbuffer->frame.contents.udp.udp.dst_port = dst_port;
+       h.length = txbuffer->frame.contents.udp.udp.length = length + sizeof(struct udp_header);
+       txbuffer->frame.contents.udp.udp.checksum = 0;
+
+       h.zero = 0;
+       r = ip_checksum(0, &h, sizeof(struct pseudo_header), 0);
+       if(length & 1) {
+               txbuffer->frame.contents.udp.payload[length] = 0;
+               length++;
+       }
+       r = ip_checksum(r, &txbuffer->frame.contents.udp.udp,
+               sizeof(struct udp_header)+length, 1);
+       txbuffer->frame.contents.udp.udp.checksum = r;
+       
+       send_packet();
+
+       return 1;
+}
+
+static udp_callback rx_callback;
+
+static void process_ip(void)
+{
+       if(rxlen < (sizeof(struct ethernet_header)+sizeof(struct udp_frame))) return;
+       /* We don't verify UDP and IP checksums and rely on the Ethernet checksum solely */
+       if(rxbuffer->frame.contents.udp.ip.version != IP_IPV4) return;
+       // check disabled for QEMU compatibility
+       //if(rxbuffer->frame.contents.udp.ip.diff_services != 0) return;
+       if(rxbuffer->frame.contents.udp.ip.total_length < sizeof(struct udp_frame)) return;
+       // check disabled for QEMU compatibility
+       //if(rxbuffer->frame.contents.udp.ip.fragment_offset != IP_DONT_FRAGMENT) return;
+       if(rxbuffer->frame.contents.udp.ip.proto != IP_PROTO_UDP) return;
+       if(rxbuffer->frame.contents.udp.ip.dst_ip != my_ip) return;
+       if(rxbuffer->frame.contents.udp.udp.length < sizeof(struct udp_header)) return;
+
+       if(rx_callback)
+               rx_callback(rxbuffer->frame.contents.udp.ip.src_ip, rxbuffer->frame.contents.udp.udp.src_port, rxbuffer->frame.contents.udp.udp.dst_port, rxbuffer->frame.contents.udp.payload, rxbuffer->frame.contents.udp.udp.length-sizeof(struct udp_header));
+}
+
+void microudp_set_callback(udp_callback callback)
+{
+       rx_callback = callback;
+}
+
+static void process_frame(void)
+{
+       int i;
+       unsigned int received_crc;
+       unsigned int computed_crc;
+
+       flush_cpu_dcache();
+       for(i=0;i<7;i++)
+               if(rxbuffer->frame.eth_header.preamble[i] != 0x55) return;
+       if(rxbuffer->frame.eth_header.preamble[7] != 0xd5) return;
+       received_crc = ((unsigned int)rxbuffer->raw[rxlen-1] << 24)
+               |((unsigned int)rxbuffer->raw[rxlen-2] << 16)
+               |((unsigned int)rxbuffer->raw[rxlen-3] <<  8)
+               |((unsigned int)rxbuffer->raw[rxlen-4]);
+       computed_crc = crc32(&rxbuffer->raw[8], rxlen-12);
+       if(received_crc != computed_crc) return;
+
+       rxlen -= 4; /* strip CRC here to be consistent with TX */
+       if(rxbuffer->frame.eth_header.ethertype == ETHERTYPE_ARP) process_arp();
+       else if(rxbuffer->frame.eth_header.ethertype == ETHERTYPE_IP) process_ip();
+}
+
+void microudp_start(unsigned char *macaddr, unsigned int ip)
+{
+       int i;
+
+       CSR_MINIMAC_EV_PENDING = MINIMAC_EV_RX0 | MINIMAC_EV_RX1 | MINIMAC_EV_TX;
+       
+       rxbuffer0 = (ethernet_buffer *)MINIMAC_RX0_BASE;
+       rxbuffer1 = (ethernet_buffer *)MINIMAC_RX1_BASE;
+       txbuffer = (ethernet_buffer *)MINIMAC_TX_BASE;
+
+       for(i=0;i<6;i++)
+               my_mac[i] = macaddr[i];
+       my_ip = ip;
+
+       cached_ip = 0;
+       for(i=0;i<6;i++)
+               cached_mac[i] = 0;
+
+       rx_callback = (udp_callback)0;
+}
+
+void microudp_service(void)
+{
+       if(CSR_MINIMAC_EV_PENDING & MINIMAC_EV_RX0) {
+               rxlen = (CSR_MINIMAC_RXCOUNT0H << 8) | CSR_MINIMAC_RXCOUNT0L;
+               rxbuffer = rxbuffer0;
+               process_frame();
+               CSR_MINIMAC_EV_PENDING = MINIMAC_EV_RX0;
+       }
+       if(CSR_MINIMAC_EV_PENDING & MINIMAC_EV_RX1) {
+               rxlen = (CSR_MINIMAC_RXCOUNT1H << 8) | CSR_MINIMAC_RXCOUNT1L;
+               rxbuffer = rxbuffer1;
+               process_frame();
+               CSR_MINIMAC_EV_PENDING = MINIMAC_EV_RX1;
+       }
+}
diff --git a/software/bios/microudp.h b/software/bios/microudp.h
new file mode 100644 (file)
index 0000000..46cac23
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Milkymist SoC (Software)
+ * Copyright (C) 2007, 2008, 2009, 2010, 2011 Sebastien Bourdeauducq
+ *
+ * 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, version 3 of the License.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __NET_MICROUDP_H
+#define __NET_MICROUDP_H
+
+#define IPTOINT(a, b, c, d) ((a << 24)|(b << 16)|(c << 8)|d)
+
+#define MICROUDP_BUFSIZE (5*1532)
+
+typedef void (*udp_callback)(unsigned int src_ip, unsigned short src_port, unsigned short dst_port, void *data, unsigned int length);
+
+void microudp_start(unsigned char *macaddr, unsigned int ip);
+int microudp_arp_resolve(unsigned int ip);
+void *microudp_get_tx_buffer(void);
+int microudp_send(unsigned short src_port, unsigned short dst_port, unsigned int length);
+void microudp_set_callback(udp_callback callback);
+void microudp_service(void);
+
+#endif /* __NET_MICROUDP_H */
diff --git a/software/bios/tftp.c b/software/bios/tftp.c
new file mode 100644 (file)
index 0000000..d155b9f
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * Milkymist SoC (Software)
+ * Copyright (C) 2007, 2008, 2009, 2010, 2012 Sebastien Bourdeauducq
+ *
+ * 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, version 3 of the License.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+
+#include "microudp.h"
+#include "tftp.h"
+
+#define PORT_OUT       69
+#define PORT_IN                7642
+
+static int format_request(char *buf, const char *filename)
+{
+       *buf++ = 0x00; /* Opcode: Request */
+       *buf++ = 0x01;
+       strcpy(buf, filename);
+       buf += strlen(filename);
+       *buf++ = 0x00;
+       *buf++ = 'o';
+       *buf++ = 'c';
+       *buf++ = 't';
+       *buf++ = 'e';
+       *buf++ = 't';
+       *buf++ = 0x00;
+       return 9+strlen(filename);
+}
+
+static int format_ack(char *buf, unsigned short block)
+{
+       *buf++ = 0x00; /* Opcode: Ack */
+       *buf++ = 0x04;
+       *buf++ = (block & 0xff00) >> 8;
+       *buf++ = (block & 0x00ff);
+       return 4;
+}
+
+static char *packet_data;
+static int total_length;
+static int transfer_finished;
+static char *dst_buffer;
+
+static void rx_callback(unsigned int src_ip, unsigned short src_port, unsigned short dst_port, void *_data, unsigned int length)
+{
+       unsigned char *data = (unsigned char *)_data;
+       unsigned short opcode;
+       unsigned short block;
+       int i;
+       int offset;
+       
+       if(length < 4) return;
+       if(dst_port != PORT_IN) return;
+       opcode = ((unsigned short)(data[0]) << 8)|((unsigned short)(data[1]));
+       block = ((unsigned short)(data[2]) << 8)|((unsigned short)(data[3]));
+       if(block < 1) return;
+       if(opcode == 3) { /* Data */
+               length -= 4;
+               offset = (block-1)*512;
+               for(i=0;i<length;i++)
+                       dst_buffer[offset+i] = data[i+4];
+               total_length += length;
+               if(length < 512)
+                       transfer_finished = 1;
+               
+               length = format_ack(packet_data, block);
+               microudp_send(PORT_IN, src_port, length);
+       }
+       if(opcode == 5) { /* Error */
+               total_length = -1;
+               transfer_finished = 1;
+       }
+}
+
+int tftp_get(unsigned int ip, const char *filename, char *buffer)
+{
+       int len;
+       int tries;
+       int i;
+       int length_before;
+       
+       if(!microudp_arp_resolve(ip))
+               return -1;
+
+       microudp_set_callback(rx_callback);
+
+       packet_data = microudp_get_tx_buffer();
+       dst_buffer = buffer;
+
+       total_length = 0;
+       transfer_finished = 0;
+       tries = 5;
+       while(1) {
+               len = format_request(packet_data, filename);
+               microudp_send(PORT_IN, PORT_OUT, len);
+               for(i=0;i<2000000;i++) {
+                       microudp_service();
+                       if((total_length > 0) || transfer_finished) break;
+               }
+               if((total_length > 0) || transfer_finished) break;
+               tries--;
+               if(tries == 0) {
+                       microudp_set_callback(NULL);
+                       return -1;
+               }
+       }
+
+       length_before = total_length;
+       while(!transfer_finished) {
+               if(length_before != total_length) {
+                       i = 12000000;
+                       length_before = total_length;
+               }
+               if(i-- == 0) {
+                       microudp_set_callback(NULL);
+                       return -1;
+               }
+               microudp_service();
+       }
+
+       microudp_set_callback(NULL);
+
+       return total_length;
+}
diff --git a/software/bios/tftp.h b/software/bios/tftp.h
new file mode 100644 (file)
index 0000000..2760933
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Milkymist SoC (Software)
+ * Copyright (C) 2007, 2008, 2009, 2010 Sebastien Bourdeauducq
+ *
+ * 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, version 3 of the License.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __NET_TFTP_H
+#define __NET_TFTP_H
+
+int tftp_get(unsigned int ip, const char *filename, char *buffer);
+
+#endif /* __NET_TFTP_H */
+
diff --git a/software/include/hw/minimac.h b/software/include/hw/minimac.h
new file mode 100644 (file)
index 0000000..c9c9d4d
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Milkymist SoC (Software)
+ * Copyright (C) 2007, 2008, 2009, 2010, 2012 Sebastien Bourdeauducq
+ *
+ * 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, version 3 of the License.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __HW_MINIMAC_H
+#define __HW_MINIMAC_H
+
+#include <hw/common.h>
+#include <csrbase.h>
+
+#define MINIMAC_CSR(x)         MMPTR(MINIMAC_BASE+(x))
+
+#define CSR_MINIMAC_PHYRST     MINIMAC_CSR(0x00)
+
+#define CSR_MINIMAC_RXCOUNT0H  MINIMAC_CSR(0x04)
+#define CSR_MINIMAC_RXCOUNT0L  MINIMAC_CSR(0x08)
+#define CSR_MINIMAC_RXCOUNT1H  MINIMAC_CSR(0x0C)
+#define CSR_MINIMAC_RXCOUNT1L  MINIMAC_CSR(0x10)
+
+#define CSR_MINIMAC_TXCOUNTH   MINIMAC_CSR(0x14)
+#define CSR_MINIMAC_TXCOUNTL   MINIMAC_CSR(0x18)
+#define CSR_MINIMAC_TXSTART    MINIMAC_CSR(0x1C)
+
+#define CSR_MINIMAC_EV_STAT    MINIMAC_CSR(0x20)
+#define CSR_MINIMAC_EV_PENDING MINIMAC_CSR(0x24)
+#define CSR_MINIMAC_EV_ENABLE  MINIMAC_CSR(0x28)
+
+#define MINIMAC_EV_RX0         0x1
+#define MINIMAC_EV_RX1         0x2
+#define MINIMAC_EV_TX          0x4
+
+#define MINIMAC_RX0_BASE       0xb0000000
+#define MINIMAC_RX1_BASE       0xb0000800
+#define MINIMAC_TX_BASE                0xb0001000
+
+#endif /* __HW_MINIMAC_H */