aboutsummaryrefslogtreecommitdiffstats
path: root/firmware/usrp3/n230
diff options
context:
space:
mode:
Diffstat (limited to 'firmware/usrp3/n230')
-rw-r--r--firmware/usrp3/n230/CMakeLists.txt35
-rwxr-xr-xfirmware/usrp3/n230/n230_burner.py359
-rwxr-xr-xfirmware/usrp3/n230/n230_debug.py387
-rw-r--r--firmware/usrp3/n230/n230_eeprom.c196
-rw-r--r--firmware/usrp3/n230/n230_eth_handlers.c340
-rw-r--r--firmware/usrp3/n230/n230_eth_handlers.h48
-rw-r--r--firmware/usrp3/n230/n230_init.c125
-rw-r--r--firmware/usrp3/n230/n230_init.h28
-rw-r--r--firmware/usrp3/n230/n230_main.c113
9 files changed, 1631 insertions, 0 deletions
diff --git a/firmware/usrp3/n230/CMakeLists.txt b/firmware/usrp3/n230/CMakeLists.txt
new file mode 100644
index 000000000..6247477f0
--- /dev/null
+++ b/firmware/usrp3/n230/CMakeLists.txt
@@ -0,0 +1,35 @@
+#
+# Copyright 2010-2014,2016 Ettus Research LLC
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+#
+
+########################################################################
+include_directories(${CMAKE_CURRENT_SOURCE_DIR})
+include_directories(${CMAKE_SOURCE_DIR}/../../host/lib/usrp/n230)
+
+list(APPEND n230_sources n230_eeprom.c n230_eth_handlers.c n230_init.c n230_main.c)
+
+########################################################################
+set(GEN_OUTPUTS_BIN_SIZE 0x7fff)
+
+add_executable(n230_main.elf ${n230_sources})
+target_link_libraries(n230_main.elf usrp3fw)
+GEN_OUTPUTS(n230_main.elf n230)
+
+#INSTALL(
+# FILES ${CMAKE_CURRENT_BINARY_DIR}/n230_main.bin
+# DESTINATION share/uhd/images
+# RENAME usrp_n230_fw.bin
+#)
diff --git a/firmware/usrp3/n230/n230_burner.py b/firmware/usrp3/n230/n230_burner.py
new file mode 100755
index 000000000..7b9920de7
--- /dev/null
+++ b/firmware/usrp3/n230/n230_burner.py
@@ -0,0 +1,359 @@
+#!/usr/bin/env python
+#
+# Copyright 2014 Ettus Research LLC
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+#
+
+import optparse
+import math
+import socket
+import struct
+import os.path
+import sys
+from array import array
+
+########################################################################
+# constants
+########################################################################
+N230_FLASH_COMM_UDP_PORT = 49154
+N230_FLASH_COMM_PAYLOAD_SIZE = 128
+N230_FLASH_COMM_SECTOR_SIZE = 65536
+
+N230_FLASH_COMM_FLAGS_ACK = 0x00000001
+N230_FLASH_COMM_FLAGS_CMD_MASK = 0x00000FF0
+N230_FLASH_COMM_FLAGS_ERROR_MASK = 0xFF000000
+
+N230_FLASH_COMM_CMD_READ_NV_DATA = 0x00000010
+N230_FLASH_COMM_CMD_WRITE_NV_DATA = 0x00000020
+N230_FLASH_COMM_CMD_READ_FPGA = 0x00000030
+N230_FLASH_COMM_CMD_WRITE_FPGA = 0x00000040
+N230_FLASH_COMM_CMD_ERASE_FPGA = 0x00000050
+
+N230_FLASH_COMM_ERR_PKT_ERROR = 0x80000000
+N230_FLASH_COMM_ERR_CMD_ERROR = 0x40000000
+N230_FLASH_COMM_ERR_SIZE_ERROR = 0x20000000
+
+N230_FLASH_COMM_SAFE_IMG_BASE = 0x000000
+N230_FLASH_COMM_PROD_IMG_BASE = 0x400000
+N230_FLASH_COMM_FPGA_IMG_MAX_SIZE = 0x400000
+
+UDP_MAX_XFER_BYTES = 256
+UDP_TIMEOUT = 3
+
+_seq = -1
+def next_seq():
+ global _seq
+ _seq = _seq+1
+ return _seq
+
+def seq():
+ return _seq
+
+########################################################################
+# helper functions
+########################################################################
+
+short = struct.Struct('>H')
+ulong = struct.Struct('>I')
+
+def unpack_flash_transaction(buf):
+ (flags, seqno, offset, size) = struct.unpack_from('!LLLL', buf)
+ check_error(flags)
+ if (seqno != seq()):
+ raise Exception("The flash transaction operation returned an incorrect sequence number")
+ data = bytes()
+ for i in xrange(16, len(buf), 1):
+ data += buf[i]
+ return (flags, offset, size, data)
+
+def pack_flash_transaction(flags, offset, size, data):
+ buf = bytes()
+ buf = struct.pack('!LLLL', flags, next_seq(), offset, size)
+ for i in range(N230_FLASH_COMM_PAYLOAD_SIZE):
+ if (i < size):
+ buf += struct.pack('!B', data[i])
+ else:
+ buf += struct.pack('!B', 0)
+ return buf
+
+def check_error(flags):
+ if flags & N230_FLASH_COMM_ERR_PKT_ERROR == N230_FLASH_COMM_ERR_PKT_ERROR:
+ raise Exception("The flash transaction operation returned a packet error")
+ if flags & N230_FLASH_COMM_ERR_CMD_ERROR == N230_FLASH_COMM_ERR_CMD_ERROR:
+ raise Exception("The flash transaction operation returned a command error")
+ if flags & N230_FLASH_COMM_ERR_SIZE_ERROR == N230_FLASH_COMM_ERR_SIZE_ERROR:
+ raise Exception("The flash transaction operation returned a size error")
+
+def chunkify(stuff, n):
+ return [stuff[i:i+n] for i in range(0, len(stuff), n)]
+
+def draw_progress_bar(percent, bar_len = 32):
+ sys.stdout.write("\r")
+ progress = ""
+ for i in range(bar_len):
+ if i < int((bar_len * percent) / 100):
+ progress += "="
+ else:
+ progress += "-"
+ sys.stdout.write("[%s] %d%%" % (progress, percent))
+ sys.stdout.flush()
+
+########################################################################
+# Burner class, holds a socket and send/recv routines
+########################################################################
+class ctrl_socket(object):
+ def __init__(self, addr):
+ self._safe_image = False
+ self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ self._sock.settimeout(UDP_TIMEOUT)
+ self._sock.connect((addr, N230_FLASH_COMM_UDP_PORT))
+ self.set_callbacks(lambda *a: None, lambda *a: None)
+
+ def set_safe_image(self, noprompt):
+ confirm_msg = ('----------------------------------------------------------------------\n'
+ 'WARNING!!! You are about to access the safe-image stored in the flash \n'
+ '----------------------------------------------------------------------\n'
+ 'Writing a non-functional image will brick the device.\n'
+ 'Are you sure you want to proceed?')
+ if not noprompt:
+ if raw_input("%s (y/N) " % confirm_msg).lower() == 'y':
+ self._safe_image = True
+ else:
+ print 'Aborted by user'
+ sys.exit(1)
+ else:
+ print '[WARNING] Operating on safe image without a prompt as requested'
+ self._safe_image = True
+
+ def set_callbacks(self, progress_cb, status_cb):
+ self._progress_cb = progress_cb
+ self._status_cb = status_cb
+
+ def send_and_recv(self, pkt):
+ self._sock.send(pkt)
+ return self._sock.recv(UDP_MAX_XFER_BYTES)
+
+ def compute_offset(self, offset):
+ base = N230_FLASH_COMM_SAFE_IMG_BASE if (self._safe_image) else N230_FLASH_COMM_PROD_IMG_BASE
+ return base + offset
+
+ def burn_fpga_to_flash(self, bitfile_path, noprompt):
+ print '[BURN] Reading ' + bitfile_path + '...'
+ with open(bitfile_path, 'rb') as bitfile:
+ header = file_bytes = bitfile.read()
+ if (self._safe_image != self.parse_bitfile_header(file_bytes)['safe-image']):
+ confirm_msg = ('----------------------------------------------------------------------\n'
+ 'WARNING!!! You are about to burn a safe image into a production slot \n'
+ ' or a production image into a safe slot. \n'
+ '----------------------------------------------------------------------\n'
+ 'This is dangerous and can cause the device to boot incorrectly.\n'
+ 'Are you sure you want to proceed?')
+ if not noprompt:
+ if raw_input("%s (y/N) " % confirm_msg).lower() != 'y':
+ print '[BURN] Aborted by user'
+ return
+ else:
+ print '[WARNING] Burning image to the wrong slot without a prompt as requested'
+
+ print '[BURN] Writing to flash...'
+ pkt_chunks = chunkify(file_bytes, N230_FLASH_COMM_PAYLOAD_SIZE)
+ offset = 0
+ for pkt_data in pkt_chunks:
+ pkt_data = array("B", pkt_data)
+ size = N230_FLASH_COMM_PAYLOAD_SIZE if (len(pkt_data) >= N230_FLASH_COMM_PAYLOAD_SIZE) else len(pkt_data)
+ #Erase sector
+ if (offset % N230_FLASH_COMM_SECTOR_SIZE == 0):
+ flags = N230_FLASH_COMM_FLAGS_ACK|N230_FLASH_COMM_CMD_ERASE_FPGA
+ out_pkt = pack_flash_transaction(flags, self.compute_offset(offset), size, pkt_data)
+ (flags, real_offset, size, data) = unpack_flash_transaction(self.send_and_recv(out_pkt))
+ #Write data
+ flags = N230_FLASH_COMM_FLAGS_ACK|N230_FLASH_COMM_CMD_WRITE_FPGA
+ out_pkt = pack_flash_transaction(flags, self.compute_offset(offset), size, pkt_data)
+ (flags, real_offset, size, data) = unpack_flash_transaction(self.send_and_recv(out_pkt))
+ #Increment
+ offset += N230_FLASH_COMM_PAYLOAD_SIZE
+ draw_progress_bar((((offset/N230_FLASH_COMM_PAYLOAD_SIZE)+1)*100)/len(pkt_chunks))
+ print('\n[BURN] DONE')
+
+ def parse_bitfile_header(self, header_bytes):
+ xil_header = dict()
+ n230_header = dict()
+ n230_header['valid'] = False
+ ptr = 0
+ #Field 1
+ if short.unpack(header_bytes[ptr:ptr+2])[0] == 9 and ulong.unpack(header_bytes[ptr+2:ptr+6])[0] == 0x0ff00ff0:
+ #Headers
+ ptr += short.unpack(header_bytes[ptr:ptr+2])[0] + 2
+ ptr += short.unpack(header_bytes[ptr:ptr+2])[0] + 1
+ #Fields a-d
+ for keynum in range(0, 4):
+ key = header_bytes[ptr]
+ ptr += 1
+ val_len = short.unpack(header_bytes[ptr:ptr+2])[0]
+ ptr += 2
+ val = header_bytes[ptr:ptr+val_len]
+ ptr += val_len
+ xil_header[key] = val
+ #Field e
+ ptr += 1
+ length = ulong.unpack(header_bytes[ptr:ptr+4])[0]
+ xil_header['bl'] = length #Bitstream length
+ ptr += 4
+ xil_header['hl'] = ptr #Header lengt
+
+ #Map Xilinx header field to N230 specific ones
+ if xil_header and xil_header['a'].split(';')[0] == 'n230':
+ n230_header['valid'] = True
+ n230_header['user-id'] = int(xil_header['a'].split(';')[1].split('=')[1], 16)
+ n230_header['safe-image'] = (n230_header['user-id'] >> 16 == 0x5AFE)
+ n230_header['product'] = xil_header['b']
+ n230_header['timestamp'] = xil_header['c'] + ' ' + xil_header['d']
+ n230_header['filesize'] = xil_header['hl'] + xil_header['bl']
+ return n230_header
+
+ def read_bitfile_header_from_flash(self):
+ max_header_size = 1024 #Should be enough
+ header_bytes = bytes()
+ for offset in range(0, max_header_size, N230_FLASH_COMM_PAYLOAD_SIZE):
+ #Read data
+ flags = N230_FLASH_COMM_FLAGS_ACK|N230_FLASH_COMM_CMD_READ_FPGA
+ out_pkt = pack_flash_transaction(flags, self.compute_offset(offset), N230_FLASH_COMM_PAYLOAD_SIZE, [0]*N230_FLASH_COMM_PAYLOAD_SIZE)
+ (flags, real_offset, size, data) = unpack_flash_transaction(self.send_and_recv(out_pkt))
+ header_bytes += data
+ return self.parse_bitfile_header(header_bytes)
+
+ def extract_fpga_from_flash(self, bitfile_path):
+ header = self.read_bitfile_header_from_flash();
+ if not header['valid']:
+ raise Exception("Could not detect a vaild Xilinx .bit burned into the flash")
+ max_offset = header['filesize']
+ print '[EXTRACT] Writing ' + bitfile_path + '...'
+ with open(bitfile_path, 'wb') as bitfile:
+ for i in range(0, int(math.ceil(float(max_offset)/N230_FLASH_COMM_PAYLOAD_SIZE))):
+ offset = i * N230_FLASH_COMM_PAYLOAD_SIZE
+ size = N230_FLASH_COMM_PAYLOAD_SIZE if (max_offset - offset >= N230_FLASH_COMM_PAYLOAD_SIZE) else (max_offset - offset)
+ #Read data
+ flags = N230_FLASH_COMM_FLAGS_ACK|N230_FLASH_COMM_CMD_READ_FPGA
+ out_pkt = pack_flash_transaction(flags, self.compute_offset(offset), size, [0]*N230_FLASH_COMM_PAYLOAD_SIZE)
+ (flags, real_offset, size, data) = unpack_flash_transaction(self.send_and_recv(out_pkt))
+ bitfile.write(data[:size])
+ draw_progress_bar(((offset*100)/max_offset) + 1)
+ print('\n[EXTRACT] DONE')
+
+ def erase_fpga_from_flash(self):
+ print '[ERASE] Erasing image from flash...'
+ for offset in range(0, N230_FLASH_COMM_FPGA_IMG_MAX_SIZE, N230_FLASH_COMM_SECTOR_SIZE):
+ flags = N230_FLASH_COMM_FLAGS_ACK|N230_FLASH_COMM_CMD_ERASE_FPGA
+ out_pkt = pack_flash_transaction(flags, self.compute_offset(offset), N230_FLASH_COMM_PAYLOAD_SIZE, [0]*N230_FLASH_COMM_PAYLOAD_SIZE)
+ (flags, real_offset, size, data) = unpack_flash_transaction(self.send_and_recv(out_pkt))
+ draw_progress_bar(((offset+N230_FLASH_COMM_SECTOR_SIZE)*100)/N230_FLASH_COMM_FPGA_IMG_MAX_SIZE)
+ print('\n[ERASE] DONE')
+
+ def wipe_user_data(self, noprompt):
+ confirm_msg = ('-------------------------------------------------------------------\n'
+ 'WARNING!!! You are about to erase all the user data from the flash \n'
+ '-------------------------------------------------------------------\n'
+ 'This will cause the device to lose the following:\n'
+ ' * IP Address (Will default to 192.168.10.2)\n'
+ ' * Subnet Mask (Will default to 255.255.255.2)\n'
+ ' * MAC Address\n'
+ ' * Serial Number\n'
+ ' * Hardware Revision\n'
+ ' * ...and other identification info\n'
+ 'Are you sure you want to proceed?')
+ if not noprompt:
+ if raw_input("%s (y/N) " % confirm_msg).lower() == 'y':
+ wipe_ok = True
+ else:
+ print '[WIPE] Aborted by user'
+ wipe_ok = False
+ else:
+ print '[WARNING] Wiping user data without prompt a as requested'
+ wipe_ok = True
+
+ if wipe_ok:
+ print '[WIPE] Erasing all user data from flash...'
+ flags = N230_FLASH_COMM_FLAGS_ACK|N230_FLASH_COMM_CMD_WRITE_NV_DATA
+ out_pkt = pack_flash_transaction(flags, 0, N230_FLASH_COMM_PAYLOAD_SIZE, [0xFF]*N230_FLASH_COMM_PAYLOAD_SIZE)
+ (flags, real_offset, size, data) = unpack_flash_transaction(self.send_and_recv(out_pkt))
+ print('[WIPE] DONE. Please power-cycle the device.')
+
+ def print_status(self):
+ header = self.read_bitfile_header_from_flash();
+ if header['valid']:
+ print('[STATUS] Detected a valid .bit header in the flash (Product = %s, Datestamp = %s%s)' % \
+ (header['product'], header['timestamp'], ', Safe-Image' if header['safe-image'] else ''))
+ else:
+ print('[STATUS] No .bit header detected. Either the flash is uninitialized or the image is corrupt.')
+
+
+########################################################################
+# command line options
+########################################################################
+def get_options():
+ parser = optparse.OptionParser()
+ parser.add_option("--addr", type="string", help="N230 device address", default='')
+ parser.add_option("--status", action="store_true", help="Print out the status of the burned image", default=False)
+ parser.add_option("--erase", action="store_true", help="Erase FPGA bitstream from flash", default=False)
+ parser.add_option("--burn", type="string", help="Path to FPGA bitstream (.bit) to burn to flash", default=None)
+ parser.add_option("--extract", type="string", help="Destination bitfile to dump contents of the extracted image", default=None)
+ parser.add_option("--safe_image", action="store_true", help="Operate on the safe image. WARNING: This could be dangerous", default=False)
+ parser.add_option("--wipe_user_data", action="store_true", help="Erase all user data like IP, MAC, S/N, etc from flash", default=False)
+ parser.add_option("--no_prompt", action="store_true", help="Suppress all warning prompts", default=False)
+ (options, args) = parser.parse_args()
+ return options
+
+########################################################################
+# main
+########################################################################
+if __name__=='__main__':
+ options = get_options()
+
+ if not options.addr: raise Exception('No address specified')
+
+ ctrl_sock = ctrl_socket(addr=options.addr)
+
+ # Initialize safe image selector first
+ if options.safe_image:
+ ctrl_sock.set_safe_image(options.no_prompt)
+
+ if options.status:
+ ctrl_sock.print_status()
+
+ # Order of operations:
+ # 1. Extract (if specified)
+ # 2. Erase (if specified)
+ # 3. Burn (if specified)
+
+ if options.extract is not None:
+ file_path = options.extract
+ ctrl_sock.print_status()
+ ctrl_sock.extract_fpga_from_flash(file_path)
+
+ if options.erase:
+ ctrl_sock.erase_fpga_from_flash()
+ ctrl_sock.print_status()
+
+ if options.burn is not None:
+ file_path = options.burn
+ extension = os.path.splitext(file_path)[1]
+ if (extension.lower() == '.bit'):
+ ctrl_sock.burn_fpga_to_flash(file_path, options.no_prompt)
+ ctrl_sock.print_status()
+ else:
+ raise Exception("Unsupported FPGA bitfile format. You must use a .bit file.")
+
+ if options.wipe_user_data:
+ ctrl_sock.wipe_user_data(options.no_prompt)
diff --git a/firmware/usrp3/n230/n230_debug.py b/firmware/usrp3/n230/n230_debug.py
new file mode 100755
index 000000000..f9ff64ab7
--- /dev/null
+++ b/firmware/usrp3/n230/n230_debug.py
@@ -0,0 +1,387 @@
+#!/usr/bin/env python
+#
+# Copyright 2010-2011 Ettus Research LLC
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+#
+
+import optparse
+import math
+import socket
+import struct
+import array
+import os.path
+import sys
+import time
+try:
+ import fcntl
+ N230_DEVICE_DISCOVERY_AVAILABLE = True
+except:
+ N230_DEVICE_DISCOVERY_AVAILABLE = False
+
+########################################################################
+# constants
+########################################################################
+N230_FW_COMMS_UDP_PORT = 49152
+N230_FW_COMMS_MAX_DATA_WORDS = 16
+
+N230_FW_COMMS_FLAGS_ACK = 0x00000001
+N230_FW_COMMS_FLAGS_ERROR_MASK = 0xF0000000
+N230_FW_COMMS_FLAGS_CMD_MASK = 0x000000F0
+
+N230_FW_COMMS_CMD_ECHO = 0x00000000
+N230_FW_COMMS_CMD_POKE32 = 0x00000010
+N230_FW_COMMS_CMD_PEEK32 = 0x00000020
+N230_FW_COMMS_CMD_BLOCK_POKE32 = 0x00000030
+N230_FW_COMMS_CMD_BLOCK_PEEK32 = 0x00000040
+
+N230_FW_COMMS_ERR_PKT_ERROR = 0x80000000
+N230_FW_COMMS_ERR_CMD_ERROR = 0x40000000
+N230_FW_COMMS_ERR_SIZE_ERROR = 0x20000000
+
+N230_FW_COMMS_ID = 0x0001ACE3
+
+N230_FW_LOADER_ADDR = 0xfa00
+N230_FW_LOADER_DATA = 0xfa04
+N230_FW_LOADER_NUM_WORDS = 8192
+N230_FW_LOADER_BOOT_DONE_ADDR = 0xA004
+N230_FW_LOADER_BOOT_TIMEOUT = 5
+
+N230_JESD204_TEST = 0xA014
+N230_FPGA_HASH_ADDR = 0xA010
+N230_FW_HASH_ADDR = 0x10004
+N230_ICAP_ADDR = 0xF800
+#ICAP_DUMMY_WORD = 0xFFFFFFFF
+#ICAP_SYNC_WORD = 0xAA995566
+#ICAP_TYPE1_NOP = 0x20000000
+#ICAP_WRITE_WBSTAR = 0x30020001
+#ICAP_WBSTAR_ADDR = 0x00000000
+#ICAP_WRITE_CMD = 0x30008001
+#ICAP_IPROG_CMD = 0x0000000F
+# Bit reversed values per Xilinx UG470 - Bits reversed within bytes.
+ICAP_DUMMY_WORD = 0xFFFFFFFF
+ICAP_SYNC_WORD = 0x5599AA66
+ICAP_TYPE1_NOP = 0x04000000
+ICAP_WRITE_WBSTAR = 0x0C400080
+ICAP_WBSTAR_ADDR = 0x00000000
+ICAP_WRITE_CMD = 0x0C000180
+ICAP_IPROG_CMD = 0x000000F0
+
+
+UDP_MAX_XFER_BYTES = 256
+UDP_TIMEOUT = 3
+FPGA_LOAD_TIMEOUT = 10
+
+_seq = -1
+def seq():
+ global _seq
+ _seq = _seq+1
+ return _seq
+
+########################################################################
+# helper functions
+########################################################################
+
+def pack_fw_command(flags, seq, num_words, addr, data_arr):
+ if (num_words > N230_FW_COMMS_MAX_DATA_WORDS):
+ raise Exception("Data size too large. Firmware supports a max 16 words per block." % (addr))
+ buf = bytes()
+ buf = struct.pack('!IIIII', N230_FW_COMMS_ID, flags, seq, num_words, addr)
+ for i in range(N230_FW_COMMS_MAX_DATA_WORDS):
+ if (i < num_words):
+ buf += struct.pack('!I', data_arr[i])
+ else:
+ buf += struct.pack('!I', 0)
+ return buf
+
+def unpack_fw_command(buf, fmt=None):
+ (id, flags, seq, num_words, addr) = struct.unpack_from('!IIIII', buf)
+ fw_check_error(flags)
+ data = []
+ if fmt is None:
+ fmt = 'I'
+ for i in xrange(20, len(buf), 4):
+ data.append(struct.unpack('!'+fmt, buf[i:i+4])[0])
+ return (flags, seq, num_words, addr, data)
+
+def fw_check_error(flags):
+ if flags & N230_FW_COMMS_ERR_PKT_ERROR == N230_FW_COMMS_ERR_PKT_ERROR:
+ raise Exception("The fiwmware operation returned a packet error")
+ if flags & N230_FW_COMMS_ERR_CMD_ERROR == N230_FW_COMMS_ERR_CMD_ERROR:
+ raise Exception("The fiwmware operation returned a command error")
+ if flags & N230_FW_COMMS_ERR_SIZE_ERROR == N230_FW_COMMS_ERR_SIZE_ERROR:
+ raise Exception("The fiwmware operation returned a size error")
+
+def chunkify(stuff, n):
+ return [stuff[i:i+n] for i in range(0, len(stuff), n)]
+
+def draw_progress_bar(percent, bar_len = 32):
+ sys.stdout.write("\r")
+ progress = ""
+ for i in range(bar_len):
+ if i < int((bar_len * percent) / 100):
+ progress += "="
+ else:
+ progress += "-"
+ sys.stdout.write("[%s] %d%%" % (progress, percent))
+ sys.stdout.flush()
+
+########################################################################
+# Discovery class
+########################################################################
+class discovery_socket(object):
+ def __init__(self):
+ self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
+ self._sock.settimeout(0.250)
+
+ def get_bcast_addrs(self):
+ max_possible = 128 # arbitrary. raise if needed.
+ num_bytes = max_possible * 32
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ names = array.array('B', '\0' * num_bytes)
+ outbytes = struct.unpack('iL', fcntl.ioctl(
+ s.fileno(),
+ 0x8912, # SIOCGIFCONF
+ struct.pack('iL', num_bytes, names.buffer_info()[0])
+ ))[0]
+ namestr = names.tostring()
+ lst = []
+ for i in range(0, outbytes, 40):
+ name = namestr[i:i+16].split('\0', 1)[0]
+ ip = map(ord, namestr[i+20:i+24])
+ mask = map(ord, fcntl.ioctl(s.fileno(), 0x891B, struct.pack('256s', name))[20:24])
+ bcast = []
+ for i in range(len(ip)):
+ bcast.append((ip[i] | (~mask[i])) & 0xFF)
+ if (name != 'lo'):
+ lst.append(str(bcast[0]) + '.' + str(bcast[1]) + '.' + str(bcast[2]) + '.' + str(bcast[3]))
+ return lst
+
+ def discover(self):
+ addrs = []
+ for bcast_addr in self.get_bcast_addrs():
+ out_pkt = pack_fw_command(N230_FW_COMMS_CMD_ECHO|N230_FW_COMMS_FLAGS_ACK, seq(), 0, 0, [0])
+ self._sock.sendto(out_pkt, (bcast_addr, N230_FW_COMMS_UDP_PORT))
+ while 1:
+ try:
+ (in_pkt, addr_pair) = self._sock.recvfrom(UDP_MAX_XFER_BYTES)
+ if len(in_pkt) < 20:
+ continue
+ (flags, ack_seq, block_size, addr, data) = unpack_fw_command(in_pkt)
+ addrs.append(addr_pair[0])
+ except socket.error:
+ break
+ return addrs
+
+
+########################################################################
+# Communications class, holds a socket and send/recv routine
+########################################################################
+class ctrl_socket(object):
+ def __init__(self, addr, port):
+ self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ self._sock.settimeout(UDP_TIMEOUT)
+ self._sock.connect((addr, port))
+ self.set_callbacks(lambda *a: None, lambda *a: None)
+ self.peek(0) #Dummy read
+
+ def set_callbacks(self, progress_cb, status_cb):
+ self._progress_cb = progress_cb
+ self._status_cb = status_cb
+
+ def send(self, pkt):
+ self._sock.send(pkt)
+
+ def recv(self):
+ return self._sock.recv(UDP_MAX_XFER_BYTES)
+
+ def send_and_recv(self, pkt):
+ self.send(pkt)
+ return self.recv()
+
+ def peek(self, peek_addr, fmt=None):
+ out_pkt = pack_fw_command(N230_FW_COMMS_CMD_PEEK32|N230_FW_COMMS_FLAGS_ACK, seq(), 1, peek_addr, [0])
+ in_pkt = self.send_and_recv(out_pkt)
+ (flags, ack_seq, block_size, addr, data) = unpack_fw_command(in_pkt, fmt)
+ return data[0]
+
+ def peek64(self, peek_addr, fmt=None):
+ out_pkt = pack_fw_command(N230_FW_COMMS_CMD_BLOCK_PEEK32|N230_FW_COMMS_FLAGS_ACK, seq(), 2, peek_addr, [0,0])
+ in_pkt = self.send_and_recv(out_pkt)
+ (flags, ack_seq, block_size, addr, data) = unpack_fw_command(in_pkt, fmt)
+ return (data[0] | (data[1] << 32))
+
+ def poke(self, poke_addr, poke_data, ack=True):
+ ack_flag = N230_FW_COMMS_FLAGS_ACK if ack else 0
+ out_pkt = pack_fw_command(N230_FW_COMMS_CMD_POKE32|ack_flag, seq(), 1, poke_addr, [poke_data])
+ self.send(out_pkt)
+ if ack:
+ in_pkt = self.recv()
+ (flags, ack_seq, block_size, addr, data) = unpack_fw_command(in_pkt)
+
+ def live_load_firmware_bin(self, bin_path):
+ raise Exception("live_load_firmware_bin not implemented yet!")
+
+ def live_load_firmware_coe(self, coe_path):
+ with open(coe_path, 'r') as coe_file:
+ print("Loading %s..." % coe_path)
+ coe_lines = [line.strip(',;\n ') for line in coe_file]
+ start_index = coe_lines.index("memory_initialization_vector=") + 1
+ coe_words = coe_lines[start_index:]
+ if len(coe_words) != N230_FW_LOADER_NUM_WORDS:
+ raise Exception("invalid COE file. Must contain 8192 words!")
+ self.poke(N230_FW_LOADER_ADDR, 0) #Load start address
+ for i in range(0, len(coe_words)):
+ self.poke(N230_FW_LOADER_DATA, int(coe_words[i],16), (i%10==0) and (i<len(coe_words)-1))
+ draw_progress_bar(((i+1)*100)/len(coe_words))
+ print("\nRebooting...")
+ out_pkt = pack_fw_command(N230_FW_COMMS_CMD_POKE32, seq(), 1, N230_FW_LOADER_BOOT_DONE_ADDR, [1])
+ self._sock.send(out_pkt)
+ self._sock.settimeout(1)
+ out_pkt = pack_fw_command(N230_FW_COMMS_CMD_PEEK32|N230_FW_COMMS_FLAGS_ACK, seq(), 1, 0, [0])
+ for i in range(N230_FW_LOADER_BOOT_TIMEOUT):
+ try:
+ self._sock.send(out_pkt)
+ in_pkt = self._sock.recv(UDP_MAX_XFER_BYTES)
+ print("Firmware is alive!")
+ self._sock.settimeout(UDP_TIMEOUT)
+ return
+ except socket.error:
+ pass
+ print("Firmware boot FAILED!!!")
+ self._sock.settimeout(UDP_TIMEOUT)
+
+ def read_hash(self):
+ fpga_hash = self.peek(N230_FPGA_HASH_ADDR)
+ fpga_status = "clean" if (fpga_hash & 0xf0000000 == 0x0) else "modified"
+ fw_hash = self.peek(N230_FW_HASH_ADDR)
+ fw_status = "clean" if (fw_hash & 0xf0000000 == 0x0) else "modified"
+ print("FPGA Version : %x (%s)" % (fpga_hash & 0xfffffff, fpga_status))
+ print("Firmware Version : %x (%s)" % (fw_hash & 0xfffffff, fw_status))
+
+ def is_claimed(self):
+ claimed = self.peek(0x10008)
+ print("Claimed : %s") % ('YES' if claimed else 'NO')
+
+ def reset_fpga(self):
+ print("Reseting USRP...")
+ ctrl_sock.poke(N230_ICAP_ADDR,ICAP_DUMMY_WORD)
+ ctrl_sock.poke(N230_ICAP_ADDR,ICAP_TYPE1_NOP)
+ ctrl_sock.poke(N230_ICAP_ADDR,ICAP_SYNC_WORD)
+ ctrl_sock.poke(N230_ICAP_ADDR,ICAP_TYPE1_NOP)
+ ctrl_sock.poke(N230_ICAP_ADDR,ICAP_WRITE_WBSTAR)
+ ctrl_sock.poke(N230_ICAP_ADDR,ICAP_WBSTAR_ADDR)
+ ctrl_sock.poke(N230_ICAP_ADDR,ICAP_TYPE1_NOP)
+ ctrl_sock.poke(N230_ICAP_ADDR,ICAP_WRITE_CMD)
+ ctrl_sock.poke(N230_ICAP_ADDR,ICAP_IPROG_CMD, False)
+ print("Waiting for FPGA to load...")
+ self._sock.settimeout(1)
+ out_pkt = pack_fw_command(N230_FW_COMMS_CMD_ECHO|N230_FW_COMMS_FLAGS_ACK, seq(), 1, 0, [0])
+ for i in range(FPGA_LOAD_TIMEOUT):
+ try:
+ in_pkt = self.send_and_recv(out_pkt)
+ (flags, ack_seq, block_size, addr, data) = unpack_fw_command(in_pkt)
+ print("FPGA loaded successfully.")
+ self._sock.settimeout(UDP_TIMEOUT)
+ return
+ except socket.error:
+ pass
+ print("FPGA load FAILED!!!")
+ self._sock.settimeout(UDP_TIMEOUT)
+
+ def jesd204_test_connector(self):
+ print("Testing JESD204 connectors. Molex cable #79576-2102 must be connected")
+ ctrl_sock.poke(N230_JESD204_TEST,0x1)
+ while True:
+ jesd204_test_status = ctrl_sock.peek(N230_JESD204_TEST)
+ if (jesd204_test_status & 0x10000 == 0x10000):
+ break
+ ctrl_sock.poke(N230_JESD204_TEST,0x0)
+ if (jesd204_test_status & 0xFFFF != 0x0):
+ print("JESD204 loopback test Failed!: Returned status is %4x" % (jesd204_test_status & 0xFFFF))
+ else:
+ print("JESD204 loopback test Passed.")
+
+########################################################################
+# command line options
+########################################################################
+def get_options():
+ parser = optparse.OptionParser()
+ parser.add_option("--discover", action="store_true",help="Find all devices connected N230 devices", default=False)
+ parser.add_option("--addr", type="string", help="N230 device address", default='')
+ parser.add_option("--peek", type="int", help="Read from memory map", default=None)
+ parser.add_option("--poke", type="int", help="Write to memory map", default=None)
+ parser.add_option("--data", type="int", help="Data for poke", default=None)
+ parser.add_option("--fw", type="string", help="Path to FW image to load", default=None)
+ parser.add_option("--hash", action="store_true",help="Display FPGA git hash", default=False)
+ parser.add_option("--reset", action="store_true",help="Reset and Reload USRP FPGA.", default=False)
+ parser.add_option("--jesd204test", action="store_true",help="Test mini-SAS connectors with loopback cable..", default=False)
+
+ (options, args) = parser.parse_args()
+ return options
+
+########################################################################
+# main
+########################################################################
+if __name__=='__main__':
+ options = get_options()
+
+ if options.discover:
+ if N230_DEVICE_DISCOVERY_AVAILABLE:
+ disc_sock = discovery_socket()
+ for addr in disc_sock.discover():
+ print '==== FOUND ' + addr + ' ===='
+ ctrl_sock = ctrl_socket(addr, N230_FW_COMMS_UDP_PORT)
+ ctrl_sock.read_hash()
+ ctrl_sock.is_claimed()
+ sys.exit()
+ else:
+ raise Exception('Discovery is only supported on Linux.')
+
+ if not options.addr:
+ raise Exception('No address specified')
+
+ ctrl_sock = ctrl_socket(options.addr, N230_FW_COMMS_UDP_PORT)
+
+ if options.fw is not None:
+ file_path = options.fw
+ extension = os.path.splitext(file_path)[1]
+ if (extension.lower() == '.coe'):
+ ctrl_sock.live_load_firmware_coe(file_path)
+ elif (extension.lower() == '.bin'):
+ ctrl_sock.live_load_firmware_bin(file_path)
+ else:
+ raise Exception("Unsupported firmware file format")
+
+ if options.hash:
+ ctrl_sock.read_hash()
+
+ if options.peek is not None:
+ addr = options.peek
+ data = ctrl_sock.peek(addr)
+ print("PEEK[0x%x (%d)] => 0x%x (%d)" % (addr,addr,data,data))
+
+ if options.poke is not None and options.data is not None:
+ addr = options.poke
+ data = options.data
+ ctrl_sock.poke(addr,data)
+ print("POKE[0x%x (%d)] <= 0x%x (%d)" % (addr,addr,data,data))
+
+ if options.reset:
+ ctrl_sock.reset_fpga()
+
+ if options.jesd204test:
+ ctrl_sock.jesd204_test_connector()
diff --git a/firmware/usrp3/n230/n230_eeprom.c b/firmware/usrp3/n230/n230_eeprom.c
new file mode 100644
index 000000000..8f756d41f
--- /dev/null
+++ b/firmware/usrp3/n230/n230_eeprom.c
@@ -0,0 +1,196 @@
+//
+// Copyright 2014 Ettus Research LLC
+//
+// 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 3 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, see <http://www.gnu.org/licenses/>.
+//
+
+#include "../../../host/lib/usrp/n230/n230_eeprom.h"
+
+#include <trace.h>
+#include <stddef.h>
+#include <flash/spi_flash.h>
+#include <flash/spif_spsn_s25flxx.h>
+#include <string.h> //memcpy
+
+#include "../../../host/lib/usrp/n230/n230_fw_defs.h"
+#include "../../../host/lib/usrp/n230/n230_fw_host_iface.h"
+
+static const wb_spi_slave_t flash_spi_slave = {
+ .base = (void*) 0xB000,
+ .slave_sel = 0x0001,
+ .clk_div = 4, //80MHz/4 = 20MHz
+ .mosi_edge = RISING,
+ .miso_edge = FALLING,
+ .lsb_first = false
+};
+
+static const spi_flash_dev_t spi_flash_device = {
+ .page_size = 256,
+ .sector_size = 65536,
+ .num_sectors = 254,
+ .bus = &flash_spi_slave
+};
+
+/***********************************************************************
+ * Non-volatile device data
+ **********************************************************************/
+#define N230_FLASH_NV_DATA_OFFSET 0x800000
+
+//Default values in case the EEPROM is not read, corrupt
+const n230_eeprom_map_t default_eeprom = {
+ .data_version_major = N230_EEPROM_VER_MAJOR,
+ .data_version_minor = N230_EEPROM_VER_MINOR,
+ .hw_revision = 0,
+ .hw_product = 0x01,
+ .gateway = N230_DEFAULT_GATEWAY,
+ .eth_info = {
+ { //eth0
+ .mac_addr = N230_DEFAULT_ETH0_MAC,
+ .subnet = N230_DEFAULT_ETH0_MASK,
+ .ip_addr = N230_DEFAULT_ETH0_IP
+ },
+ { //eth1
+ .mac_addr = N230_DEFAULT_ETH1_MAC,
+ .subnet = N230_DEFAULT_ETH1_MASK,
+ .ip_addr = N230_DEFAULT_ETH1_IP
+ }
+ }
+};
+
+//EEPROM cache
+static spi_flash_session_t flash_session = {.device = NULL};
+static n230_eeprom_map_t eeprom_cache;
+static bool cache_dirty = true;
+
+bool read_n230_eeprom()
+{
+ bool status = false;
+ if (flash_session.device == NULL) { //Initialize flash session structure for the first time
+ wb_spi_init(spi_flash_device.bus);
+ spif_init(&flash_session, &spi_flash_device, spif_spsn_s25flxx_operations());
+ }
+ spif_read_sync(&flash_session, N230_FLASH_NV_DATA_OFFSET, &eeprom_cache, sizeof(n230_eeprom_map_t));
+
+ //Verify data format
+ status = (eeprom_cache.data_version_major == default_eeprom.data_version_major);
+ //Sanity communication info
+ if (eeprom_cache.eth_info[0].ip_addr == 0xFFFFFFFF)
+ eeprom_cache.eth_info[0].ip_addr = default_eeprom.eth_info[0].ip_addr;
+ if (eeprom_cache.eth_info[1].ip_addr == 0xFFFFFFFF)
+ eeprom_cache.eth_info[1].ip_addr = default_eeprom.eth_info[1].ip_addr;
+ if (eeprom_cache.eth_info[0].subnet == 0xFFFFFFFF)
+ eeprom_cache.eth_info[0].subnet = default_eeprom.eth_info[0].subnet;
+ if (eeprom_cache.eth_info[1].subnet == 0xFFFFFFFF)
+ eeprom_cache.eth_info[1].subnet = default_eeprom.eth_info[1].subnet;
+
+ if (!status) {
+ UHD_FW_TRACE(WARN, "read_n230_eeprom: Initialized cache to the default map.");
+ memcpy(&eeprom_cache, &default_eeprom, sizeof(n230_eeprom_map_t));
+ }
+ cache_dirty = !status;
+ return status;
+}
+
+bool write_n230_eeprom()
+{
+ //Assumption: sizeof(n230_eeprom_map_t) <= flash_page_size
+ //This function would need to be reimplemented if this assumption is no longer true
+ if (sizeof(n230_eeprom_map_t) > flash_session.device->page_size) {
+ UHD_FW_TRACE(ERROR, "write_n230_eeprom: sizeof(n230_eeprom_map_t) > flash_page_size");
+ return false;
+ }
+
+ bool status = true;
+ if (cache_dirty) {
+ n230_eeprom_map_t device_eeprom;
+ spif_read_sync(&flash_session, N230_FLASH_NV_DATA_OFFSET, &device_eeprom, sizeof(n230_eeprom_map_t));
+ if (memcmp(&eeprom_cache, &device_eeprom, sizeof(n230_eeprom_map_t)) != 0) {
+ //Cache does not match read state. Write.
+ UHD_FW_TRACE(DEBUG, "write_n230_eeprom: Writing data to flash...");
+ status = spif_erase_sector_sync(&flash_session, N230_FLASH_NV_DATA_OFFSET);
+ if (status) {
+ status = spif_write_page_sync(
+ &flash_session, N230_FLASH_NV_DATA_OFFSET, &eeprom_cache, sizeof(n230_eeprom_map_t));
+ }
+ if (!status) {
+ UHD_FW_TRACE(ERROR, "write_n230_eeprom: Operation failed!");
+ }
+ cache_dirty = !status;
+ } else {
+ UHD_FW_TRACE(DEBUG, "write_n230_eeprom: No new data. Write skipped.");
+ //Cache matches read state. So mark as clean
+ cache_dirty = false;
+ }
+ }
+ return status;
+}
+
+bool is_n230_eeprom_cache_dirty()
+{
+ return cache_dirty;
+}
+
+n230_eeprom_map_t* get_n230_eeprom_map()
+{
+ cache_dirty = true;
+ return &eeprom_cache;
+}
+
+const n230_eeprom_map_t* get_n230_const_eeprom_map()
+{
+ return &eeprom_cache;
+}
+
+const n230_eth_eeprom_map_t* get_n230_ethernet_info(uint32_t iface) {
+ if (iface >= N230_NUM_ETH_PORTS) {
+ UHD_FW_TRACE_FSTR(ERROR,
+ "get_n230_ethernet_info called with iface=%d when there are only %d ports!!!",
+ iface, N230_NUM_ETH_PORTS);
+ }
+ return &(get_n230_const_eeprom_map()->eth_info[iface]);
+}
+
+
+/***********************************************************************
+ * Storage for bootstrap FPGA Image
+ **********************************************************************/
+#define N230_FLASH_FPGA_IMAGE_OFFSET 0x000000
+#define N230_FLASH_FPGA_IMAGE_SIZE 0x400000
+#define N230_FLASH_NUM_FPGA_IMAGES 2
+
+void read_n230_fpga_image_page(uint32_t offset, void *buf, uint32_t num_bytes)
+{
+ if (offset >= (N230_FLASH_NUM_FPGA_IMAGES * N230_FLASH_FPGA_IMAGE_SIZE)) {
+ UHD_FW_TRACE_FSTR(ERROR, "read_n230_fpga_image_page: Offset 0x%x out of bounds", offset);
+ }
+ spif_read_sync(&flash_session, N230_FLASH_FPGA_IMAGE_OFFSET + offset, buf, num_bytes);
+}
+
+bool write_n230_fpga_image_page(uint32_t offset, const void *buf, uint32_t num_bytes)
+{
+ if (offset >= (N230_FLASH_NUM_FPGA_IMAGES * N230_FLASH_FPGA_IMAGE_SIZE)) {
+ UHD_FW_TRACE_FSTR(ERROR, "write_n230_fpga_image_page: Offset 0x%x out of bounds", offset);
+ return false;
+ }
+ return spif_write_page_sync(&flash_session, N230_FLASH_FPGA_IMAGE_OFFSET + offset, buf, num_bytes);
+}
+
+bool erase_n230_fpga_image_sector(uint32_t offset)
+{
+ if (offset >= (N230_FLASH_NUM_FPGA_IMAGES * N230_FLASH_FPGA_IMAGE_SIZE)) {
+ UHD_FW_TRACE_FSTR(ERROR, "erase_n230_fpga_image_sector: Offset 0x%x out of bounds", offset);
+ return false;
+ }
+ return spif_erase_sector_sync(&flash_session, N230_FLASH_FPGA_IMAGE_OFFSET + offset);
+}
diff --git a/firmware/usrp3/n230/n230_eth_handlers.c b/firmware/usrp3/n230/n230_eth_handlers.c
new file mode 100644
index 000000000..b291bb39f
--- /dev/null
+++ b/firmware/usrp3/n230/n230_eth_handlers.c
@@ -0,0 +1,340 @@
+//
+// Copyright 2014 Ettus Research LLC
+//
+// 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 3 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, see <http://www.gnu.org/licenses/>.
+//
+
+#include "n230_eth_handlers.h"
+
+#include <wb_utils.h>
+#include <string.h> //memcmp
+#include <u3_net_stack.h>
+#include <print_addrs.h>
+#include <trace.h>
+#include "../../../host/lib/usrp/common/fw_comm_protocol.h"
+#include "../../../host/lib/usrp/n230/n230_fw_defs.h"
+#include "../n230/n230_fw_host_iface.h"
+#include "../../../host/lib/usrp/n230/n230_eeprom.h"
+
+static n230_host_shared_mem_t* host_shared_mem_ptr;
+
+static const soft_reg_field_t LED_REG_FIELD_ETH_LINK2 = {.num_bits=1, .shift=0};
+static const soft_reg_field_t LED_REG_FIELD_ETH_LINK1 = {.num_bits=1, .shift=1};
+static const soft_reg_field_t LED_REG_FIELD_ETH_ACT2 = {.num_bits=1, .shift=2};
+static const soft_reg_field_t LED_REG_FIELD_ETH_ACT1 = {.num_bits=1, .shift=3};
+
+/***********************************************************************
+ * Handler for host <-> firmware communication
+ **********************************************************************/
+
+static inline void n230_poke32(const uint32_t addr, const uint32_t data)
+{
+ if (addr >= N230_FW_HOST_SHMEM_RW_BASE_ADDR && addr <= N230_FW_HOST_SHMEM_MAX_ADDR) {
+ host_shared_mem_ptr->buff[(addr - N230_FW_HOST_SHMEM_BASE_ADDR)/sizeof(uint32_t)] = data;
+ } else if (addr < N230_FW_HOST_SHMEM_BASE_ADDR) {
+ wb_poke32(addr, data);
+ }
+}
+
+static inline uint32_t n230_peek32(const uint32_t addr)
+{
+ if (addr >= N230_FW_HOST_SHMEM_BASE_ADDR && addr <= N230_FW_HOST_SHMEM_MAX_ADDR) {
+ return host_shared_mem_ptr->buff[(addr - N230_FW_HOST_SHMEM_BASE_ADDR)/sizeof(uint32_t)];
+ } else if (addr < N230_FW_HOST_SHMEM_BASE_ADDR) {
+ return wb_peek32(addr);
+ } else {
+ return 0;
+ }
+}
+
+void n230_handle_udp_fw_comms(
+ const uint8_t ethno,
+ const struct ip_addr *src, const struct ip_addr *dst,
+ const uint16_t src_port, const uint16_t dst_port,
+ const void *buff, const size_t num_bytes)
+{
+ if (buff == NULL) {
+ UHD_FW_TRACE(WARN, "n230_handle_udp_fw_comms got an ICMP_DUR");
+ /* We got here from ICMP_DUR undeliverable packet */
+ /* Future space for hooks to tear down streaming radios etc */
+ } else if (num_bytes != sizeof(fw_comm_pkt_t)) {
+ UHD_FW_TRACE(WARN, "n230_handle_udp_fw_comms got an unknown request (bad size).");
+ } else {
+ const fw_comm_pkt_t *request = (const fw_comm_pkt_t *)buff;
+ fw_comm_pkt_t response;
+ bool send_response = process_fw_comm_protocol_pkt(
+ request, &response,
+ N230_FW_PRODUCT_ID,
+ (uint32_t)ethno,
+ n230_poke32, n230_peek32);
+
+ if (send_response) {
+ u3_net_stack_send_udp_pkt(ethno, src, dst_port, src_port, &response, sizeof(response));
+ }
+ }
+}
+
+void n230_register_udp_fw_comms_handler(n230_host_shared_mem_t* shared_mem_ptr)
+{
+ host_shared_mem_ptr = shared_mem_ptr;
+ u3_net_stack_register_udp_handler(N230_FW_COMMS_UDP_PORT, &n230_handle_udp_fw_comms);
+}
+
+
+/***********************************************************************
+ * Handler for UDP framer program packets
+ **********************************************************************/
+void program_udp_framer(
+ const uint8_t ethno,
+ const uint32_t sid,
+ const struct ip_addr *dst_ip,
+ const uint16_t dst_port,
+ const uint16_t src_port)
+{
+ const eth_mac_addr_t *dst_mac = u3_net_stack_arp_cache_lookup(dst_ip);
+ const size_t vdest = (sid >> 16) & 0xff;
+
+ uint32_t framer_base =
+ ((ethno == 1) ? SR_ZPU_ETHINT1 : SR_ZPU_ETHINT0) + SR_ZPU_ETHINT_FRAMER_BASE;
+
+ //setup source framer
+ const eth_mac_addr_t *src_mac = u3_net_stack_get_mac_addr(ethno);
+ wb_poke32(SR_ADDR(WB_SBRB_BASE, framer_base + ETH_FRAMER_SRC_MAC_HI),
+ (((uint32_t)src_mac->addr[0]) << 8) | (((uint32_t)src_mac->addr[1]) << 0));
+ wb_poke32(SR_ADDR(WB_SBRB_BASE, framer_base + ETH_FRAMER_SRC_MAC_LO),
+ (((uint32_t)src_mac->addr[2]) << 24) | (((uint32_t)src_mac->addr[3]) << 16) |
+ (((uint32_t)src_mac->addr[4]) << 8) | (((uint32_t)src_mac->addr[5]) << 0));
+ wb_poke32(SR_ADDR(WB_SBRB_BASE, framer_base + ETH_FRAMER_SRC_IP_ADDR), u3_net_stack_get_ip_addr(ethno)->addr);
+ wb_poke32(SR_ADDR(WB_SBRB_BASE, framer_base + ETH_FRAMER_SRC_UDP_PORT), src_port);
+
+ //setup destination framer
+ wb_poke32(SR_ADDR(WB_SBRB_BASE, framer_base + ETH_FRAMER_DST_RAM_ADDR), vdest);
+ wb_poke32(SR_ADDR(WB_SBRB_BASE, framer_base + ETH_FRAMER_DST_IP_ADDR), dst_ip->addr);
+ wb_poke32(SR_ADDR(WB_SBRB_BASE, framer_base + ETH_FRAMER_DST_UDP_MAC),
+ (((uint32_t)dst_port) << 16) |
+ (((uint32_t)dst_mac->addr[0]) << 8) | (((uint32_t)dst_mac->addr[1]) << 0));
+ wb_poke32(SR_ADDR(WB_SBRB_BASE, framer_base + ETH_FRAMER_DST_MAC_LO),
+ (((uint32_t)dst_mac->addr[2]) << 24) | (((uint32_t)dst_mac->addr[3]) << 16) |
+ (((uint32_t)dst_mac->addr[4]) << 8) | (((uint32_t)dst_mac->addr[5]) << 0));
+}
+
+void handle_udp_prog_framer(
+ const uint8_t ethno,
+ const struct ip_addr *src, const struct ip_addr *dst,
+ const uint16_t src_port, const uint16_t dst_port,
+ const void *buff, const size_t num_bytes)
+{
+ if (buff == NULL) {
+ /* We got here from ICMP_DUR undeliverable packet */
+ /* Future space for hooks to tear down streaming radios etc */
+ } else {
+ const uint32_t sid = ((const uint32_t *)buff)[1];
+ program_udp_framer(ethno, sid, src, src_port, dst_port);
+ UHD_FW_TRACE_FSTR(INFO, "Reprogrammed eth%d framer. Src=%s:%d, Dest=%s:%d",
+ ethno,ip_addr_to_str(src),src_port,ip_addr_to_str(dst),dst_port);
+ }
+}
+
+void n230_register_udp_prog_framer()
+{
+ u3_net_stack_register_udp_handler(N230_FW_COMMS_CVITA_PORT, &handle_udp_prog_framer);
+}
+
+
+/***********************************************************************
+ * Handler for flash programming interface over UDP
+ **********************************************************************/
+
+void n230_handle_flash_prog_comms(
+ const uint8_t ethno,
+ const struct ip_addr *src, const struct ip_addr *dst,
+ const uint16_t src_port, const uint16_t dst_port,
+ const void *buff, const size_t num_bytes)
+{
+ if (buff == NULL) {
+ UHD_FW_TRACE(WARN, "n230_handle_flash_prog_comms got an ICMP_DUR");
+ /* We got here from ICMP_DUR undeliverable packet */
+ /* Future space for hooks to tear down streaming radios etc */
+ } else if (num_bytes != sizeof(n230_flash_prog_t)) {
+ UHD_FW_TRACE(WARN, "n230_handle_flash_prog_comms got an unknown request (bad size).");
+ } else {
+ const n230_flash_prog_t *request = (const n230_flash_prog_t *)buff;
+ n230_flash_prog_t response;
+ bool ack_requested = request->flags & N230_FLASH_COMM_FLAGS_ACK;
+
+ //Request is valid. Copy it into the reply.
+ memcpy(&response, request, sizeof(n230_flash_prog_t));
+
+ switch (request->flags & N230_FLASH_COMM_FLAGS_CMD_MASK) {
+ case N230_FLASH_COMM_CMD_READ_NV_DATA: {
+ UHD_FW_TRACE(DEBUG, "n230_handle_flash_prog_comms::read_nv_data()");
+ //Offset ignored because all non-volatile data fits in a packet.
+ if (is_n230_eeprom_cache_dirty()) {
+ read_n230_eeprom();
+ }
+ //EEPROM cache is up-to-date. Copy it into the packet.
+ //Assumption: Cache size < 256. If this is no longer true, the offset field
+ //will have to be used.
+ memcpy(response.data, get_n230_const_eeprom_map(), sizeof(n230_eeprom_map_t));
+ ack_requested = true;
+ } break;
+
+ case N230_FLASH_COMM_CMD_WRITE_NV_DATA: {
+ UHD_FW_TRACE(DEBUG, "n230_handle_flash_prog_comms::write_nv_data()");
+ //Offset ignored because all non-volatile data fits in a packet.
+ memcpy(get_n230_eeprom_map(), request->data, sizeof(n230_eeprom_map_t));
+ if (!write_n230_eeprom()) {
+ response.flags |= N230_FLASH_COMM_ERR_CMD_ERROR;
+ }
+ } break;
+
+ case N230_FLASH_COMM_CMD_READ_FPGA: {
+ UHD_FW_TRACE_FSTR(DEBUG, "n230_handle_flash_prog_comms::read_fpga_page(offset=0x%x, size=%d)",
+ request->offset, request->size);
+ read_n230_fpga_image_page(request->offset, response.data, request->size);
+ ack_requested = true;
+ } break;
+
+ case N230_FLASH_COMM_CMD_WRITE_FPGA: {
+ UHD_FW_TRACE_FSTR(DEBUG, "n230_handle_flash_prog_comms::write_fpga_page(offset=0x%x, size=%d)",
+ request->offset, request->size);
+ if (!write_n230_fpga_image_page(request->offset, request->data, request->size)) {
+ response.flags |= N230_FLASH_COMM_ERR_CMD_ERROR;
+ }
+ } break;
+
+ case N230_FLASH_COMM_CMD_ERASE_FPGA: {
+ UHD_FW_TRACE_FSTR(DEBUG, "n230_handle_flash_prog_comms::erase_fpga_sector(offset=0x%x)",
+ request->offset);
+ if (!erase_n230_fpga_image_sector(request->offset)) {
+ response.flags |= N230_FLASH_COMM_ERR_CMD_ERROR;
+ }
+ } break;
+
+ default :{
+ UHD_FW_TRACE(ERROR, "n230_handle_flash_prog_comms got an invalid command.");
+ response.flags |= FW_COMM_ERR_CMD_ERROR;
+ }
+ }
+ //Send a reply if ack requested
+ if (ack_requested) {
+ u3_net_stack_send_udp_pkt(ethno, src, dst_port, src_port, &response, sizeof(response));
+ }
+ }
+}
+
+void n230_register_flash_comms_handler()
+{
+ u3_net_stack_register_udp_handler(N230_FW_COMMS_FLASH_PROG_PORT, &n230_handle_flash_prog_comms);
+}
+
+/***********************************************************************
+ * Handler for SFP state changes
+ **********************************************************************/
+#define SFPP_STATUS_MODABS_CHG (1 << 5) // Has MODABS changed since last read?
+#define SFPP_STATUS_TXFAULT_CHG (1 << 4) // Has TXFAULT changed since last read?
+#define SFPP_STATUS_RXLOS_CHG (1 << 3) // Has RXLOS changed since last read?
+#define SFPP_STATUS_MODABS (1 << 2) // MODABS state
+#define SFPP_STATUS_TXFAULT (1 << 1) // TXFAULT state
+#define SFPP_STATUS_RXLOS (1 << 0) // RXLOS state
+
+static bool links_up[N230_MAX_NUM_ETH_PORTS] = {};
+static uint32_t packet_count[N230_MAX_NUM_ETH_PORTS] = {};
+
+void n230_poll_sfp_status(const uint32_t eth, bool force, bool* state_updated)
+{
+ // Has MODDET/MODAbS changed since we last looked?
+ uint32_t rb = wb_peek32(SR_ADDR(WB_SBRB_BASE, (eth==0) ? RB_ZPU_SFP_STATUS0 : RB_ZPU_SFP_STATUS1));
+
+ if (rb & SFPP_STATUS_RXLOS_CHG)
+ UHD_FW_TRACE_FSTR(DEBUG, "eth%1d RXLOS changed state: %d", eth, (rb & SFPP_STATUS_RXLOS));
+ if (rb & SFPP_STATUS_TXFAULT_CHG)
+ UHD_FW_TRACE_FSTR(DEBUG, "eth%1d TXFAULT changed state: %d", eth, ((rb & SFPP_STATUS_TXFAULT) >> 1));
+ if (rb & SFPP_STATUS_MODABS_CHG)
+ UHD_FW_TRACE_FSTR(DEBUG, "eth%1d MODABS changed state: %d", eth, ((rb & SFPP_STATUS_MODABS) >> 2));
+
+ //update the link up status
+ if ((rb & SFPP_STATUS_RXLOS_CHG) || (rb & SFPP_STATUS_TXFAULT_CHG) || (rb & SFPP_STATUS_MODABS_CHG) || force)
+ {
+ const bool old_link_up = links_up[eth];
+ const uint32_t status_reg_addr = (eth==0) ? RB_ZPU_SFP_STATUS0 : RB_ZPU_SFP_STATUS1;
+
+ uint32_t sfpp_status = wb_peek32(SR_ADDR(WB_SBRB_BASE, status_reg_addr)) & 0xFFFF;
+ if ((sfpp_status & (SFPP_STATUS_RXLOS|SFPP_STATUS_TXFAULT|SFPP_STATUS_MODABS)) == 0) {
+ int8_t timeout = 100;
+ bool link_up = false;
+ do {
+ link_up = ((wb_peek32(SR_ADDR(WB_SBRB_BASE, status_reg_addr)) >> 16) & 0x1) != 0;
+ } while (!link_up && timeout-- > 0);
+
+ links_up[eth] = link_up;
+ } else {
+ links_up[eth] = false;
+ }
+
+ if (!old_link_up && links_up[eth]) u3_net_stack_send_arp_request(eth, u3_net_stack_get_ip_addr(eth));
+ UHD_FW_TRACE_FSTR(INFO, "The link on eth port %u is %s", eth, links_up[eth]?"up":"down");
+ if (rb & SFPP_STATUS_MODABS_CHG) {
+ // MODDET has changed state since last checked
+ if (rb & SFPP_STATUS_MODABS) {
+ // MODDET is high, module currently removed.
+ UHD_FW_TRACE_FSTR(INFO, "An SFP+ module has been removed from eth port %d.", eth);
+ } else {
+ // MODDET is low, module currently inserted.
+ // Return status.
+ UHD_FW_TRACE_FSTR(INFO, "A new SFP+ module has been inserted into eth port %d.", eth);
+ }
+ }
+ *state_updated = true;
+ } else {
+ *state_updated = false;
+ }
+}
+
+void n230_update_link_act_state(soft_reg_t* led_reg)
+{
+ static bool first_poll = 1;
+ static uint32_t poll_cnt;
+
+ bool activity[N230_MAX_NUM_ETH_PORTS] = {};
+ for (uint32_t i = 0; i < N230_NUM_ETH_PORTS; i++) {
+ if (first_poll) {
+ links_up[i] = 0;
+ packet_count[i] = 0;
+ poll_cnt = 0;
+ }
+
+ //Check SFP status and update links_up
+ bool link_state_from_sfp = false;
+ n230_poll_sfp_status(i, first_poll, &link_state_from_sfp);
+
+ //Check packet counters less frequently to keep the LED on for a visible duration
+ uint32_t cnt = wb_peek32(SR_ADDR(WB_SBRB_BASE, (i==0)?RB_ZPU_ETH0_PKT_CNT:RB_ZPU_ETH1_PKT_CNT));
+ activity[i] = (cnt != packet_count[i]);
+ packet_count[i] = cnt;
+
+ //Update links_up if there is activity only if the SFP
+ //handler has not updated it
+ if (activity[i] && !link_state_from_sfp) links_up[i] = true;
+ }
+
+ //TODO: Swap this when Ethernet port swap issues is fixed
+ soft_reg_write(led_reg, LED_REG_FIELD_ETH_LINK2, links_up[0]?1:0);
+ soft_reg_write(led_reg, LED_REG_FIELD_ETH_LINK1, links_up[1]?1:0);
+ soft_reg_write(led_reg, LED_REG_FIELD_ETH_ACT2, activity[0]?1:0);
+ soft_reg_write(led_reg, LED_REG_FIELD_ETH_ACT1, activity[1]?1:0);
+
+ first_poll = 0;
+}
+
diff --git a/firmware/usrp3/n230/n230_eth_handlers.h b/firmware/usrp3/n230/n230_eth_handlers.h
new file mode 100644
index 000000000..67afbb246
--- /dev/null
+++ b/firmware/usrp3/n230/n230_eth_handlers.h
@@ -0,0 +1,48 @@
+//
+// Copyright 2014 Ettus Research LLC
+//
+// 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 3 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, see <http://www.gnu.org/licenses/>.
+//
+
+#ifndef INCLUDED_N230_ETH_HANDLERS_H
+#define INCLUDED_N230_ETH_HANDLERS_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <lwip/ip_addr.h>
+#include <wb_soft_reg.h>
+#include "../../../host/lib/usrp/n230/n230_fw_host_iface.h"
+
+/*!
+ * Registrar for host firmware communications handler.
+ */
+void n230_register_udp_fw_comms_handler(n230_host_shared_mem_t* shared_mem_ptr);
+
+/*!
+ * Registrar for framer programmer handler.
+ */
+void n230_register_udp_prog_framer();
+
+/*!
+ * Registrar for host firmware communications handler.
+ */
+void n230_register_flash_comms_handler();
+
+/*!
+ * Handle SFP updates.
+ */
+void n230_update_link_act_state(soft_reg_t* led_reg);
+
+#endif /* INCLUDED_N230_ETH_HANDLERS_H */
diff --git a/firmware/usrp3/n230/n230_init.c b/firmware/usrp3/n230/n230_init.c
new file mode 100644
index 000000000..14f5ebd77
--- /dev/null
+++ b/firmware/usrp3/n230/n230_init.c
@@ -0,0 +1,125 @@
+//
+// Copyright 2014 Ettus Research LLC
+//
+// 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 3 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, see <http://www.gnu.org/licenses/>.
+//
+
+#include <cron.h>
+#include <printf.h>
+#include <wb_utils.h>
+#include <wb_uart.h>
+#include <wb_i2c.h>
+#include <wb_pkt_iface64.h>
+#include <u3_net_stack.h>
+#include <print_addrs.h>
+#include <trace.h>
+#include "../../../host/lib/usrp/n230/n230_eeprom.h"
+#include "n230_init.h"
+#include "../../../host/lib/usrp/n230/n230_fw_defs.h"
+
+static wb_pkt_iface64_config_t pkt_config;
+
+static void putc(void *p, char c)
+{
+//If FW_TRACE_LEVEL is defined, then the trace level is set
+//to a non-zero number. Turn on the debug UART to enable tracing
+#ifdef UHD_FW_TRACE_LEVEL
+ wb_uart_putc(WB_DBG_UART_BASE, c);
+#endif
+}
+
+static uint32_t get_counter_val()
+{
+ return wb_peek32(SR_ADDR(WB_SBRB_BASE, RB_ZPU_COUNTER));
+}
+
+void n230_init(void)
+{
+ //TODO: We may need to remove the debug UART before we release.
+ //Initialize the debug UART first.
+ wb_uart_init(WB_DBG_UART_BASE, CPU_CLOCK_FREQ/DBG_UART_BAUD);
+ init_printf(NULL, putc);
+
+ //Now we can init the rest with prints
+ UHD_FW_TRACE_FSTR(INFO, "[ZPU Init Begin -- CPU CLOCK is %d MHz]", (CPU_CLOCK_FREQ/1000000));
+
+ //Initialize cron and the per millisecond cron job
+ UHD_FW_TRACE(INFO, "Initializing cron...");
+ cron_init(get_counter_val, CPU_CLOCK_FREQ);
+ cron_job_init(PER_MILLISEC_CRON_JOBID, 1);
+ cron_job_init(PER_SECOND_CRON_JOBID, 1000);
+
+ //Initialize rate for I2C cores
+ UHD_FW_TRACE(INFO, "Initializing I2C...");
+ for (uint32_t i = 0; i < N230_NUM_ETH_PORTS; i++) {
+ wb_i2c_init((i==1)?WB_ETH1_I2C_BASE:WB_ETH0_I2C_BASE, CPU_CLOCK_FREQ);
+ }
+
+ //Initialize eeprom
+ read_n230_eeprom();
+
+ UHD_FW_TRACE(INFO, "Initializing network stack...");
+ init_network_stack();
+}
+
+void init_network_stack(void)
+{
+ //Hold Ethernet PHYs in reset
+ wb_poke32(SR_ADDR(WB_SBRB_BASE, SR_ZPU_SW_RST), SR_ZPU_SW_RST_PHY);
+
+ //Initialize ethernet packet interface
+ pkt_config = wb_pkt_iface64_init(WB_PKT_RAM_BASE, WB_PKT_RAM_CTRL_OFFSET);
+ u3_net_stack_init(&pkt_config);
+
+ //Initialize MACs
+ for (uint32_t i = 0; i < N230_NUM_ETH_PORTS; i++) {
+ init_ethernet_mac(i);
+ }
+
+ //Pull Ethernet PHYs out of reset
+ wb_poke32(SR_ADDR(WB_SBRB_BASE, SR_ZPU_SW_RST), SR_ZPU_SW_RST_NONE);
+}
+
+void init_ethernet_mac(uint32_t iface_num)
+{
+ UHD_FW_TRACE_FSTR(INFO, "Initializing eth%d...", iface_num);
+
+ //Get interface info from the EEPROM (or defaults otherwise)
+ const n230_eth_eeprom_map_t* eth_eeprom_map = get_n230_ethernet_info(iface_num);
+ const eth_mac_addr_t *my_mac = (const eth_mac_addr_t *) &(eth_eeprom_map->mac_addr);
+ const struct ip_addr *my_ip = (const struct ip_addr *) &(eth_eeprom_map->ip_addr);
+ const struct ip_addr *subnet = (const struct ip_addr *) &(eth_eeprom_map->subnet);
+
+ //Init software fields related to ethernet
+ u3_net_stack_init_eth(iface_num, my_mac, my_ip, subnet);
+
+ uint32_t dispatcher_base =
+ ((iface_num == 1) ? SR_ZPU_ETHINT1 : SR_ZPU_ETHINT0) + SR_ZPU_ETHINT_DISPATCHER_BASE;
+
+ //Program dispatcher
+ wb_poke32(SR_ADDR(WB_SBRB_BASE, dispatcher_base + 0),
+ (my_mac->addr[5] << 0) | (my_mac->addr[4] << 8) | (my_mac->addr[3] << 16) | (my_mac->addr[2] << 24));
+ wb_poke32(SR_ADDR(WB_SBRB_BASE, dispatcher_base + 1), (my_mac->addr[1] << 0) | (my_mac->addr[0] << 8));
+ wb_poke32(SR_ADDR(WB_SBRB_BASE, dispatcher_base + 2), my_ip->addr);
+ wb_poke32(SR_ADDR(WB_SBRB_BASE, dispatcher_base + 4), 0/*nofwd*/);
+ wb_poke32(SR_ADDR(WB_SBRB_BASE, dispatcher_base + 5), (ICMP_IRQ << 8) | 0); //no fwd: type, code
+
+ //DEBUG: Print initialized info
+ UHD_FW_TRACE_FSTR(INFO, "-- MAC%u: %s", iface_num, mac_addr_to_str(u3_net_stack_get_mac_addr(iface_num)));
+ UHD_FW_TRACE_FSTR(INFO, "-- IP%u: %s", iface_num, ip_addr_to_str(u3_net_stack_get_ip_addr(iface_num)));
+ UHD_FW_TRACE_FSTR(INFO, "-- SUBNET%u: %s", iface_num, ip_addr_to_str(u3_net_stack_get_subnet(iface_num)));
+ UHD_FW_TRACE_FSTR(INFO, "-- BCAST%u: %s", iface_num, ip_addr_to_str(u3_net_stack_get_bcast(iface_num)));
+}
+
+
diff --git a/firmware/usrp3/n230/n230_init.h b/firmware/usrp3/n230/n230_init.h
new file mode 100644
index 000000000..e2231909e
--- /dev/null
+++ b/firmware/usrp3/n230/n230_init.h
@@ -0,0 +1,28 @@
+//
+// Copyright 2014 Ettus Research LLC
+//
+// 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 3 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, see <http://www.gnu.org/licenses/>.
+//
+
+#ifndef INCLUDED_N230_INIT_H
+#define INCLUDED_N230_INIT_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+void n230_init(void);
+void init_network_stack(void);
+void init_ethernet_mac(uint32_t iface_num);
+
+#endif /* INCLUDED_B250_INIT_H */
diff --git a/firmware/usrp3/n230/n230_main.c b/firmware/usrp3/n230/n230_main.c
new file mode 100644
index 000000000..a6c12e56d
--- /dev/null
+++ b/firmware/usrp3/n230/n230_main.c
@@ -0,0 +1,113 @@
+//
+// Copyright 2014 Ettus Research LLC
+//
+// 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 3 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, see <http://www.gnu.org/licenses/>.
+//
+
+#include <cron.h>
+#include <wb_soft_reg.h>
+#include <u3_net_stack.h>
+#include <trace.h>
+#include "../../../host/lib/usrp/n230/n230_fw_defs.h"
+#include "../../../host/lib/usrp/n230/n230_fw_host_iface.h"
+#include "n230_eth_handlers.h"
+#include "n230_init.h"
+
+//The version hash should come from a cmake build variable
+//If it doesn't then the build system does not support the feature
+//so just default to 0xFFFFFFFF
+#ifndef UHD_VERSION_HASH
+#define UHD_VERSION_HASH 0xFFFFFFFF
+#endif
+
+//TODO: This is just for initial debugging.
+static soft_reg_t g_led_register;
+
+//Shared memory
+static n230_host_shared_mem_t g_host_shared_mem;
+
+//Functions
+static void n230_handle_claim();
+
+/***********************************************************************
+ * Main loop runs all the handlers
+ **********************************************************************/
+int main(void)
+{
+ //Initialize host shared mem
+ g_host_shared_mem.data.fw_compat_num = N230_FW_COMPAT_NUM;
+ g_host_shared_mem.data.fw_version_hash = UHD_VERSION_HASH;
+
+ //Main initialization function
+ n230_init();
+
+ //Initialize UDP Handlers
+ n230_register_udp_fw_comms_handler(&g_host_shared_mem);
+ n230_register_udp_prog_framer();
+ n230_register_flash_comms_handler();
+
+ initialize_writeonly_soft_reg(&g_led_register, SR_ADDR(WB_SBRB_BASE, SR_ZPU_LEDS));
+
+ uint32_t heart_beat = 0;
+ while(true)
+ {
+ //TODO: This is just for initial debugging. Once the firmware
+ //is somewhat stable we should delete this cron job
+ if (cron_job_run_due(PER_SECOND_CRON_JOBID)) {
+ //Everything in this block runs approx once per second
+ if (heart_beat % 10 == 0) {
+ UHD_FW_TRACE_FSTR(INFO, "0.1Hz Heartbeat (%u)", heart_beat);
+ }
+ heart_beat++;
+ }
+
+ if (cron_job_run_due(PER_MILLISEC_CRON_JOBID)) {
+ //Everything in this block runs approx once per millisecond
+ n230_handle_claim();
+ n230_update_link_act_state(&g_led_register);
+ }
+
+ //run the network stack - poll and handle
+ u3_net_stack_handle_one();
+ }
+ return 0;
+}
+
+// Watchdog timer for claimer
+static void n230_handle_claim()
+{
+ static uint32_t last_time = 0;
+ static size_t timeout = 0;
+
+ if (g_host_shared_mem.data.claim_time == 0) {
+ //If time is 0 if the claim was forfeit
+ g_host_shared_mem.data.claim_status = 0;
+ } else if (last_time != g_host_shared_mem.data.claim_time) {
+ //If the time changes, reset timeout
+ g_host_shared_mem.data.claim_status = 1;
+ timeout = 0;
+ } else {
+ //Otherwise increment for timeout
+ timeout++;
+ }
+
+ //Always stash the last seen time
+ last_time = g_host_shared_mem.data.claim_time;
+
+ //Timeout logic
+ if (timeout > N230_CLAIMER_TIMEOUT_IN_MS) {
+ g_host_shared_mem.data.claim_time = 0;
+ }
+}
+