Prowl diags

Change-Id: I997a6d27485e618b4fd0345f19dc2fd6177fb828
diff --git a/Makefile b/Makefile
index cdc600d..82a7d8b 100644
--- a/Makefile
+++ b/Makefile
@@ -90,6 +90,10 @@
 DIRS+=diags
 endif
 
+ifeq ($(BR2_TARGET_GENERIC_PLATFORM_NAME),gfrg240)
+DIRS+=diags
+endif
+
 ifeq ($(BUILD_CONMAN),y)
 DIRS+=conman
 endif
diff --git a/diags/Makefile b/diags/Makefile
index b0fcdb9..1a7386b 100644
--- a/diags/Makefile
+++ b/diags/Makefile
@@ -10,6 +10,9 @@
 ifeq ($(BR2_TARGET_GENERIC_PLATFORM_NAME),gflt110)
  DIRS += chameleon
 endif
+ifeq ($(BR2_TARGET_GENERIC_PLATFORM_NAME),gfrg240)
+ DIRS += prowl
+endif
 
 PYTHON?=python
 
@@ -24,7 +27,7 @@
 clean:   $(addsuffix /clean,$(DIRS))
 install-libs: $(addsuffix /install-libs,$(DIRS))
 
-windcharger/all spacecast/all chameleon/all:	common/all
+windcharger/all spacecast/all chameleon/all prowl/all:	common/all
 
 # The install targets in the recursive call use setuptools to build the python
 # packages. These cannot be run in parallel, as they appear to race with each
