#!/usr/bin/python
# -*- coding: utf-8 -*-
#    Copyright 2012-2016
#    Peter C. Wallace <pcw@mesanet.com> and Jeff Epler <jepler@unpythonic.net>
#    Butchered by Moses McKnight <moses@texband.net> to do initial setup
#    on ethernet cards
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

import optparse
import re
import readline
import socket
import struct
import binascii

parser = optparse.OptionParser("%prog [options] [commands]",
    description="Setup Mesa ethernet cards using the LBP16 protocol")
parser.add_option("-i", "--ip", dest="sip",
    help="IP address of board",
    metavar="X.Y.Z.W", default="192.168.1.121")
parser.add_option("-p", "--port", dest="sport",
    help="UDP port of board", type="int", default=27181)
parser.add_option("-t", "--timeout", dest="timeout",
    help="Response timeout in seconds", type="float", default=.2)
parser.add_option("-m", "--mac", dest="mac", default=None,
    help="Last 4 digits of mac address")
parser.add_option("-e", "--eeip", dest="eeip", metavar="X.Y.Z.W",
    default="10.10.10.10",
    help="IP address to write to EEPROM")
parser.add_option("-n", "--name", type="string", dest="board_name",
    default="EBOB-01",
    help="Board Name to write to EEPROM")
options, args = parser.parse_args()

if options.mac is None: raise SystemExit, "-m or --mac must be used to supply the last 4 digits of the mac address"

space = 0
address = 0x00
info = False
increment = True

sizemap = {1: 0, 2: 1, 4: 2, 8: 3}

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
s.settimeout(options.timeout)

def transact(sdata, quiet=False, response=True):
    sdata = "".join(sdata.split()).decode("hex")
    s.sendto(sdata, (options.sip, options.sport))
    if not response: return
    try:
        data, daddr = s.recvfrom(1280)
        if not quiet: print "<", data.encode("hex")
        if not quiet: print "     ", re.sub('[^ -~]', '.', data)
        return data
    except socket.timeout:
        if not quiet: print "! no response"
        return None

def make_read_request(space, info, size, increment, address, nbytes):
    return struct.pack("<HH",
        (1<<14) | (space << 10) | (info << 13)
        | (sizemap[size] << 8) | (increment << 7) | (nbytes/size),
        address)

def make_write_request(space, info, size, increment, address, ena_code, bytes):
    if ena_code is None:
        var = struct.pack("<HH", (1<<15) | (1<<14) | (space << 10) | (info << 13)
        | (sizemap[size] << 8) | (increment << 7) | (len(bytes) / size),
        address) + bytes
    else:
        var = struct.pack("<HHH", (1<<15) | (1<<14) | (6<<10) | (1<<8) | 1,
        0x001A, (ena_code<<8) | space) + struct.pack("<HH",
        (1<<15) | (1<<14) | (space << 10) | (info << 13)
        | (sizemap[size] << 8) | (increment << 7) | (len(bytes) / size),
        address) + bytes
    return var

def optimal_size(space, info, address, nbytes):
    if info: return 2
    info = transact(make_read_request(space, True, 2, True, 2, 4).encode("hex"), quiet=True)
    if info is None:
        raise RuntimeError, "Failed to get information about memory space %d" % space
    memsizes, memranges = struct.unpack("<HH", info)
    maxaddr = 1 << (memranges & 0x3f)
    #print "# Note: space %d has maxaddr %d (memsizes=0x%x memranges=0x%x)" % (space, maxaddr, memsizes, memranges)
    # This gets the wrong limit with my firmware on space 6, so disabling it
    #if address + nbytes >= maxaddr: raise ValueError, "Address out of range (address=%d nbytes=%d maxaddr=%d)" % (address, nbytes, maxaddr)
    for i in (3,2,1,0):
        b = (1<<i)
        if address % b or nbytes % b or not memsizes & b: continue
        return b
    raise ValueError, "Access size incompatible with address or length (address=%d nbytes=%d memsizes=%d)" % (address, nbytes, memsizes)

def write_mac():
    m1 = 0x00601baa
    data = struct.pack('<HL', int(options.mac), m1)
    size = optimal_size(2, False, 0x0002, len(data))
    command = make_write_request(2, False, size, True, 0x0002, 0x36, data)
    command = command.encode("hex")
    print "Write MAC address >", command
    transact(command, response=False)

    command = make_read_request(2, False, size, True, 0x0002, 6)
    command = command.encode("hex")
    print "Read MAC address >", command
    transact(command)

def write_ip():
    data = binascii.hexlify(socket.inet_aton(options.eeip))
    data = struct.pack('<I', long(data, 16))
    size = optimal_size(2, False, 0x0020, len(data))
    command = make_write_request(2, False, size, True, 0x0020, 0x5a, data)
    command = command.encode("hex")
    print "Write IP address >", command
    transact(command, response=False)

    command = make_read_request(2, False, size, True, 0x0020, 4)
    command = command.encode("hex")
    print "Read IP address >", command
    transact(command)

def write_boardname():
    data = options.board_name
    for i in range(16-len(data)):
        data = data + '\x00'
    size = optimal_size(2, False, 0x0010, len(data))
    command = make_write_request(2, False, size, True, 0x0010, 0x36, data)
    command = command.encode("hex")
    print "Write board name >", command
    transact(command, response=False)

    command = make_read_request(2, False, size, True, 0x0010, 16)
    command = command.encode("hex")
    print "Read board name >", command
    transact(command)
    
def firmware_reload():
    data = "01"
    size = optimal_size(6, False, 0x001C, len(data))
    command = make_write_request(6, False, size, True, 0x001C, None, data)
    command = command.encode("hex")
    print "Reloading firmware >", command
    transact(command, response=False)


write_mac()
write_ip()
write_boardname()
firmware_reload()

