Merge "Refactor isoping and add unit tests."
diff --git a/hnvram/hnvram_main.c b/hnvram/hnvram_main.c
index 1148bb0..a406caa 100644
--- a/hnvram/hnvram_main.c
+++ b/hnvram/hnvram_main.c
@@ -14,7 +14,7 @@
 #include "hmx_upgrade_nvram.h"
 
 // Max length of data in an NVRAM field
-#define NVRAM_MAX_DATA  4096
+#define NVRAM_MAX_DATA  (64*1024)
 
 // Number of bytes of GPN to be represented as hex data
 #define GPN_HEX_BYTES 4
@@ -33,13 +33,14 @@
 }
 
 void usage(const char* progname) {
-  printf("Usage: %s [-d | [-q|-b] [-r|-k] VARNAME] [ [-n] -w VARNAME=value]\n", progname);
+  printf("Usage: %s [-d | [-q|-b] [-r|-k] VARNAME] [ [-n [-p [RO|RW]]] -w VARNAME=value]\n", progname);
   printf("\t-d : dump all NVRAM variables\n");
   printf("\t-r VARNAME : read VARNAME from NVRAM\n");
   printf("\t-q : quiet mode, suppress the variable name and equal sign\n");
   printf("\t-b : read VARNAME from NVRAM in raw binary format, e.g. dumping a binary key\n");
   printf("\t-w VARNAME=value : write value to VARNAME in NVRAM.\n");
   printf("\t-n : toggles whether -w can create new variables. Default is off\n");
+  printf("\t-p [RW|RO] : toggles what partition new writes (-n) used. Default is RW\n");
   printf("\t-k VARNAME : delete existing key/value pair from NVRAM.\n");
 }
 
@@ -190,7 +191,14 @@
   return (int)ret;
 }
 
-char* read_nvram(const char* name, char* output, int outlen, int quiet) {
+// name - name of key to be read
+// output - buffer for value of key
+// outlen - length of buffer
+// quiet - whether buffer is KEY=VAL or VAL
+// part_used - in the case of dynamically added variables (is_field = false),
+//     returns what partition we found the key in
+char* read_nvram(const char* name, char* output, int outlen, int quiet,
+                 HMX_NVRAM_PARTITION_E* part_used) {
   const hnvram_field_t* field = get_nvram_field(name);
   int is_field = (field != NULL);
 
@@ -205,10 +213,18 @@
     }
   } else {
     format_type = HNVRAM_STRING;
-    DRV_Error e = HMX_NVRAM_Read(HMX_NVRAM_PARTITION_RW, (unsigned char*)name,
-                                 0, data, sizeof(data), &data_len);
+
+    // Try both partitions
+    *part_used = HMX_NVRAM_PARTITION_RW;
+    DRV_Error e = HMX_NVRAM_Read(*part_used, (unsigned char*)name, 0, data,
+                                 sizeof(data), &data_len);
     if (e != DRV_OK) {
-      return NULL;
+      *part_used = HMX_NVRAM_PARTITION_RO;
+      e = HMX_NVRAM_Read(*part_used, (unsigned char*)name, 0, data,
+                         sizeof(data), &data_len);
+      if (e != DRV_OK) {
+        return NULL;
+      }
     }
   }
   char formatbuf[NVRAM_MAX_DATA * 2];
