//
//    Copyright (C) 2013-2014 Michael Geszkiewicz
//
//    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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
//

#ifdef __linux__
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/ip.h>       // struct ip and IP_MAXPACKET (which is 65535)
#include <netinet/udp.h>      // struct udphdr
#elif _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#endif
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include "types.h"
#include "common.h"
#include "bitfile.h"
#include "eth_boards.h"
#include "eeprom_remote.h"
#include "lbp16.h"
#include "eeprom.h"

extern board_t boards[MAX_BOARDS];
extern int boards_count;

#ifdef __linux__
int sd;
socklen_t len;
#elif _WIN32
SOCKET sd;
int len;
#endif
struct sockaddr_in dst_addr, src_addr;

// eth access functions

inline int eth_send_packet(void *packet, int size) {
    return sendto(sd, (char*) packet, size, 0, (struct sockaddr *) &dst_addr, sizeof(dst_addr));
}

inline int eth_recv_packet(void *buffer, int size) {
    return recvfrom(sd, (char*) buffer, size, 0, (struct sockaddr *) &src_addr, &len);
}

static void eth_socket_nonblocking() {
#ifdef __linux__
    int val = fcntl(sd, F_GETFL);

    val = val | O_NONBLOCK;
    fcntl(sd, F_SETFL, val);
#elif _WIN32
    u_long iMode = 1;

    ioctlsocket(sd, FIONBIO, &iMode);
#endif
}

static void eth_socket_blocking() {
#ifdef __linux__
    int val = fcntl(sd, F_GETFL);

    val = val & ~O_NONBLOCK;
    fcntl(sd, F_SETFL, val);
#elif _WIN32
    u_long iMode = 0;

    ioctlsocket(sd, FIONBIO, &iMode);
#endif
}

void eth_socket_set_dest_ip(char *addr_name) {
    dst_addr.sin_addr.s_addr = inet_addr(addr_name);
}

static char *eth_socket_get_src_ip() {
    return inet_ntoa(src_addr.sin_addr);
}

static int eth_read(llio_t *self, u32 addr, void *buffer, int size) {
    (void)self;
    if ((size/4) > LBP16_MAX_PACKET_DATA_SIZE) {
        printf("ERROR: LBP16: Requested %d units to read, but protocol supports up to %d units to be read per packet\n", size/4, LBP16_MAX_PACKET_DATA_SIZE);
        return -1;
    }

    return lbp16_read(CMD_READ_HOSTMOT2_ADDR32_INCR(size/4), addr, buffer, size);
}

static int eth_write(llio_t *self, u32 addr, void *buffer, int size) {
    (void)self;
    if ((size/4) > LBP16_MAX_PACKET_DATA_SIZE) {
        printf("ERROR: LBP16: Requested %d units to write, but protocol supports up to %d units to be write per packet\n", size/4, LBP16_MAX_PACKET_DATA_SIZE);
        return -1;
    }

    return lbp16_write(CMD_WRITE_HOSTMOT2_ADDR32_INCR(size/4), addr, buffer, size);
}

static int eth_board_open(board_t *board) {
    eth_socket_set_dest_ip(board->dev_addr);
    eeprom_init(&(board->llio));
    lbp16_read(CMD_READ_FLASH_IDROM, FLASH_ID_REG, &(board->flash_id), 4);
    if (board->fallback_support == 1) {
        eeprom_prepare_boot_block(board->flash_id);
        board->flash_start_address = eeprom_calc_user_space(board->flash_id);
    } else {
        board->flash_start_address = 0;
    }
    return 0;
}

static int eth_board_close(board_t *board) {
    (void)board;
    return 0;
}