diff --git a/diags/prowl/Makefile b/diags/prowl/Makefile
new file mode 100644
index 0000000..fee512d
--- /dev/null
+++ b/diags/prowl/Makefile
@@ -0,0 +1,65 @@
+default:	all
+
+BINARY = diags
+
+TARGETS=$(BINARY)
+INSTALL=install
+PREFIX=$(DESTDIR)/usr
+BINDIR=$(PREFIX)/bin
+LIBDIR=$(PREFIX)/lib
+INCLUDEDIR=$(PREFIX)/include
+
+CC=$(CROSS_COMPILE)gcc
+CXX=$(CROSS_COMPILE)g++
+RM=rm -f
+CFLAGS = -Wall -Wimplicit -Wno-unknown-pragmas -W -std=c99 -D_GNU_SOURCE
+
+CFLAGS += $(EXTRA_CFLAGS)
+LDFLAGS += $(EXTRA_LDFLAGS)
+
+# enable the platform we're supporting
+ifeq ($(BR2_PACKAGE_BCM_NEXUS),y)
+  CFLAGS += -DBROADCOM
+  NOSTUB=1
+endif
+ifeq ($(BR2_PACKAGE_MINDSPEED_DRIVERS),y)
+  CFLAGS += -DMINDSPEED
+  NOSTUB=1
+endif
+
+ifeq ($(BR2_TARGET_GOOGLE_PLATFORM),gfiberlt)
+  CFLAGS += -DGFIBER_LT
+  NOSTUB=1
+endif
+
+ifndef NOSTUB
+  CFLAGS += -DSTUB
+  LDFLAGS += -lm
+endif
+
+CFLAGS += -g
+
+IFLAGS += $(patsubst %,-I%,$(INC_DIRS))
+
+CFILES = $(wildcard ../common/*.c *.c)
+OFILES = $(patsubst %.c,%.o,$(CFILES))
+
+all:	$(TARGETS)
+
+install:
+	$(INSTALL) -m 0755 diags $(BINDIR)/
+
+install-libs:
+	@:
+
+test:
+	@:
+
+$(BINARY):	$(OFILES)
+	$(CC) $^ $(LDFLAGS) -o $@
+
+%.o:	%.c
+	$(CC) $(CFLAGS) $(IFLAGS) -c $^ -c
+
+clean:
+	$(RM) $(OFILES) $(BINARY)
diff --git a/diags/prowl/common.h b/diags/prowl/common.h
new file mode 100644
index 0000000..d932ed1
--- /dev/null
+++ b/diags/prowl/common.h
@@ -0,0 +1,14 @@
+/*
+ * (C) Copyright 2015 Google, Inc.
+ * All rights reserved.
+ *
+ */
+
+#ifndef VENDOR_GOOGLE_DIAGS_SPACECAST_COMMON_H_
+#define VENDOR_GOOGLE_DIAGS_SPACECAST_COMMON_H_
+
+#define MAX_PKT_SIZE 4096
+#define FAIL_TEXT "FAILED:"
+#define PASS_TEXT "PASSED:"
+
+#endif  // VENDOR_GOOGLE_DIAGS_SPACECAST_COMMON_H_
diff --git a/diags/prowl/diagutil.c b/diags/prowl/diagutil.c
new file mode 100644
index 0000000..76410d1
--- /dev/null
+++ b/diags/prowl/diagutil.c
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2015 Google Inc.
+ * All rights reserved.
+ * diagutil -- Linux-based Hardware Diagnostic Utilities
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#define DIAGS_VERSION "1.2"
+
+/* External Functions */
+int ioread(int argc, char **argv);
+int iowrite(int argc, char **argv);
+int iowrite_only(int argc, char *argv[]);
+int i2cprobe(int argc, char **argv);
+int i2cread(int argc, char **argv);
+int i2cwrite(int argc, char **argv);
+int board_temp(int argc, char *argv[]);
+int led_set(int argc, char *argv[]);
+int led_set_pwm(int argc, char *argv[]);
+int switch_state(int argc, char *argv[]);
+int poe_disable(int argc, char *argv[]);
+int phy_read(int argc, char *argv[]);
+int phy_write(int argc, char *argv[]);
+int loopback_test(int argc, char *argv[]);
+
+/* Define the command structure */
+typedef struct {
+  const char *name;
+  int (*funcp)(int, char **);
+} tCOMMAND;
+
+void printVersion() { printf("%s\n", DIAGS_VERSION); }
+
+int version(int argc, char *argv[]) {
+  // This is to avoid unused params warning
+  if ((argc != 1) || (argv[0] == NULL)) {
+    printf("Invalid command parameter\n");
+  }
+  printVersion();
+  return 0;
+}
+
+/* Table of supported commands */
+tCOMMAND command_list[] = {
+    {"ioread", ioread},
+    {"iowrite", iowrite},
+    {"iowrite_only", iowrite_only},
+    {"", NULL},
+    {"i2cread", i2cread},
+    {"i2cwrite", i2cwrite},
+    {"i2cprobe", i2cprobe},
+    {"board_temp", board_temp},
+    {"led_set", led_set},
+    {"led_set_pwm", led_set_pwm},
+    {"", NULL},
+    {"switch_state", switch_state},
+    {"poe_disable", poe_disable},
+    {"", NULL},
+    {"phy_read", phy_read},
+    {"phy_write", phy_write},
+    {"loopback_test", loopback_test},
+    {"", NULL},
+    {"version", version},
+    {"", NULL},
+    {NULL, NULL},
+};
+
+static void usage(void) {
+  int i;
+
+  printf("Supported commands:\n");
+
+  for (i = 0; command_list[i].name != NULL; ++i) {
+    printf("\t%s\n", command_list[i].name);
+  }
+
+  return;
+}
+
+int main(int argc, char *argv[]) {
+  int i;
+
+  if (argc > 1) {
+    /* Search the command list for a match */
+    for (i = 0; command_list[i].name != NULL; ++i) {
+      if (strcmp(argv[1], command_list[i].name) == 0) {
+        return command_list[i].funcp(argc - 1, &argv[1]);
+      }
+    }
+  }
+
+  /* no command or bad command */
+  usage();
+
+  return 0;
+}
diff --git a/diags/prowl/eth_test.c b/diags/prowl/eth_test.c
new file mode 100644
index 0000000..f59fab5
--- /dev/null
+++ b/diags/prowl/eth_test.c
@@ -0,0 +1,519 @@
+/*
+ * (C) Copyright 2015 Google, Inc.
+ * All rights reserved.
+ *
+ */
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <linux/if_packet.h>
+#include <linux/types.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <netinet/ether.h>
+#include <netinet/in.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <unistd.h>
+#include "../common/util.h"
+#include "../common/io.h"
+#include "common.h"
+
+#define WAN_PORT_NAME "eth1_1"
+#define ETH_TEST_LINE_MAX 4096
+#define TX_BYTES_PATH "cat /sys/class/net/" WAN_PORT_NAME "/statistics/tx_bytes"
+#define RX_BYTES_APTH "cat /sys/class/net/" WAN_PORT_NAME "/statistics/rx_bytes"
+#define ETH_TRAFFIC_TEST_PERIOD_SYMBOL "-p"
+#define ETH_TRAFFIC_MAX_REPORT_PERIOD 50
+#define ETH_TRAFFIC_MAX_GE_REPORT_PERIOD 15
+#define ETH_TRAFFIC_REPORT_PERIOD 50
+#define ETH_PKTS_LEN_DEFAULT 32
+#define ETH_STAT_WAIT_PERIOD 1  // sec
+#define BUF_SIZ 1536
+#define ETH_PKTS_SENT_BEFORE_WAIT 0xFF
+#define SCAN_CMD_FORMAT "%256s"
+// 1G
+#define ETH_TRAFFIC_PER_PERIOD_MAX \
+  (((unsigned int)ETH_TRAFFIC_MAX_REPORT_PERIOD) * ((unsigned int)131072000))
+#define ONE_MEG (1024 * 1024)
+#define ETH_STAT_PERCENT_MARGIN 95
+
+void send_mac_pkt(char *if_name, char *out_name, unsigned int xfer_len,
+                  unsigned int xfer_wait, int n,
+                  const unsigned char *dst_mac1) {
+  int sockfd, i;
+  struct ifreq if_idx;
+  struct ifreq if_mac, out_mac;
+  int tx_len = 0;
+  char sendbuf[BUF_SIZ];
+  struct ether_header *eh = (struct ether_header *)sendbuf;
+  struct sockaddr_ll socket_address;
+  unsigned char dst_mac[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+  /* Open RAW socket to send on */
+  if ((sockfd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW)) == -1) {
+    perror("socket");
+  }
+
+  /* Get the index of the interface to send on */
+  memset(&if_idx, 0, sizeof(if_idx));
+  safe_strncpy(if_idx.ifr_name, if_name, IFNAMSIZ - 1);
+  if (ioctl(sockfd, SIOCGIFINDEX, &if_idx) < 0) {
+    perror("SIOCGIFINDEX");
+  }
+  /* Get the MAC address of the interface to send on */
+  memset(&out_mac, 0, sizeof(out_mac));
+  if (out_name != NULL) {
+    safe_strncpy(out_mac.ifr_name, out_name, IFNAMSIZ - 1);
+    if (ioctl(sockfd, SIOCGIFHWADDR, &out_mac) < 0) {
+      perror("out SIOCGIFHWADDR");
+    }
+  }
+  memset(&if_mac, 0, sizeof(if_mac));
+  safe_strncpy(if_mac.ifr_name, if_name, IFNAMSIZ - 1);
+  if (ioctl(sockfd, SIOCGIFHWADDR, &if_mac) < 0) {
+    perror("SIOCGIFHWADDR");
+  }
+  if (out_name != NULL) {
+    dst_mac[0] = ((uint8_t *)&out_mac.ifr_hwaddr.sa_data)[0];
+    dst_mac[1] = ((uint8_t *)&out_mac.ifr_hwaddr.sa_data)[1];
+    dst_mac[2] = ((uint8_t *)&out_mac.ifr_hwaddr.sa_data)[2];
+    dst_mac[3] = ((uint8_t *)&out_mac.ifr_hwaddr.sa_data)[3];
+    dst_mac[4] = ((uint8_t *)&out_mac.ifr_hwaddr.sa_data)[4];
+    dst_mac[5] = ((uint8_t *)&out_mac.ifr_hwaddr.sa_data)[5];
+  } else if (dst_mac1 != NULL) {
+    dst_mac[0] = dst_mac1[0];
+    dst_mac[1] = dst_mac1[1];
+    dst_mac[2] = dst_mac1[2];
+    dst_mac[3] = dst_mac1[3];
+    dst_mac[4] = dst_mac1[4];
+    dst_mac[5] = dst_mac1[5];
+  } else {
+    printf("Invalid out_name and dst_mac.\n");
+    return;
+  }
+
+  /* Construct the Ethernet header */
+  // memset(sendbuf, 0, BUF_SIZ);
+  for (i = 0; i < BUF_SIZ; ++i) {
+    sendbuf[i] = 0xA5;
+  }
+  /* Ethernet header */
+  eh->ether_shost[0] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[0];
+  eh->ether_shost[1] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[1];
+  eh->ether_shost[2] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[2];
+  eh->ether_shost[3] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[3];
+  eh->ether_shost[4] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[4];
+  eh->ether_shost[5] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[5];
+  eh->ether_dhost[0] = dst_mac[0];
+  eh->ether_dhost[1] = dst_mac[1];
+  eh->ether_dhost[2] = dst_mac[2];
+  eh->ether_dhost[3] = dst_mac[3];
+  eh->ether_dhost[4] = dst_mac[4];
+  eh->ether_dhost[5] = dst_mac[5];
+  /* Ethertype field */
+  eh->ether_type = htons(ETH_P_IP);
+  tx_len += sizeof(struct ether_header);
+  // printf("TX MAC %02x:%02x:%02x:%02x:%02x:%02x RX MAC %02x:%02x:%02x:%02x:%02x:%02x\n", eh->ether_shost[0],eh->ether_shost[1],eh->ether_shost[2],eh->ether_shost[3],eh->ether_shost[4],eh->ether_shost[5],eh->ether_dhost[0],eh->ether_dhost[1],eh->ether_dhost[2],eh->ether_dhost[3],eh->ether_dhost[4],eh->ether_dhost[5]);
+
+  /* Packet data */
+  sendbuf[tx_len++] = 0xde;
+  sendbuf[tx_len++] = 0xad;
+  sendbuf[tx_len++] = 0xbe;
+  sendbuf[tx_len++] = 0xef;
+
+  /* Index of the network device */
+  socket_address.sll_ifindex = if_idx.ifr_ifindex;
+  /* Address length*/
+  socket_address.sll_halen = ETH_ALEN;
+  /* Destination MAC */
+  socket_address.sll_addr[0] = dst_mac[0];
+  socket_address.sll_addr[1] = dst_mac[1];
+  socket_address.sll_addr[2] = dst_mac[2];
+  socket_address.sll_addr[3] = dst_mac[3];
+  socket_address.sll_addr[4] = dst_mac[4];
+  socket_address.sll_addr[5] = dst_mac[5];
+
+  /* Send packet */
+  if (n < 0) {
+    while (1) {
+      if (sendto(sockfd, sendbuf, xfer_len, 0,
+                 (struct sockaddr *)&socket_address,
+                 sizeof(struct sockaddr_ll)) < 0) {
+        printf("Send failed at msg %d\n", i);
+        break;
+      }
+      if (xfer_wait > 0) {
+        if ((i & ETH_PKTS_SENT_BEFORE_WAIT) == 0) {
+          usleep(xfer_wait);
+        }
+      }
+    }
+  } else {
+    for (i = 0; i < n; ++i) {
+      if (sendto(sockfd, sendbuf, xfer_len, 0,
+                 (struct sockaddr *)&socket_address,
+                 sizeof(struct sockaddr_ll)) < 0) {
+        printf("Send failed at msg %d\n", i);
+        break;
+      }
+      if (xfer_wait > 0) {
+        if ((i & ETH_PKTS_SENT_BEFORE_WAIT) == 0) {
+          usleep(xfer_wait);
+        }
+      }
+    }
+  }
+  close(sockfd);
+}
+
+static void phy_read_usage(void) {
+  printf("phy_read <ifname> <reg>\n");
+  printf("Example:\n");
+  printf("phy_read lan0 2\n");
+}
+
+int phy_read(int argc, char *argv[]) {
+  int reg, val;
+
+  if (argc != 3) {
+    phy_read_usage();
+    return -1;
+  }
+
+  reg = strtol(argv[2], NULL, 0);
+  mdio_init();
+  mdio_set_interface(argv[1]);
+  val = mdio_read(reg);
+  mdio_done();
+
+  if (val < 0) {
+    printf("Read PHY %s reg %d failed\n", argv[1], reg);
+    return -1;
+  }
+  printf("PHY %s Reg %d = 0x%x\n", argv[1], reg, val);
+  return 0;
+}
+
+static void phy_write_usage(void) {
+  printf("phy_write <ifname> <reg> <val>\n");
+  printf("Example:\n");
+  printf("phy_write lan0 22 0x6\n");
+}
+
+int phy_write(int argc, char *argv[]) {
+  int reg, val, rc;
+
+  if (argc != 4) {
+    phy_write_usage();
+    return -1;
+  }
+
+  reg = strtol(argv[2], NULL, 0);
+  val = strtol(argv[3], NULL, 16);
+  mdio_init();
+  mdio_set_interface(argv[1]);
+  rc = mdio_write(reg, val);
+  mdio_done();
+
+  if (rc < 0) {
+    printf("Write PHY %s reg %d val 0x%x failed\n", argv[1], reg, val);
+    return -1;
+  }
+  printf("PHY %s Reg %d = 0x%x\n", argv[1], reg, val);
+  return 0;
+}
+
+/* If extra is not NULL, rsp is returned as the string followed extra */
+int scan_command(char *command, char *rsp, char *extra) {
+  FILE *fp;
+  fp = popen(command, "r");
+  if (fp != NULL) {
+    if (extra != NULL) {
+      while (fscanf(fp, "%s", rsp) != EOF) {
+        if (!strcmp(rsp, extra)) {
+          if (fscanf(fp, "%s", rsp) <= 0)
+            return -1;
+          else
+            return 0;
+        }
+      }
+    } else {
+      fscanf(fp, SCAN_CMD_FORMAT, rsp);
+    }
+    pclose(fp);
+  } else {
+    return -1;
+  }
+  return 0;
+}
+
+int net_stat(unsigned int *rx_bytes, unsigned int *tx_bytes) {
+  static unsigned int tx_stat = 0;
+  static unsigned int rx_stat = 0;
+  char rsp[ETH_TEST_LINE_MAX];
+  unsigned int tmp;
+
+  for (tmp = 0; tmp < 2; ++tmp) {
+    // system_cmd(RX_BYTES_APTH);
+    // system_cmd(TX_BYTES_PATH);
+    scan_command(RX_BYTES_APTH, rsp, NULL);
+    scan_command(TX_BYTES_PATH, rsp, NULL);
+    sleep(1);
+  }
+  if (scan_command(RX_BYTES_APTH, rsp, NULL) == 0) *rx_bytes = strtoul(rsp, NULL, 10);
+  if (scan_command(TX_BYTES_PATH, rsp, NULL) == 0) *tx_bytes = strtoul(rsp, NULL, 10);
+  // printf("STAT: TX %d RX %d\n", *tx_bytes, *rx_bytes);
+
+  if (*tx_bytes >= tx_stat) {
+    *tx_bytes -= tx_stat;
+    tx_stat += *tx_bytes;
+  } else {
+    tmp = *tx_bytes;
+    // tx_bytes is uint. It will continue to increment till wrap around
+    // When it wraps around, the current value will be less than the
+    // previous one. That is why this logic kicked in.
+    *tx_bytes += (0xffffffff - tx_stat);
+    tx_stat = tmp;
+  }
+
+  if (*rx_bytes >= rx_stat) {
+    *rx_bytes -= rx_stat;
+    rx_stat += *rx_bytes;
+  } else {
+    tmp = *rx_bytes;
+    // rx_bytes is uint. It will continue to increment till wrap around
+    // When it wraps around, the current value will be less than the
+    // previous one. That is why this logic kicked in.
+    *rx_bytes += (0xffffffff - rx_stat);
+    rx_stat = tmp;
+  }
+  return 0;
+}
+
+// Return 0 if lost carrier. Otherwise, 1
+int get_carrier_state(char *name) {
+  char command[ETH_TEST_LINE_MAX], rsp[ETH_TEST_LINE_MAX];
+
+  snprintf(command, sizeof(command), "cat /sys/class/net/%s/carrier", name);
+  if (scan_command(command, rsp, NULL) == 0) {
+    if (strcmp(rsp, "0") != 0) return 1;
+  }
+  return 0;
+}
+
+// This is the same as sleep but monitor the link carrier every second
+// Return true if the carrier is good every second. Otherwise false
+bool sleep_and_check_carrier(int duration, char *if_name) {
+  bool good_carrier = true;
+  int i;
+  for (i = 0; i < duration; ++i) {
+    if (get_carrier_state(if_name) == 0) good_carrier = false;
+    sleep(1);
+  }
+  return good_carrier;
+}
+
+int get_if_ip(char *name, unsigned int *ip) {
+  char command[ETH_TEST_LINE_MAX], rsp[ETH_TEST_LINE_MAX];
+  bool found = false;
+
+  snprintf(command, sizeof(command), "ip addr show %s", name);
+  if (scan_command(command, rsp, "inet") == 0) {
+    if (sscanf(rsp, "%u.%u.%u.%u", ip, (ip + 1), (ip + 2), (ip + 3)) <= 0) {
+      return -1;
+    }
+    found = true;
+  }
+
+  if (!found) {
+    return -1;
+  }
+
+  return 0;
+}
+
+static void loopback_test_usage(void) {
+  printf(
+      "loopback_test <duration in secs> [<%s print-period in secs>]\n",
+      ETH_TRAFFIC_TEST_PERIOD_SYMBOL);
+  printf("- duration >=1 or -1 (forever)\n");
+  printf("- print-period >= 0 and <= %d\n", ETH_TRAFFIC_MAX_REPORT_PERIOD);
+  printf("- print-period > 0 if duration > 0\n");
+  printf("- print-period = 0 prints only the summary\n");
+}
+
+int loopback_test(int argc, char *argv[]) {
+  int duration, num = -1, collected_count = 0;
+  int pid, pid1, print_period = ETH_TRAFFIC_REPORT_PERIOD;
+  unsigned int pkt_len = ETH_PKTS_LEN_DEFAULT, rx_bytes, tx_bytes;
+  bool print_every_period = true, traffic_problem = false, problem = false;
+  float average_throughput = 0.0, throughput;
+  unsigned char dst_mac[6] = {0, 0, 0, 0, 0, 0};
+  bool gig_traffic = false;
+
+  if ((argc < 2) || (argc > 5)) {
+    printf("Invalid number of parameters: %d\n", argc);
+    loopback_test_usage();
+    return -1;
+  }
+
+  duration = strtol(argv[1], NULL, 0);
+  if ((duration < -1) || (duration == 0)) {
+    printf("Invalid duration %d:%s\n", duration, argv[1]);
+    loopback_test_usage();
+    return -1;
+  }
+
+  if (argc == 3) {
+    if (strcmp(argv[2], "-g") != 0) {
+      printf("Invalid option %s\n", argv[4]);
+      loopback_test_usage();
+      return -1;
+    } else {
+      gig_traffic = true;
+    }
+  }
+
+  if (argc >= 4) {
+    if (strcmp(argv[2], ETH_TRAFFIC_TEST_PERIOD_SYMBOL) != 0) {
+      printf("Invalid option %s\n", argv[2]);
+      loopback_test_usage();
+      return -1;
+    }
+
+    print_period = strtoul(argv[3], NULL, 0);
+    if (((print_period == 0) && (duration < 0)) || (print_period < 0) ||
+        (print_period > ETH_TRAFFIC_MAX_REPORT_PERIOD)) {
+      printf("Invalid print period: %d:%s\n", print_period, argv[3]);
+      loopback_test_usage();
+      return -1;
+    }
+    if (print_period == 0) {
+      print_every_period = false;
+      print_period = ETH_TRAFFIC_REPORT_PERIOD;
+    }
+  }
+
+  if (argc == 5) {
+    if (strcmp(argv[4], "-g") != 0) {
+      printf("Invalid option %s\n", argv[4]);
+      loopback_test_usage();
+      return -1;
+    } else {
+      gig_traffic = true;
+    }
+  }
+
+  net_stat(&rx_bytes, &tx_bytes);
+
+  if (gig_traffic) {
+    /*
+    system_cmd("ethtool -s eth1_0 autoneg off");
+    sleep(1);
+    system_cmd("ethtool -s eth1_0 autoneg on");
+    sleep(1);
+    system_cmd("ethtool -s eth1_0 autoneg off");
+    sleep(1);
+    system_cmd("ethtool -s eth1_0 autoneg on");
+    sleep(1);
+    system_cmd("ethtool -s eth1_0 autoneg off");
+    sleep(1);
+    */
+    if (print_period > ETH_TRAFFIC_MAX_GE_REPORT_PERIOD)
+      print_period = ETH_TRAFFIC_MAX_GE_REPORT_PERIOD;
+    system_cmd("ethtool -s eth1_1 autoneg off duplex full speed 1000");
+    // printf("1G loopback\n");
+    // Need to set crossover
+  } else {
+    system_cmd("ethtool -s eth1_1 autoneg off duplex full speed 10");
+    // printf("10M loopback\n");
+  }
+  // system_cmd("brctl delif br0 eth1_0");
+  system_cmd("brctl delif br0 eth1_1");
+  // system_cmd("brctl delif br0 wifi0");
+  // system_cmd("ethtool -s " WAN_PORT_NAME " autoneg off duplex full speed 10");
+  // printf("ethtool -s eth1_0 autoneg off\n");
+  sleep(9);
+
+  pid = fork();
+  if (pid < 0) {
+    printf("Server fork error %d, errno %d\n", pid, errno);
+    return -1;
+  }
+  if (pid == 0) {
+    // Child process
+    send_mac_pkt(WAN_PORT_NAME, NULL, pkt_len, 1000, num, dst_mac);
+    // send_mac_pkt(WAN_PORT_NAME, "eth1_0", pkt_len, 0, num, dst_mac);
+    exit(0);
+  }
+  // Parent process
+  pid1 = pid;
+
+  while (duration != 0) {
+    if (duration >= 0) {
+      if (duration <= print_period) {
+        problem = !sleep_and_check_carrier(duration, WAN_PORT_NAME);
+        print_period = duration;
+        duration = 0;
+        kill(pid1, SIGKILL);
+        // printf("Killed processes %d and %d\n", pid1, pid2);
+      } else {
+        duration -= print_period;
+        problem = !sleep_and_check_carrier(print_period, WAN_PORT_NAME);
+      }
+    } else {
+      problem = !sleep_and_check_carrier(print_period, WAN_PORT_NAME);
+    }
+
+    if (duration > 0) kill(pid1, SIGSTOP);
+    sleep(ETH_STAT_WAIT_PERIOD);
+    net_stat(&rx_bytes, &tx_bytes);
+    printf("carrier %d: TX %d RX %d\n", !(problem), tx_bytes, rx_bytes);
+    if (duration > 0) kill(pid1, SIGCONT);
+    ++collected_count;
+    // Give 1% margin
+    if ((rx_bytes == 0) ||
+        (((tx_bytes / 100) * ETH_STAT_PERCENT_MARGIN) > rx_bytes)) {
+      problem = true;
+    }
+    if ((rx_bytes > ETH_TRAFFIC_PER_PERIOD_MAX) ||
+        (tx_bytes > ETH_TRAFFIC_PER_PERIOD_MAX)) {
+      problem = true;
+    }
+    traffic_problem |= problem;
+    if (!problem) {
+      throughput = (((float)rx_bytes) * 8) / (float)(print_period * ONE_MEG);
+      average_throughput += throughput;
+    } else {
+      throughput = 0.0;
+    }
+    if (print_every_period) {
+      printf("%s %s: %3.3f Mb/s (%d:%d)\n", (problem) ? FAIL_TEXT : PASS_TEXT,
+             WAN_PORT_NAME, throughput, tx_bytes, rx_bytes);
+    }
+    problem = false;
+  }
+
+  if (gig_traffic) {
+    // Need to enable crossover
+  }
+
+  average_throughput /= ((float)collected_count);
+  printf("%s overall %s: %3.3f Mb/s\n",
+         (traffic_problem) ? FAIL_TEXT : PASS_TEXT, argv[1],
+         average_throughput);
+
+  system_cmd("ethtool -s " WAN_PORT_NAME " autoneg on");
+  // system_cmd("brctl addif br0 " WAN_PORT_NAME);
+  // printf("ethtool -s eth1_0 autoneg on\n");
+  return 0;
+}
diff --git a/diags/prowl/gpio.c b/diags/prowl/gpio.c
new file mode 100644
index 0000000..5602fed
--- /dev/null
+++ b/diags/prowl/gpio.c
@@ -0,0 +1,81 @@
+/*
+ * (C) Copyright 2016 Google, Inc.
+ * All rights reserved.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include "../common/util.h"
+#include "i2c.h"
+
+static void switch_state_usage(void) {
+  printf("switch_state\n");
+  printf("Example:\n");
+  printf(" switch_state\n");
+}
+
+int  switch_state(int argc, char *argv[]) {
+  if (argc != 1 || argv == NULL) {
+    switch_state_usage();
+    return -1;
+  }
+
+  system_cmd("echo 5 > /sys/class/gpio/export");
+  system_cmd("cat /sys/class/gpio/gpio5/value");
+
+  return 0;
+}
+
+static void poe_disable_usage(void) {
+  printf("poe_disable [<0 | 1>]\n");
+  printf("Example:\n");
+  printf(" poe_disable 1\n");
+}
+
+int poe_disable(int argc, char *argv[]) {
+  FILE *fp;
+  char rsp[1024];
+
+  if (argc > 2) {
+    poe_disable_usage();
+    return -1;
+  }
+
+  system_cmd("echo 4 > /sys/class/gpio/export");
+  fp = popen("cat /sys/class/gpio/gpio4/direction", "r");
+  if (fp == NULL) {
+    printf("Failed to open GPIO4\n");
+    return -1;
+  }
+  fscanf(fp, "%s", rsp);
+  pclose(fp);
+  if (strcmp(rsp, "out") != 0) {
+    system_cmd("echo \"out\" > /sys/class/gpio/gpio4/direction");
+  }
+  if ( argc == 1) {
+    printf("PoE Disable: ");
+    fflush(stdout);
+    system_cmd("cat /sys/class/gpio/gpio4/value");
+  } else {
+    if (strcmp(argv[1], "0") == 0) {
+      system_cmd("echo 0 > /sys/class/gpio/gpio4/value");
+      printf("PoE Disable set to ");
+      fflush(stdout);
+      system_cmd("cat /sys/class/gpio/gpio4/value");
+    } else if (strcmp(argv[1], "1") == 0) {
+      system_cmd("echo 1 > /sys/class/gpio/gpio4/value");
+      printf("PoE Disable set to ");
+      fflush(stdout);
+      system_cmd("cat /sys/class/gpio/gpio4/value");
+    } else {
+      poe_disable_usage();
+      return -1;
+    }
+  }
+
+  return 0;
+}
+
diff --git a/diags/prowl/i2c.c b/diags/prowl/i2c.c
new file mode 100644
index 0000000..1107bfc
--- /dev/null
+++ b/diags/prowl/i2c.c
@@ -0,0 +1,163 @@
+/*
+ * (C) Copyright 2014 Google, Inc.
+ * All rights reserved.
+ *
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/file.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "i2c.h"
+
+extern int ioctl(int, int, void *);
+
+int i2cr(int controller, uint8_t device_addr, uint32_t cell_addr,
+         uint32_t addr_len, uint32_t data_len, uint8_t *buf) {
+  char filename[FILENAME_SIZE];
+  int file;
+  int i;
+  uint32_t temp;
+  unsigned char addrbuf[4]; /* up to 4 byte addressing ! */
+  int return_code = 0;
+  struct i2c_msg message[2];
+  struct i2c_rdwr_ioctl_data rdwr_arg;
+  unsigned int read_data_len;
+
+  /* open the I2C adapter */
+  snprintf(filename, sizeof(filename), I2C_DEV_FILE, controller);
+  if ((file = open(filename, O_RDWR)) < 0) {
+    printf("I2C Error open file %s: %s", filename, strerror(errno));
+    return -1;
+  }
+
+  if ((return_code = flock(file, LOCK_EX)) < 0) {
+    printf("I2C Error lock file %s: %s", filename, strerror(errno));
+    goto i2cr_cleanup;
+  }
+
+  /* if we need to send addr, use combined transaction */
+  if (addr_len > 0) {
+    /* build struct i2c_msg 0 */
+    message[0].addr = device_addr;
+    message[0].flags = 0;
+    message[0].len = addr_len;
+    temp = cell_addr;
+    /* store addr into buffer */
+    for (i = addr_len - 1; i >= 0; i--) {
+      addrbuf[i] = temp & 0xff;
+      temp >>= 8;
+    }
+    message[0].buf = addrbuf;
+
+    /* build struct i2c_msg 1 */
+    message[1].addr = device_addr;
+    message[1].flags = I2C_M_RD;
+    message[1].len = data_len;
+    message[1].buf = buf;
+
+    /* build arg */
+    rdwr_arg.msgs = message;
+    rdwr_arg.nmsgs = 2;
+
+    return_code = ioctl(file, I2C_RDWR, &rdwr_arg);
+
+    /* since we pass in rdwr_arg.nmsgs = 2, expect a return of 2 on success */
+    if (return_code == 2) {
+      return_code = 0;
+    }
+
+    goto i2cr_cleanup;
+  }
+
+  /* setup device address */
+  if ((return_code = ioctl(file, I2C_SLAVE, (void *)(uintptr_t)device_addr)) <
+      0) {
+    printf("I2C Error: Could not set device address to %x: %s", device_addr,
+           strerror(errno));
+    goto i2cr_cleanup;
+  }
+
+  /* now read data out of the device */
+  read_data_len = read(file, buf, data_len);
+  if (read_data_len == data_len) {
+    return_code = 0;
+  }
+
+i2cr_cleanup:
+  flock(file, LOCK_UN);
+  close(file);
+
+  return return_code;
+}
+
+int i2cw(int controller, uint8_t device_addr, uint32_t cell_addr,
+         uint32_t addr_len, uint32_t data_len, uint8_t *buf) {
+  char filename[FILENAME_SIZE];
+  int file;
+  int i;
+  uint32_t temp;
+  uint8_t tempbuf[I2C_PAGE_SIZE + 4];
+  int return_code;
+  uint8_t *writebuf = buf;
+  unsigned int write_data_len;
+
+  /* check data len */
+  if (data_len > I2C_PAGE_SIZE) {
+    return -1;
+  }
+
+  /* open the corrrsponding I2C adapter */
+  snprintf(filename, sizeof(filename), I2C_DEV_FILE, controller);
+  if ((file = open(filename, O_RDWR)) < 0) {
+    printf("I2C Error open file %s: %s", filename, strerror(errno));
+    return -1;
+  }
+
+  if ((return_code = flock(file, LOCK_EX)) < 0) {
+    printf("I2C Error lock file %s: %s", filename, strerror(errno));
+    goto i2cw_cleanup;
+  }
+
+  /* setup device address */
+  if ((return_code =
+           ioctl(file, I2C_SLAVE_FORCE, (void *)(uintptr_t)device_addr)) < 0) {
+    printf("I2C Error: Could not set device address to %x: %s", device_addr,
+           strerror(errno));
+    goto i2cw_cleanup;
+  }
+
+  /* if we need to send addr */
+  if (addr_len > 0) {
+    temp = cell_addr;
+    /* store addr into buffer */
+    for (i = (int)(addr_len - 1); i >= 0; i--) {
+      tempbuf[i] = temp & 0xff;
+      temp >>= 8;
+    }
+    /* copy data over into tempbuf, right after the cell address */
+    for (i = 0; i < (int)(data_len); i++) {
+      tempbuf[addr_len + i] = buf[i];
+    }
+    writebuf = tempbuf;
+  }
+
+  /* now write addr + data out to the device */
+  write_data_len = write(file, writebuf, addr_len + data_len);
+
+  if (write_data_len == (addr_len + data_len)) {
+    return_code = 0;
+  }
+
+i2cw_cleanup:
+  flock(file, LOCK_UN);
+  close(file);
+
+  return return_code;
+}
diff --git a/diags/prowl/i2c.h b/diags/prowl/i2c.h
new file mode 100644
index 0000000..0c5f028
--- /dev/null
+++ b/diags/prowl/i2c.h
@@ -0,0 +1,44 @@
+/*
+ * (C) Copyright 2014 Google, Inc.
+ * All rights reserved.
+ *
+ */
+
+#ifndef optimus_i2c_h
+#define optimus_i2c_h
+
+#include <inttypes.h>
+
+#define I2C_DEV_FILE   "/dev/i2c-%d"
+#define I2C_PAGE_SIZE  16
+#define I2C_M_RD       0x01
+#define FILENAME_SIZE  64
+
+/*
+ * I2C Message - used for pure i2c transaction, also from /dev interface
+ */
+struct i2c_msg {
+  uint16_t addr;  /* slave address */
+  uint16_t flags;
+  uint16_t len;   /* msg length */
+  uint8_t *buf;   /* pointer to msg data */
+};
+
+/* This is the structure as used in the I2C_RDWR ioctl call */
+struct i2c_rdwr_ioctl_data {
+  struct i2c_msg *msgs;  /* pointers to i2c_msgs */
+  uint32_t nmsgs;        /* number of i2c_msgs */
+};
+
+#define I2C_SLAVE        0x0703  /* Change slave address */
+#define I2C_SLAVE_FORCE  0x0706  /* Use this slave address, even if it
+                                    is already in use by a driver! */
+#define I2C_RDWR         0x0707  /* Combined R/W transfer (one stop only) */
+
+int i2cr(int controller, uint8_t device_addr, uint32_t cell_addr,
+         uint32_t addr_len, uint32_t data_len, uint8_t* buf);
+
+int i2cw(int controller, uint8_t device_addr, uint32_t cell_addr,
+         uint32_t addr_len, uint32_t data_len, uint8_t* buf);
+
+#endif  // optimus_i2c_h
diff --git a/diags/prowl/i2c_cmd.c b/diags/prowl/i2c_cmd.c
new file mode 100644
index 0000000..d85f7e0
--- /dev/null
+++ b/diags/prowl/i2c_cmd.c
@@ -0,0 +1,328 @@
+/*
+ * (C) Copyright 2014 Google, Inc.
+ * All rights reserved.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include "../common/util.h"
+#include "i2c.h"
+
+#define I2C_READ_BUF_SIZE 1024
+#define DISPLAY_WIDTH 8
+#define LED_BUS 0
+#define LED_ADDR 0x62
+#define LED_SELECT_REG 0x5
+
+int i2cread(int argc, char *argv[]);
+int i2cwrite(int argc, char *argv[]);
+int i2cprobe(int argc, char *argv[]);
+
+static void i2cread_usage(void) {
+  printf(
+      "i2cread bus# dev-address register-offset"
+      " address-len num-byte-to-read\n");
+  printf("Example:\n");
+  printf("i2cread 1 0x2c 0x40 1 1\n");
+  printf(
+      "Read from bus 1  device 0x2c, register 0x40,"
+      " address length is 1, read 1 byte\n");
+}
+
+int i2cread(int argc, char *argv[]) {
+  uint8_t device_addr;
+  uint32_t cell_addr;
+  uint32_t addr_len;
+  uint32_t data_len;
+  uint8_t *buf;
+  int j, k;
+  int return_code;
+  int controller;
+
+  if (argc < 6) {
+    i2cread_usage();
+    return -1;
+  }
+
+  controller = strtoul(argv[1], NULL, 0);
+  device_addr = (uint8_t)strtoul(argv[2], NULL, 0);
+  cell_addr = strtoul(argv[3], NULL, 0);
+  addr_len = strtoul(argv[4], NULL, 0);
+  data_len = strtoul(argv[5], NULL, 0);
+
+  if (data_len >= I2C_READ_BUF_SIZE) {
+    printf("ERROR: Size %s too large\n", argv[5]);
+    return -1;
+  }
+
+  buf = (uint8_t *)malloc(I2C_READ_BUF_SIZE);
+  if (buf == NULL) {
+    printf("ERROR: malloc failed (out of memory)\n");
+    return -1;
+  }
+
+  return_code =
+      i2cr(controller, device_addr, cell_addr, addr_len, data_len, buf);
+  if (return_code != 0) {
+    printf("Read ERROR: return code = %d\n", return_code);
+    free(buf);
+    return return_code;
+  }
+
+  /* display */
+  for (j = 0; j < (int)(data_len); j += DISPLAY_WIDTH) {
+    printf("\n@0x%04X\t:", cell_addr + j);
+    for (k = j; (k < (int)(data_len)) && (k < (j + DISPLAY_WIDTH)); k++) {
+      printf("%02X", buf[k]);
+    }
+    /* fill up space if finish before display width */
+    if ((k == (int)(data_len)) && (k < (j + DISPLAY_WIDTH))) {
+      for (k = data_len; k < (j + DISPLAY_WIDTH); k++) {
+        printf("  ");
+      }
+    }
+    printf("\t");
+    for (k = j; (k < (int)(data_len)) && (k < (j + DISPLAY_WIDTH)); k++) {
+      if ((buf[k] >= 0x20) && (buf[k] < 0x7f)) {
+        printf("%c", buf[k]);
+      } else {
+        printf("%c", '.');
+      }
+    }
+    printf("\n");
+  }
+
+  printf("\n--------------------------------------------\n");
+
+  free(buf);
+  return 0;
+}
+
+static void i2cwrite_usage(void) {
+  printf(
+      "i2cwrite bus# dev-address register-offset"
+      " address-len data-len data\n");
+  printf("Example:\n");
+  printf("i2cwrite 1 0x2c 0x40 1 1 0x80\n");
+  printf(
+      "Write to bus 1  device 0x2c, register 0x40,"
+      " address length is 1, 1 byte data, data value is 0x80\n");
+}
+
+int i2cwrite(int argc, char *argv[]) {
+  uint8_t device_addr;
+  uint32_t cell_addr;
+  uint32_t addr_len;
+  uint32_t data_len;
+  uint32_t data;
+  uint8_t buf[4];
+  int return_code;
+  int controller;
+  int i;
+
+  if (argc < 6) {
+    i2cwrite_usage();
+    return -1;
+  }
+
+  controller = strtoul(argv[1], NULL, 0);
+  device_addr = (uint8_t)strtoul(argv[2], NULL, 0);
+  cell_addr = strtoul(argv[3], NULL, 0);
+  addr_len = strtoul(argv[4], NULL, 0);
+  data_len = strtoul(argv[5], NULL, 0);
+
+  if (data_len > 4) {
+    printf("ERROR: Size %s too large\n", argv[5]);
+    return -1;
+  }
+
+  data = strtoul(argv[6], NULL, 0);
+
+  /* store data into buffer */
+  for (i = data_len - 1; i >= 0; i--) {
+    buf[i] = data & 0xff;
+    data >>= 8;
+  }
+
+  return_code =
+      i2cw(controller, device_addr, cell_addr, addr_len, data_len, buf);
+  if (return_code != 0) {
+    printf("Write ERROR: return code = %d\n", return_code);
+    return return_code;
+  }
+
+  return 0;
+}
+
+static void i2cprobe_usage(void) {
+  printf("i2cprobe bus#\n");
+  printf("Example:\n");
+  printf("i2cprobe 2\n");
+}
+
+int i2cprobe(int argc, char *argv[]) {
+  uint8_t device_addr;
+  uint8_t buf[1];
+  int return_code;
+  int controller;
+
+  if (argc < 2) {
+    i2cprobe_usage();
+    return -1;
+  }
+
+  controller = strtoul(argv[1], NULL, 0);
+
+  for (device_addr = 1; device_addr < 127; device_addr++) {
+    /* Avoid probing these devices */
+    if ((device_addr == 0x69) || (device_addr == 0x0C)) {
+      continue;
+    }
+    return_code = i2cr(controller, device_addr, 0, 1, 1, buf);
+    /*
+    if (return_code != 0) {
+      return_code = i2cr(controller, device_addr, 0, 0, 1, buf);
+    }
+    */
+    if (return_code == 0) {
+      printf("Address 0x%02X responding\n", device_addr);
+    }
+  }
+
+  return 0;
+}
+
+static void board_temp_usage(void) {
+  printf("board_temp\n");
+  printf("Example:\n");
+  printf("board_temp\n");
+}
+
+int board_temp(int argc, char *argv[]) {
+  if (argc != 1 || argv == NULL) {
+    board_temp_usage();
+    return -1;
+  }
+
+  system_cmd("cat /sys/bus/i2c/drivers/ds1775/0-0048/temp_val");
+
+  return 0;
+}
+
+static void led_set_usage(void) {
+  printf("led_set <red | blue> <on | off>\n");
+  printf("Example:\n");
+  printf("led_set blue on\n");
+}
+
+int led_set(int argc, char *argv[]) {
+  int led = 0; // 0 for blue and 1 for read
+  bool is_off = true;
+  static const int kLedMask[2] = {0x3, 0xc}, kLedOffMask[2] = {0x1, 0x4};
+  uint8_t setting;
+
+  if (argc != 3) {
+    led_set_usage();
+    return -1;
+  }
+
+  if (strcmp(argv[1], "blue") == 0) {
+    led = 0;
+  } else if (strcmp(argv[1], "red") == 0) {
+    led = 1;
+  } else {
+    printf("Unknown LED %s\n", argv[1]);
+    led_set_usage();
+    return -1;
+  }
+
+  if (strcmp(argv[2], "on") == 0) {
+    is_off = false;
+  } else if (strcmp(argv[2], "off") == 0) {
+    is_off = true;
+  } else {
+    printf("Unknown LED setting %s\n", argv[2]);
+    led_set_usage();
+    return -1;
+  }
+
+  if (i2cr(LED_BUS, LED_ADDR, LED_SELECT_REG, 1, 1, &setting) < 0) {
+    printf("Failed to read LED selector register.\n");
+    return -1;
+  }
+
+  setting &= ~kLedMask[led];
+  if (is_off) setting |= kLedOffMask[led];
+
+  if (i2cw(LED_BUS, LED_ADDR, LED_SELECT_REG, 1, 1, &setting) < 0) {
+    printf("Failed to write LED selector register of 0x%x.\n", setting);
+    return -1;
+  }
+
+  printf("LED %s is set to %s\n", (led == 0) ? "blue" : "red", (is_off) ? "off" : "on");
+
+  return 0;
+}
+
+static void led_set_pwm_usage(void) {
+  printf("led_set_pwm <red | blue> <0-255>\n");
+  printf("Example:\n");
+  printf("led_set_pwm blue 10\n");
+}
+
+int led_set_pwm(int argc, char *argv[]) {
+  int led = 0; // 0 for blue and 1 for read
+  bool is_off = true;
+  static const int kLedPwmMask[2] = {0x3, 0xc}, kLedPwmVal[2] = {0x2, 0xc};
+  static const int kLedPwmReg[2] = {2, 4};
+  uint8_t setting, pwm;
+  unsigned int tmp;
+
+  if (argc != 3) {
+    led_set_pwm_usage();
+    return -1;
+  }
+
+  if (strcmp(argv[1], "blue") == 0) {
+    led = 0;
+  } else if (strcmp(argv[1], "red") == 0) {
+    led = 1;
+  } else {
+    printf("Unknown LED %s\n", argv[1]);
+    led_set_pwm_usage();
+    return -1;
+  }
+
+  tmp = get_num(argv[2]);
+  if (tmp > 255) {
+    printf("Invalid pwm value: %d\n", tmp);
+    led_set_pwm_usage();
+    return -1;
+  }
+  pwm = tmp;
+
+  if (i2cr(LED_BUS, LED_ADDR, LED_SELECT_REG, 1, 1, &setting) < 0) {
+    printf("Failed to read LED selector register.\n");
+    return -1;
+  }
+
+  setting &= ~kLedPwmMask[led];
+  if (is_off) setting |= kLedPwmVal[led];
+
+  if (i2cw(LED_BUS, LED_ADDR, LED_SELECT_REG, 1, 1, &setting) < 0) {
+    printf("Failed to write LED selector register of 0x%x.\n", setting);
+    return -1;
+  }
+
+  if (i2cw(LED_BUS, LED_ADDR, kLedPwmReg[led], 1, 1, &pwm) < 0) {
+    printf("Failed to write LED PWM register %d of 0x%x.\n", kLedPwmReg[led], pwm);
+    return -1;
+  }
+
+  printf("LED %s PWM is set to %d\n", (led == 0) ? "blue" : "red", pwm);
+
+  return 0;
+}
diff --git a/diags/prowl/mdio.c b/diags/prowl/mdio.c
new file mode 100644
index 0000000..357f949
--- /dev/null
+++ b/diags/prowl/mdio.c
@@ -0,0 +1,66 @@
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <syslog.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+
+#include <linux/sockios.h>
+#include <linux/types.h>
+
+static int skfd = -1;
+static struct ifreq ifr;
+
+struct mii_data {
+  __u16 phy_id;
+  __u16 reg_num;
+  __u16 val_in;
+  __u16 val_out;
+};
+
+int mdio_read(int location) {
+  struct mii_data *mii = (struct mii_data *)&ifr.ifr_data;
+  mii->reg_num = location;
+  if (ioctl(skfd, SIOCGMIIREG, &ifr) < 0) {
+    fprintf(stderr, "SIOCGMIIREG on %s failed: %s\n", ifr.ifr_name,
+            strerror(errno));
+    return -1;
+  }
+  return mii->val_out;
+}
+
+int mdio_write(int location, int value) {
+  struct mii_data *mii = (struct mii_data *)&ifr.ifr_data;
+  mii->reg_num = location;
+  mii->val_in = value;
+  if (ioctl(skfd, SIOCSMIIREG, &ifr) < 0) {
+    fprintf(stderr, "SIOCSMIIREG on %s failed: %s\n", ifr.ifr_name,
+            strerror(errno));
+    return -1;
+  }
+  return 0;
+}
+
+void mdio_init() {
+  if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+    perror("socket init failed");
+    exit(-1);
+  }
+}
+
+int mdio_set_interface(const char *ifname) {
+  strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
+  if (ioctl(skfd, SIOCGMIIPHY, &ifr) < 0) {
+    if (errno != ENODEV)
+      fprintf(stderr, "SIOCGMIIPHY on '%s' failed: %s\n", ifname,
+              strerror(errno));
+    return -1;
+  }
+  return 0;
+}
+
+void mdio_done() { close(skfd); }
diff --git a/diags/prowl/mdio.h b/diags/prowl/mdio.h
new file mode 100644
index 0000000..16159c5
--- /dev/null
+++ b/diags/prowl/mdio.h
@@ -0,0 +1,17 @@
+#ifndef VENDOR_GOOGLE_DIAGS_SPACECAST_MDIO_H_
+#define VENDOR_GOOGLE_DIAGS_SPACECAST_MDIO_H_
+
+// In order to read/write PHY registers, it needs to do it in the
+// following sequence:
+// mdio_init
+// mdio_set_interface
+// mdio_read and/or mdio_write (as many as required)
+// mdio_done
+
+int mdio_read(int location);
+int mdio_write(int location, int value);
+void mdio_init();
+int mdio_set_interface(const char* ifname);
+void mdio_done();
+
+#endif  // VENDOR_GOOGLE_DIAGS_SPACECAST_MDIO_H_