@@ -228,7 +244,8 @@
                             unsigned char* output, unsigned int* outlen) {
   int len = strlen(input);
   if (len > *outlen) {
-    len = *outlen;
+    // Data is too large, don't permit a partial write.
+    return NULL;
   }
 
   strncpy((char*)output, input, len);
@@ -353,25 +370,24 @@
 }
 
 DRV_Error clear_nvram(char* optarg) {
-  DRV_Error e = HMX_NVRAM_Remove(HMX_NVRAM_PARTITION_RW,
-                                 (unsigned char*)optarg);
-  if (e == DRV_ERR) {
-    // Avoid throwing error message if variable already cleared
+  DRV_Error err1 = HMX_NVRAM_Remove(HMX_NVRAM_PARTITION_RW,
+                                    (unsigned char*)optarg);
+  DRV_Error err2 = HMX_NVRAM_Remove(HMX_NVRAM_PARTITION_RO,
+                                    (unsigned char*)optarg);
+
+  // Avoid throwing error message if variable already cleared
+  if ((err1 == DRV_ERR || err1 == DRV_OK) &&
+      (err2 == DRV_ERR || err2 == DRV_OK)) {
     return DRV_OK;
   }
-  return e;
+
+  fprintf(stderr, "Error while deleting key %s. RW: %d RO: %d.\n", optarg,
+          err1, err2);
+  return DRV_ERR;
 }
 
-int write_nvram(char* optarg) {
-  char* equal = strchr(optarg, '=');
-  if (equal == NULL) {
-    return -1;
-  }
 
-  char* name = optarg;
-  *equal = '\0';
-  char* value = ++equal;
-
+int write_nvram(char* name, char* value, HMX_NVRAM_PARTITION_E desired_part) {
   const hnvram_field_t* field = get_nvram_field(name);
   int is_field = (field != NULL);
 
@@ -382,6 +398,12 @@
     format_type = HNVRAM_STRING;
   }
 
+  if (strlen(value) > NVRAM_MAX_DATA) {
+    fprintf(stderr, "Value length %d exceeds maximum data size of %d\n",
+      strlen(value), NVRAM_MAX_DATA);
+    return -2;
+  }
+
   unsigned char nvram_value[NVRAM_MAX_DATA];
   unsigned int nvram_len = sizeof(nvram_value);
   if (parse_nvram(format_type, value, nvram_value, &nvram_len) == NULL) {
@@ -390,22 +412,61 @@
 
   if (!is_field) {
     char tmp[NVRAM_MAX_DATA] = {0};
-    int key_exists = (read_nvram(name, tmp, NVRAM_MAX_DATA, 1) != NULL);
-    if (!can_add_flag && !key_exists) {
-      fprintf(stderr, "Key not found in NVRAM. Add -n to allow creation %s\n",
-              name);
-      return -3;
+    HMX_NVRAM_PARTITION_E part_used;
+    if (read_nvram(name, tmp, NVRAM_MAX_DATA, 1, &part_used) == NULL) {
+      return -3; // Write failed: Variable not found
     }
-    DRV_Error er = HMX_NVRAM_Write(HMX_NVRAM_PARTITION_RW, (unsigned char*)name,
-                                   0, nvram_value, nvram_len);
-    if (er != DRV_OK) {
+
+    if (desired_part != HMX_NVRAM_PARTITION_UNSPECIFIED &&
+        desired_part != part_used) {
+      fprintf(stderr, "Variable already exists in other partition: %s\n", name);
       return -4;
     }
-  } else {
-    if (HMX_NVRAM_SetField(field->nvram_type, 0,
-                           nvram_value, nvram_len) != DRV_OK) {
+
+    DRV_Error er = HMX_NVRAM_Write(part_used, (unsigned char*)name, 0,
+                                   nvram_value, nvram_len);
+    if (er != DRV_OK) {
       return -5;
     }
+  } else {
+    if (desired_part != HMX_NVRAM_PARTITION_UNSPECIFIED) {
+      fprintf(stderr, "Partition was specified (%d) on a field variable: %s\n",
+              desired_part, name);
+      return -6;
+    }
+    if (HMX_NVRAM_SetField(field->nvram_type, 0,
+                           nvram_value, nvram_len) != DRV_OK) {
+      return -7;
+    }
+  }
+
+  return 0;
+}
+
+// Adds new variable to HNVRAM in desired_partition as STRING
+int write_nvram_new(char* name, char* value,
+                    HMX_NVRAM_PARTITION_E desired_part) {
+  char tmp[NVRAM_MAX_DATA] = {0};
+  unsigned char nvram_value[NVRAM_MAX_DATA];
+  unsigned int nvram_len = sizeof(nvram_value);
+  if (parse_nvram(HNVRAM_STRING, value, nvram_value, &nvram_len) == NULL) {
+    return -1;
+  }
+
+  if (!can_add_flag) {
+    fprintf(stderr, "Key not found in NVRAM. Add -n to allow creation %s\n",
+            name);
+    return -2;
+  }
+
+  if (desired_part == HMX_NVRAM_PARTITION_UNSPECIFIED) {
+    desired_part = HMX_NVRAM_PARTITION_RW;
+  }
+
+  DRV_Error er = HMX_NVRAM_Write(desired_part, (unsigned char*)name, 0,
+                                 nvram_value, nvram_len);
+  if (er != DRV_OK) {
+    return -3;
   }
 
   return 0;
@@ -425,9 +486,11 @@
   int op_cnt = 0;  // operation
   int q_flag = 0;  // quiet: don't output name of variable.
   int b_flag = 0;  // binary: output the binary format
+  // Desired partition for new writes.
+  HMX_NVRAM_PARTITION_E desired_part = HMX_NVRAM_PARTITION_UNSPECIFIED;
   char output[NVRAM_MAX_DATA];
   int c;
-  while ((c = getopt(argc, argv, "dbqrnw:k:")) != -1) {
+  while ((c = getopt(argc, argv, "dbqrnp:w:k:")) != -1) {
     switch(c) {
       case 'b':
         b_flag = 1;
@@ -438,10 +501,35 @@
       case 'n':
         can_add_flag = 1;
         break;
+      case 'p':
+        if (strcmp(optarg, "RO") == 0) {
+          desired_part = HMX_NVRAM_PARTITION_RO;
+        } else if (strcmp(optarg, "RW") == 0) {
+          desired_part = HMX_NVRAM_PARTITION_RW;
+        } else {
+          fprintf(stderr, "Invalid partition: %s. Use RW or RO\n", optarg);
+          exit(1);
+        }
+        break;
       case 'w':
         {
           char* duparg = strdup(optarg);
-          if (write_nvram(duparg) != 0) {
+          char* equal = strchr(duparg, '=');
+          if (equal == NULL) {
+            return -1;
+          }
+
+          char* name = duparg;
+          *equal = '\0';
+          char* value = equal + 1;
+
+          int ret = write_nvram(name, value, desired_part);
+          if (ret == -3 && can_add_flag) {
+            // key not found, and we are authorized to add a new one
+            ret = write_nvram_new(name, value, desired_part);
+          }
+
+          if (ret != 0) {
             fprintf(stderr, "Unable to write %s\n", duparg);
             free(duparg);
             exit(1);
@@ -503,7 +591,8 @@
           }
           fwrite(output, 1, len, stdout);
         } else {
-          if (read_nvram(argv[optind], output, sizeof(output), q_flag) == NULL) {
+          HMX_NVRAM_PARTITION_E part_used;
+          if (read_nvram(argv[optind], output, sizeof(output), q_flag, &part_used) == NULL) {
             fprintf(stderr, "Unable to read %s\n", argv[optind]);
             exit(1);
           }
diff --git a/hnvram/hnvram_test.cc b/hnvram/hnvram_test.cc
index 39f7e4a..643a436 100644
--- a/hnvram/hnvram_test.cc
+++ b/hnvram/hnvram_test.cc
@@ -8,16 +8,31 @@
 
 int libupgrade_verbose = 1;
 
-char* HMX_NVRAM_Read_Data = NULL;
+char* HMX_NVRAM_Read_Data_RO = NULL;
+char* HMX_NVRAM_Read_Data_RW = NULL;
+
+char* get_Read_Data(HMX_NVRAM_PARTITION_E partition) {
+  if (partition == HMX_NVRAM_PARTITION_RO) {
+    return HMX_NVRAM_Read_Data_RO;
+  } else {
+    return HMX_NVRAM_Read_Data_RW;
+  }
+}
+
 DRV_Error HMX_NVRAM_Read(HMX_NVRAM_PARTITION_E partition,
                          unsigned char* pName, unsigned int offset,
                          unsigned char* pValue, unsigned int ulSize,
                          unsigned int* pLen) {
-  if (HMX_NVRAM_Read_Data == NULL) {
+  if (get_Read_Data(partition) == NULL) {
     return DRV_ERR;
+  }
+  if (partition == HMX_NVRAM_PARTITION_RO) {
+    snprintf((char*)pValue, ulSize, "%s", HMX_NVRAM_Read_Data_RO);
+    *pLen = strlen(HMX_NVRAM_Read_Data_RO);
+    return DRV_OK;
   } else {
-    snprintf((char*)pValue, ulSize, "%s", HMX_NVRAM_Read_Data);
-    *pLen = strlen(HMX_NVRAM_Read_Data);
+    snprintf((char*)pValue, ulSize, "%s", HMX_NVRAM_Read_Data_RW);
+    *pLen = strlen(HMX_NVRAM_Read_Data_RW);
     return DRV_OK;
   }
 }
@@ -25,17 +40,26 @@
 DRV_Error HMX_NVRAM_Write(HMX_NVRAM_PARTITION_E partition,
                          unsigned char* pName, unsigned int offset,
                          unsigned char* pValue, unsigned int ulSize) {
-  HMX_NVRAM_Read_Data = (char*)malloc(ulSize);
-  snprintf(HMX_NVRAM_Read_Data, sizeof(pValue), "%s", (char*)pValue);
+  if (partition == HMX_NVRAM_PARTITION_RO) {
+    HMX_NVRAM_Read_Data_RO = (char*)malloc(ulSize);
+    snprintf(HMX_NVRAM_Read_Data_RO, sizeof(pValue), "%s", (char*)pValue);
+  } else {
+    HMX_NVRAM_Read_Data_RW = (char*)malloc(ulSize);
+    snprintf(HMX_NVRAM_Read_Data_RW, sizeof(pValue), "%s", (char*)pValue);
+  }
   return DRV_OK;
 }
 
 DRV_Error HMX_NVRAM_Remove(HMX_NVRAM_PARTITION_E partition,
                            unsigned char* pName) {
-  if (HMX_NVRAM_Read_Data == NULL) {
+  if (get_Read_Data(partition) == NULL) {
     return DRV_ERR;
   }
-  HMX_NVRAM_Read_Data = NULL;
+  if (partition == HMX_NVRAM_PARTITION_RO) {
+    HMX_NVRAM_Read_Data_RO = NULL;
+  } else {
+    HMX_NVRAM_Read_Data_RW = NULL;
+  }
   return DRV_OK;
 }
 
@@ -84,7 +108,8 @@
     virtual ~HnvramTest() {}
 
     virtual void SetUp() {
-      HMX_NVRAM_Read_Data = NULL;
+      HMX_NVRAM_Read_Data_RO = NULL;
+      HMX_NVRAM_Read_Data_RW = NULL;
       HMX_NVRAM_GetField_Data = NULL;
       HMX_NVRAM_SetField_Data = NULL;
       HMX_NVRAM_SetField_Len = -1;
@@ -184,104 +209,145 @@
 
 TEST_F(HnvramTest, TestReadFieldNvram) {
   char output[256];
+  HMX_NVRAM_PARTITION_E part;
   HMX_NVRAM_GetField_Data = "TestSystemId";
   EXPECT_STREQ("SYSTEM_ID=TestSystemId",
-               read_nvram("SYSTEM_ID", output, sizeof(output), 0));
+               read_nvram("SYSTEM_ID", output, sizeof(output), 0, &part));
   EXPECT_STREQ("TestSystemId",
-               read_nvram("SYSTEM_ID", output, sizeof(output), 1));
+               read_nvram("SYSTEM_ID", output, sizeof(output), 1, &part));
   HMX_NVRAM_GetField_Data = NULL;
-  EXPECT_EQ(NULL, read_nvram("FAKE_SYSTEM_ID", output, sizeof(output), 1));
+  EXPECT_EQ(NULL, read_nvram("FAKE_SYSTEM_ID", output, sizeof(output), 1,
+                             &part));
 }
 
 TEST_F(HnvramTest, TestReadVariableNvram) {
   char output[256];
-  HMX_NVRAM_Read_Data = strdup("ABC123");
+  HMX_NVRAM_PARTITION_E part;
+  HMX_NVRAM_Read_Data_RW = strdup("ABC123");
   EXPECT_STREQ("TEST_VARIABLE=ABC123",
-               read_nvram("TEST_VARIABLE", output, sizeof(output), 0));
+               read_nvram("TEST_VARIABLE", output, sizeof(output), 0, &part));
+  EXPECT_EQ((int)HMX_NVRAM_PARTITION_RW, part);
   EXPECT_STREQ("ABC123",
-               read_nvram("TEST_VARIABLE", output, sizeof(output), 1));
-  HMX_NVRAM_Read_Data = NULL;
-  EXPECT_STREQ(NULL, read_nvram("TEST_VARIABLE", output, sizeof(output), 1));
+               read_nvram("TEST_VARIABLE", output, sizeof(output), 1, &part));
+  EXPECT_EQ((int)HMX_NVRAM_PARTITION_RW, part);
+  HMX_NVRAM_Read_Data_RW = NULL;
+  EXPECT_STREQ(NULL, read_nvram("TEST_VARIABLE", output, sizeof(output), 1,
+                                &part));
 }
 
 TEST_F(HnvramTest, TestWriteFieldNvram) {
   // Type integer
-  char* testdata = strdup("ACTIVATED_KERNEL_NUM=1");
-  EXPECT_EQ(DRV_OK, write_nvram(testdata));
+  char* key = strdup("ACTIVATED_KERNEL_NUM");
+  char* val = strdup("1");
+  EXPECT_EQ(DRV_OK, write_nvram(key, val, HMX_NVRAM_PARTITION_UNSPECIFIED));
   EXPECT_EQ(0x01, *HMX_NVRAM_SetField_Data);
   EXPECT_EQ(1, HMX_NVRAM_SetField_Len);
 
   // Type string
-  testdata = strdup("ACTIVATED_KERNEL_NAME=kernel1");
-  EXPECT_EQ(DRV_OK, write_nvram(testdata));
+  key = strdup("ACTIVATED_KERNEL_NAME");
+  val = strdup("kernel1");
+  EXPECT_EQ(DRV_OK, write_nvram(key, val, HMX_NVRAM_PARTITION_UNSPECIFIED));
   EXPECT_STREQ("kernel1", (char*)HMX_NVRAM_SetField_Data);
   EXPECT_EQ(7, HMX_NVRAM_SetField_Len);
 
   // Make sure it called SetField and not HMX_NVRAM_Write
-  EXPECT_EQ (NULL, HMX_NVRAM_Read_Data);
+  EXPECT_EQ (NULL, HMX_NVRAM_Read_Data_RW);
+  EXPECT_EQ (NULL, HMX_NVRAM_Read_Data_RO);
 
   // Should fail trying to change value of non-exsting field
-  testdata = strdup("FAKE_FIELD=abc123");
-  EXPECT_NE(0, write_nvram(testdata));
-  free(testdata);
+  key = strdup("FAKE_FIELD");
+  val = strdup("abc123");
+  EXPECT_NE(0, write_nvram(key, val, HMX_NVRAM_PARTITION_UNSPECIFIED));
+  free(key);
+  free(val);
 }
 
-TEST_F(HnvramTest, TestWriteVariableNvram) {
+void testWriteVariableNvram(HMX_NVRAM_PARTITION_E partition, HMX_NVRAM_PARTITION_E other) {
   char* key = strdup("TEST_FIELD");
   char* val = strdup("abc123");
-  char* keyval = strdup("TEST_FIELD=abc123");
 
   // Fail to add new one without -n
-  EXPECT_NE(0, write_nvram(strdup(keyval)));
+  EXPECT_NE(0, write_nvram(key, val, HMX_NVRAM_PARTITION_UNSPECIFIED));
+  EXPECT_NE(0, write_nvram(key, val, HMX_NVRAM_PARTITION_RW));
+  EXPECT_NE(0, write_nvram(key, val, HMX_NVRAM_PARTITION_RO));
 
-  // Add new one successfully
   can_add_flag = 1;
-  EXPECT_EQ(0, write_nvram(keyval));
-  EXPECT_STREQ(val,HMX_NVRAM_Read_Data);
+  EXPECT_EQ(-3, write_nvram(key, val, HMX_NVRAM_PARTITION_UNSPECIFIED));
+  EXPECT_EQ(-3, write_nvram(key, val, HMX_NVRAM_PARTITION_RO));
+  EXPECT_EQ(-3, write_nvram(key, val, HMX_NVRAM_PARTITION_RW));
+  // Add new one successfully
+  EXPECT_EQ(0, write_nvram_new(key, val, partition));
+  EXPECT_STREQ(val, get_Read_Data(partition));
 
   // Should be able to read value
   char output[256];
-  EXPECT_STREQ(val, read_nvram(key, output, sizeof(output), 1));
+  HMX_NVRAM_PARTITION_E part_used;
+  EXPECT_STREQ(val, read_nvram(key, output, sizeof(output), 1, &part_used));
+
+  // Make sure read came from right partition
+  EXPECT_EQ(partition, part_used);
 
   char* val2 = strdup("987def");
-  char* keyval2 = strdup("TEST_FIELD=987def");
 
   // Should be able to change value
-  EXPECT_EQ(0, write_nvram(keyval2));
-  EXPECT_STREQ(val2,HMX_NVRAM_Read_Data);
+  EXPECT_EQ(0, write_nvram(key, val2, HMX_NVRAM_PARTITION_UNSPECIFIED));
+  EXPECT_STREQ(val2, get_Read_Data(partition));
+
+  // And back again, this time with correct partition specified
+  EXPECT_EQ(0, write_nvram(key, val, partition));
+  EXPECT_STREQ(val, get_Read_Data(partition));
+
+  // Should fail when specifying wrong partition
+  EXPECT_EQ(-4, write_nvram(key, val2, other));
+  EXPECT_EQ(-4, write_nvram(key, val2, HMX_NVRAM_PARTITION_W_RAWFS));
 
   free(key);
   free(val);
-  free(keyval);
   free(val2);
-  free(keyval2);
 }
 
-TEST_F(HnvramTest, TestClearNvram) {
+TEST_F(HnvramTest, TestWriteVariableNvramRO) {
+  testWriteVariableNvram(HMX_NVRAM_PARTITION_RO, HMX_NVRAM_PARTITION_RW);
+}
+
+TEST_F(HnvramTest, TestWriteVariableNvramRW) {
+  testWriteVariableNvram(HMX_NVRAM_PARTITION_RW, HMX_NVRAM_PARTITION_RO);
+}
+
+void testClearNvram(HMX_NVRAM_PARTITION_E partition) {
   char* key = strdup("TEST_FIELD2");
   char* val = strdup("abc123");
-  char* keyval = strdup("TEST_FIELD2=abc123");
   // No error if variable already cleared
   EXPECT_EQ(DRV_OK, clear_nvram(key));
 
-  // Create new one
+  // Create new var
   can_add_flag = 1;
-  EXPECT_EQ(0, write_nvram(keyval));
-  EXPECT_STREQ(val, HMX_NVRAM_Read_Data);
+  EXPECT_EQ(-3, write_nvram(key, val, HMX_NVRAM_PARTITION_UNSPECIFIED));
+  EXPECT_EQ(0, write_nvram_new(key, val, partition));
+  EXPECT_STREQ(val, get_Read_Data(partition));
 
   // Should be able to read value
   char output[256];
-  EXPECT_STREQ(val, read_nvram(key, output, sizeof(output), 1));
+  HMX_NVRAM_PARTITION_E part_used;
+  EXPECT_STREQ(val, read_nvram(key, output, sizeof(output), 1, &part_used));
+  EXPECT_EQ((int)partition, part_used);
 
   // Should be able to kill it
   EXPECT_EQ(DRV_OK, clear_nvram(key));
 
   // Should fail reading value
-  EXPECT_STREQ(NULL, read_nvram(key, output, sizeof(output), 1));
+  EXPECT_STREQ(NULL, read_nvram(key, output, sizeof(output), 1, &part_used));
 
   free(key);
   free(val);
-  free(keyval);
+}
+
+TEST_F(HnvramTest, TestClearNvramRO) {
+  testClearNvram(HMX_NVRAM_PARTITION_RO);
+}
+
+TEST_F(HnvramTest, TestClearNvramRW) {
+  testClearNvram(HMX_NVRAM_PARTITION_RW);
 }
 
 int main(int argc, char** argv) {
diff --git a/rcu_audio/ti-rcu-audio.cc b/rcu_audio/ti-rcu-audio.cc
index 67dd869..e174477 100644
--- a/rcu_audio/ti-rcu-audio.cc
+++ b/rcu_audio/ti-rcu-audio.cc
@@ -44,7 +44,11 @@
   uint8 prev = 0;
   int msgs = 0, missed = 0, errors = 0;
 
-  is = get_socket_or_die();
+  if ((is = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
+    perror("socket(AF_UNIX)");
+    exit(1);
+  }
+
   memset(&sun, 0, sizeof(sun));
   sun.sun_family = AF_UNIX;
   snprintf(sun.sun_path, sizeof(sun.sun_path), "%s", TI_AUDIO_PATH);
diff --git a/taxonomy/pcaptest.py b/taxonomy/pcaptest.py
index 02c2039..3e2ea1f 100644
--- a/taxonomy/pcaptest.py
+++ b/taxonomy/pcaptest.py
@@ -19,6 +19,8 @@
   ('', './testdata/pcaps/HTC Sensation 2.4GHz.pcap'),
   ('', './testdata/pcaps/HTC Thunderbolt 2.4GHz.pcap'),
   ('', './testdata/pcaps/HTC Titan 2.4GHz.pcap'),
+  ('', './testdata/pcaps/iPad Mini 4th gen 5GHz MK6L2LL Broadcast Probe.pcap'),
+  ('', './testdata/pcaps/iPad Mini 4th gen 5GHz MK6L2LL Specific Probe.pcap'),
   ('', './testdata/pcaps/Lenovo_T440_80211ac_2x2_Windows8_2_4_GHz.pcap'),
   ('', './testdata/pcaps/LG E900 2.4GHz.pcap'),
   ('', './testdata/pcaps/LG G2X 2.4GHz.pcap'),
@@ -81,6 +83,8 @@
   ('Nest Thermostat v1 or v2', './testdata/pcaps/Nest Thermostat 2.4GHz.pcap'),
   ('Roku 2 or 3 or Streaming Stick', './testdata/pcaps/Roku 3 2.4GHz 4230.pcap'),
   ('Roku 2 or 3 or Streaming Stick', './testdata/pcaps/Roku 3 5GHz 4230.pcap'),
+  ('Roku 4 or TV', './testdata/pcaps/Roku 4 2.4GHz.pcap'),
+  ('Roku 4 or TV', './testdata/pcaps/Roku 4 5GHz.pcap'),
   ('Samsung Galaxy Note or S2+', './testdata/pcaps/Samsung Galaxy S2+ 5GHz.pcap'),
   ('Samsung Galaxy Note or S2+', './testdata/pcaps/Samsung Galaxy Note 5GHz.pcap'),
   ('Samsung Galaxy S2 or Infuse', './testdata/pcaps/Samsung Galaxy S2 2.4GHz.pcap'),
diff --git a/taxonomy/testdata/dhcp.leases b/taxonomy/testdata/dhcp.leases
index f3eae5e..51d4538 100644
--- a/taxonomy/testdata/dhcp.leases
+++ b/taxonomy/testdata/dhcp.leases
@@ -70,3 +70,6 @@
 1432237016 70:48:0f:00:00:00 192.168.42.59 iPadPro12_9
 1432237016 6c:c2:17:00:00:00 192.168.42.60 HPPrinter
 1432237016 dc:2b:2a:95:bc:77 192.168.42.61 iPhoone 6s+
+1432237016 2c:33:61:00:00:00 192.168.42.62 iPhoone 7
+1432237016 58:bd:a3:00:00:00 192.168.42.63 Wii
+1432237016 28:0d:fc:00:00:00 192.168.42.64 Playstation 3
diff --git a/taxonomy/testdata/dhcp.signatures b/taxonomy/testdata/dhcp.signatures
index 0c79a18..614f33d 100644
--- a/taxonomy/testdata/dhcp.signatures
+++ b/taxonomy/testdata/dhcp.signatures
@@ -62,3 +62,6 @@
 70:48:0f:00:00:00 1,3,6,15,119,252
 6c:c2:17:00:00:00 6,3,1,15,66,67,13,44,12,81,252
 dc:2b:2a:95:bc:77 1,3,6,15,119,252
+2c:33:61:00:00:00 1,3,6,15,119,252
+58:bd:a3:00:00:00 1,3,6,15,28,33
+28:0d:fc:00:00:00 1,3,15,6
diff --git a/taxonomy/testdata/pcaps/Playstation 3 2.4GHz Specific Probe.pcap b/taxonomy/testdata/pcaps/Playstation 3 2.4GHz Specific Probe.pcap
new file mode 100644
index 0000000..020011f
--- /dev/null
+++ b/taxonomy/testdata/pcaps/Playstation 3 2.4GHz Specific Probe.pcap
Binary files differ
diff --git a/taxonomy/testdata/pcaps/Wii 2.4GHz Broadcast Probe.pcap b/taxonomy/testdata/pcaps/Wii 2.4GHz Broadcast Probe.pcap
new file mode 100644
index 0000000..3b86e87
--- /dev/null
+++ b/taxonomy/testdata/pcaps/Wii 2.4GHz Broadcast Probe.pcap
Binary files differ
diff --git a/taxonomy/testdata/pcaps/Wii 2.4GHz Specific Probe.pcap b/taxonomy/testdata/pcaps/Wii 2.4GHz Specific Probe.pcap
new file mode 100644
index 0000000..4f7292a
--- /dev/null
+++ b/taxonomy/testdata/pcaps/Wii 2.4GHz Specific Probe.pcap
Binary files differ
diff --git a/taxonomy/testdata/pcaps/iPhone 7 5GHz.pcap b/taxonomy/testdata/pcaps/iPhone 7 5GHz.pcap
new file mode 100644
index 0000000..f910dc3
--- /dev/null
+++ b/taxonomy/testdata/pcaps/iPhone 7 5GHz.pcap
Binary files differ
diff --git a/taxonomy/wifi.py b/taxonomy/wifi.py
index 6b48061..36c92fc 100644
--- a/taxonomy/wifi.py
+++ b/taxonomy/wifi.py
@@ -355,9 +355,9 @@
     'wifi4|probe:0,1,50,3,45,127,107,221(001018,2),221(00904c,51),221(0050f2,8),htcap:01bc,htagg:1b,htmcs:0000ffff,extcap:00000804|assoc:0,1,33,36,48,50,45,70,221(001018,2),221(00904c,51),221(0050f2,2),htcap:01bc,htagg:1b,htmcs:0000ffff,txpow:1201|os:ios':
         ('iPad Mini', '3rd gen', '2.4GHz'),
 
-    'wifi4|probe:0,1,45,127,107,191,221(0050f2,8),221(001018,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,extcap:0400088400000040|assoc:0,1,33,36,45,127,191,221(001018,2),221(0050f2,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:e002,extcap:0400000000000040|os:ios':
+    'wifi4|probe:0,1,45,127,107,191,221(0050f2,8),221(001018,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,extcap:0400088400000040|assoc:0,1,33,36,45,127,191,221(001018,2),221(0050f2,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:e002,extcap:0400000000000040|name:ipad':
         ('iPad Mini', '4th gen', '5GHz'),
-    'wifi4|probe:0,1,45,127,107,191,221(0050f2,8),221(001018,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,extcap:0400088400000040|assoc:0,1,33,36,48,70,45,127,191,221(001018,2),221(0050f2,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:e002,extcap:0400000000000040|os:ios':
+    'wifi4|probe:0,1,45,127,107,191,221(0050f2,8),221(001018,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,extcap:0400088400000040|assoc:0,1,33,36,48,70,45,127,191,221(001018,2),221(0050f2,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f815832,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:e002,extcap:0400000000000040|name:ipad':
         ('iPad Mini', '4th gen', '5GHz'),
 
     'wifi4|probe:0,1,3,50|assoc:0,1,48,50|os:ios':
@@ -470,6 +470,9 @@
     'wifi4|probe:0,1,50,3,45,127,107,221(0050f2,8),221(001018,2),htcap:002d,htagg:17,htmcs:0000ffff,extcap:0400088400000040|assoc:0,1,50,33,36,48,70,45,127,221(001018,2),221(0050f2,2),htcap:002d,htagg:17,htmcs:000000ff,txpow:1202,extcap:0000000000000040|os:ios':
         ('iPhone 6s/6s+', '', '2.4GHz'),
 
+    'wifi4|probe:0,1,45,127,107,191,221(0017f2,10),221(0050f2,8),221(001018,2),htcap:006f,htagg:17,htmcs:000000ff,vhtcap:0f807032,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:00000884|assoc:0,1,33,36,48,70,54,45,127,191,199,221(0017f2,10),221(001018,2),221(0050f2,2),htcap:006f,htagg:17,htmcs:0000ffff,vhtcap:0f811032,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:13f9,extcap:000008|os:ios':
+        ('iPhone 7', '', '5GHz'),
+
     'wifi4|probe:0,1,45,127,107,191,221(0050f2,8),221(001018,2),htcap:0063,htagg:17,htmcs:000000ff,vhtcap:0f805032,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:0400088400000040|assoc:0,1,33,36,45,127,221(001018,2),221(0050f2,2),htcap:0063,htagg:17,htmcs:000000ff,txpow:e002,extcap:000008|os:ios':
         ('iPhone SE', '', '5GHz'),
     'wifi4|probe:0,1,50,3,45,127,107,221(0050f2,8),221(001018,2),htcap:0021,htagg:17,htmcs:000000ff,extcap:0400088400000040|assoc:0,1,50,33,36,45,127,221(001018,2),221(0050f2,2),htcap:0021,htagg:17,htmcs:000000ff,txpow:1402,extcap:0000000000000040|os:ios':
@@ -618,8 +621,6 @@
         ('Nexus 5', '', '5GHz'),
     'wifi4|probe:0,1,3,45,127,191,221(001018,2),221(00904c,51),htcap:016f,htagg:17,htmcs:000000ff,vhtcap:0f805932,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:0000000000000040|assoc:0,1,33,36,48,45,127,191,221(001018,2),221(0050f2,2),htcap:016f,htagg:17,htmcs:000000ff,vhtcap:0f805932,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,txpow:e003,extcap:0000000000000040|oui:lg':
         ('Nexus 5', '', '5GHz'),
-    'wifi4|probe:0,1,45,127,191,221(001018,2),221(00904c,51),htcap:016f,htagg:17,htmcs:000000ff,vhtcap:0f805932,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:0000000000000040|assoc:0,1,33,36,48,45,127,191,221(001018,2),221(0050f2,2),htcap:016f,htagg:17,htmcs:000000ff,vhtcap:0f805932,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,txpow:e003,extcap:0000000000000040|oui:lg':
-        ('Nexus 5', '', '5GHz'),
     'wifi4|probe:0,1,45,127,191,221(001018,2),221(00904c,51),htcap:016f,htagg:17,htmcs:000000ff,vhtcap:0f805932,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:0000000000000040|assoc:0,1,33,36,45,127,191,221(001018,2),221(0050f2,2),htcap:016f,htagg:17,htmcs:000000ff,vhtcap:0f805932,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,txpow:e003,extcap:0000000000000040|oui:lg':
         ('Nexus 5', '', '5GHz'),
     'wifi4|probe:0,1,45,127,107,191,221(506f9a,16),221(001018,2),221(00904c,51),221(00904c,4),221(0050f2,8),htcap:016f,htagg:17,htmcs:000000ff,vhtcap:0f805932,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,extcap:0000088001400040|assoc:0,1,33,36,48,45,127,70,191,221(001018,2),221(00904c,4),221(0050f2,2),htcap:016f,htagg:17,htmcs:000000ff,vhtcap:0f805932,vhtrxmcs:0000fffe,vhttxmcs:0000fffe,txpow:e003,extcap:0000008001400040|oui:lg':
@@ -788,6 +789,9 @@
     'wifi4|probe:0,1,50,45,221(0050f2,4),htcap:01ad,htagg:02,htmcs:0000ffff,wps:WPS_SUPPLICANT_STATION|assoc:0,1,50,45,48,221(0050f2,2),htcap:01ad,htagg:02,htmcs:0000ffff|os:panasonictv':
         ('Panasonic TV', '', '2.4GHz'),
 
+    'wifi4|probe:0,1|assoc:0,1,221(005043,1)|os:playstation':
+        ('Playstation', '3', '2.4GHz'),
+
     'wifi4|probe:0,1,50|assoc:0,1,50,48,221(005043,1)|os:playstation':
         ('Playstation', '3 or 4', '2.4GHz'),
 
@@ -830,12 +834,6 @@
     'wifi4|probe:0,1,50,3,45,127,221(001018,2),221(00904c,51),htcap:19bc,htagg:16,htmcs:0000ffff,extcap:0000000000000040|assoc:0,1,33,36,50,45,127,221(001018,2),221(0050f2,2),htcap:19bc,htagg:16,htmcs:0000ffff,txpow:140a,extcap:0000000000000040|os:roku':
         ('Roku', 'Streaming Stick 3600', '2.4GHz'),
 
-    # Roku TV NP-YW
-    'wifi4|probe:0,1,45,127,191,221(001018,2),htcap:01ad,htagg:17,htmcs:0000ffff,vhtcap:0f8159b2,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,extcap:0000000000000040|assoc:0,1,33,36,48,45,127,191,199,221(001018,2),221(0050f2,2),htcap:01ad,htagg:17,htmcs:0000ffff,vhtcap:0f8159b2,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:1109,extcap:0000000000000040|os:roku':
-        ('Roku TV', '', '5GHz'),
-    'wifi4|probe:0,1,50,3,45,221(001018,2),htcap:01ad,htagg:17,htmcs:0000ffff|assoc:0,1,50,33,36,48,45,221(001018,2),221(0050f2,2),htcap:01ad,htagg:17,htmcs:0000ffff,txpow:1209|os:roku':
-        ('Roku TV', '', '2.4GHz'),
-
     # Roku 1 models 2000, 2050, 2100, and XD
     'wifi4|probe:0,1,50,45,221(001018,2),221(00904c,51),htcap:186e,htagg:1a,htmcs:0000ffff|assoc:0,1,33,36,48,50,45,221(001018,2),221(00904c,51),221(0050f2,2),htcap:186e,htagg:1a,htmcs:0000ffff,txpow:1308|os:roku':
         ('Roku', '1', '2.4GHz'),
@@ -866,15 +864,15 @@
     'wifi4|probe:0,1,45,127,221(001018,2),221(00904c,51),htcap:093c,htagg:16,htmcs:0000ffff,extcap:0000000000000040|assoc:0,1,33,36,48,45,127,221(001018,2),221(0050f2,2),htcap:093c,htagg:16,htmcs:0000ffff,txpow:110a,extcap:0000000000000040|os:roku':
         ('Roku', '3', '5GHz'),
 
-    # Roku 4 model 4400
+    # Roku 4 model 4400 or Roku TV NP-YW
     'wifi4|probe:0,1,45,127,191,221(001018,2),htcap:01ad,htagg:17,htmcs:0000ffff,vhtcap:0f8159b2,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,extcap:0000000000000040|assoc:0,1,33,36,48,45,127,191,199,221(001018,2),221(0050f2,2),htcap:01ad,htagg:17,htmcs:0000ffff,vhtcap:0f8159b2,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:1109,extcap:0000000000000040|os:roku':
-        ('Roku', '4', '5GHz'),
+        ('Roku', '4 or TV', '5GHz'),
     'wifi4|probe:0,1,45,191,221(001018,2),htcap:01ad,htagg:17,htmcs:0000ffff,vhtcap:0f8159b2,vhtrxmcs:0000fffa,vhttxmcs:0000fffa|assoc:0,1,33,36,48,45,191,199,221(001018,2),221(0050f2,2),htcap:01ad,htagg:17,htmcs:0000ffff,vhtcap:0f8159b2,vhtrxmcs:0000fffa,vhttxmcs:0000fffa,txpow:1109|os:roku':
-        ('Roku', '4', '5GHz'),
+        ('Roku', '4 or TV', '5GHz'),
     'wifi4|probe:0,1,50,3,45,127,221(001018,2),htcap:01ad,htagg:17,htmcs:0000ffff,extcap:0000000000000040|assoc:0,1,50,33,36,48,45,221(001018,2),221(0050f2,2),htcap:01ad,htagg:17,htmcs:0000ffff,txpow:1209|os:roku':
-        ('Roku', '4', '2.4GHz'),
+        ('Roku', '4 or TV', '2.4GHz'),
     'wifi4|probe:0,1,50,3,45,221(001018,2),htcap:01ad,htagg:17,htmcs:0000ffff|assoc:0,1,50,33,36,48,45,221(001018,2),221(0050f2,2),htcap:01ad,htagg:17,htmcs:0000ffff,txpow:1209|os:roku':
-        ('Roku', '4', '2.4GHz'),
+        ('Roku', '4 or TV', '2.4GHz'),
 
     'wifi4|probe:0,1,50,3,45,htcap:0020,htagg:01,htmcs:000000ff|assoc:0,1,50,45,61,48,221(0050f2,2),htcap:0020,htagg:01,htmcs:000000ff|oui:samsung':
         ('Samsung Galaxy', 'Mini', '2.4GHz'),
@@ -1129,8 +1127,6 @@
         ('TiVo', 'BOLT', '2.4GHz'),
     'wifi4|probe:0,1,50,3,45,127,221(001018,2),htcap:01ad,htagg:17,htmcs:0000ffff,extcap:0000008001000040|assoc:0,1,50,33,36,48,45,127,221(00904c,51),221(001018,2),221(0050f2,2),htcap:01ad,htagg:17,htmcs:0000ffff,txpow:1207,extcap:0000008001000040|os:tivo':
         ('TiVo', 'BOLT', '2.4GHz'),
-    'wifi4|probe:0,1,50,3,45,127,221(00904c,51),221(001018,2),htcap:01ad,htagg:17,htmcs:0000ffff,extcap:0000008001000040|assoc:0,1,50,33,36,48,45,127,221(00904c,51),221(001018,2),221(0050f2,2),htcap:01ad,htagg:17,htmcs:0000ffff,txpow:1207,extcap:0000008001000040|os:tivo':
-        ('TiVo', 'BOLT', '2.4GHz'),
 
     # TIVO-746
     'wifi4|probe:0,1,50,221(00904c,51),45,48,htcap:13ce,htagg:1b,htmcs:0000ffff|assoc:0,1,33,36,50,221(0050f2,2),221(00904c,51),45,221(002163,1),221(002163,4),48,htcap:13ce,htagg:1b,htmcs:0000ffff,txpow:0f0f|os:tivo':
diff --git a/wifi/qca9880_cal.py b/wifi/qca9880_cal.py
new file mode 100755
index 0000000..4e5cc0c
--- /dev/null
+++ b/wifi/qca9880_cal.py
@@ -0,0 +1,240 @@
+#!/usr/bin/python -S
+
+"""Check and fix mis-calibrated QCA9880 modules on gfrg200/gfrg210.
+
+   Some modules were delivered to customers mis-calibrated. This script will
+   check if the module is affected, and if so, generate a patch that will be
+   used after driver reload.
+"""
+import glob
+import os
+import os.path
+import experiment
+import utils
+
+NO_CAL_EXPERIMENT = 'WifiNoCalibrationPatch'
+PLATFORM_FILE = '/etc/platform'
+CALIBRATION_DIR = '/tmp/ath10k_cal'
+CAL_PATCH_FILE = 'cal_data_patch.bin'
+ATH10K_CAL_DATA = '/sys/kernel/debug/ieee80211/phy[0-9]*/ath10k/cal_data'
+OUI_OFFSET = 6
+OUI_LEN = 3
+VERSION_OFFSET = 45
+VERSION_LEN = 3
+SUSPECT_OUIS = ((0x28, 0x24, 0xff), (0x48, 0xa9, 0xd2), (0x60, 0x02, 0xb4),
+                (0xbc, 0x30, 0x7d), (0xbc, 0x30, 0x7e))
+MISCALIBRATED_VERSION_FIELD = (0x0, 0x0, 0x0)
+MODULE_PATH = '/sys/class/net/{}/device/driver/module'
+
+
+def _log(msg):
+  utils.log('ath10k calibration: {}'.format(msg))
+
+
+def _is_ath10k(interface):
+  """Check if interface is driven by the ath10k driver.
+
+  Args:
+    interface: The interface to be checked. eg wlan1
+
+  Returns:
+    True if ath10k, otherwise False.
+  """
+  try:
+    return os.readlink(MODULE_PATH.format(interface)).find('ath10k')
+  except OSError:
+    return False
+
+
+def _oui_string(oui):
+  """Convert OUI from bytes to a string.
+
+  Args:
+    oui: OUI in byte format.
+
+  Returns:
+    OUI is string format separated by ':'. Eg. 88:dc:96.
+  """
+  return ':'.join('{:02x}'.format(ord(b)) for b in oui)
+
+
+def _version_string(version):
+  """Convert version from bytes to a string.
+
+  Args:
+    version: version in byte format.
+
+  Returns:
+    Three byte version string in hex format: 0x00 0x00 0x00
+  """
+
+  return ' '.join('0x{:02x}'.format(ord(b)) for b in version)
+
+
+def _is_module_miscalibrated():
+  """Check the QCA8990 module to see if it is improperly calibrated.
+
+  There are two manufacturers of the modules, Senao and Wistron of which only
+  Wistron modules are suspect. Wistron provided a list of OUIs manufactured
+  which are listed in SUSPECT_OUIS. Modules manufactured by Winstron containing
+  V02 at offset VERSION_OFFSET have been corrected, while those containing 3
+  zero's at this offset are still suspect and will be considered mis-calibrated.
+
+  Returns:
+    True if module is mis-calibrated, None if it can't be determined, and False
+    otherwise.
+  """
+
+  try:
+    cal_data_path = _ath10k_cal_data_path()
+    if cal_data_path is None:
+      return None
+
+    with open(cal_data_path, mode='rb') as f:
+      f.seek(OUI_OFFSET)
+      oui = f.read(OUI_LEN)
+      f.seek(VERSION_OFFSET)
+      version = f.read(VERSION_LEN)
+
+  except IOError as e:
+    _log('unable to open cal_data {}: {}'.format(cal_data_path, e.strerror))
+    return None
+
+  if oui not in (bytearray(s) for s in SUSPECT_OUIS):
+    _log('OUI {} is properly calibrated.'.format(_oui_string(oui)))
+    return False
+
+  if version != (bytearray(MISCALIBRATED_VERSION_FIELD)):
+    _log('version field {} signals proper calibration.'.
+         format(_version_string(version)))
+    return False
+
+  _log('May be mis-calibrated. OUI: {} version: {}'.
+       format(_oui_string(oui), _version_string(version)))
+
+  return True
+
+
+def _is_previously_calibrated():
+  """Check if this calibration script already ran since the last boot.
+
+  Returns:
+    True if calibration checks already ran, False otherwise.
+  """
+  return os.path.exists(CALIBRATION_DIR)
+
+
+def _create_calibration_dir():
+  """Create calibration directory.
+
+  Calibration directory contains the calibration patch file.
+  If the directory is empty it signals that calibration checks have already
+  run.
+
+  Returns:
+    True if directory exists or is created, false if any error.
+  """
+  try:
+    if not os.path.isdir(CALIBRATION_DIR):
+      os.makedirs(CALIBRATION_DIR)
+      return True
+  except OSError as e:
+    _log('unable to create calibration dir {}: {}.'.
+         format(CALIBRATION_DIR, e.strerror))
+    return False
+
+  return True
+
+
+def _ath10k_cal_data_path():
+  """Find the current path to cal data.
+
+  This path encodes the phy number, which is usually phy1, but if the
+  driver load order changed or if this runs after a reload, the phy
+  number will change.
+
+  Returns:
+    Path to cal_data in debugfs.
+  """
+
+  return glob.glob(ATH10K_CAL_DATA)[0]
+
+
+def _generate_calibration_patch():
+  """Create calibration patch and write to storage.
+
+  Returns:
+    True for success or False for failure.
+  """
+  try:
+    with open(_ath10k_cal_data_path(), mode='rb') as f:
+      cal_data = bytearray(f.read())
+  except IOError as e:
+    _log('cal patch: unable to open for read {}: {}.'.
+         format(_ath10k_cal_data_path(), e.strerror))
+    return False
+
+  # Patch cal_data here once we get the actual calibration data.
+  # For now just return False until we get the data.
+  _log('patch not generated as data not supplied yet.')
+  # pylint: disable=unreachable
+  return False
+
+  if not _create_calibration_dir():
+    return False
+
+  try:
+    patched_file = os.path.join(CALIBRATION_DIR, CAL_PATCH_FILE)
+    open(patched_file, 'wb').write(cal_data)
+  except IOError as e:
+    _log('unable to open for writing {}: {}.'.format(patched_file, e.strerror))
+    return False
+
+  return True
+
+
+def _reload_driver():
+  """Reload the ath10k driver so it picks up modified calibration file."""
+  ret = utils.subprocess_quiet(('rmmod', 'ath10k_pci'))
+  if ret != 0:
+    _log('rmmod ath10k_pci failed: {}.'.format(ret))
+    return
+
+  ret = utils.subprocess_quiet(('modprobe', 'ath10k_pci'))
+  if ret != 0:
+    _log('modprobe ath10k_pci failed: {}.'.format(ret))
+    return
+
+  _log('reload ath10k driver complete')
+
+
+def qca8990_calibration():
+  """Main QCA8990 calibration check."""
+
+  if experiment.enabled(NO_CAL_EXPERIMENT):
+    _log('experiment {} on. Skip calibration check.'.format(NO_CAL_EXPERIMENT))
+    return
+
+  if _is_previously_calibrated():
+    _log('calibration check completed earlier.')
+    return
+
+  if not _is_ath10k('wlan1'):
+    _log('this platform does not use ath10k.')
+    return
+
+  cal_result = _is_module_miscalibrated()
+  if cal_result is None:
+    _log('unknown if miscalibrated.')
+  elif not cal_result:
+    _log('module is NOT miscalibrated.')
+    # Creating an empty directory signals that this script has already run.
+    _create_calibration_dir()
+  else:
+    if _generate_calibration_patch():
+      _log('generated new patch.')
+      _reload_driver()
+
+
+if __name__ == '__main__':
+  qca8990_calibration()
diff --git a/wifi/wifi.py b/wifi/wifi.py
index 4204791..34bb5e8 100755
--- a/wifi/wifi.py
+++ b/wifi/wifi.py
@@ -19,6 +19,7 @@
 import iw
 import options
 import persist
+import qca9880_cal
 import quantenna
 import utils
 
@@ -254,6 +255,9 @@
         'no wifi interface found for band=%s channel=%s suffix=%s',
         band, channel, opt.interface_suffix)
 
+  # Check for calibration errors on ath10k.
+  qca9880_cal.qca8990_calibration()
+
   found_active_config = False
   for other_interface in (set(iw.find_all_interfaces_from_phy(phy)) -
                           set([interface])):