// Look for an Ethernet board.
// Returns 0 if it finds one, -1 if it doesn't find one.
static int eth_scan_one_addr(board_access_t *access) {
    lbp16_cmd_addr packet, packet2;
    int ret = 0;
    u32 cookie;

    LBP16_INIT_PACKET4(packet, CMD_READ_HM2_COOKIE, HM2_COOKIE_REG);
    lbp16_send_packet_checked(&packet, sizeof(packet));
    sleep_ns(2*1000*1000);
    size_t recv = lbp16_recv_packet(&cookie, sizeof(cookie));

    if ((recv > 0) && (cookie == HM2_COOKIE)) {
        char buff[16];
        board_t *board = &boards[boards_count];

        board_init_struct(board);

        eth_socket_blocking();
        LBP16_INIT_PACKET4(packet2, CMD_READ_BOARD_INFO_ADDR16_INCR(8), 0);
        memset(buff, 0, sizeof(buff));
        lbp16_send_packet_checked(&packet2, sizeof(packet2));
        lbp16_recv_packet_checked(&buff, sizeof(buff));

        if (strncmp(buff, "7I80DB-16", 9) == 0) {
            board->type = BOARD_ETH;
            strncpy(board->dev_addr, eth_socket_get_src_ip(), 16);
            strncpy(board->llio.board_name, buff, 16);
            board->llio.num_ioport_connectors = 4;
            board->llio.pins_per_connector = 17;
            board->llio.ioport_connector_name[0] = "J2";
            board->llio.ioport_connector_name[1] = "J3";
            board->llio.ioport_connector_name[2] = "J4";
            board->llio.ioport_connector_name[3] = "J5";
            board->llio.fpga_part_number = "6slx16ftg256";
            board->llio.num_leds = 4;
            board->llio.read = &eth_read;
            board->llio.write = &eth_write;
            board->llio.write_flash = &remote_write_flash;
            board->llio.verify_flash = &remote_verify_flash;
            board->llio.backup_flash = &remote_backup_flash;
            board->llio.restore_flash = &remote_restore_flash;
            board->llio.reset = &lbp16_board_reset;
            board->llio.reload = &lbp16_board_reload;
            board->open = &eth_board_open;
            board->close = &eth_board_close;
            board->print_info = &eth_print_info;
            board->flash = BOARD_FLASH_REMOTE;
            board->fallback_support = 1;
            board->llio.verbose = access->verbose;
            boards_count ++;
        } else if (strncmp(buff, "7I80DB-25", 9) == 0) {
            board->type = BOARD_ETH;
            strncpy(board->dev_addr, eth_socket_get_src_ip(), 16);
            strncpy(board->llio.board_name, buff, 16);
            board->llio.num_ioport_connectors = 4;
            board->llio.pins_per_connector = 17;
            board->llio.ioport_connector_name[0] = "J2";
            board->llio.ioport_connector_name[1] = "J3";
            board->llio.ioport_connector_name[2] = "J4";
            board->llio.ioport_connector_name[3] = "J5";
            board->llio.fpga_part_number = "6slx25ftg256";
            board->llio.num_leds = 4;
            board->llio.read = &eth_read;
            board->llio.write = &eth_write;
            board->llio.write_flash = &remote_write_flash;
            board->llio.verify_flash = &remote_verify_flash;
            board->llio.backup_flash = &remote_backup_flash;
            board->llio.restore_flash = &remote_restore_flash;
            board->llio.reset = &lbp16_board_reset;
            board->llio.reload = &lbp16_board_reload;
            board->open = &eth_board_open;
            board->close = &eth_board_close;
            board->print_info = &eth_print_info;
            board->flash = BOARD_FLASH_REMOTE;
            board->fallback_support = 1;
            board->llio.verbose = access->verbose;
            boards_count ++;
        } else if (strncmp(buff, "7I80HD-16", 9) == 0) {
            board->type = BOARD_ETH;
            strncpy(board->dev_addr, eth_socket_get_src_ip(), 16);
            strncpy(board->llio.board_name, buff, 16);
            board->llio.num_ioport_connectors = 3;
            board->llio.pins_per_connector = 24;
            board->llio.ioport_connector_name[0] = "P1";
            board->llio.ioport_connector_name[1] = "P2";
            board->llio.ioport_connector_name[2] = "P3";
            board->llio.fpga_part_number = "6slx16ftg256";
            board->llio.num_leds = 4;
            board->llio.read = &eth_read;
            board->llio.write = &eth_write;
            board->llio.write_flash = &remote_write_flash;
            board->llio.verify_flash = &remote_verify_flash;
            board->llio.backup_flash = &remote_backup_flash;
            board->llio.restore_flash = &remote_restore_flash;
            board->llio.reset = &lbp16_board_reset;
            board->llio.reload = &lbp16_board_reload;
            board->open = &eth_board_open;
            board->close = &eth_board_close;
            board->print_info = &eth_print_info;
            board->flash = BOARD_FLASH_REMOTE;
            board->fallback_support = 1;
            board->llio.verbose = access->verbose;
            boards_count ++;
        } else if (strncmp(buff, "7I80HD-25", 9) == 0) {
            board->type = BOARD_ETH;
            strncpy(board->dev_addr, eth_socket_get_src_ip(), 16);
            strncpy(board->llio.board_name, buff, 16);
            board->llio.num_ioport_connectors = 3;
            board->llio.pins_per_connector = 24;
            board->llio.ioport_connector_name[0] = "P1";
            board->llio.ioport_connector_name[1] = "P2";
            board->llio.ioport_connector_name[2] = "P3";
            board->llio.fpga_part_number = "6slx25ftg256";
            board->llio.num_leds = 4;
            board->llio.read = &eth_read;
            board->llio.write = &eth_write;
            board->llio.write_flash = &remote_write_flash;
            board->llio.verify_flash = &remote_verify_flash;
            board->llio.backup_flash = &remote_backup_flash;
            board->llio.restore_flash = &remote_restore_flash;
            board->llio.reset = &lbp16_board_reset;
            board->llio.reload = &lbp16_board_reload;
            board->open = &eth_board_open;
            board->close = &eth_board_close;
            board->print_info = &eth_print_info;
            board->flash = BOARD_FLASH_REMOTE;
            board->fallback_support = 1;
            board->llio.verbose = access->verbose;
            boards_count ++;
        } else if (strncmp(buff, "7I76E-16", 9) == 0) {
            board->type = BOARD_ETH;
            strncpy(board->dev_addr, eth_socket_get_src_ip(), 16);
            strncpy(board->llio.board_name, buff, 16);
            board->llio.num_ioport_connectors = 3;
            board->llio.pins_per_connector = 17;
            board->llio.ioport_connector_name[0] = "on-card";
            board->llio.ioport_connector_name[1] = "P1";
            board->llio.ioport_connector_name[2] = "P2";
            board->llio.bob_hint[0] = BOB_7I76;
            board->llio.fpga_part_number = "6slx16ftg256";
            board->llio.num_leds = 4;
            board->llio.read = &eth_read;
            board->llio.write = &eth_write;
            board->llio.write_flash = &remote_write_flash;
            board->llio.verify_flash = &remote_verify_flash;
            board->llio.backup_flash = &remote_backup_flash;
            board->llio.restore_flash = &remote_restore_flash;
            board->llio.reset = &lbp16_board_reset;
            board->llio.reload = &lbp16_board_reload;
            board->open = &eth_board_open;
            board->close = &eth_board_close;
            board->print_info = &eth_print_info;
            board->flash = BOARD_FLASH_REMOTE;
            board->fallback_support = 1;
            board->llio.verbose = access->verbose;
            boards_count ++;
        } else if (strncmp(buff, "7I92", 4) == 0) {
            board->type = BOARD_ETH;
            strncpy(board->dev_addr, eth_socket_get_src_ip(), 16);
            strncpy(board->llio.board_name, buff, 16);
            board->llio.num_ioport_connectors = 2;
            board->llio.pins_per_connector = 17;
            board->llio.ioport_connector_name[0] = "P2";
            board->llio.ioport_connector_name[1] = "P1";
            board->llio.fpga_part_number = "6slx9tqg144";
            board->llio.num_leds = 4;
            board->llio.read = &eth_read;
            board->llio.write = &eth_write;
            board->llio.write_flash = &remote_write_flash;
            board->llio.verify_flash = &remote_verify_flash;
            board->llio.backup_flash = &remote_backup_flash;
            board->llio.restore_flash = &remote_restore_flash;
            board->llio.reset = &lbp16_board_reset;
            board->llio.reload = &lbp16_board_reload;
            board->open = &eth_board_open;
            board->close = &eth_board_close;
            board->print_info = &eth_print_info;
            board->flash = BOARD_FLASH_REMOTE;
            board->fallback_support = 1;
            board->llio.verbose = access->verbose;
            boards_count ++;
        } else if (strncmp(buff, "7I93", 4) == 0) {
            board->type = BOARD_ETH;
            strncpy(board->dev_addr, eth_socket_get_src_ip(), 16);
            strncpy(board->llio.board_name, buff, 16);
            board->llio.num_ioport_connectors = 2;
            board->llio.pins_per_connector = 24;
            board->llio.ioport_connector_name[0] = "P2";
            board->llio.ioport_connector_name[1] = "P1";
            board->llio.fpga_part_number = "6slx9tqg144";
            board->llio.num_leds = 4;
            board->llio.read = &eth_read;
            board->llio.write = &eth_write;
            board->llio.write_flash = &remote_write_flash;
            board->llio.verify_flash = &remote_verify_flash;
            board->llio.backup_flash = &remote_backup_flash;
            board->llio.restore_flash = &remote_restore_flash;
            board->llio.reset = &lbp16_board_reset;
            board->llio.reload = &lbp16_board_reload;
            board->open = &eth_board_open;
            board->close = &eth_board_close;
            board->print_info = &eth_print_info;
            board->flash = BOARD_FLASH_REMOTE;
            board->fallback_support = 1;
            board->llio.verbose = access->verbose;
            boards_count ++;
        } else if (strncmp(buff, "7I94", 4) == 0) {
            board->type = BOARD_ETH;
            strncpy(board->dev_addr, eth_socket_get_src_ip(), 16);
            strncpy(board->llio.board_name, buff, 16);
            board->llio.num_ioport_connectors = 2;
            board->llio.pins_per_connector = 21;
            board->llio.ioport_connector_name[0] = "Expansion+Serial 0..1";
            board->llio.ioport_connector_name[1] = "Serial 1..7";
            board->llio.bob_hint[0] = BOB_7I94_0;
            board->llio.bob_hint[1] = BOB_7I94_1;
            board->llio.fpga_part_number = "6slx9tqg144";
            board->llio.num_leds = 4;
            board->llio.read = &eth_read;
            board->llio.write = &eth_write;
            board->llio.write_flash = &remote_write_flash;
            board->llio.verify_flash = &remote_verify_flash;
            board->llio.backup_flash = &remote_backup_flash;
            board->llio.restore_flash = &remote_restore_flash;
            board->llio.reset = &lbp16_board_reset;
            board->llio.reload = &lbp16_board_reload;
            board->open = &eth_board_open;
            board->close = &eth_board_close;
            board->print_info = &eth_print_info;
            board->flash = BOARD_FLASH_REMOTE;
            board->fallback_support = 1;
            board->llio.verbose = access->verbose;
            boards_count ++;
        } else if (strncmp(buff, "7I95", 4) == 0) {
            board->type = BOARD_ETH;
            strncpy(board->dev_addr, eth_socket_get_src_ip(), 16);
            strncpy(board->llio.board_name, buff, 16);
            board->llio.num_ioport_connectors = 2;
            board->llio.pins_per_connector = 29;
            board->llio.ioport_connector_name[0] = "Step/DIR+Serial+Encoders";
            board->llio.ioport_connector_name[1] = "I/O+Expansion";
            board->llio.bob_hint[0] = BOB_7I95_0;
            board->llio.bob_hint[1] = BOB_7I95_1;
            board->llio.fpga_part_number = "6slx9tqg144";
            board->llio.num_leds = 4;
            board->llio.read = &eth_read;
            board->llio.write = &eth_write;
            board->llio.write_flash = &remote_write_flash;
            board->llio.verify_flash = &remote_verify_flash;
            board->llio.backup_flash = &remote_backup_flash;
            board->llio.restore_flash = &remote_restore_flash;
            board->llio.reset = &lbp16_board_reset;
            board->llio.reload = &lbp16_board_reload;
            board->open = &eth_board_open;
            board->close = &eth_board_close;
            board->print_info = &eth_print_info;
            board->flash = BOARD_FLASH_REMOTE;
            board->fallback_support = 1;
            board->llio.verbose = access->verbose;
            boards_count ++;
        } else if (strncmp(buff, "7I96", 4) == 0) {
            board->type = BOARD_ETH;
            strncpy(board->dev_addr, eth_socket_get_src_ip(), 16);
            strncpy(board->llio.board_name, buff, 16);
            board->llio.num_ioport_connectors = 3;
            board->llio.pins_per_connector = 17;
            board->llio.ioport_connector_name[0] = "TB3";
            board->llio.ioport_connector_name[1] = "TB1/TB2";
            board->llio.ioport_connector_name[2] = "P1";
            board->llio.bob_hint[0] = BOB_7I96_0;
            board->llio.bob_hint[1] = BOB_7I96_1;
            board->llio.bob_hint[2] = BOB_7I96_2;
            board->llio.fpga_part_number = "6slx9tqg144";
            board->llio.num_leds = 4;
            board->llio.read = &eth_read;
            board->llio.write = &eth_write;
            board->llio.write_flash = &remote_write_flash;
            board->llio.verify_flash = &remote_verify_flash;
            board->llio.backup_flash = &remote_backup_flash;
            board->llio.restore_flash = &remote_restore_flash;
            board->llio.reset = &lbp16_board_reset;
            board->llio.reload = &lbp16_board_reload;
            board->open = &eth_board_open;
            board->close = &eth_board_close;
            board->print_info = &eth_print_info;
            board->flash = BOARD_FLASH_REMOTE;
            board->fallback_support = 1;
            board->llio.verbose = access->verbose;
            boards_count ++;
        } else if (strncmp(buff, "7I97", 4) == 0) {
            board->type = BOARD_ETH;
            strncpy(board->dev_addr, eth_socket_get_src_ip(), 16);
            strncpy(board->llio.board_name, buff, 16);
            board->llio.num_ioport_connectors = 3;
            board->llio.pins_per_connector = 17;
            board->llio.ioport_connector_name[0] = "TB1/TB2/TB3";
            board->llio.ioport_connector_name[1] = "TB4/TB5";
            board->llio.ioport_connector_name[2] = "P1";
            board->llio.bob_hint[0] = BOB_7I97_0;
            board->llio.bob_hint[1] = BOB_7I97_1;
            board->llio.bob_hint[2] = BOB_7I97_2;
            board->llio.fpga_part_number = "6slx9tqg144";
            board->llio.num_leds = 4;
            board->llio.read = &eth_read;
            board->llio.write = &eth_write;
            board->llio.write_flash = &remote_write_flash;
            board->llio.verify_flash = &remote_verify_flash;
            board->llio.backup_flash = &remote_backup_flash;
            board->llio.restore_flash = &remote_restore_flash;
            board->llio.reset = &lbp16_board_reset;
            board->llio.reload = &lbp16_board_reload;
            board->open = &eth_board_open;
            board->close = &eth_board_close;
            board->print_info = &eth_print_info;
            board->flash = BOARD_FLASH_REMOTE;
            board->fallback_support = 1;
            board->llio.verbose = access->verbose;
            boards_count ++;
        } else if (strncmp(buff, "7I98", 4) == 0) {
            board->type = BOARD_ETH;
            strncpy(board->dev_addr, eth_socket_get_src_ip(), 16);
            strncpy(board->llio.board_name, buff, 16);
            board->llio.num_ioport_connectors = 3;
            board->llio.pins_per_connector = 17;
            board->llio.ioport_connector_name[0] = "P1";
            board->llio.ioport_connector_name[1] = "P2";
            board->llio.ioport_connector_name[2] = "P3";
            board->llio.fpga_part_number = "6slx9tqg144";
            board->llio.num_leds = 4;
            board->llio.read = &eth_read;
            board->llio.write = &eth_write;
            board->llio.write_flash = &remote_write_flash;
            board->llio.verify_flash = &remote_verify_flash;
            board->llio.backup_flash = &remote_backup_flash;
            board->llio.restore_flash = &remote_restore_flash;
            board->llio.reset = &lbp16_board_reset;
            board->llio.reload = &lbp16_board_reload;
            board->open = &eth_board_open;
            board->close = &eth_board_close;
            board->print_info = &eth_print_info;
            board->flash = BOARD_FLASH_REMOTE;
            board->fallback_support = 1;
            board->llio.verbose = access->verbose;
            boards_count ++;
         } else {
            printf("Unsupported ethernet device %s at %s\n", buff, eth_socket_get_src_ip());
            ret = -1;
        }

        eth_socket_nonblocking();
    } else {
        ret = -1;
    }
    return ret;
}

// public functions

int eth_boards_init(board_access_t *access) {
    (void)access;
// open socket
#ifdef __linux__
    sd = socket (PF_INET, SOCK_DGRAM, 0);
#elif _WIN32
    int iResult;
    WSADATA wsaData;
    u_long iMode = 1;    // block mode in windows

    iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
    if (iResult != 0) {
        printf("WSAStartup failed: %d\n", iResult);
        return;
    }

    sd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (sd == 0) {
        printf("Can't open socket: %d %d\n", sd, errno);
        return;
    }
#endif
    src_addr.sin_family = AF_INET;
    dst_addr.sin_family = AF_INET;
    src_addr.sin_port = htons(LBP16_UDP_PORT);
    dst_addr.sin_port = htons(LBP16_UDP_PORT);
    len = sizeof(src_addr);
    lbp16_init(BOARD_ETH);
    return 0;
}

void eth_boards_cleanup(board_access_t *access) {
    (void)access;
    int i;

    for (i = 0; i < boards_count; i++) {
        board_t *board = &boards[i];

        eeprom_cleanup(&(board->llio));
    }

    close(sd);
}

// Scan for Ethernet board(s).
// Returns 0 if it found one, -1 on failure.
int eth_boards_scan(board_access_t *access) {
    char addr[16];
    int i;
    char *ptr;
    int r = 0;

    if (access->address == 0) {
        access->dev_addr = LBP16_HW_IP;
    }

#ifdef __linux__
    if (inet_pton(AF_INET, access->dev_addr, addr) != 1) {
#elif _WIN32
    struct sockaddr ss;
    int size;
    if (WSAStringToAddress(access->dev_addr, AF_INET, NULL, (struct sockaddr *)&ss, &size) != 0) {
#endif
        return -1;
    };

    eth_socket_nonblocking();

    if (access->address == 1) {
        eth_socket_set_dest_ip(access->dev_addr);
        r = eth_scan_one_addr(access);
    } else {
        strncpy(addr, access->dev_addr, 16);
        ptr = strrchr(addr, '.');
        *ptr = '\0';

        // We're scanning for boards, return Success (0) if we find at
        // least one, return Fail (-1) if we don't find any.
        r = -1;

        for (i = 1; i < 255; i++) {
            char addr_name[32];
            int this_r;

            sprintf(addr_name, "%s.%d", addr, i);
            eth_socket_set_dest_ip(addr_name);
            this_r = eth_scan_one_addr(access);
            if (this_r == 0) {
                r = 0;
            }
        }
    }
    eth_socket_blocking();
    return r;
}

void eth_print_info(board_t *board) {
    lbp16_cmd_addr packet;
    int i, j;
    u32 flash_id;
    char *mem_types[16] = {NULL, "registers", "memory", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "EEPROM", "flash"};
    char *mem_writeable[2] = {"RO", "RW"};
    char *acc_types[4] = {"8-bit", "16-bit", "32-bit", "64-bit"};
    char *led_debug_types[2] = {"Hostmot2", "eth debug"};
    char *boot_jumpers_types[4] = {"fixed "LBP16_HW_IP, "fixed from EEPROM", "from BOOTP", "INVALID"};
    lbp_mem_info_area mem_area;
    lbp_eth_eeprom_area eth_area;
    lbp_timers_area timers_area;
    lbp_status_area stat_area;
    lbp_info_area info_area;
    lbp16_cmd_addr cmds[LBP16_MEM_SPACE_COUNT];

    printf("\nETH device %s at ip=%s\n", board->llio.board_name, eth_socket_get_src_ip());
    if (board->llio.verbose == 0)
        return;
        
    LBP16_INIT_PACKET4(cmds[0], CMD_READ_AREA_INFO_ADDR16_INCR(LBP16_SPACE_HM2, sizeof(mem_area)/2), 0);
    LBP16_INIT_PACKET4(cmds[1], CMD_READ_AREA_INFO_ADDR16_INCR(LBP16_SPACE_ETH_CHIP, sizeof(mem_area)/2), 0);
    LBP16_INIT_PACKET4(cmds[2], CMD_READ_AREA_INFO_ADDR16_INCR(LBP16_SPACE_ETH_EEPROM, sizeof(mem_area)/2), 0);
    LBP16_INIT_PACKET4(cmds[3], CMD_READ_AREA_INFO_ADDR16_INCR(LBP16_SPACE_FPGA_FLASH, sizeof(mem_area)/2), 0);
    LBP16_INIT_PACKET4(cmds[4], 0, 0);
    LBP16_INIT_PACKET4(cmds[5], 0, 0);
    LBP16_INIT_PACKET4(cmds[6], CMD_READ_AREA_INFO_ADDR16_INCR(LBP16_SPACE_COMM_CTRL, sizeof(mem_area)/2), 0);
    LBP16_INIT_PACKET4(cmds[7], CMD_READ_AREA_INFO_ADDR16_INCR(LBP16_SPACE_BOARD_INFO, sizeof(mem_area)/2), 0);

    LBP16_INIT_PACKET4(packet, CMD_READ_ETH_EEPROM_ADDR16_INCR(sizeof(eth_area)/2), 0);
    memset(&eth_area, 0, sizeof(eth_area));
    lbp16_send_packet_checked(&packet, sizeof(packet));
    lbp16_recv_packet_checked(&eth_area, sizeof(eth_area));

    LBP16_INIT_PACKET4(packet, CMD_READ_COMM_CTRL_ADDR16_INCR(sizeof(stat_area)/2), 0);
    memset(&stat_area, 0, sizeof(stat_area));
    lbp16_send_packet_checked(&packet, sizeof(packet));
    lbp16_recv_packet_checked(&stat_area, sizeof(stat_area));

    LBP16_INIT_PACKET4(packet, CMD_READ_BOARD_INFO_ADDR16_INCR(sizeof(info_area)/2), 0);
    memset(&info_area, 0, sizeof(info_area));
    lbp16_send_packet_checked(&packet, sizeof(packet));
    lbp16_recv_packet_checked(&info_area, sizeof(info_area));

    if (info_area.LBP16_version >= 3) {
        LBP16_INIT_PACKET4(cmds[4], CMD_READ_AREA_INFO_ADDR16_INCR(LBP16_SPACE_TIMER, sizeof(mem_area)/2), 0);
        LBP16_INIT_PACKET4(packet, CMD_READ_TIMER_ADDR16_INCR(sizeof(timers_area)/2), 0);
        memset(&timers_area, 0, sizeof(timers_area));
        lbp16_send_packet_checked(&packet, sizeof(packet));
        lbp16_recv_packet_checked(&timers_area, sizeof(timers_area));
    }

    printf("Communication:\n");
    printf("  transport layer: ethernet IPv4 UDP\n");
    printf("  ip address: %s\n", board->dev_addr);
    printf("  mac address: %02X:%02X:%02X:%02X:%02X:%02X\n", HI_BYTE(eth_area.mac_addr_hi), LO_BYTE(eth_area.mac_addr_hi),
      HI_BYTE(eth_area.mac_addr_mid), LO_BYTE(eth_area.mac_addr_mid), HI_BYTE(eth_area.mac_addr_lo), LO_BYTE(eth_area.mac_addr_lo));
    printf("  protocol: LBP16 version %d\n", info_area.LBP16_version);

    show_board_info(board);

    printf("Board firmware info:\n");
    printf("  memory spaces:\n");
    for (i = 0; i < LBP16_MEM_SPACE_COUNT; i++) {
        u32 size;

        if ((cmds[i].cmd_lo == 0) && (cmds[i].cmd_hi == 0)) continue;
        memset(&mem_area, 0, sizeof(mem_area));
        lbp16_send_packet_checked(&cmds[i], sizeof(cmds[i]));
        lbp16_recv_packet_checked(&mem_area, sizeof (mem_area));

        printf("    %d: %.*s (%s, %s", i, (int)sizeof(mem_area.name), mem_area.name, mem_types[(mem_area.size  >> 8) & 0x7F],
          mem_writeable[(mem_area.size & 0x8000) >> 15]);
        for (j = 0; j < 4; j++) {
            if ((mem_area.size & 0xFF) & 1 << j)
                printf(", %s)", acc_types[j]);
        }
        size = pow(2, mem_area.range & 0x3F);
        show_formatted_size(size);
        if (((mem_area.size  >> 8) & 0x7F) >= 0xE)
            printf(", page size: %u, erase size: %u",
              (unsigned int) pow(2, (mem_area.range >> 6) & 0x1F), (unsigned int) pow(2, (mem_area.range >> 11) & 0x1F));
        printf("\n");
    }
 
    printf("  [space 0] HostMot2\n");
    printf("  [space 2] Ethernet eeprom:\n");
    printf("    mac address: %02X:%02X:%02X:%02X:%02X:%02X\n", HI_BYTE(eth_area.mac_addr_hi), LO_BYTE(eth_area.mac_addr_hi),
      HI_BYTE(eth_area.mac_addr_mid), LO_BYTE(eth_area.mac_addr_mid), HI_BYTE(eth_area.mac_addr_lo), LO_BYTE(eth_area.mac_addr_lo));
    printf("    ip address: %u.%u.%u.%u\n", HI_BYTE(eth_area.ip_addr_hi), LO_BYTE(eth_area.ip_addr_hi), HI_BYTE(eth_area.ip_addr_lo), LO_BYTE(eth_area.ip_addr_lo));
    printf("    board name: %.*s\n", (int)sizeof(eth_area.name), eth_area.name);
    printf("    user leds: %s\n", led_debug_types[eth_area.led_debug & 0x1]);

    printf("  [space 3] FPGA flash eeprom:\n");
    lbp16_read(CMD_READ_FLASH_IDROM, FLASH_ID_REG, &flash_id, 4);
    printf("    flash size: %s (id: 0x%02X)\n", eeprom_get_flash_type(flash_id), flash_id);

    if (info_area.LBP16_version >= 3) {
        printf("  [space 4] timers:\n");
        printf("    uSTimeStampReg: 0x%04X\n", timers_area.uSTimeStampReg);
        printf("    WaituSReg: 0x%04X\n", timers_area.WaituSReg);
        printf("    HM2Timeout: 0x%04X\n", timers_area.HM2Timeout);
    }

    printf("  [space 6] LBP16 control/status:\n");
    printf("    packets received: all %d, UDP %d, bad %d\n", stat_area.RXPacketCount, stat_area.RXGoodCount, stat_area.RXBadCount);
    printf("    packets sent: all %d, UDP %d, bad %d\n", stat_area.TXPacketCount, stat_area.TXGoodCount, stat_area.TXBadCount);
    printf("    parse errors: %d, mem errors %d, write errors %d\n", stat_area.LBPParseErrors, stat_area.LBPMemErrors, stat_area.LBPWriteErrors);
    printf("    error flags: 0x%04X\n", stat_area.ErrorReg);
    printf("    debug LED ptr: 0x%04X\n", stat_area.DebugLEDPtr);
    printf("    scratch: 0x%04X\n", stat_area.Scratch);

    printf("  [space 7] LBP16 info:\n");
    printf("    board name: %.*s\n", (int)sizeof(info_area.name), info_area.name);
    printf("    LBP16 protocol version %d\n", info_area.LBP16_version);
    printf("    board firmware version %d\n", info_area.firmware_version);
    printf("    IP address jumpers at boot: %s\n", boot_jumpers_types[info_area.jumpers]);
}

int eth_set_remote_ip(char *ip_addr) {
    lbp16_write_ip_addr_packets write_ip_pck;
    u32 addr;

#ifdef __linux__
    if (inet_pton(AF_INET, ip_addr, &addr) != 1) {
#elif _WIN32
    struct sockaddr ss;
    int size;
    if (WSAStringToAddress(ip_addr, AF_INET, NULL, (struct sockaddr *)&addr, &size) != 0) {
#endif
        printf("Error: invalid format of IP address\n");
        return -EINVAL;
    }
    addr = htonl(addr);
    LBP16_INIT_PACKET6(write_ip_pck.write_ena_pck, CMD_WRITE_COMM_CTRL_ADDR16(1), COMM_CTRL_WRITE_ENA_REG, 0x5A02);
    LBP16_INIT_PACKET8(write_ip_pck.eth_write_ip_pck, CMD_WRITE_ETH_EEPROM_ADDR16_INCR(2), ETH_EEPROM_IP_REG, addr);
    sendto (sd, (char*) &write_ip_pck, sizeof(write_ip_pck), 0, (struct sockaddr *) &dst_addr, sizeof(dst_addr));
    return 0;
}
