Clean up and refactor; new hw support

(Originally, https://android-review.googlesource.com/#/c/341550/)

This change addresses portability, a pn80t platform abstraction, and
nq-nci support.

Refactor/clean up:
- Adds darwin-sysdeps.c to help avoid reverting again.
- Clean up Android.bp
- T=1: moved T=1 to using bit_specs to keep some
  of the readability of bitfields without incurring
  weird toolchain side effects.
- T=1 will still rely on compilers keeping uchars
  aligned and check it with a div-by-zero build
  assertion.
- ESE platform specific methods are now wrapped.
- Adjusted error message constant usage.
- Enclosing {} for every if statement.
- Moved to relative headers for inclusion into other code
  bases.
- Added a comment to log.h to make debugging easier globally
  in libese code.

PN80T:
- Common code now shared across different
  wire configurations.
- Add support for kernel based driver (called nq-nci)
  which interacts with the nq-nci behavior for power
  management.
- Added cooldown/end of session code to pn80t/common.c
- Migrated the ese_nxp_sample code to NQ_NCI and added the empty
  session to test the cooldown code submission.

Bug: 34193473,35105409
Change-Id: I8fc320c8c236282ed103ef3ee3cb8c0dc99d8bcb
Test: unittests pass, tested ese-relay on hardware forwarding globalplatform pro
diff --git a/Android.bp b/Android.bp
index 97af836..fce386e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -14,4 +14,11 @@
 // limitations under the License.
 //
 
-subdirs = ["libese", "libese-teq1", "libese-hw", "examples", "tools"]
+subdirs = [
+    "libese-sysdeps",
+    "libese",
+    "libese-teq1",
+    "libese-hw",
+    "examples",
+    "tools",
+]
diff --git a/NOTICE b/NOTICE
index 952429e..a0ac881 100644
--- a/NOTICE
+++ b/NOTICE
@@ -1,4 +1,4 @@
-Copyright (C) 2016 The Android Open Source Project
+Copyright (C) 2017 The Android Open Source Project
 
 icensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
diff --git a/README.md b/README.md
index efca4c6..273b3f5 100644
--- a/README.md
+++ b/README.md
@@ -19,10 +19,82 @@
 
 ## Usage
 
-(TBD: See tools/ and example/)
+Public client interface for Embedded Secure Elements.
+
+Prior to use in a file, import all necessary variables with:
+
+    ESE_INCLUDE_HW(SOME_HAL_IMPL);
+
+Instantiate in a function with:
+
+    ESE_DECLARE(my_ese, SOME_HAL_IMPL);
+
+or
+
+    struct EseInterface my_ese = ESE_INITIALIZER(SOME_HAL_IMPL);
+
+or
+
+    struct EseInterface *my_ese = malloc(sizeof(struct EseInterface));
+    ...
+    ese_init(my_ese, SOME_HAL_IMPL);
+
+To initialize the hardware abstraction, call:
+
+    ese_open(my_ese);
+
+To release any claimed resources, call
+
+    ese_close(my_ese)
+
+when interface use is complete.
+
+To perform a transmit-receive cycle, call
+
+    ese_transceive(my_ese, ...);
+
+with a filled transmit buffer with total data length and
+an empty receive buffer and a maximum fill length.
+A negative return value indicates an error and a hardware
+specific code and string may be collected with calls to
+
+    ese_error_code(my_ese);
+    ese_error_message(my_ese);
+
+The EseInterface is not safe for concurrent access.
+(Patches welcome! ;).
+
+# Components
+
+libese is broken into multiple pieces:
+  * libese
+  * libese-sysdeps
+  * libese-hw
+  * libese-teq1
+
+*libese* provides the headers and wrappers for writing libese clients
+and for implementing hardware backends.  It depends on a backend being
+provided as per *libese-hw* and on *libese-sysdeps*.
+
+*libese-sysdeps* provides the system level libraries that are needed by
+libese provided software.  If libese is being ported to a new environment,
+like a bootloader or non-Linux OS, this library may need to be replaced.
+(Also take a look at libese/include/ese/log.h for the macro definitions
+ that may be needed.)
+
+*libese-hw* provides existing libese hardware backends.
+
+*libese-teq1* provides a T=1 compatible transcieve function that may be
+used by a hardware backend.  It comes with some prequisites for use,
+such as a specifically structured set of error messages and
+EseInteface pad usage, but otherwise it does not depends on any specific
+functionality not abstracted via the libese EseOperations structure.
+
 
 ## Supported backends
 
-At present, only sample backends and a Linux SPIdev driven NXP
-developer board are supported.
+There are two test backends, fake and echo, as well as one
+real backend for the NXP PN80T/PN81A.
 
+The NXP backends support both a direct kernel driver and
+a Linux SPIdev interface.
diff --git a/examples/Android.bp b/examples/Android.bp
index d4a6960..1678ba0 100644
--- a/examples/Android.bp
+++ b/examples/Android.bp
@@ -14,27 +14,19 @@
 // limitations under the License.
 //
 
-example_static_libraries = []
-
-example_shared_libraries = [
-    "liblog",
-    "libese",
-    "libese-teq1",
-    "libese-hw-nxp-pn80t-spidev",
-]
-
 cc_binary {
     name: "ese_nxp_sample",
     srcs: ["ese_nxp_sample.c"],
     host_supported: false,
-    shared_libs: example_shared_libraries,
-
     target: {
-      darwin: {
-        enabled: false,
-      },
-      windows: {
-        enabled: false,
-      },
+        darwin: {
+            enabled: false,
+        },
     },
+    shared_libs: [
+        "liblog",
+        "libese",
+        "libese-teq1",
+        "libese-hw-nxp-pn80t-nq-nci",
+    ],
 }
diff --git a/examples/ese_nxp_sample.c b/examples/ese_nxp_sample.c
index 392ce63..7eae531 100644
--- a/examples/ese_nxp_sample.c
+++ b/examples/ese_nxp_sample.c
@@ -22,19 +22,17 @@
 #include <unistd.h>
 
 #include <ese/ese.h>
-/* Note, the struct could be build just as well. */
-#include <ese/hw/nxp/pn80t/boards/hikey-spidev.h>
-ESE_INCLUDE_HW(ESE_HW_NXP_PN80T_SPIDEV);
+ESE_INCLUDE_HW(ESE_HW_NXP_PN80T_NQ_NCI);
 
 /* APDU: CLA INS P1-P2 Lc Data Le */
 struct Apdu {
-  size_t length;
+  uint32_t length;
   const uint8_t *bytes;
   const char *desc;
 };
 
 struct ApduSession {
-  size_t count;
+  uint32_t count;
   const char *desc;
   const struct Apdu *apdus[];
 };
@@ -63,6 +61,10 @@
         },
 };
 
+const struct ApduSession kEmptySession = {
+    .count = 0, .desc = "Empty session (cooldown only)", .apdus = {},
+};
+
 /* Define the loader service sessions here! */
 const uint8_t kSelectJcopIdentifyBytes[] = {
     0x00, 0xA4, 0x04, 0x00, 0x09, 0xA0, 0x00,
@@ -75,17 +77,18 @@
 };
 
 const struct ApduSession *kSessions[] = {
-    &kGetCplcSession,
+    &kGetCplcSession, &kEmptySession,
 };
 
 int main() {
-  struct EseInterface ese = ESE_INITIALIZER(ESE_HW_NXP_PN80T_SPIDEV);
+  struct EseInterface ese = ESE_INITIALIZER(ESE_HW_NXP_PN80T_NQ_NCI);
+  void *ese_hw_open_data = NULL;
   size_t s = 0;
   for (; s < sizeof(kSessions) / sizeof(kSessions[0]); ++s) {
     int recvd;
-    size_t apdu_index = 0;
+    uint32_t apdu_index = 0;
     uint8_t rx_buf[1024];
-    if (ese_open(&ese, (void *)(&nxp_boards_hikey_spidev))) {
+    if (ese_open(&ese, ese_hw_open_data) < 0) {
       printf("Cannot open hw\n");
       if (ese_error(&ese))
         printf("eSE error (%d): %s\n", ese_error_code(&ese),
@@ -94,10 +97,10 @@
     }
     printf("Running session %s\n", kSessions[s]->desc);
     for (; apdu_index < kSessions[s]->count; ++apdu_index) {
-      size_t i;
+      uint32_t i;
       const struct Apdu *apdu = kSessions[s]->apdus[apdu_index];
-      printf("Sending APDU %zu: %s\n", apdu_index, apdu->desc);
-      printf("Sending %zu bytes to card\n", apdu->length);
+      printf("Sending APDU %u: %s\n", apdu_index, apdu->desc);
+      printf("Sending %u bytes to card\n", apdu->length);
       printf("TX: ");
       for (i = 0; i < apdu->length; ++i)
         printf("%.2X ", apdu->bytes[i]);
diff --git a/libese-hw/Android.bp b/libese-hw/Android.bp
index 0922f1f..7cd8ac4 100644
--- a/libese-hw/Android.bp
+++ b/libese-hw/Android.bp
@@ -18,7 +18,7 @@
     name: "libese-hw-fake",
     srcs: ["ese_hw_fake.c"],
     host_supported: true,
-    cflags: ["-DLOG_NDEBUG=0"],
+    cflags: ["-DLOG_NDEBUG=0", "-std=c99"],
     shared_libs: ["liblog", "libese"],
 }
 
@@ -26,7 +26,7 @@
     name: "libese-hw-echo",
     host_supported: true,
     srcs: ["ese_hw_echo.c"],
-    cflags: ["-DLOG_NDEBUG=0"],
+    cflags: ["-DLOG_NDEBUG=0", "-std=c99"],
     shared_libs: ["liblog", "libese", "libese-teq1"],
 }
 
diff --git a/libese-hw/ese_hw_echo.c b/libese-hw/ese_hw_echo.c
index cbb2fea..264ab2b 100644
--- a/libese-hw/ese_hw_echo.c
+++ b/libese-hw/ese_hw_echo.c
@@ -16,15 +16,13 @@
  * Implement a simple T=1 echo endpoint.
  */
 
-#define LOG_TAG "libese-hw-echo"
-
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 
-#include <ese/ese.h>
-#include <ese/log.h>
-#include <ese/teq1.h>
+#include "../libese-teq1/include/ese/teq1.h"
+#include "../libese/include/ese/ese.h"
+#include "../libese/include/ese/log.h"
 
 struct EchoState {
   struct Teq1Frame frame;
@@ -33,23 +31,22 @@
   int recvd;
 };
 
-#define ECHO_STATE(ese) (*(struct EchoState **)(&ese->pad[0]))
+#define ECHO_STATE(ese) (*(struct EchoState **)(&ese->pad[1]))
 
 static int echo_open(struct EseInterface *ese, void *hw_opts) {
   struct EchoState *es = hw_opts; /* shorter than __attribute */
   struct EchoState **es_ptr;
-  if (!ese)
-    return -1;
   if (sizeof(ese->pad) < sizeof(struct EchoState *)) {
     /* This is a compile-time correctable error only. */
     ALOGE("Pad size too small to use Echo HW (%zu < %zu)", sizeof(ese->pad),
           sizeof(struct EchoState *));
     return -1;
   }
-  es_ptr = (struct EchoState **)(&ese->pad[0]);
+  es_ptr = &ECHO_STATE(ese);
   *es_ptr = malloc(sizeof(struct EchoState));
-  if (!*es_ptr)
+  if (!*es_ptr) {
     return -1;
+  }
   es = ECHO_STATE(ese);
   es->rx_fill = &es->frame.header.NAD;
   es->tx_sent = es->rx_fill;
@@ -57,24 +54,27 @@
   return 0;
 }
 
-static int echo_close(struct EseInterface *ese) {
+static void echo_close(struct EseInterface *ese) {
   struct EchoState *es;
-  if (!ese)
-    return -1;
   es = ECHO_STATE(ese);
+  if (!es) {
+    return;
+  }
   free(es);
-  return 0;
+  es = NULL;
 }
 
-static size_t echo_receive(struct EseInterface *ese, uint8_t *buf, size_t len,
-                           int complete) {
+static uint32_t echo_receive(struct EseInterface *ese, uint8_t *buf,
+                             uint32_t len, int complete) {
   struct EchoState *es = ECHO_STATE(ese);
   ALOGV("interface attempting to read data");
-  if (!es->recvd)
+  if (!es->recvd) {
     return 0;
+  }
 
-  if (len > sizeof(es->frame) - (es->tx_sent - &es->frame.header.NAD))
+  if (len > sizeof(es->frame) - (es->tx_sent - &es->frame.header.NAD)) {
     return 0;
+  }
 
   /* NAD was polled for so skip it. */
   memcpy(buf, es->tx_sent, len);
@@ -87,12 +87,13 @@
   return sizeof(es->frame.header) + es->frame.header.LEN;
 }
 
-static size_t echo_transmit(struct EseInterface *ese, const uint8_t *buf,
-                            size_t len, int complete) {
+static uint32_t echo_transmit(struct EseInterface *ese, const uint8_t *buf,
+                              uint32_t len, int complete) {
   struct EchoState *es = ECHO_STATE(ese);
   ALOGV("interface transmitting data");
-  if (len > sizeof(es->frame) - (es->rx_fill - &es->frame.header.NAD))
+  if (len > sizeof(es->frame) - (es->rx_fill - &es->frame.header.NAD)) {
     return 0;
+  }
   memcpy(es->rx_fill, buf, len);
   es->rx_fill += len;
   es->recvd = complete;
@@ -114,8 +115,9 @@
   const struct Teq1ProtocolOptions *opts = ese->ops->opts;
   ALOGV("interface polling for start of frame/host node address: %x", poll_for);
   /* In reality, we should be polling at intervals up to the timeout. */
-  if (timeout > 0.0)
+  if (timeout > 0.0) {
     usleep(timeout * 1000);
+  }
   if (poll_for == opts->host_address) {
     ALOGV("interface received NAD");
     if (!complete) {
@@ -142,7 +144,7 @@
   return 0;
 }
 
-static const struct Teq1ProtocolOptions teq1_options = {
+static const struct Teq1ProtocolOptions kTeq1Options = {
     .host_address = 0xAA,
     .node_address = 0xBB,
     .bwt = 3.14152f,
@@ -150,21 +152,27 @@
     .preprocess = &echo_preprocess,
 };
 
-static const struct EseOperations ops = {
-    .name = "eSE Echo Hardware (fake)",
-    .open = &echo_open,
-    .hw_receive = &echo_receive,
-    .hw_transmit = &echo_transmit,
-    .transceive = &teq1_transceive,
-    .poll = &echo_poll,
-    .close = &echo_close,
-    .opts = &teq1_options,
-};
-ESE_DEFINE_HW_OPS(ESE_HW_ECHO, ops);
+uint32_t echo_transceive(struct EseInterface *ese, const uint8_t *const tx_buf,
+                         uint32_t tx_len, uint8_t *rx_buf, uint32_t rx_len) {
+  return teq1_transceive(ese, &kTeq1Options, tx_buf, tx_len, rx_buf, rx_len);
+}
 
 static const char *kErrorMessages[] = {
     "T=1 hard failure.",        /* TEQ1_ERROR_HARD_FAIL */
     "T=1 abort.",               /* TEQ1_ERROR_ABORT */
     "T=1 device reset failed.", /* TEQ1_ERROR_DEVICE_ABORT */
 };
-ESE_DEFINE_HW_ERRORS(ESE_HW_ECHO, kErrorMessages);
+
+static const struct EseOperations ops = {
+    .name = "eSE Echo Hardware (fake)",
+    .open = &echo_open,
+    .hw_receive = &echo_receive,
+    .hw_transmit = &echo_transmit,
+    .transceive = &echo_transceive,
+    .poll = &echo_poll,
+    .close = &echo_close,
+    .opts = &kTeq1Options,
+    .errors = kErrorMessages,
+    .errors_count = sizeof(kErrorMessages),
+};
+ESE_DEFINE_HW_OPS(ESE_HW_ECHO, ops);
diff --git a/libese-hw/ese_hw_fake.c b/libese-hw/ese_hw_fake.c
index 8fe514f..310ef06 100644
--- a/libese-hw/ese_hw_fake.c
+++ b/libese-hw/ese_hw_fake.c
@@ -16,7 +16,28 @@
  * Minimal functions that only validate arguments.
  */
 
-#include <ese/ese.h>
+#include "../libese/include/ese/ese.h"
+
+enum EseFakeHwError {
+  kEseFakeHwErrorEarlyClose,
+  kEseFakeHwErrorReceiveDuringTransmit,
+  kEseFakeHwErrorInvalidReceiveSize,
+  kEseFakeHwErrorTransmitDuringReceive,
+  kEseFakeHwErrorInvalidTransmitSize,
+  kEseFakeHwErrorTranscieveWhileBusy,
+  kEseFakeHwErrorEmptyTransmit,
+  kEseFakeHwErrorMax,
+};
+
+static const char *kErrorMessages[] = {
+    "Interface closed without finishing transmission.",
+    "Receive called without completing transmission.",
+    "Invalid receive buffer supplied with non-zero length.",
+    "Transmit called without completing reception.",
+    "Invalid transmit buffer supplied with non-zero length.",
+    "Transceive called while other I/O in process.",
+    "Transmitted no data.", /* Can reach this by setting tx_len = 0. */
+};
 
 static int fake_open(struct EseInterface *ese,
                      void *hw_opts __attribute__((unused))) {
@@ -25,50 +46,45 @@
   return 0;
 }
 
-static int fake_close(struct EseInterface *ese) {
-  if (!ese)
-    return -1;
+static void fake_close(struct EseInterface *ese) {
   if (!ese->pad[0] || !ese->pad[1]) {
     /* Set by caller. ese->error.is_error = 1; */
-    ese_set_error(ese, 0);
-    return -1;
+    ese_set_error(ese, kEseFakeHwErrorEarlyClose);
+    return;
   }
-  return 0;
 }
 
-static size_t fake_receive(struct EseInterface *ese, uint8_t *buf, size_t len,
-                           int complete) {
-  if (!ese)
-    return -1;
+static uint32_t fake_receive(struct EseInterface *ese, uint8_t *buf,
+                             uint32_t len, int complete) {
   if (!ese->pad[1]) {
-    ese_set_error(ese, 1);
+    ese_set_error(ese, kEseFakeHwErrorReceiveDuringTransmit);
     return -1;
   }
   ese->pad[0] = complete;
   if (!buf && len) {
-    ese_set_error(ese, 2);
+    ese_set_error(ese, kEseFakeHwErrorInvalidReceiveSize);
     return -1;
   }
-  if (!len)
+  if (!len) {
     return 0;
+  }
   return len;
 }
 
-static size_t fake_transmit(struct EseInterface *ese, const uint8_t *buf,
-                            size_t len, int complete) {
-  if (!ese)
-    return -1;
+static uint32_t fake_transmit(struct EseInterface *ese, const uint8_t *buf,
+                              uint32_t len, int complete) {
   if (!ese->pad[0]) {
-    ese_set_error(ese, 3);
+    ese_set_error(ese, kEseFakeHwErrorTransmitDuringReceive);
     return -1;
   }
   ese->pad[1] = complete;
   if (!buf && len) {
-    ese_set_error(ese, 4);
+    ese_set_error(ese, kEseFakeHwErrorInvalidTransmitSize);
     return -1;
   }
-  if (!len)
+  if (!len) {
     return 0;
+  }
   return len;
 }
 
@@ -76,7 +92,7 @@
                      int complete) {
   /* Poll begins a receive-train so transmit needs to be completed. */
   if (!ese->pad[1]) {
-    ese_set_error(ese, 1);
+    ese_set_error(ese, kEseFakeHwErrorReceiveDuringTransmit);
     return -1;
   }
   if (timeout == 0.0f) {
@@ -91,26 +107,27 @@
   return 0;
 }
 
-size_t fake_transceive(struct EseInterface *ese, const uint8_t *tx_buf,
-                       size_t tx_len, uint8_t *rx_buf, size_t rx_len) {
-  size_t processed = 0;
+uint32_t fake_transceive(struct EseInterface *ese, const uint8_t *tx_buf,
+                         uint32_t tx_len, uint8_t *rx_buf, uint32_t rx_len) {
+  uint32_t processed = 0;
   if (!ese->pad[0] || !ese->pad[1]) {
-    ese_set_error(ese, 5);
+    ese_set_error(ese, kEseFakeHwErrorTranscieveWhileBusy);
     return 0;
   }
   while (processed < tx_len) {
-    size_t sent = fake_transmit(ese, tx_buf, tx_len, 0);
+    uint32_t sent = fake_transmit(ese, tx_buf, tx_len, 0);
     if (sent == 0) {
-      if (ese->error.is_err)
+      if (ese_error(ese)) {
         return 0;
-      ese_set_error(ese, 6);
+      }
+      ese_set_error(ese, kEseFakeHwErrorEmptyTransmit);
       return 0;
     }
     processed += sent;
   }
   fake_transmit(ese, NULL, 0, 1); /* Complete. */
   if (fake_poll(ese, 0xad, 10, 0) != 1) {
-    ese_set_error(ese, -2);
+    ese_set_error(ese, kEseGlobalErrorPollTimedOut);
     return 0;
   }
   /* A real implementation would have protocol errors to contend with. */
@@ -127,20 +144,8 @@
     .poll = &fake_poll,
     .close = &fake_close,
     .opts = NULL,
+    .errors = kErrorMessages,
+    .errors_count = sizeof(kErrorMessages),
 };
 ESE_DEFINE_HW_OPS(ESE_HW_FAKE, ops);
 
-/* TODO(wad) move opts to data.
-const void *ESE_HW_FAKE_data = NULL;
-*/
-
-static const char *kErrorMessages[] = {
-    "Interface closed without finishing transmission.",
-    "Receive called without completing transmission.",
-    "Invalid receive buffer supplied with non-zero length.",
-    "Transmit called without completing reception.",
-    "Invalid transmit buffer supplied with non-zero length.",
-    "Transceive called while other I/O in process.",
-    "Transmitted no data.", /* Can reach this by setting tx_len = 0. */
-};
-ESE_DEFINE_HW_ERRORS(ESE_HW_FAKE, kErrorMessages);
diff --git a/libese-hw/nxp/Android.bp b/libese-hw/nxp/Android.bp
index f0ce732..0e0208e 100644
--- a/libese-hw/nxp/Android.bp
+++ b/libese-hw/nxp/Android.bp
@@ -15,21 +15,43 @@
 //
 
 cc_library {
-    name: "libese-hw-nxp-pn80t-spidev",
-    host_supported: false,
+    name: "libese-hw-nxp-pn80t-common",
     debug: {
-      cflags: ["-DLOG_NDEBUG=0"],
+        cflags: ["-DLOG_NDEBUG=0"],
     },
-    srcs: ["pn80t_spidev.c"],
-    local_include_dirs: ["include"],
+    srcs: ["pn80t/common.c"],
     shared_libs: ["liblog", "libese", "libese-teq1"],
+    local_include_dirs: ["include"],
     export_include_dirs: ["include"],
     target: {
+        darwin: {
+          enabled: false,
+        },
+    },
+}
+
+cc_defaults {
+    name: "pn80t_platform",
+    target: {
       darwin: {
-        enabled: false,
-      },
-      windows: {
-        enabled: false,
+          enabled: false,
       },
     },
+    cflags: ["-fvisibility=internal"],
+    local_include_dirs: ["include"],
+    export_include_dirs: ["include"],
+    shared_libs: ["liblog", "libese", "libese-teq1"],
+    static_libs: ["libese-hw-nxp-pn80t-common"],
+}
+
+cc_library {
+    name: "libese-hw-nxp-pn80t-spidev",
+    defaults: ["pn80t_platform"],
+    srcs: ["pn80t/linux_spidev.c"],
+}
+
+cc_library {
+    name: "libese-hw-nxp-pn80t-nq-nci",
+    defaults: ["pn80t_platform"],
+    srcs: ["pn80t/nq_nci.c"],
 }
diff --git a/libese-hw/nxp/include/ese/hw/nxp/pn80t/boards/hikey-spidev.h b/libese-hw/nxp/include/ese/hw/nxp/pn80t/boards/hikey-spidev.h
index 4f73003..3026f02 100644
--- a/libese-hw/nxp/include/ese/hw/nxp/pn80t/boards/hikey-spidev.h
+++ b/libese-hw/nxp/include/ese/hw/nxp/pn80t/boards/hikey-spidev.h
@@ -19,6 +19,12 @@
 
 static const struct NxpSpiBoard nxp_boards_hikey_spidev = {
   .dev_path = "/dev/spidev0.0",
-  .reset_gpio = 488,   /* GPIO2_0 */
-  .svdd_pwr_req_gpio = 490, /* GPIO2_2 */
+  .gpios = {
+    488, /* kBoardGpioEseRst = GPIO2_0 */
+    490, /* kBoardGpioEseSvddPwrReq = GPIO2_2 */
+    -1,  /* kBoardGpioNfcVen = unused */
+  },
+  .mode = 0,
+  .bits = 8,
+  .speed = 1000000L,
 };
diff --git a/libese-hw/nxp/include/ese/hw/nxp/pn80t/common.h b/libese-hw/nxp/include/ese/hw/nxp/pn80t/common.h
new file mode 100644
index 0000000..0125f24
--- /dev/null
+++ b/libese-hw/nxp/include/ese/hw/nxp/pn80t/common.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ESE_HW_NXP_PN80T_COMMON_H_
+#define ESE_HW_NXP_PN80T_COMMON_H_ 1
+
+#include "../../libese-teq1/include/ese/teq1.h"
+#include "../../libese/include/ese/ese.h"
+#include "../../libese/include/ese/log.h"
+#include "platform.h"
+
+/* Card state is _required_ to be at the front of eSE pad. */
+struct NxpState {
+  void *handle;
+};
+
+/* pad[0] is reserved for T=1. Lazily go to the middle. */
+#define NXP_PN80T_STATE(ese)                                                   \
+  ((struct NxpState *)(&ese->pad[ESE_INTERFACE_STATE_PAD / 2]))
+
+void nxp_pn80t_close(struct EseInterface *ese);
+uint32_t nxp_pn80t_transceive(struct EseInterface *ese,
+                              const uint8_t *const tx_buf, uint32_t tx_len,
+                              uint8_t *rx_buf, uint32_t rx_len);
+int nxp_pn80t_poll(struct EseInterface *ese, uint8_t poll_for, float timeout,
+                   int complete);
+int nxp_pn80t_reset(struct EseInterface *ese);
+int nxp_pn80t_open(struct EseInterface *ese, void *board);
+
+enum NxpPn80tError {
+  kNxpPn80tError = kTeq1ErrorMax,
+  kNxpPn80tErrorPollRead,
+  kNxpPn80tErrorPlatformInit,
+  kNxpPn80tErrorResetToggle,
+  kNxpPn80tErrorTransmit,
+  kNxpPn80tErrorTransmitSize,
+  kNxpPn80tErrorReceive,
+  kNxpPn80tErrorReceiveSize,
+  kNxpPn80tErrorMax,  /* sizeof(kNxpPn80tErrorMessages) */
+};
+
+extern const char *kNxpPn80tErrorMessages[];
+#endif  /* ESE_HW_NXP_PN80T_COMMON_H_ */
diff --git a/libese-hw/nxp/include/ese/hw/nxp/pn80t/platform.h b/libese-hw/nxp/include/ese/hw/nxp/pn80t/platform.h
new file mode 100644
index 0000000..8dad425
--- /dev/null
+++ b/libese-hw/nxp/include/ese/hw/nxp/pn80t/platform.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ESE_HW_NXP_PN80T_PLATFORM_H_
+#define ESE_HW_NXP_PN80T_PLATFORM_H_ 1
+
+typedef void *(pn80t_platform_initialize_t)(void *);
+typedef int (pn80t_platform_release_t)(void *);
+typedef int (pn80t_platform_toggle_t)(void *, int);
+typedef int (pn80t_platform_wait_t)(void *, long usec);
+
+/* Pn80tPlatform
+ *
+ * Provides the callbacks necessary to interface with the platform, be it the Linux
+ * kernel or a bootloader in pn80t_common.c
+ *
+ * All "required" functions must be provided.
+ * All "optional" functions may be set to NULL.
+ *
+ */
+struct Pn80tPlatform {
+  /* Required: Initializes the hardware and platform opaque handle. */
+  pn80t_platform_initialize_t *const initialize;
+  /* Required: free memory and release resources as needed. */
+  pn80t_platform_release_t *const release;
+  /* Required: determines eSE specific power. 1 = on, 0 = off. */
+  pn80t_platform_toggle_t *const toggle_reset;  /* ESE_RST or other power control. */
+  /* Optional: determines global NFC power: 1 = on, 0 = off */
+  pn80t_platform_toggle_t *const toggle_ven;  /* NFC_VEN */
+  /* Optional: enables eSE power control via |toggle_reset|. 1 = on, 0 = off */
+  pn80t_platform_toggle_t *const toggle_power_req;  /* SVDD_PWR_REQ */
+  /* Required: provides a usleep() equivalent. */
+  pn80t_platform_wait_t *const wait;
+};
+
+#endif
diff --git a/libese-hw/nxp/include/ese/hw/nxp/spi_board.h b/libese-hw/nxp/include/ese/hw/nxp/spi_board.h
index 109e348..ddcb64b 100644
--- a/libese-hw/nxp/include/ese/hw/nxp/spi_board.h
+++ b/libese-hw/nxp/include/ese/hw/nxp/spi_board.h
@@ -17,10 +17,20 @@
 #ifndef ESE_HW_NXP_SPI_BOARD_H_
 #define ESE_HW_NXP_SPI_BOARD_H_ 1
 
+typedef enum {
+  kBoardGpioEseRst = 0,
+  kBoardGpioEseSvddPwrReq,
+  kBoardGpioNfcVen,
+  kBoardGpioMax,
+} BoardGpio;
+
+/* Allow GPIO assignment and configuration to vary without a new device definition. */
 struct NxpSpiBoard {
   const char *dev_path;
-  int reset_gpio;
-  int svdd_pwr_req_gpio;
+  int gpios[kBoardGpioMax];
+  uint8_t mode;
+  uint32_t bits;
+  uint32_t speed;
 };
 
 #endif  /* ESE_HW_NXP_SPI_BOARD_H_ */
diff --git a/libese-hw/nxp/pn80t/README.md b/libese-hw/nxp/pn80t/README.md
new file mode 100644
index 0000000..49d6f29
--- /dev/null
+++ b/libese-hw/nxp/pn80t/README.md
@@ -0,0 +1,28 @@
+# NXP PN80T/PN81A support
+
+libese support for the PN80T series of embedded secure elements is
+implemented using a common set of shared hardware functions and
+communication specific behavior.
+
+The common behavior is found in "common.h".  Support for using Linux
+spidev іs in "linux\_spidev.c", and support for using a simple nq-nci
+associated kernel driver is in "nq\_nci.c".
+
+# Implementing a new backend
+
+When implementing a new backend, the required header is:
+
+    #include "../include/ese/hw/nxp/pn80t/common.h"
+
+Once included, the implementation must provide its own
+receive and transmit libese-hw functions and a struct Pn80tPlatform,
+defined in "../include/ese/hw/nxp/pn80t/platform.h".
+
+Platform requires a dedicated initialize, release, wait functions as
+well support for controlling NFC\_VEN, SVDD\_PWR\_REQ, and reset.
+
+See the bottom of the other implementations for an example of the
+required exports.
+
+Any other functionality, such as libese-sysdeps, may also need to be
+provided separately.
diff --git a/libese-hw/nxp/pn80t/common.c b/libese-hw/nxp/pn80t/common.c
new file mode 100644
index 0000000..e8d8096
--- /dev/null
+++ b/libese-hw/nxp/pn80t/common.c
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Support SPI communication with NXP PN553/PN80T secure element.
+ */
+
+#include "include/ese/hw/nxp/pn80t/common.h"
+
+#ifndef INT_MAX
+#define INT_MAX 2147483647
+#endif
+
+int nxp_pn80t_preprocess(const struct Teq1ProtocolOptions *const opts,
+                         struct Teq1Frame *frame, int tx) {
+  if (tx) {
+    /* Recompute the LRC with the NAD of 0x00 */
+    frame->header.NAD = 0x00;
+    frame->INF[frame->header.LEN] = teq1_compute_LRC(frame);
+    frame->header.NAD = opts->node_address;
+    ALOGV("interface is preprocessing outbound frame");
+  } else {
+    /* Replace the NAD with 0x00 so the LRC check passes. */
+    ALOGV("interface is preprocessing inbound frame (%x->%x)",
+          frame->header.NAD, 0x00);
+    if (frame->header.NAD != opts->host_address) {
+      ALOGV("Rewriting from unknown NAD: %x", frame->header.NAD);
+    }
+    frame->header.NAD = 0x00;
+    ALOGV("Frame length: %x", frame->header.LEN);
+  }
+  return 0;
+}
+
+static const struct Teq1ProtocolOptions kTeq1Options = {
+    .host_address = 0xA5,
+    .node_address = 0x5A,
+    .bwt = 1.624f,   /* cwt by default would be ~8k * 1.05s */
+    .etu = 0.00105f, /* seconds */
+    .preprocess = &nxp_pn80t_preprocess,
+};
+
+int nxp_pn80t_open(struct EseInterface *ese, void *board) {
+  struct NxpState *ns;
+  const struct Pn80tPlatform *platform;
+  if (sizeof(ese->pad) < sizeof(struct NxpState *)) {
+    /* This is a compile-time correctable error only. */
+    ALOGE("Pad size too small to use NXP HW (%zu < %zu)", sizeof(ese->pad),
+          sizeof(struct NxpState));
+    return -1;
+  }
+  platform = ese->ops->opts;
+
+  /* Ensure all required functions exist */
+  if (!platform->initialize || !platform->release || !platform->toggle_reset ||
+      !platform->wait) {
+    ALOGE("Required functions not implemented in supplied platform");
+    return -1;
+  }
+
+  ns = NXP_PN80T_STATE(ese);
+  TEQ1_INIT_CARD_STATE((struct Teq1CardState *)(&ese->pad[0]));
+  ns->handle = platform->initialize(board);
+  if (!ns->handle) {
+    ALOGE("platform initialization failed");
+    ese_set_error(ese, kNxpPn80tErrorPlatformInit);
+    return -1;
+  }
+  /* Toggle all required power GPIOs.
+   * Each platform may prefer to handle the power
+   * muxing specific. E.g., if NFC is in use, it would
+   * be unwise to unset VEN.  However, the implementation
+   * here will attempt it if supported.
+   */
+  if (platform->toggle_ven) {
+    platform->toggle_ven(ns->handle, 1);
+  }
+  if (platform->toggle_power_req) {
+    platform->toggle_power_req(ns->handle, 1);
+  }
+  /* Power on eSE */
+  platform->toggle_reset(ns->handle, 1);
+  /* Let the eSE boot. */
+  platform->wait(ns->handle, 5000);
+  return 0;
+}
+
+int nxp_pn80t_reset(struct EseInterface *ese) {
+  const struct Pn80tPlatform *platform = ese->ops->opts;
+  struct NxpState *ns = NXP_PN80T_STATE(ese);
+  if (platform->toggle_reset(ns->handle, 0) < 0) {
+    ese_set_error(ese, kNxpPn80tErrorResetToggle);
+    return -1;
+  }
+  platform->wait(ns->handle, 1000);
+  if (platform->toggle_reset(ns->handle, 1) < 0) {
+    ese_set_error(ese, kNxpPn80tErrorResetToggle);
+    return -1;
+  }
+  return 0;
+}
+
+int nxp_pn80t_poll(struct EseInterface *ese, uint8_t poll_for, float timeout,
+                   int complete) {
+  struct NxpState *ns = NXP_PN80T_STATE(ese);
+  const struct Pn80tPlatform *platform = ese->ops->opts;
+  /* Attempt to read a 8-bit character once per 8-bit character transmission
+   * window (in seconds). */
+  int intervals = (int)(0.5f + timeout / (7.0f * kTeq1Options.etu));
+  uint8_t byte = 0xff;
+  ALOGV("interface polling for start of frame/host node address: %x", poll_for);
+  /* If we had interrupts, we could just get notified by the driver. */
+  do {
+    /*
+     * In practice, if complete=true, then no transmission
+     * should attempt again until after 1000usec.
+     */
+    if (ese->ops->hw_receive(ese, &byte, 1, complete) != 1) {
+      ALOGV("failed to read one byte");
+      ese_set_error(ese, kNxpPn80tErrorPollRead);
+      return -1;
+    }
+    if (byte == poll_for) {
+      ALOGV("Polled for byte seen: %x with %d intervals remaining.", poll_for,
+            intervals);
+      ALOGV("RX[0]: %.2X", byte);
+      return 1;
+    } else {
+      ALOGV("No match (saw %x)", byte);
+    }
+    platform->wait(ns->handle,
+                   7.0f * kTeq1Options.etu * 1000000.0f); /* s -> us */
+    ALOGV("poll interval %d: no match.", intervals);
+  } while (intervals-- > 0);
+  return -1;
+}
+
+uint32_t nxp_pn80t_transceive(struct EseInterface *ese,
+                              const uint8_t *const tx_buf, uint32_t tx_len,
+                              uint8_t *rx_buf, uint32_t rx_len) {
+  /* TODO(wad) Should we toggle power on each call? */
+  return teq1_transceive(ese, &kTeq1Options, tx_buf, tx_len, rx_buf, rx_len);
+}
+
+uint32_t nxp_pn80t_send_cooldown(struct EseInterface *ese) {
+  const struct Pn80tPlatform *platform = ese->ops->opts;
+  const uint8_t kCooldown[] = {0xa5, 0xc5, 0x00, 0xc5};
+  uint8_t rx_buf[8];
+  uint32_t *res = (uint32_t *)(&rx_buf[3]);
+  ese->ops->hw_transmit(ese, kCooldown, sizeof(kCooldown), 1);
+  nxp_pn80t_poll(ese, kTeq1Options.host_address, 5.0f, 0);
+  ese->ops->hw_receive(ese, rx_buf, sizeof(rx_buf), 1);
+  if (rx_buf[2] == 4) {
+    ALOGI("Cooldown value is %u", *res);
+    return *res;
+  } else {
+    ALOGI("Cooldown value unavailable");
+  }
+  return 0;
+}
+
+void nxp_pn80t_close(struct EseInterface *ese) {
+  struct NxpState *ns;
+  const struct Pn80tPlatform *platform = ese->ops->opts;
+  ALOGV("%s: called", __func__);
+  ns = NXP_PN80T_STATE(ese);
+  nxp_pn80t_send_cooldown(ese);
+  platform->toggle_reset(ns->handle, 0);
+  if (platform->toggle_power_req) {
+    platform->toggle_power_req(ns->handle, 0);
+  }
+  if (platform->toggle_ven) {
+    platform->toggle_ven(ns->handle, 0);
+  }
+  platform->release(ns->handle);
+  ns->handle = NULL;
+}
+
+const char *kNxpPn80tErrorMessages[] = {
+    /* The first three are required by teq1_transceive use. */
+    "T=1 hard failure.", "T=1 abort.", "T=1 device reset failed.",
+    /* The rest are pn80t impl specific. */
+    "failed to read one byte", "unable to initialize platform",
+    "failed to read", "attempted to receive too much data",
+    "attempted to transfer too much data", "failed to transmit",
+    "failed to toggle reset",
+};
diff --git a/libese-hw/nxp/pn80t/linux_spidev.c b/libese-hw/nxp/pn80t/linux_spidev.c
new file mode 100644
index 0000000..d4ac386
--- /dev/null
+++ b/libese-hw/nxp/pn80t/linux_spidev.c
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Defines the PN80T spidev device and platform wrappers consumed in
+ * the common code.
+ */
+
+#include <fcntl.h>
+#include <limits.h>
+#include <linux/spi/spidev.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include "../include/ese/hw/nxp/pn80t/common.h"
+#include "../include/ese/hw/nxp/spi_board.h"
+
+struct Handle {
+  int spi_fd;
+  struct NxpSpiBoard *board;
+};
+
+int gpio_set(int num, int val) {
+  char val_path[256];
+  char val_chr = (val ? '1' : '0');
+  int fd;
+  if (num < 0) {
+    return 0;
+  }
+  if (snprintf(val_path, sizeof(val_path), "/sys/class/gpio/gpio%d/value",
+               num) >= (int)sizeof(val_path)) {
+    return -1;
+  }
+  printf("Gpio @ %s\n", val_path);
+  fd = open(val_path, O_WRONLY);
+  if (fd < 0) {
+    return -1;
+  }
+  if (write(fd, &val_chr, 1) < 0) {
+    close(fd);
+    return -1;
+  }
+  close(fd);
+  return 0;
+}
+
+int platform_toggle_ven(void *blob, int val) {
+  struct Handle *handle = blob;
+  printf("Toggling VEN: %d\n", val);
+  return gpio_set(handle->board->gpios[kBoardGpioNfcVen], val);
+}
+
+int platform_toggle_reset(void *blob, int val) {
+  struct Handle *handle = blob;
+  printf("Toggling RST: %d\n", val);
+  return gpio_set(handle->board->gpios[kBoardGpioEseRst], val);
+}
+
+int platform_toggle_power_req(void *blob, int val) {
+  struct Handle *handle = blob;
+  printf("Toggling SVDD_PWR_REQ: %d\n", val);
+  return gpio_set(handle->board->gpios[kBoardGpioEseSvddPwrReq], val);
+}
+
+int gpio_configure(int num, int out, int val) {
+  char dir_path[256];
+  char numstr[8];
+  char dir[5];
+  int fd;
+  /* <0 is unmapped. No work to do! */
+  if (num < 0) {
+    return 0;
+  }
+  if (snprintf(dir, sizeof(dir), "%s", (out ? "out" : "in")) >=
+      (int)sizeof(dir)) {
+    return -1;
+  }
+  if (snprintf(dir_path, sizeof(dir_path), "/sys/class/gpio/gpio%d/direction",
+               num) >= (int)sizeof(dir_path)) {
+    return -1;
+  }
+  if (snprintf(numstr, sizeof(numstr), "%d", num) >= (int)sizeof(numstr)) {
+    return -1;
+  }
+  fd = open("/sys/class/gpio/export", O_WRONLY);
+  if (fd < 0) {
+    return -1;
+  }
+  /* Exporting can only happen once, so instead of stat()ing, just ignore
+   * errors. */
+  (void)write(fd, numstr, strlen(numstr));
+  close(fd);
+
+  fd = open(dir_path, O_WRONLY);
+  if (fd < 0) {
+    return -1;
+  }
+  if (write(fd, dir, strlen(dir)) < 0) {
+    close(fd);
+    return -1;
+  }
+  close(fd);
+  return gpio_set(num, val);
+}
+
+void *platform_init(void *hwopts) {
+  struct NxpSpiBoard *board = hwopts;
+  struct Handle *handle;
+  int gpio = 0;
+
+  handle = malloc(sizeof(*handle));
+  if (!handle) {
+    return NULL;
+  }
+  handle->board = board;
+
+  /* Initialize the mapped GPIOs */
+  for (; gpio < kBoardGpioMax; ++gpio) {
+    if (gpio_configure(board->gpios[gpio], 1, 1) < 0) {
+      free(handle);
+      return NULL;
+    }
+  }
+
+  handle->spi_fd = open(board->dev_path, O_RDWR);
+  if (handle->spi_fd < 0) {
+    free(handle);
+    return NULL;
+  }
+  /* If we need anything fancier, we'll need MODE32 in the headers. */
+  if (ioctl(handle->spi_fd, SPI_IOC_WR_MODE, &board->mode) < 0) {
+    close(handle->spi_fd);
+    free(handle);
+    return NULL;
+  }
+  if (ioctl(handle->spi_fd, SPI_IOC_WR_BITS_PER_WORD, &board->bits) < 0) {
+    close(handle->spi_fd);
+    free(handle);
+    return NULL;
+  }
+  if (ioctl(handle->spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &board->speed) < 0) {
+    close(handle->spi_fd);
+    free(handle);
+    return NULL;
+  }
+  printf("Linux SPIDev initialized\n");
+  return (void *)handle;
+}
+
+int platform_release(void *blob) {
+  struct Handle *handle = blob;
+  close(handle->spi_fd);
+  free(handle);
+  /* Note, we don't unconfigure the GPIOs. */
+  return 0;
+}
+
+int platform_wait(void *blob __attribute__((unused)), long usec) {
+  return usleep((useconds_t)usec);
+}
+
+uint32_t spidev_transmit(struct EseInterface *ese, const uint8_t *buf,
+                         uint32_t len, int complete) {
+  struct NxpState *ns = NXP_PN80T_STATE(ese);
+  struct Handle *handle = ns->handle;
+  struct spi_ioc_transfer tr = {
+      .tx_buf = (unsigned long)buf,
+      .rx_buf = 0,
+      .len = (uint32_t)len,
+      .delay_usecs = 0,
+      .speed_hz = 0,
+      .bits_per_word = 0,
+      .cs_change = !!complete,
+  };
+  ssize_t ret = -1;
+  ALOGV("spidev:%s: called [%d]", __func__, len);
+  if (len > INT_MAX) {
+    ese_set_error(ese, kNxpPn80tErrorTransmitSize);
+    ALOGE("Unexpectedly large transfer attempted: %u", len);
+    return 0;
+  }
+  ret = ioctl(handle->spi_fd, SPI_IOC_MESSAGE(1), &tr);
+  if (ret < 1) {
+    ese_set_error(ese, kNxpPn80tErrorTransmit);
+    ALOGE("%s: failed to write to hw (ret=%zd)", __func__, ret);
+    return 0;
+  }
+  return len;
+}
+
+uint32_t spidev_receive(struct EseInterface *ese, uint8_t *buf, uint32_t len,
+                        int complete) {
+  struct NxpState *ns = NXP_PN80T_STATE(ese);
+  struct Handle *handle = ns->handle;
+  ssize_t ret = -1;
+  struct spi_ioc_transfer tr = {
+      .tx_buf = 0,
+      .rx_buf = (unsigned long)buf,
+      .len = (uint32_t)len,
+      .delay_usecs = 0,
+      .speed_hz = 0,
+      .bits_per_word = 0,
+      .cs_change = !!complete,
+  };
+  ALOGV("spidev:%s: called [%d]", __func__, len);
+  if (len > INT_MAX) {
+    ese_set_error(ese, kNxpPn80tErrorReceiveSize);
+    ALOGE("Unexpectedly large receive attempted: %u", len);
+    return 0;
+  }
+  ret = ioctl(handle->spi_fd, SPI_IOC_MESSAGE(1), &tr);
+  if (ret < 1) {
+    ALOGE("%s: failed to read from hw (ret=%zd)", __func__, ret);
+    ese_set_error(ese, kNxpPn80tErrorReceive);
+    return 0;
+  }
+  ALOGV("%s: read bytes: %zd", __func__, len);
+  return len;
+}
+
+static const struct Pn80tPlatform kPn80tLinuxSpidevPlatform = {
+    .initialize = &platform_init,
+    .release = &platform_release,
+    .toggle_reset = &platform_toggle_reset,
+    .toggle_ven = &platform_toggle_ven,
+    .toggle_power_req = &platform_toggle_power_req,
+    .wait = &platform_wait,
+};
+
+static const struct EseOperations ops = {
+    .name = "NXP PN80T/PN81A (PN553)",
+    .open = &nxp_pn80t_open,
+    .hw_receive = &spidev_receive,
+    .hw_transmit = &spidev_transmit,
+    .hw_reset = &nxp_pn80t_reset,
+    .transceive = &nxp_pn80t_transceive,
+    .poll = &nxp_pn80t_poll,
+    .close = &nxp_pn80t_close,
+    .opts = &kPn80tLinuxSpidevPlatform,
+    .errors = kNxpPn80tErrorMessages,
+    .errors_count = kNxpPn80tErrorMax,
+};
+__attribute__((visibility("default")))
+ESE_DEFINE_HW_OPS(ESE_HW_NXP_PN80T_SPIDEV, ops);
diff --git a/libese-hw/nxp/pn80t/nq_nci.c b/libese-hw/nxp/pn80t/nq_nci.c
new file mode 100644
index 0000000..5b20f0f
--- /dev/null
+++ b/libese-hw/nxp/pn80t/nq_nci.c
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Platform implementation for a nq-nci extension driver.
+ *
+ * The driver presents the following interface on a miscdev:
+ * - ioctl():
+ *   - for setting and getting power.
+ *     This handles SVDD_PWR_REQ and NFC_VEN muxing.
+ *     (ESE_RST is not connected in this case.)
+ * - read():
+ *   - For reading arbitrary amounts of data.
+ *     CS is asserted and deasserted on each call, but the clock
+ *     also appears to do the same which keeps the ese data available
+ *     as far as I can tell.
+ * - write():
+ *   - For writing arbitrary amounts of data.
+ *     CS is asserted as with read() calls, so the less fragmented
+ *     the better.
+ *
+ * All GPIO toggling and chip select requirements are handled behind this
+ * interface.
+ *
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include "../include/ese/hw/nxp/pn80t/common.h"
+
+#ifndef UNUSED
+#define UNUSED(x) x __attribute__((unused))
+#endif
+
+/* From kernel/drivers/nfc/nq-nci.h */
+#define ESE_SET_PWR _IOW(0xE9, 0x02, unsigned int)
+#define ESE_GET_PWR _IOR(0xE9, 0x03, unsigned int)
+
+static const char kDevicePath[] = "/dev/pn81a";
+
+struct PlatformHandle {
+  int fd;
+};
+
+int platform_toggle_reset(void *blob, int val) {
+  const struct PlatformHandle *handle = blob;
+  /* 0=power and 1=no power in the kernel. */
+  return ioctl(handle->fd, ESE_SET_PWR, !val);
+}
+
+void *platform_init(void *hwopts) {
+  /* TODO(wad): It may make sense to pass in the dev path here. */
+  if (hwopts != NULL) {
+    return NULL;
+  }
+
+  struct PlatformHandle *handle = calloc(1, sizeof(*handle));
+  if (!handle) {
+    ALOGV("%s: unable to allocate memory for handle", __func__);
+    return NULL;
+  }
+  handle->fd = open(kDevicePath, O_RDWR);
+  if (handle->fd < 0) {
+    free(handle);
+    return NULL;
+  }
+  return handle;
+}
+
+int platform_release(void *blob) {
+  struct PlatformHandle *handle = blob;
+  /* Power off and cooldown should've happened via common code. */
+  close(handle->fd);
+  free(handle);
+  return 0;
+}
+
+int platform_wait(void *UNUSED(blob), long usec) {
+  return usleep((useconds_t)usec);
+}
+
+uint32_t nq_transmit(struct EseInterface *ese, const uint8_t *buf, uint32_t len,
+                     int UNUSED(complete)) {
+  struct NxpState *ns = NXP_PN80T_STATE(ese);
+  const struct Pn80tPlatform *platform = ese->ops->opts;
+  uint32_t bytes = 0;
+  ALOGV("nq_nci:%s: called [%d]", __func__, len);
+  if (len > INT_MAX) {
+    ese_set_error(ese, kNxpPn80tErrorTransmitSize);
+    ALOGE("Unexpectedly large transfer attempted: %u", len);
+    return 0;
+  }
+  if (len == 0)
+    return len;
+  const struct PlatformHandle *handle = ns->handle;
+  while (bytes < len) {
+    ssize_t ret = write(handle->fd, (void *)(buf + bytes), len - bytes);
+    if (ret < 0) {
+      if (errno == EAGAIN || errno == EINTR) {
+        continue;
+      }
+      ese_set_error(ese, kNxpPn80tErrorTransmit);
+      ALOGE("%s: failed to write to hw (ret=%zd, errno=%d)", __func__, ret,
+            errno);
+      return 0;
+    }
+    bytes += ret;
+  }
+  return len;
+}
+
+uint32_t nq_receive(struct EseInterface *ese, uint8_t *buf, uint32_t len,
+                    int UNUSED(complete)) {
+  const struct Pn80tPlatform *platform = ese->ops->opts;
+  struct NxpState *ns = NXP_PN80T_STATE(ese);
+  ALOGV("nq_nci:%s: called [%d]", __func__, len);
+  if (!ns) {
+    ALOGE("NxpState was NULL");
+    return 0;
+  }
+  if (len > INT_MAX) {
+    ese_set_error(ese, kNxpPn80tErrorReceiveSize);
+    ALOGE("Unexpectedly large receive attempted: %u", len);
+    return 0;
+  }
+  const struct PlatformHandle *handle = ns->handle;
+  if (len == 0) {
+    return 0;
+  }
+  uint32_t bytes = 0;
+  while (bytes < len) {
+    ssize_t ret = read(handle->fd, (void *)(buf + bytes), len - bytes);
+    if (ret < 0) {
+      if (errno == EAGAIN || errno == EINTR) {
+        continue;
+      }
+      ALOGE("%s: failed to read from hw (ret=%zd, errno=%d)", __func__, ret,
+            errno);
+      ese_set_error(ese, kNxpPn80tErrorReceive);
+      return 0;
+    }
+    bytes += ret;
+  }
+  ALOGV("%s: read bytes: %u", __func__, bytes);
+  return len;
+}
+
+static const struct Pn80tPlatform kPn80tNqNciPlatform = {
+    .initialize = &platform_init,
+    .release = &platform_release,
+    .toggle_reset = &platform_toggle_reset,
+    .toggle_ven = NULL,
+    .toggle_power_req = NULL,
+    .wait = &platform_wait,
+};
+
+static const struct EseOperations ops = {
+    .name = "NXP PN80T/PN81A (NQ-NCI:PN553)",
+    .open = &nxp_pn80t_open,
+    .hw_receive = &nq_receive,
+    .hw_transmit = &nq_transmit,
+    .hw_reset = &nxp_pn80t_reset,
+    .transceive = &nxp_pn80t_transceive,
+    .poll = &nxp_pn80t_poll,
+    .close = &nxp_pn80t_close,
+    .opts = &kPn80tNqNciPlatform,
+    .errors = kNxpPn80tErrorMessages,
+    .errors_count = kNxpPn80tErrorMax,
+};
+__attribute__((visibility("default")))
+ESE_DEFINE_HW_OPS(ESE_HW_NXP_PN80T_NQ_NCI, ops);
diff --git a/libese-hw/nxp/pn80t_spidev.c b/libese-hw/nxp/pn80t_spidev.c
deleted file mode 100644
index 3f63103..0000000
--- a/libese-hw/nxp/pn80t_spidev.c
+++ /dev/null
@@ -1,346 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * Support SPI communication with NXP PN553/PN80T secure element.
- */
-
-#include <fcntl.h>
-#include <limits.h>
-#include <linux/spi/spidev.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/ioctl.h>
-#include <unistd.h>
-
-#include <ese/ese.h>
-#include <ese/hw/nxp/spi_board.h>
-#include <ese/teq1.h>
-#define LOG_TAG "libese-hw"
-#include <ese/log.h>
-
-/* Card state is _required_ to be at the front of eSE pad. */
-struct NxpState {
-  struct Teq1CardState card_state;
-  int spi_fd;
-  struct NxpSpiBoard *board;
-};
-#define NXP_PN80T_SPIDEV_STATE(ese) ((struct NxpState *)(&ese->pad[0]))
-
-int gpio_set(int num, int val) {
-  char val_path[256];
-  char val_chr = (val ? '1' : '0');
-  int fd;
-  if (snprintf(val_path, sizeof(val_path), "/sys/class/gpio/gpio%d/value",
-               num) >= (int)sizeof(val_path))
-    return -1;
-  fd = open(val_path, O_WRONLY);
-  if (fd < 0)
-    return -1;
-  if (write(fd, &val_chr, 1) < 0) {
-    close(fd);
-    return -1;
-  }
-  close(fd);
-  return 0;
-}
-
-int gpio_configure(int num, int out, int val) {
-  char dir_path[256];
-  char numstr[8];
-  char dir[5];
-  int fd;
-  if (snprintf(dir, sizeof(dir), "%s", (out ? "out" : "in")) >=
-      (int)sizeof(dir))
-    return -1;
-  if (snprintf(dir_path, sizeof(dir_path), "/sys/class/gpio/gpio%d/direction",
-               num) >= (int)sizeof(dir_path))
-    return -1;
-  if (snprintf(numstr, sizeof(numstr), "%d", num) >= (int)sizeof(numstr))
-    return -1;
-  fd = open("/sys/class/gpio/export", O_WRONLY);
-  if (fd < 0)
-    return -1;
-  /* Exporting can only happen once, so instead of stat()ing, just ignore
-   * errors. */
-  (void)write(fd, numstr, strlen(numstr));
-  close(fd);
-
-  fd = open(dir_path, O_WRONLY);
-  if (fd < 0)
-    return -1;
-  if (write(fd, dir, strlen(dir)) < 0) {
-    close(fd);
-    return -1;
-  }
-  close(fd);
-  return gpio_set(num, val);
-}
-
-int nxp_pn80t_open(struct EseInterface *ese, void *hw_opts) {
-  struct NxpState *ns;
-  uint8_t mode = 0;
-  uint32_t bits = 8;
-  uint32_t speed = 1000000L;
-  if (!ese)
-    return -1;
-  if (sizeof(ese->pad) < sizeof(struct NxpState *)) {
-    /* This is a compile-time correctable error only. */
-    ALOGE("Pad size too small to use NXP HW (%zu < %zu)", sizeof(ese->pad),
-          sizeof(struct NxpState));
-    return -1;
-  }
-  ns = NXP_PN80T_SPIDEV_STATE(ese);
-  TEQ1_INIT_CARD_STATE((&ns->card_state));
-  ns->board = (struct NxpSpiBoard *)(hw_opts);
-
-  /* Configure ESE_SVDD_PWR_REQ */
-  /* TODO(wad): We can leave this low and move it to set/unset
-   * in a transceive() wrapper.
-   */
-  if (gpio_configure(ns->board->svdd_pwr_req_gpio, 1, 1) < 0) {
-    ese_set_error(ese, 13);
-    return -1;
-  }
-
-  /* Configure ESE_RST_GPIO */
-  if (gpio_configure(ns->board->reset_gpio, 1, 1) < 0) {
-    ese_set_error(ese, 12);
-    return -1;
-  }
-
-  ns->spi_fd = open(ns->board->dev_path, O_RDWR);
-  if (ns->spi_fd < 0) {
-    ALOGE("failed to open spidev: %s", ns->board->dev_path);
-    ese_set_error(ese, 4);
-    return -1;
-  }
-
-  /* If we need anything fancier, we'll need MODE32 in the headers. */
-  if (ioctl(ns->spi_fd, SPI_IOC_WR_MODE, &mode) < 0) {
-    ALOGE("failed to set spidev mode to %d", mode);
-    ese_set_error(ese, 5);
-    return -1;
-  }
-  if (ioctl(ns->spi_fd, SPI_IOC_WR_BITS_PER_WORD, &bits) < 0) {
-    ALOGE("failed to set spidev bits per word to %d", bits);
-    ese_set_error(ese, 6);
-    return -1;
-  }
-  if (ioctl(ns->spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) < 0) {
-    ALOGE("failed to set spidev max speed to %dhz", speed);
-    ese_set_error(ese, 7);
-    return -1;
-  }
-  return 0;
-}
-
-int nxp_pn80t_close(struct EseInterface *ese) {
-  struct NxpState *ns;
-  if (!ese)
-    return -1;
-  ns = NXP_PN80T_SPIDEV_STATE(ese);
-  close(ns->spi_fd);
-  /* We're done. */
-  gpio_set(ns->board->svdd_pwr_req_gpio, 0);
-  return 0;
-}
-
-size_t nxp_pn80t_receive(struct EseInterface *ese, uint8_t *buf, size_t len,
-                         int complete) {
-  struct NxpState *ns = NXP_PN80T_SPIDEV_STATE(ese);
-  size_t recvd = len;
-  struct spi_ioc_transfer tr = {
-      .tx_buf = 0,
-      .rx_buf = (unsigned long)buf,
-      .len = (uint32_t)len,
-      .delay_usecs = 0,
-      .speed_hz = 0,
-      .bits_per_word = 0,
-      .cs_change = !!complete,
-  };
-
-  if (len > UINT_MAX) {
-    ese_set_error(ese, 9);
-    ALOGE("Unexpectedly large receive attempted: %zu", len);
-    return 0;
-  }
-
-  ALOGV("interface attempting to receive card data");
-  if (ioctl(ns->spi_fd, SPI_IOC_MESSAGE(1), &tr) < 1) {
-    ese_set_error(ese, 8);
-    return 0;
-  }
-  ALOGV("card sent %zu bytes", len);
-  for (recvd = 0; recvd < len; ++recvd)
-    ALOGV("RX[%zu]: %.2X", recvd, buf[recvd]);
-  if (complete) {
-    ALOGV("card sent a frame");
-    /* XXX: cool off the bus for 1ms [t_3] */
-  }
-  return recvd;
-}
-
-int nxp_pn80t_reset(struct EseInterface *ese) {
-  struct NxpState *ns = NXP_PN80T_SPIDEV_STATE(ese);
-  if (gpio_set(ns->board->reset_gpio, 0) < 0) {
-    ese_set_error(ese, 13);
-    return -1;
-  }
-  usleep(1000);
-  if (gpio_set(ns->board->reset_gpio, 1) < 0) {
-    ese_set_error(ese, 13);
-    return -1;
-  }
-  return 0;
-}
-
-size_t nxp_pn80t_transmit(struct EseInterface *ese, const uint8_t *buf,
-                          size_t len, int complete) {
-  struct NxpState *ns = NXP_PN80T_SPIDEV_STATE(ese);
-  size_t recvd = len;
-  struct spi_ioc_transfer tr = {
-      .tx_buf = (unsigned long)buf,
-      .rx_buf = 0,
-      .len = (uint32_t)len,
-      .delay_usecs = 0,
-      .speed_hz = 0,
-      .bits_per_word = 0,
-      .cs_change = !!complete,
-  };
-  ALOGV("interface transmitting data");
-  if (len > UINT_MAX) {
-    ese_set_error(ese, 10);
-    ALOGE("Unexpectedly large transfer attempted: %zu", len);
-    return 0;
-  }
-
-  ALOGV("interface attempting to transmit data");
-  for (recvd = 0; recvd < len; ++recvd)
-    ALOGV("TX[%zu]: %.2X", recvd, buf[recvd]);
-  if (ioctl(ns->spi_fd, SPI_IOC_MESSAGE(1), &tr) < 1) {
-    ese_set_error(ese, 11);
-    return 0;
-  }
-  ALOGV("interface sent %zu bytes", len);
-  if (complete) {
-    ALOGV("interface sent a frame");
-    /* TODO(wad): Remove this once we have live testing. */
-    usleep(1000); /* t3 = 1ms */
-  }
-  return recvd;
-}
-
-int nxp_pn80t_poll(struct EseInterface *ese, uint8_t poll_for, float timeout,
-                   int complete) {
-  struct NxpState *es = NXP_PN80T_SPIDEV_STATE(ese);
-  const struct Teq1ProtocolOptions *opts = ese->ops->opts;
-  /* Attempt to read a 8-bit character once per 8-bit character transmission
-   * window (in seconds). */
-  int intervals = (int)(0.5f + timeout / (7.0f * opts->etu));
-  uint8_t byte = 0xff;
-  ALOGV("interface polling for start of frame/host node address: %x", poll_for);
-  /* If we weren't using spidev, we could just get notified by the driver. */
-  do {
-    struct spi_ioc_transfer tr = {
-        .tx_buf = 0,
-        .rx_buf = (unsigned long)&byte,
-        .len = 1,
-        .delay_usecs = 0,
-        .speed_hz = 0,
-        .bits_per_word = 0,
-        .cs_change = !!complete,
-    };
-    /*
-     * In practice, if complete=true, then no transmission
-     * should attempt again until after 1000usec.
-     */
-    if (ioctl(es->spi_fd, SPI_IOC_MESSAGE(1), &tr) < 1) {
-      ALOGV("spidev (fd:%d) failed to read one byte", es->spi_fd);
-      ese_set_error(ese, 3);
-      return -1;
-    }
-    if (byte == poll_for) {
-      ALOGV("Polled for byte seen: %x with %d intervals remaining.", poll_for,
-            intervals);
-      ALOGV("RX[0]: %.2X", byte);
-      return 1;
-    } else {
-      ALOGV("No match (saw %x)", byte);
-    }
-    usleep(7.0f * opts->etu * 1000000.0f); /* s -> us */
-    ALOGV("poll interval %d: no match.", intervals);
-  } while (intervals-- > 0);
-  return -1;
-}
-
-int nxp_pn80t_preprocess(const struct Teq1ProtocolOptions *const opts,
-                         struct Teq1Frame *frame, int tx) {
-  if (tx) {
-    /* Recompute the LRC with the NAD of 0x00 */
-    frame->header.NAD = 0x00;
-    frame->INF[frame->header.LEN] = teq1_compute_LRC(frame);
-    frame->header.NAD = opts->node_address;
-    ALOGV("interface is preprocessing outbound frame");
-  } else {
-    /* Replace the NAD with 0x00 so the LRC check passes. */
-    ALOGV("interface is preprocessing inbound frame (%x->%x)",
-          frame->header.NAD, 0x00);
-    if (frame->header.NAD != opts->host_address)
-      ALOGV("Rewriting from unknown NAD: %x", frame->header.NAD);
-    frame->header.NAD = 0x00;
-    ALOGV("Frame length: %x", frame->header.LEN);
-  }
-  return 0;
-}
-
-static const struct Teq1ProtocolOptions teq1_options = {
-    .host_address = 0xA5,
-    .node_address = 0x5A,
-    .bwt = 1.624f,   /* cwt by default would be ~8k * 1.05s */
-    .etu = 0.00105f, /* seconds */
-    .preprocess = &nxp_pn80t_preprocess,
-};
-
-static const struct EseOperations ops = {
-    .name = "NXP PN80T (PN553)",
-    .open = &nxp_pn80t_open,
-    .hw_receive = &nxp_pn80t_receive,
-    .hw_transmit = &nxp_pn80t_transmit,
-    .hw_reset = &nxp_pn80t_reset,
-    .transceive = &teq1_transceive,
-    .poll = &nxp_pn80t_poll,
-    .close = &nxp_pn80t_close,
-    .opts = &teq1_options,
-};
-ESE_DEFINE_HW_OPS(ESE_HW_NXP_PN80T_SPIDEV, ops);
-
-static const char *kErrorMessages[] = {
-    "T=1 hard failure.",                         /* TEQ1_ERROR_HARD_FAIL */
-    "T=1 abort.",                                /* TEQ1_ERROR_ABORT */
-    "T=1 device reset failed.",                  /* TEQ1_ERROR_DEVICE_ABORT */
-    "spidev failed to read one byte",            /* 3 */
-    "unable to open spidev device",              /* 4 */
-    "unable to set spidev mode",                 /* 5 */
-    "unable to set spidev bits per word",        /* 6 */
-    "unable to set spidev max speed in hz",      /* 7 */
-    "spidev failed to read",                     /* 8 */
-    "attempted to receive more than uint_max",   /* 9 */
-    "attempted to transfer more than uint_max",  /* 10 */
-    "spidev failed to transmit",                 /* 11 */
-    "unable to configure ESE_RST gpio",          /* 12 */
-    "unable to configure ESE_SVDD_PWR_REQ gpio", /* 13 */
-    "unable to toggle ESE_SVDD_PWR_REQ",         /* 14 */
-};
-ESE_DEFINE_HW_ERRORS(ESE_HW_NXP_PN80T_SPIDEV, kErrorMessages);
diff --git a/libese-hw/tests/Android.bp b/libese-hw/tests/Android.bp
index 1ce0b6b..75aaf21 100644
--- a/libese-hw/tests/Android.bp
+++ b/libese-hw/tests/Android.bp
@@ -14,30 +14,14 @@
 // limitations under the License.
 //
 
-cc_defaults {
-    name: "libese_hw_tests_default",
-
-    multilib: {
-        lib32: {
-            suffix: "32",
-        },
-        lib64: {
-            suffix: "64",
-        },
-    },
-}
-
-test_libraries = [
-    "libese",
-    "libese-teq1",
-    "libese-hw-echo",
-    "liblog",
-]
-
 cc_test {
     name: "ese_hw_tests",
-    defaults: ["libese_tests_default"],
     srcs: ["ese_hw_echo_tests.cpp"],
     host_supported: true,
-    shared_libs: test_libraries,
+    shared_libs: [
+        "libese",
+        "libese-teq1",
+        "libese-hw-echo",
+        "liblog",
+    ],
 }
diff --git a/libese-hw/tests/ese_hw_echo_tests.cpp b/libese-hw/tests/ese_hw_echo_tests.cpp
index 4c62822..793f506 100644
--- a/libese-hw/tests/ese_hw_echo_tests.cpp
+++ b/libese-hw/tests/ese_hw_echo_tests.cpp
@@ -34,7 +34,7 @@
     ASSERT_EQ(0, ese_open(&ese_, NULL));
   }
   virtual void TearDown() {
-    ASSERT_EQ(0, ese_close(&ese_));
+    ese_close(&ese_);
   }
   struct EseInterface ese_;
 };
diff --git a/libese-sysdeps/Android.bp b/libese-sysdeps/Android.bp
new file mode 100644
index 0000000..b9b412a
--- /dev/null
+++ b/libese-sysdeps/Android.bp
@@ -0,0 +1,44 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_library {
+    name: "libese-sysdeps",
+    host_supported: true,
+    srcs: [],
+    local_include_dirs: [
+        "include",
+    ],
+    cflags: ["-std=c99"],
+    debug:  {
+        cflags: ["-DLOG_NDEBUG=0"],
+    },
+    target: {
+        android: {
+            srcs: ["android-sysdeps.c"],
+        },
+
+        darwin: {
+            srcs: ["darwin-sysdeps.c"],
+        },
+
+        linux: {
+            srcs: ["android-sysdeps.c"],
+        },
+    },
+
+    shared_libs: [],
+    export_include_dirs: ["include"],
+}
diff --git a/libese-sysdeps/android-sysdeps.c b/libese-sysdeps/android-sysdeps.c
new file mode 100644
index 0000000..5cfb447
--- /dev/null
+++ b/libese-sysdeps/android-sysdeps.c
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+#include <string.h>
+
+void *ese_memcpy(void *__dest, const void *__src, uint64_t __n) {
+  return memcpy(__dest, __src, __n);
+}
+
+void *ese_memset(void *__s, int __c, uint64_t __n) {
+  return memset(__s, __c, __n);
+}
diff --git a/libese-sysdeps/darwin-sysdeps.c b/libese-sysdeps/darwin-sysdeps.c
new file mode 100644
index 0000000..5cfb447
--- /dev/null
+++ b/libese-sysdeps/darwin-sysdeps.c
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+#include <string.h>
+
+void *ese_memcpy(void *__dest, const void *__src, uint64_t __n) {
+  return memcpy(__dest, __src, __n);
+}
+
+void *ese_memset(void *__s, int __c, uint64_t __n) {
+  return memset(__s, __c, __n);
+}
diff --git a/libese-sysdeps/include/ese/sysdeps.h b/libese-sysdeps/include/ese/sysdeps.h
new file mode 100644
index 0000000..94ebfe5
--- /dev/null
+++ b/libese-sysdeps/include/ese/sysdeps.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef ESE_SYSDEPS_H__
+#define ESE_SYSDEPS_H__ 1
+
+#include <stdbool.h> /* bool */
+#include <stdint.h> /* uint*_t */
+
+#ifndef NULL
+#define NULL ((void *)(0))
+#endif
+
+extern void *ese_memcpy(void *__dest, const void *__src, uint64_t __n);
+extern void *ese_memset(void *__s, int __c, uint64_t __n);
+
+#endif  /* ESE_SYSDEPS_H__ */
diff --git a/libese-teq1/Android.bp b/libese-teq1/Android.bp
index 86b9a41..76454f4 100644
--- a/libese-teq1/Android.bp
+++ b/libese-teq1/Android.bp
@@ -23,24 +23,24 @@
     local_include_dirs: ["include"],
 
     // Ensure that only explicitly exported symbols are visible.
-    cflags: ["-fvisibility=internal"],
+    cflags: ["-fvisibility=internal", "-std=c99"],
     debug: {
-      cflags: ["-DLOG_NDEBUG=0"],
+        cflags: ["-DLOG_NDEBUG=0"],
     },
-    shared_libs: ["liblog", "libese"],
+    shared_libs: ["liblog", "libese", "libese-sysdeps"],
     export_include_dirs: ["include"],
 }
 
 cc_library {
-  name: "libese-teq1-private",
-  host_supported: true,
+    name: "libese-teq1-private",
+    host_supported: true,
 
-  srcs: ["teq1.c"],
-  local_include_dirs: ["include"],
+    srcs: ["teq1.c"],
+    local_include_dirs: ["include"],
 
-  // Ensure that only explicitly exported symbols are visible.
-  shared_libs: ["liblog", "libese"],
-  export_include_dirs: ["include", "."],
+    // Ensure that only explicitly exported symbols are visible.
+    shared_libs: ["liblog", "libese", "libese-sysdeps"],
+    export_include_dirs: ["include", "."],
 }
 
 subdirs = ["tests"]
diff --git a/libese-teq1/include/ese/teq1.h b/libese-teq1/include/ese/teq1.h
index 61831a5..8a3fa2e 100644
--- a/libese-teq1/include/ese/teq1.h
+++ b/libese-teq1/include/ese/teq1.h
@@ -21,20 +21,22 @@
 extern "C" {
 #endif
 
-#include <ese/ese.h>
+#include "../../../libese/include/ese/ese.h"
+#include "../../../libese/include/ese/bit_spec.h"
 
-/* Reserved codes for T=1 devices. */
-#define TEQ1_ERROR_HARD_FAIL 0
-#define TEQ1_ERROR_ABORT 1
-#define TEQ1_ERROR_DEVICE_RESET 2
-
+/* Reserved codes for T=1 devices in EseOperation­>errors. */
+enum Teq1Error {
+ kTeq1ErrorHardFail = 0,
+ kTeq1ErrorAbort,
+ kTeq1ErrorDeviceReset,
+ kTeq1ErrorMax,
+};
 
 enum pcb_type {
   kPcbTypeInfo0 = 0x0,
   kPcbTypeInfo1 = 0x1,
   kPcbTypeReceiveReady = 0x2,
   kPcbTypeSupervisory = 0x3,
-  kPcbTypeMax,
 };
 
 enum super_type {
@@ -44,54 +46,52 @@
   kSuperTypeWTX = 0x3,
 };
 
-struct PCB {
-  union {
-    /* Info bits */
-    struct {
-      uint8_t reserved:5;  /* Should be 0. */
-      uint8_t more_data:1;
-      uint8_t send_seq:1;
-      uint8_t bit8:1;  /* Must be 0 for I-blocks. */
-    } I;  /* Information Block */
-    /* receive bits */
-    struct {
-      uint8_t parity_err:1;  /* char parity or redundancy code err */
-      uint8_t other_err:1;  /*  any other errors */
-      uint8_t unused_1:2;
-      uint8_t next_seq:1;  /* If the same seq as last frame, then err even if other bits are 0. */
-      uint8_t unused_0:1;
-      uint8_t pcb_type:2;  /* Always (1, 0)=2 for R */
-    } R;  /* Receive ready block */
-    struct {
-      uint8_t type:2;
-      uint8_t unused_0:3;
-      uint8_t response:1;
-      uint8_t pcb_type:2;
-    } S;  /* Supervisory block */
-    struct {
-      uint8_t data:6;
-      uint8_t type:2; /* I = 0|1, R = 2, S = 3 */
-    };  /* Bit7-8 access for block type access. */
-    /* Bitwise access */
-    struct {
-     uint8_t bit0:1;  /* lsb */
-     uint8_t bit1:1;
-     uint8_t bit2:1;
-     uint8_t bit3:1;
-     uint8_t bit4:1;
-     uint8_t bit5:1;
-     uint8_t bit6:1;
-     uint8_t bit7:1; /* msb */
-    } bits;
-    uint8_t val;
-  };
+struct PcbSpec {
+  struct bit_spec type;
+  struct bit_spec data;
+  struct {
+    struct bit_spec more_data;
+    struct bit_spec send_seq;
+  } I;
+  struct {
+    struct bit_spec parity_err;
+    struct bit_spec other_err;
+    struct bit_spec next_seq;
+  } R;
+  struct {
+    struct bit_spec type;
+    struct bit_spec response;
+  } S;
+};
+
+const static struct PcbSpec PCB = {
+  .type = { .value = 3, .shift = 6, },
+  .data = { .value = 63, .shift = 0, },
+  .I = {
+    .more_data = { .value = 1, .shift = 5, },
+    .send_seq = { .value = 1, .shift = 6, },
+  },
+  .R = {
+    /* char parity or redundancy code err */
+    .parity_err = { .value = 1, .shift = 0, },
+    /* any other errors */
+    .other_err = { .value = 1, .shift = 1, },
+    /* If the same seq as last frame, then err even if other bits are 0. */
+    .next_seq = { .value = 1, .shift = 4, },
+  },
+  .S = {
+    .type = { .value = 3, .shift = 0, },
+    .response = { .value = 1, .shift = 5, },
+  },
 };
 
 struct Teq1Header {
   uint8_t NAD;
-  struct PCB PCB;
+  uint8_t PCB;
   uint8_t LEN;
-} __attribute__((packed));
+};
+#define TEQ1HEADER_SIZE 3
+#define TEQ1FRAME_SIZE INF_LEN + 1 + TEQ1HEADER_SIZE
 
 #define INF_LEN 254
 #define IFSC 254
@@ -103,10 +103,10 @@
       union {
         uint8_t INF[INF_LEN + 1]; /* Up to 254 with trailing LRC byte. */
       };
-      /* uint8_t LRC;  If CRC was supported, it would be uint16_t. */
+      /* If CRC was supported, it would be uint16_t. */
     };
   };
-} __attribute__((packed));
+};
 
 
 /*
@@ -122,6 +122,7 @@
     uint8_t seq_bits;
   } seq;
 };
+
 /* Set "last sent" to 1 so we start at 0. */
 #define TEQ1_INIT_CARD_STATE(CARD) \
   (CARD)->seq.card = 1; \
@@ -146,7 +147,11 @@
   teq1_protocol_preprocess_op_t *preprocess;
 };
 
+/* PCB bits */
+#define kTeq1PcbType (3 << 6)
+
 /* I-block bits */
+#define kTeq1InfoType        (0 << 6)
 #define kTeq1InfoMoreBit     (1 << 5)
 #define kTeq1InfoSeqBit      (1 << 6)
 
@@ -176,9 +181,10 @@
 #define TEQ1_S_ABORT(R) (kTeq1SuperType | ((R) << 5) | kTeq1SuperAbortBit)
 #define TEQ1_S_IFS(R) (kTeq1SuperType | ((R) << 5) | kTeq1SuperIfsBit)
 
-size_t teq1_transceive(struct EseInterface *ese,
-                       const uint8_t *const tx_buf, size_t tx_len,
-                       uint8_t *rx_buf, size_t rx_max);
+uint32_t teq1_transceive(struct EseInterface *ese,
+                         const struct Teq1ProtocolOptions *opts,
+                         const uint8_t *const tx_buf, uint32_t tx_len,
+                         uint8_t *rx_buf, uint32_t rx_max);
 
 uint8_t teq1_compute_LRC(const struct Teq1Frame *frame);
 
diff --git a/libese-teq1/teq1.c b/libese-teq1/teq1.c
index 9d0b2a5..40d2b1d 100644
--- a/libese-teq1/teq1.c
+++ b/libese-teq1/teq1.c
@@ -14,13 +14,9 @@
  * limitations under the License.
  */
 
-#include <string.h>
-
-#include <ese/ese.h>
-#include <ese/teq1.h>
-
-#define LOG_TAG "ESE_T=1"
-#include <ese/log.h>
+#include "include/ese/teq1.h"
+#include "../libese/include/ese/ese.h"
+#include "../libese/include/ese/log.h"
 
 #include "teq1_private.h"
 
@@ -94,10 +90,15 @@
   }
 }
 
-int teq1_transmit(struct EseInterface *ese, struct Teq1Frame *frame) {
-  /* Set during ese_open() by teq1_init. */
-  const struct Teq1ProtocolOptions *opts = ese->ops->opts;
+void teq1_dump_buf(const char *prefix, const uint8_t *buf, uint32_t len) {
+  uint32_t recvd = 0;
+  for (recvd = 0; recvd < len; ++recvd)
+    ALOGV("%s[%u]: %.2X", prefix, recvd, buf[recvd]);
+}
 
+int teq1_transmit(struct EseInterface *ese,
+                  const struct Teq1ProtocolOptions *opts,
+                  struct Teq1Frame *frame) {
   /* Set correct node address. */
   frame->header.NAD = opts->node_address;
 
@@ -106,40 +107,48 @@
 
   /*
    * If the card does something weird, like expect an CRC/LRC based on a
-   * different
-   * header value, the preprocessing can handle it.
+   * different header value, the preprocessing can handle it.
    */
-  if (opts->preprocess)
+  if (opts->preprocess) {
     opts->preprocess(opts, frame, 1);
+  }
 
   /*
    * Begins transmission and ignore errors.
    * Failed transmissions will result eventually in a resync then reset.
    */
-  teq1_trace_transmit(frame->header.PCB.val, frame->header.LEN);
+  teq1_trace_transmit(frame->header.PCB, frame->header.LEN);
+  teq1_dump_transmit(frame->val, sizeof(frame->header) + frame->header.LEN + 1);
   ese->ops->hw_transmit(ese, frame->val,
                         sizeof(frame->header) + frame->header.LEN + 1, 1);
-  /* Even though in practice any WTX BWT extension starts when the above
-   * transmit ends.
-   * it is easier to implement it in the polling timeout of receive.
+  /*
+   * Even though in practice any WTX BWT extension starts when the above
+   * transmit ends, it is easier to implement it in the polling timeout of
+   * receive.
    */
   return 0;
 }
 
-int teq1_receive(struct EseInterface *ese, float timeout,
+int teq1_receive(struct EseInterface *ese,
+                 const struct Teq1ProtocolOptions *opts, float timeout,
                  struct Teq1Frame *frame) {
-  const struct Teq1ProtocolOptions *opts = ese->ops->opts;
   /* Poll the bus until we see the start of frame indicator, the interface NAD.
    */
-  if (ese->ops->poll(ese, opts->host_address, timeout, 0) < 0) {
+  int bytes_consumed = ese->ops->poll(ese, opts->host_address, timeout, 0);
+  if (bytes_consumed < 0 || bytes_consumed > 1) {
     /* Timed out or comm error. */
+    ALOGV("%s: comm error: %d", __func__, bytes_consumed);
     return -1;
   }
-  /* We polled for the NAD above. */
-  frame->header.NAD = opts->host_address;
+  /* We polled for the NAD above -- if it was consumed, set it here. */
+  if (bytes_consumed) {
+    frame->header.NAD = opts->host_address;
+  }
   /* Get the remainder of the header, but keep the line &open. */
-  ese->ops->hw_receive(ese, (uint8_t *)(&frame->header.PCB),
-                       sizeof(frame->header) - 1, 0);
+  ese->ops->hw_receive(ese, (uint8_t *)(&frame->header.NAD + bytes_consumed),
+                       sizeof(frame->header) - bytes_consumed, 0);
+  teq1_dump_receive((uint8_t *)(&frame->header.NAD + bytes_consumed),
+                    sizeof(frame->header) - bytes_consumed);
   if (frame->header.LEN == 255) {
     ALOGV("received invalid LEN of 255");
     /* Close the receive window and return failure. */
@@ -152,15 +161,17 @@
    */
   ese->ops->hw_receive(ese, (uint8_t *)(&(frame->INF[0])),
                        frame->header.LEN + 1, 1);
-  teq1_trace_receive(frame->header.PCB.val, frame->header.LEN);
+  teq1_dump_receive((uint8_t *)(&(frame->INF[0])), frame->header.LEN + 1);
+  teq1_trace_receive(frame->header.PCB, frame->header.LEN);
 
   /*
    * If the card does something weird, like expect an CRC/LRC based on a
    * different
    * header value, the preprocessing should fix up here prior to the LRC check.
    */
-  if (opts->preprocess)
+  if (opts->preprocess) {
     opts->preprocess(opts, frame, 0);
+  }
 
   /* LRC and other protocol goodness checks are not done here. */
   return frame->header.LEN; /* Return data bytes read. */
@@ -168,15 +179,17 @@
 
 uint8_t teq1_fill_info_block(struct Teq1State *state, struct Teq1Frame *frame) {
   uint32_t inf_len = INF_LEN;
-  if (state->ifs < inf_len)
+  if (state->ifs < inf_len) {
     inf_len = state->ifs;
-  switch (frame->header.PCB.type) {
+  }
+  switch (bs_get(PCB.type, frame->header.PCB)) {
   case kPcbTypeInfo0:
   case kPcbTypeInfo1: {
     uint32_t len = state->app_data.tx_len;
-    if (len > inf_len)
+    if (len > inf_len) {
       len = inf_len;
-    memcpy(frame->INF, state->app_data.tx_buf, len);
+    }
+    ese_memcpy(frame->INF, state->app_data.tx_buf, len);
     frame->header.LEN = (len & 0xff);
     ALOGV("Copying %x bytes of app data for transmission", frame->header.LEN);
     /* Incrementing here means the caller MUST handle retransmit with prepared
@@ -184,9 +197,10 @@
     state->app_data.tx_len -= len;
     state->app_data.tx_buf += len;
     /* Perform chained transmission if needed. */
-    frame->header.PCB.I.more_data = 0;
-    if (state->app_data.tx_len > 0)
-      frame->header.PCB.I.more_data = 1;
+    bs_assign(&frame->header.PCB, PCB.I.more_data, 0);
+    if (state->app_data.tx_len > 0) {
+      frame->header.PCB |= bs_mask(PCB.I.more_data, 1);
+    }
     return len;
   }
   case kPcbTypeSupervisory:
@@ -198,14 +212,15 @@
 }
 
 void teq1_get_app_data(struct Teq1State *state, struct Teq1Frame *frame) {
-  switch (frame->header.PCB.type) {
+  switch (bs_get(PCB.type, frame->header.PCB)) {
   case kPcbTypeInfo0:
   case kPcbTypeInfo1: {
     uint8_t len = frame->header.LEN;
     /* TODO(wad): Some data will be left on the table. Should this error out? */
-    if (len > state->app_data.rx_len)
+    if (len > state->app_data.rx_len) {
       len = state->app_data.rx_len;
-    memcpy(state->app_data.rx_buf, frame->INF, len);
+    }
+    ese_memcpy(state->app_data.rx_buf, frame->INF, len);
     /* The original caller must retain the starting pointer to determine
      * actual available data.
      */
@@ -227,8 +242,9 @@
                                struct Teq1Frame *rx_frame) {
   uint8_t lrc = 0;
   int chained = 0;
-  if (rx_frame->header.PCB.val == 255)
+  if (rx_frame->header.PCB == 255) {
     return R(0, 1, 0); /* Other error */
+  }
 
   lrc = teq1_compute_LRC(rx_frame);
   if (rx_frame->INF[rx_frame->header.LEN] != lrc) {
@@ -238,46 +254,42 @@
   }
 
   /* Check if we were chained and increment the last sent sequence. */
-  switch (tx_frame->header.PCB.type) {
+  switch (bs_get(PCB.type, tx_frame->header.PCB)) {
   case kPcbTypeInfo0:
   case kPcbTypeInfo1:
-    chained = tx_frame->header.PCB.I.more_data;
-    state->card_state->seq.interface = tx_frame->header.PCB.I.send_seq;
+    chained = bs_get(PCB.I.more_data, tx_frame->header.PCB);
+    state->card_state->seq.interface =
+        bs_get(PCB.I.send_seq, tx_frame->header.PCB);
   }
 
   /* Check if we've gone down an easy to catch error hole. The rest will turn up
    * on the
    * txrx switch.
    */
-  switch (rx_frame->header.PCB.type) {
+  switch (bs_get(PCB.type, rx_frame->header.PCB)) {
   case kPcbTypeSupervisory:
-    if (rx_frame->header.PCB.val != S(RESYNC, RESPONSE) &&
-        rx_frame->header.LEN != 1)
+    if (rx_frame->header.PCB != S(RESYNC, RESPONSE) &&
+        rx_frame->header.LEN != 1) {
       return R(0, 1, 0);
+    }
     break;
   case kPcbTypeReceiveReady:
-    if (rx_frame->header.LEN != 0)
+    if (rx_frame->header.LEN != 0) {
       return R(0, 1, 0);
-#if 0
-/* Handled explicitly. */
-    /* R() blocks are always an error if we're not chaining. */
-    if (!chained)
-      return R(0, 1, 0);
-    /* If we are chaining, the R must be the next seq. */
-    if (rx_frame->header.PCB.R.next_seq == state->card_state->seq.interface)
-      return R(0, 1, 0);
-#endif
+    }
     break;
   case kPcbTypeInfo0:
   case kPcbTypeInfo1:
     /* I-blocks must always alternate for each endpoint. */
-    if (rx_frame->header.PCB.I.send_seq == state->card_state->seq.card) {
-      ALOGV("Got seq %d expected %d", rx_frame->header.PCB.I.send_seq,
+    if ((bs_get(PCB.I.send_seq, rx_frame->header.PCB)) ==
+        state->card_state->seq.card) {
+      ALOGV("Got seq %d expected %d",
+            bs_get(PCB.I.send_seq, rx_frame->header.PCB),
             state->card_state->seq.card);
       return R(0, 1, 0);
     }
     /* Update the card's last I-block seq. */
-    state->card_state->seq.card = rx_frame->header.PCB.I.send_seq;
+    state->card_state->seq.card = bs_get(PCB.I.send_seq, rx_frame->header.PCB);
   default:
     break;
   };
@@ -291,25 +303,25 @@
   /* 0 = TX, 1 = RX */
   /* msb = tx pcb, lsb = rx pcb */
   /* BUG_ON(!rx_frame && !tx_frame && !next_tx); */
-  uint16_t txrx = TEQ1_RULE(tx_frame->header.PCB.val, rx_frame->header.PCB.val);
-  struct PCB R_err;
+  uint16_t txrx = TEQ1_RULE(tx_frame->header.PCB, rx_frame->header.PCB);
+  uint8_t R_err;
 
   while (1) {
     /* Timeout errors come like invalid frames: 255. */
-    if ((R_err.val = teq1_frame_error_check(state, tx_frame, rx_frame)) != 0) {
+    if ((R_err = teq1_frame_error_check(state, tx_frame, rx_frame)) != 0) {
       ALOGV("incoming frame failed the error check");
       state->last_error_message = "Invalid frame received";
       /* Mark the frame as bad for our rule evaluation. */
-      txrx = TEQ1_RULE(tx_frame->header.PCB.val, 255);
+      txrx = TEQ1_RULE(tx_frame->header.PCB, 255);
       state->errors++;
       /* Rule 6.4 */
-      if (state->errors >= 6) { /* XXX: Review error count lifetime. */
+      if (state->errors >= 6) {
         return kRuleResultResetDevice;
       }
       /* Rule 7.4.2 */
       if (state->errors >= 3) {
         /* Rule 7.4.1: state should start with error count = 2 */
-        next_tx->header.PCB.val = S(RESYNC, REQUEST);
+        next_tx->header.PCB = S(RESYNC, REQUEST);
         /* Resync result in a fresh session, so we should just continue here. */
         return kRuleResultContinue;
       }
@@ -327,25 +339,22 @@
       teq1_get_app_data(state, rx_frame);
       return kRuleResultComplete;
 
-      /* Read app data & return. */
-      teq1_get_app_data(state, rx_frame);
-      return kRuleResultComplete;
-
     /* Card begins chained response. */
     case TEQ1_RULE(I(0, 0), I(0, 1)):
     case TEQ1_RULE(I(1, 0), I(1, 1)):
       /* Prep R(N(S)) */
       teq1_get_app_data(state, rx_frame);
-      next_tx->header.PCB.val = TEQ1_R(!rx_frame->header.PCB.I.send_seq, 0, 0);
+      next_tx->header.PCB =
+          TEQ1_R(!bs_get(PCB.I.send_seq, rx_frame->header.PCB), 0, 0);
       next_tx->header.LEN = 0;
       return kRuleResultContinue;
 
     /*** Rule 2.2, Rule 5: Chained transmission ***/
     case TEQ1_RULE(I(0, 1), R(1, 0, 0)):
     case TEQ1_RULE(I(1, 1), R(0, 0, 0)):
-      /* Send next block */
-      next_tx->header.PCB.val = I(0, 0);
-      next_tx->header.PCB.I.send_seq = rx_frame->header.PCB.R.next_seq;
+      /* Send next block -- error-checking assures the R seq is our next seq. */
+      next_tx->header.PCB =
+          TEQ1_I(bs_get(PCB.R.next_seq, rx_frame->header.PCB), 0);
       teq1_fill_info_block(state, next_tx); /* Sets M-bit and LEN. */
       return kRuleResultContinue;
 
@@ -358,7 +367,7 @@
       case TEQ1_RULE(I(1, 1), S(WTX, REQUEST)):
       */
       /* Send S(WTX, RESPONSE) with same INF */
-      next_tx->header.PCB.val = S(WTX, RESPONSE);
+      next_tx->header.PCB = S(WTX, RESPONSE);
       next_tx->header.LEN = 1;
       next_tx->INF[0] = rx_frame->INF[0];
       state->wait_mult = rx_frame->INF[0];
@@ -377,7 +386,7 @@
     /* Don't support a IFS_REQUEST if we sent an error R-block. */
     case TEQ1_RULE(R(0, 0, 0), S(IFS, REQUEST)):
     case TEQ1_RULE(R(1, 0, 0), S(IFS, REQUEST)):
-      next_tx->header.PCB.val = S(IFS, RESPONSE);
+      next_tx->header.PCB = S(IFS, RESPONSE);
       next_tx->header.LEN = 1;
       next_tx->INF[0] = rx_frame->INF[0];
       state->ifs = rx_frame->INF[0];
@@ -393,7 +402,9 @@
     case TEQ1_RULE(R(1, 0, 0), I(1, 1)):
       /* Chaining continued; consume partial data and send R(N(S)) */
       teq1_get_app_data(state, rx_frame);
-      next_tx->header.PCB.val = TEQ1_R(!rx_frame->header.PCB.I.send_seq, 0, 0);
+      /* The card seq bit will be tracked/validated earlier. */
+      next_tx->header.PCB =
+          TEQ1_R(!bs_get(PCB.I.send_seq, rx_frame->header.PCB), 0, 0);
       return kRuleResultContinue;
 
     /* Rule 6: Interface can send a RESYNC */
@@ -414,10 +425,11 @@
     case TEQ1_RULE(I(1, 0), 255):
     case TEQ1_RULE(I(0, 1), 255):
     case TEQ1_RULE(I(1, 1), 255):
-      next_tx->header.PCB.val = R_err.val;
-      next_tx->header.PCB.R.next_seq = tx_frame->header.PCB.I.send_seq;
+      next_tx->header.PCB = R_err;
+      bs_assign(&next_tx->header.PCB, PCB.R.next_seq,
+                bs_get(PCB.I.send_seq, tx_frame->header.PCB));
       ALOGV("Rule 7.1,7.5,7.6: bad rx - sending error R: %x = %s",
-            next_tx->header.PCB.val, teq1_pcb_to_name(next_tx->header.PCB.val));
+            next_tx->header.PCB, teq1_pcb_to_name(next_tx->header.PCB));
       return kRuleResultSingleShot; /* So we still can retransmit the original.
                                        */
 
@@ -430,10 +442,10 @@
     case TEQ1_RULE(I(1, 0), R(0, 0, 1)):
     case TEQ1_RULE(I(1, 0), R(0, 1, 0)):
     case TEQ1_RULE(I(1, 0), R(0, 1, 1)):
-      next_tx->header.PCB.val = R(0, 0, 0);
-      next_tx->header.PCB.R.next_seq = tx_frame->header.PCB.I.send_seq;
+      next_tx->header.PCB =
+          TEQ1_R(bs_get(PCB.I.send_seq, tx_frame->header.PCB), 0, 0);
       ALOGV("Rule 7.1,7.5,7.6: weird rx - sending error R: %x = %s",
-            next_tx->header.PCB.val, teq1_pcb_to_name(next_tx->header.PCB.val));
+            next_tx->header.PCB, teq1_pcb_to_name(next_tx->header.PCB));
       return kRuleResultSingleShot;
 
     /* Rule 7.2: Retransmit the _same_ R-block. */
@@ -499,7 +511,7 @@
     case TEQ1_RULE(R(1, 0, 1), S(ABORT, REQUEST)):
     case TEQ1_RULE(R(1, 1, 0), S(ABORT, REQUEST)):
     case TEQ1_RULE(R(1, 1, 1), S(ABORT, REQUEST)):
-      next_tx->header.PCB.val = S(ABORT, REQUEST);
+      next_tx->header.PCB = S(ABORT, REQUEST);
       return kRuleResultContinue; /* Takes over prior flow. */
     case TEQ1_RULE(S(ABORT, RESPONSE), 255):
       return kRuleResultRetransmit;
@@ -515,9 +527,9 @@
      * For supported flows: If an operation was paused to
      * send it, the caller may then switch to that state and resume.
      */
-    if (rx_frame->header.PCB.val != 255) {
+    if (rx_frame->header.PCB != 255) {
       ALOGV("Unexpected frame. Marking error and re-evaluating.");
-      rx_frame->header.PCB.val = 255;
+      rx_frame->header.PCB = 255;
       continue;
     }
 
@@ -531,68 +543,75 @@
  *   if testing becomes onerous given the loop below.
  */
 
-API size_t teq1_transceive(struct EseInterface *ese,
-                           const uint8_t *const tx_buf, size_t tx_len,
-                           uint8_t *rx_buf, size_t rx_len) {
+API uint32_t teq1_transceive(struct EseInterface *ese,
+                             const struct Teq1ProtocolOptions *opts,
+                             const uint8_t *const tx_buf, uint32_t tx_len,
+                             uint8_t *rx_buf, uint32_t rx_len) {
   struct Teq1Frame tx_frame[2];
   struct Teq1Frame rx_frame;
   struct Teq1Frame *tx = &tx_frame[0];
-  int was_reset = 0;
   int active = 0;
-  int done = 0;
+  bool was_reset = false;
+  bool done = false;
   enum RuleResult result = kRuleResultComplete;
-  const struct Teq1ProtocolOptions *opts = ese->ops->opts;
   struct Teq1CardState *card_state = (struct Teq1CardState *)(&ese->pad[0]);
   struct Teq1State init_state =
       TEQ1_INIT_STATE(tx_buf, tx_len, rx_buf, rx_len, card_state);
   struct Teq1State state =
       TEQ1_INIT_STATE(tx_buf, tx_len, rx_buf, rx_len, card_state);
 
+  _static_assert(TEQ1HEADER_SIZE == sizeof(struct Teq1Header),
+                 "Ensure compiler alignment/padding matches wire protocol.");
+  _static_assert(TEQ1FRAME_SIZE == sizeof(struct Teq1Frame),
+                 "Ensure compiler alignment/padding matches wire protocol.");
+
   /* First I-block is always I(0, M). After that, modulo 2. */
-  tx->header.PCB.val = TEQ1_I(!card_state->seq.interface, 0);
+  tx->header.PCB = TEQ1_I(!card_state->seq.interface, 0);
   teq1_fill_info_block(&state, tx);
 
   teq1_trace_header();
   while (!done) {
     /* Populates the node address and LRC prior to attempting to transmit. */
-    teq1_transmit(ese, tx);
+    teq1_transmit(ese, opts, tx);
 
     /* If tx was pointed to the inactive frame for a single shot, restore it
      * now. */
     tx = &tx_frame[active];
 
     /* Clear the RX frame. */
-    memset(&rx_frame, 0xff, sizeof(rx_frame));
+    ese_memset(&rx_frame, 0xff, sizeof(rx_frame));
 
     /* -1 indicates a timeout or failure from hardware. */
-    if (teq1_receive(ese, opts->bwt * (float)state.wait_mult, &rx_frame) < 0) {
+    if (teq1_receive(ese, opts, opts->bwt * (float)state.wait_mult, &rx_frame) <
+        0) {
       /* TODO(wad): If the ese_error(ese) == 1, should this go ahead and fail?
        */
       /* Failures are considered invalid blocks in the rule engine below. */
-      rx_frame.header.PCB.val = 255;
+      rx_frame.header.PCB = 255;
     }
     /* Always reset |wait_mult| once we have calculated the timeout. */
     state.wait_mult = 1;
 
     /* Clear the inactive frame header for use as |next_tx|. */
-    memset(&tx_frame[!active].header, 0, sizeof(tx_frame[!active].header));
+    ese_memset(&tx_frame[!active].header, 0, sizeof(tx_frame[!active].header));
 
     result = teq1_rules(&state, tx, &rx_frame, &tx_frame[!active]);
     ALOGV("[ %s ]", teq1_rule_result_to_name(result));
     switch (result) {
     case kRuleResultComplete:
-      done = 1;
+      done = true;
       break;
     case kRuleResultRetransmit:
       /* TODO(wad) Find a clean way to move into teq1_rules(). */
-      if (state.retransmits++ < 3)
+      if (state.retransmits++ < 3) {
         continue;
-      if (tx->header.PCB.val == S(RESYNC, REQUEST)) {
-        ese_set_error(ese, TEQ1_ERROR_HARD_FAIL);
+      }
+      if (tx->header.PCB == S(RESYNC, REQUEST)) {
+        ese_set_error(ese, kTeq1ErrorHardFail);
         return 0;
       }
       /* Fall through */
-      tx_frame[!active].header.PCB.val = S(RESYNC, REQUEST);
+      tx_frame[!active].header.PCB = S(RESYNC, REQUEST);
     case kRuleResultContinue:
       active = !active;
       tx = &tx_frame[active];
@@ -600,10 +619,10 @@
       state.errors = 0;
       continue;
     case kRuleResultHardFail:
-      ese_set_error(ese, TEQ1_ERROR_HARD_FAIL);
+      ese_set_error(ese, kTeq1ErrorHardFail);
       return 0;
     case kRuleResultAbort:
-      ese_set_error(ese, TEQ1_ERROR_ABORT);
+      ese_set_error(ese, kTeq1ErrorAbort);
       return 0;
     case kRuleResultSingleShot:
       /*
@@ -615,19 +634,19 @@
       continue;
     case kRuleResultResetDevice:
       if (was_reset || !ese->ops->hw_reset || ese->ops->hw_reset(ese) == -1) {
-        ese_set_error(ese, TEQ1_ERROR_DEVICE_RESET);
+        ese_set_error(ese, kTeq1ErrorDeviceReset);
         return 0; /* Don't keep resetting -- hard fail. */
       }
-      was_reset = 1;
+      was_reset = true;
     /* Fall through to session reset. */
     case kRuleResultResetSession:
       /* Roll back state and reset. */
       state = init_state;
       TEQ1_INIT_CARD_STATE(state.card_state);
       /* Reset the active frame. */
-      memset(tx, 0, sizeof(*tx));
+      ese_memset(tx, 0, sizeof(*tx));
       /* Load initial I-block. */
-      tx->header.PCB.val = I(0, 0);
+      tx->header.PCB = I(0, 0);
       teq1_fill_info_block(&state, tx);
       continue;
     }
diff --git a/libese-teq1/teq1_private.h b/libese-teq1/teq1_private.h
index 3151753..6bd3d3c 100644
--- a/libese-teq1/teq1_private.h
+++ b/libese-teq1/teq1_private.h
@@ -25,6 +25,11 @@
 #define API __attribute__ ((visibility("default")))
 #endif  /* API */
 
+/* Mimic C11 _Static_assert behavior for a C99 world. */
+#ifndef _static_assert
+#define _static_assert(what, why) { while (!(1 / (!!(what)))); }
+#endif
+
 /*
  * Enable T=1 format to reduce to case integers.
  * Ensure there are tests to map TEQ1_X() to the shorthand below.
@@ -66,9 +71,9 @@
   struct Teq1CardState *card_state;
   struct {
     uint8_t *tx_buf;
-    size_t tx_len;
+    uint32_t tx_len;
     uint8_t *rx_buf;
-    size_t rx_len;
+    uint32_t rx_len;
   } app_data;
 };
 
@@ -102,8 +107,13 @@
 
 const char *teq1_rule_result_to_name(enum RuleResult result);
 const char *teq1_pcb_to_name(uint8_t pcb);
-int teq1_transmit(struct EseInterface *ese, struct Teq1Frame *frame);
-int teq1_receive(struct EseInterface *ese, float timeout, struct Teq1Frame *frame);
+int teq1_transmit(struct EseInterface *ese,
+                  const struct Teq1ProtocolOptions *opts,
+                  struct Teq1Frame *frame);
+int teq1_receive(struct EseInterface *ese,
+                 const struct Teq1ProtocolOptions *opts,
+                 float timeout,
+                 struct Teq1Frame *frame);
 uint8_t teq1_fill_info_block(struct Teq1State *state, struct Teq1Frame *frame);
 void teq1_get_app_data(struct Teq1State *state, struct Teq1Frame *frame);
 uint8_t teq1_frame_error_check(struct Teq1State *state,
@@ -114,6 +124,8 @@
                            struct Teq1Frame *rx_frame,
                            struct Teq1Frame *next_tx);
 
+#define teq1_dump_transmit(_B, _L) teq1_dump_buf("TX", (_B), (_L))
+#define teq1_dump_receive(_B, _L) teq1_dump_buf("RX", (_B), (_L))
 
 #ifdef __cplusplus
 }  /* extern "C" */
diff --git a/libese-teq1/tests/Android.bp b/libese-teq1/tests/Android.bp
index 9cb2fb4..a54a0af 100644
--- a/libese-teq1/tests/Android.bp
+++ b/libese-teq1/tests/Android.bp
@@ -14,28 +14,13 @@
 // limitations under the License.
 //
 
-cc_defaults {
-    name: "libese_teq1_tests_default",
-    multilib: {
-        lib32: {
-            suffix: "32",
-        },
-        lib64: {
-            suffix: "64",
-        },
-    },
-}
-
-test_libraries = [
-    "libese",
-    "libese-teq1-private",
-    "liblog",
-]
-
 cc_test {
     name: "ese_teq1_unittests",
-    defaults: ["libese_teq1_tests_default"],
     srcs: ["teq1_unittests.cpp"],
     host_supported: true,
-    shared_libs: test_libraries,
+    shared_libs: [
+        "libese",
+        "libese-teq1-private",
+        "liblog",
+    ],
 }
diff --git a/libese-teq1/tests/teq1_unittests.cpp b/libese-teq1/tests/teq1_unittests.cpp
index 77118fa..aef5e0a 100644
--- a/libese-teq1/tests/teq1_unittests.cpp
+++ b/libese-teq1/tests/teq1_unittests.cpp
@@ -36,7 +36,6 @@
 // - Unittests of each function
 // - teq1_rules matches Annex A of ISO 7816-3
 
-
 // Tests teq1_frame_error_check to avoid testing every combo that
 // ends in 255 in the rule engine.
 class Teq1FrameErrorCheck : public virtual Test {
@@ -60,16 +59,16 @@
   /* The PCBs above are all valid for a sent unchained I block with advancing
    * sequence #s.
    */
-  tx_frame_.header.PCB.val = TEQ1_I(0, 0);
+  tx_frame_.header.PCB = TEQ1_I(0, 0);
   state_.card_state = &card_state_;
   state_.card_state->seq.card = 1;
   while (*pcb != 255) {
-    rx_frame_.header.PCB.val = *pcb;
+    rx_frame_.header.PCB = *pcb;
     rx_frame_.header.LEN = 2;
     rx_frame_.INF[0] = 'A';
     rx_frame_.INF[1] = 'B';
     rx_frame_.INF[2] = teq1_compute_LRC(&rx_frame_);
-    EXPECT_EQ(0, teq1_frame_error_check(&state_, &tx_frame_, &rx_frame_)) << teq1_pcb_to_name(rx_frame_.header.PCB.val);
+    EXPECT_EQ(0, teq1_frame_error_check(&state_, &tx_frame_, &rx_frame_)) << teq1_pcb_to_name(rx_frame_.header.PCB);
     rx_frame_.INF[2] = teq1_compute_LRC(&rx_frame_) - 1;
     // Reset so we check the LRC error instead of a wrong seq.
     state_.card_state->seq.card = !state_.card_state->seq.card;
@@ -94,8 +93,9 @@
     tx_data_(INF_LEN, 'A'),
     rx_data_(INF_LEN, 'B'),
     card_state_({ .seq = { .card = 1, .interface = 1, }, }),
-    state_(TEQ1_INIT_STATE(tx_data_.data(), tx_data_.size(),
-                           rx_data_.data(), rx_data_.size(), &card_state_)) {
+    state_(TEQ1_INIT_STATE(tx_data_.data(), static_cast<uint32_t>(tx_data_.size()),
+                           rx_data_.data(), static_cast<uint32_t>(rx_data_.size()),
+                           &card_state_)) {
     memset(&tx_frame_, 0, sizeof(struct Teq1Frame));
     memset(&tx_next_, 0, sizeof(struct Teq1Frame));
     memset(&rx_frame_, 0, sizeof(struct Teq1Frame));
@@ -122,12 +122,12 @@
 class Teq1CompleteTest : public Teq1ErrorFreeTest {
  public:
   virtual void SetUp() {
-    tx_frame_.header.PCB.val = TEQ1_I(0, 0);
+    tx_frame_.header.PCB = TEQ1_I(0, 0);
     teq1_fill_info_block(&state_, &tx_frame_);
     // Check that the tx_data was fully consumed.
     EXPECT_EQ(0UL, state_.app_data.tx_len);
 
-    rx_frame_.header.PCB.val = TEQ1_I(0, 0);
+    rx_frame_.header.PCB = TEQ1_I(0, 0);
     rx_frame_.header.LEN = INF_LEN;
     ASSERT_EQ(static_cast<unsigned long>(INF_LEN), tx_data_.size());  // Catch fixture changes.
     // Supply TX data and make sure it overwrites RX data on consumption.
@@ -137,15 +137,15 @@
 
   virtual void RunRules() {
     teq1_trace_header();
-    teq1_trace_transmit(tx_frame_.header.PCB.val, tx_frame_.header.LEN);
-    teq1_trace_receive(rx_frame_.header.PCB.val, rx_frame_.header.LEN);
+    teq1_trace_transmit(tx_frame_.header.PCB, tx_frame_.header.LEN);
+    teq1_trace_receive(rx_frame_.header.PCB, rx_frame_.header.LEN);
 
     enum RuleResult result = teq1_rules(&state_,  &tx_frame_, &rx_frame_, &tx_next_);
     EXPECT_EQ(0, state_.errors);
     EXPECT_EQ(NULL,  state_.last_error_message)
       << "Last error: " << state_.last_error_message;
-    EXPECT_EQ(0, tx_next_.header.PCB.val)
-      << "Actual next TX: " << teq1_pcb_to_name(tx_next_.header.PCB.val);
+    EXPECT_EQ(0, tx_next_.header.PCB)
+      << "Actual next TX: " << teq1_pcb_to_name(tx_next_.header.PCB);
     EXPECT_EQ(kRuleResultComplete, result)
      << "Actual result name: " << teq1_rule_result_to_name(result);
   }
@@ -171,8 +171,8 @@
 };
 
 TEST_F(Teq1CompleteTest, I10_I10_data) {
-  tx_frame_.header.PCB.val = TEQ1_I(1, 0);
-  rx_frame_.header.PCB.val = TEQ1_I(0, 0);
+  tx_frame_.header.PCB = TEQ1_I(1, 0);
+  rx_frame_.header.PCB = TEQ1_I(0, 0);
   rx_frame_.INF[INF_LEN] = teq1_compute_LRC(&rx_frame_);
   RunRules();
   // Ensure that the rx_frame data was copied out to rx_data.
@@ -184,28 +184,28 @@
 // Note, IFS is not tested as it is not supported on current hardware.
 
 TEST_F(Teq1ErrorFreeTest, I00_WTX0_WTX1_data) {
-  tx_frame_.header.PCB.val = TEQ1_I(0, 0);
+  tx_frame_.header.PCB = TEQ1_I(0, 0);
   teq1_fill_info_block(&state_, &tx_frame_);
   // Check that the tx_data was fully consumed.
   EXPECT_EQ(0UL, state_.app_data.tx_len);
 
-  rx_frame_.header.PCB.val = TEQ1_S_WTX(0);
+  rx_frame_.header.PCB = TEQ1_S_WTX(0);
   rx_frame_.header.LEN = 1;
   rx_frame_.INF[0] = 2; /* Wait x 2 */
   rx_frame_.INF[1] = teq1_compute_LRC(&rx_frame_);
 
   teq1_trace_header();
-  teq1_trace_transmit(tx_frame_.header.PCB.val, tx_frame_.header.LEN);
-  teq1_trace_receive(rx_frame_.header.PCB.val, rx_frame_.header.LEN);
+  teq1_trace_transmit(tx_frame_.header.PCB, tx_frame_.header.LEN);
+  teq1_trace_receive(rx_frame_.header.PCB, rx_frame_.header.LEN);
 
   enum RuleResult result = teq1_rules(&state_,  &tx_frame_, &rx_frame_, &tx_next_);
-  teq1_trace_transmit(tx_next_.header.PCB.val, tx_next_.header.LEN);
+  teq1_trace_transmit(tx_next_.header.PCB, tx_next_.header.LEN);
 
   EXPECT_EQ(0, state_.errors);
   EXPECT_EQ(NULL,  state_.last_error_message)
     << "Last error: " << state_.last_error_message;
-  EXPECT_EQ(TEQ1_S_WTX(1), tx_next_.header.PCB.val)
-    << "Actual next TX: " << teq1_pcb_to_name(tx_next_.header.PCB.val);
+  EXPECT_EQ(TEQ1_S_WTX(1), tx_next_.header.PCB)
+    << "Actual next TX: " << teq1_pcb_to_name(tx_next_.header.PCB);
   EXPECT_EQ(state_.wait_mult, 2);
   EXPECT_EQ(state_.wait_mult, rx_frame_.INF[0]);
   // Ensure the next call will use the original TX frame.
@@ -221,9 +221,9 @@
     state_.app_data.tx_buf = tx_data_.data();
     teq1_fill_info_block(&state_, &tx_frame_);
     // Ensure More bit was set.
-    EXPECT_EQ(1, tx_frame_.header.PCB.I.more_data);
+    EXPECT_EQ(1, bs_get(PCB.I.more_data, tx_frame_.header.PCB));
     // Check that the tx_data was fully consumed.
-    EXPECT_EQ(static_cast<size_t>(oversized_data_len_ - INF_LEN),
+    EXPECT_EQ(static_cast<uint32_t>(oversized_data_len_ - INF_LEN),
               state_.app_data.tx_len);
     // No one is checking the TX LRC since there is no card present.
 
@@ -231,11 +231,11 @@
     rx_frame_.INF[0] = teq1_compute_LRC(&rx_frame_);
 
     teq1_trace_header();
-    teq1_trace_transmit(tx_frame_.header.PCB.val, tx_frame_.header.LEN);
-    teq1_trace_receive(rx_frame_.header.PCB.val, rx_frame_.header.LEN);
+    teq1_trace_transmit(tx_frame_.header.PCB, tx_frame_.header.LEN);
+    teq1_trace_receive(rx_frame_.header.PCB, rx_frame_.header.LEN);
 
     enum RuleResult result = teq1_rules(&state_,  &tx_frame_, &rx_frame_, &tx_next_);
-    teq1_trace_transmit(tx_next_.header.PCB.val, tx_next_.header.LEN);
+    teq1_trace_transmit(tx_next_.header.PCB, tx_next_.header.LEN);
     EXPECT_EQ(0, state_.errors);
     EXPECT_EQ(NULL,  state_.last_error_message)
       << "Last error: " << state_.last_error_message;
@@ -243,7 +243,7 @@
       << "Actual result name: " << teq1_rule_result_to_name(result);
     // Check that the tx_buf was drained already for the next frame.
     // ...
-    EXPECT_EQ(static_cast<size_t>(oversized_data_len_ - (2 * INF_LEN)),
+    EXPECT_EQ(static_cast<uint32_t>(oversized_data_len_ - (2 * INF_LEN)),
               state_.app_data.tx_len);
     // Belt and suspenders: make sure no RX buf was used.
     EXPECT_EQ(rx_data_.size(), state_.app_data.rx_len);
@@ -253,29 +253,29 @@
 
 TEST_F(Teq1ErrorFreeChainingTest, I01_R1_I11_chaining) {
   oversized_data_len_ = INF_LEN * 3;
-  tx_frame_.header.PCB.val = TEQ1_I(0, 0);
-  rx_frame_.header.PCB.val = TEQ1_R(1, 0, 0);
+  tx_frame_.header.PCB = TEQ1_I(0, 0);
+  rx_frame_.header.PCB = TEQ1_R(1, 0, 0);
   RunRules();
-  EXPECT_EQ(TEQ1_I(1, 1), tx_next_.header.PCB.val)
-    << "Actual next TX: " << teq1_pcb_to_name(tx_next_.header.PCB.val);
+  EXPECT_EQ(TEQ1_I(1, 1), tx_next_.header.PCB)
+    << "Actual next TX: " << teq1_pcb_to_name(tx_next_.header.PCB);
 };
 
 TEST_F(Teq1ErrorFreeChainingTest, I11_R0_I01_chaining) {
   oversized_data_len_ = INF_LEN * 3;
-  tx_frame_.header.PCB.val = TEQ1_I(1, 0);
-  rx_frame_.header.PCB.val = TEQ1_R(0, 0, 0);
+  tx_frame_.header.PCB = TEQ1_I(1, 0);
+  rx_frame_.header.PCB = TEQ1_R(0, 0, 0);
   RunRules();
-  EXPECT_EQ(TEQ1_I(0, 1), tx_next_.header.PCB.val)
-    << "Actual next TX: " << teq1_pcb_to_name(tx_next_.header.PCB.val);
+  EXPECT_EQ(TEQ1_I(0, 1), tx_next_.header.PCB)
+    << "Actual next TX: " << teq1_pcb_to_name(tx_next_.header.PCB);
 };
 
 TEST_F(Teq1ErrorFreeChainingTest, I11_R0_I00_chaining) {
   oversized_data_len_ = INF_LEN * 2;  // Exactly 2 frames worth.
-  tx_frame_.header.PCB.val = TEQ1_I(1, 0);
-  rx_frame_.header.PCB.val = TEQ1_R(0, 0, 0);
+  tx_frame_.header.PCB = TEQ1_I(1, 0);
+  rx_frame_.header.PCB = TEQ1_R(0, 0, 0);
   RunRules();
-  EXPECT_EQ(TEQ1_I(0, 0), tx_next_.header.PCB.val)
-    << "Actual next TX: " << teq1_pcb_to_name(tx_next_.header.PCB.val);
+  EXPECT_EQ(TEQ1_I(0, 0), tx_next_.header.PCB)
+    << "Actual next TX: " << teq1_pcb_to_name(tx_next_.header.PCB);
 };
 
 //
@@ -290,19 +290,19 @@
     state_.app_data.rx_len = 0;
     state_.app_data.tx_len = 0;
 
-    tx_frame_.header.PCB.val = TEQ1_I(0, 0);
+    tx_frame_.header.PCB = TEQ1_I(0, 0);
     teq1_fill_info_block(&state_, &tx_frame_);
     // No one is checking the TX LRC since there is no card present.
 
     // Assume the card may not even set the error bit.
     rx_frame_.header.LEN = 0;
-    rx_frame_.header.PCB.val = TEQ1_R(0, 0, 0);
+    rx_frame_.header.PCB = TEQ1_R(0, 0, 0);
     rx_frame_.INF[0] = teq1_compute_LRC(&rx_frame_);
   }
   virtual void TearDown() {
     teq1_trace_header();
-    teq1_trace_transmit(tx_frame_.header.PCB.val, tx_frame_.header.LEN);
-    teq1_trace_receive(rx_frame_.header.PCB.val, rx_frame_.header.LEN);
+    teq1_trace_transmit(tx_frame_.header.PCB, tx_frame_.header.LEN);
+    teq1_trace_receive(rx_frame_.header.PCB, rx_frame_.header.LEN);
 
     enum RuleResult result = teq1_rules(&state_,  &tx_frame_, &rx_frame_, &tx_next_);
     // Not counted as an error as it was on the card-side.
@@ -315,22 +315,22 @@
 };
 
 TEST_F(Teq1Retransmit, I00_R000_I00) {
-  rx_frame_.header.PCB.val = TEQ1_R(0, 0, 0);
+  rx_frame_.header.PCB = TEQ1_R(0, 0, 0);
   rx_frame_.INF[0] = teq1_compute_LRC(&rx_frame_);
 };
 
 TEST_F(Teq1Retransmit, I00_R001_I00) {
-  rx_frame_.header.PCB.val = TEQ1_R(0, 0, 1);
+  rx_frame_.header.PCB = TEQ1_R(0, 0, 1);
   rx_frame_.INF[0] = teq1_compute_LRC(&rx_frame_);
 };
 
 TEST_F(Teq1Retransmit, I00_R010_I00) {
-  rx_frame_.header.PCB.val = TEQ1_R(0, 1, 0);
+  rx_frame_.header.PCB = TEQ1_R(0, 1, 0);
   rx_frame_.INF[0] = teq1_compute_LRC(&rx_frame_);
 };
 
 TEST_F(Teq1Retransmit, I00_R011_I00) {
-  rx_frame_.header.PCB.val = TEQ1_R(0, 1, 1);
+  rx_frame_.header.PCB = TEQ1_R(0, 1, 1);
   rx_frame_.INF[0] = teq1_compute_LRC(&rx_frame_);
 }
 
@@ -339,25 +339,25 @@
   state_.app_data.rx_len = 0;
   state_.app_data.tx_len = 0;
 
-  tx_frame_.header.PCB.val = TEQ1_I(0, 0);
+  tx_frame_.header.PCB = TEQ1_I(0, 0);
   teq1_fill_info_block(&state_, &tx_frame_);
   // No one is checking the TX LRC since there is no card present.
 
-  rx_frame_.header.PCB.val = TEQ1_I(0, 0);
+  rx_frame_.header.PCB = TEQ1_I(0, 0);
   rx_frame_.header.LEN = 0;
   rx_frame_.INF[0] = teq1_compute_LRC(&rx_frame_) - 1;
 
   teq1_trace_header();
-  teq1_trace_transmit(tx_frame_.header.PCB.val, tx_frame_.header.LEN);
-  teq1_trace_receive(rx_frame_.header.PCB.val, rx_frame_.header.LEN);
+  teq1_trace_transmit(tx_frame_.header.PCB, tx_frame_.header.LEN);
+  teq1_trace_receive(rx_frame_.header.PCB, rx_frame_.header.LEN);
 
   enum RuleResult result = teq1_rules(&state_,  &tx_frame_, &rx_frame_, &tx_next_);
   EXPECT_EQ(1, state_.errors);
   const char *kNull = NULL;
   EXPECT_NE(kNull, state_.last_error_message);
   EXPECT_STREQ("Invalid frame received", state_.last_error_message);
-  EXPECT_EQ(TEQ1_R(0, 0, 1), tx_next_.header.PCB.val)
-    << "Actual next TX: " << teq1_pcb_to_name(tx_next_.header.PCB.val);
+  EXPECT_EQ(TEQ1_R(0, 0, 1), tx_next_.header.PCB)
+    << "Actual next TX: " << teq1_pcb_to_name(tx_next_.header.PCB);
   EXPECT_EQ(kRuleResultSingleShot, result)
    << "Actual result name: " << teq1_rule_result_to_name(result);
 };
diff --git a/libese/Android.bp b/libese/Android.bp
index 1511b3a..411001c 100644
--- a/libese/Android.bp
+++ b/libese/Android.bp
@@ -23,16 +23,18 @@
     ],
 
     local_include_dirs: [
-       "include",
+        "include",
     ],
 
     // Ensure that only explicitly exported symbols are visible.
-    cflags: ["-fvisibility=internal"],
+    cflags: ["-fvisibility=internal", "-std=c99"],
+    // This doesn't work yet, but is good documentation for when
+    // debugging is needed.
     debug:  {
         cflags: ["-DLOG_NDEBUG=0"],
-     },
+    },
 
-    shared_libs: ["liblog"],
+    shared_libs: ["libese-sysdeps", "liblog"],
     export_include_dirs: ["include"],
 }
 
diff --git a/libese/ese.c b/libese/ese.c
index 1e49e22..2a0048f 100644
--- a/libese/ese.c
+++ b/libese/ese.c
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#include <ese/ese.h>
-#include <ese/log.h>
+#include "include/ese/ese.h"
+#include "include/ese/log.h"
 
 #include "ese_private.h"
 
@@ -27,84 +27,91 @@
 };
 #define ESE_MESSAGES(x) (sizeof(x) / sizeof((x)[0]))
 
-/* TODO(wad): Make the default visibility on this one default default? */
-API const char *ese_name(struct EseInterface *ese) {
-  if (!ese)
+API const char *ese_name(const struct EseInterface *ese) {
+  if (!ese) {
     return kNullEse;
-  if (ese->ops->name)
+  }
+  if (ese->ops->name) {
     return ese->ops->name;
+  }
   return kUnknownHw;
 }
 
 API int ese_open(struct EseInterface *ese, void *hw_opts) {
-  if (!ese)
+  if (!ese) {
     return -1;
+  }
   ALOGV("opening interface '%s'", ese_name(ese));
-  if (ese->ops->open)
+  if (ese->ops->open) {
     return ese->ops->open(ese, hw_opts);
+  }
   return 0;
 }
 
-API const char *ese_error_message(struct EseInterface *ese) {
+API const char *ese_error_message(const struct EseInterface *ese) {
   return ese->error.message;
 }
 
-API int ese_error_code(struct EseInterface *ese) { return ese->error.code; }
+API int ese_error_code(const struct EseInterface *ese) {
+  return ese->error.code;
+}
 
-API int ese_error(struct EseInterface *ese) { return ese->error.is_err; }
+API bool ese_error(const struct EseInterface *ese) { return ese->error.is_err; }
 
 API void ese_set_error(struct EseInterface *ese, int code) {
-  if (!ese)
+  if (!ese) {
     return;
+  }
   /* Negative values are reserved for API wide messages. */
   ese->error.code = code;
-  ese->error.is_err = 1;
+  ese->error.is_err = true;
   if (code < 0) {
     code = -(code + 1); /* Start at 0. */
-    if ((size_t)(code) >= ESE_MESSAGES(kEseErrorMessages)) {
+    if ((uint32_t)(code) >= ESE_MESSAGES(kEseErrorMessages)) {
       LOG_ALWAYS_FATAL("Unknown global error code passed to ese_set_error(%d)",
                        code);
     }
     ese->error.message = kEseErrorMessages[code];
     return;
   }
-  if ((size_t)(code) >= ese->errors_count) {
+  if ((uint32_t)(code) >= ese->ops->errors_count) {
     LOG_ALWAYS_FATAL("Unknown hw error code passed to ese_set_error(%d)", code);
   }
-  ese->error.message = ese->errors[code];
+  ese->error.message = ese->ops->errors[code];
 }
 
 /* Blocking. */
-API int ese_transceive(struct EseInterface *ese, uint8_t *const tx_buf,
-                       size_t tx_len, uint8_t *rx_buf, size_t rx_max) {
-  size_t recvd = 0;
-  if (!ese)
+API int ese_transceive(struct EseInterface *ese, const uint8_t *tx_buf,
+                       uint32_t tx_len, uint8_t *rx_buf, uint32_t rx_max) {
+  uint32_t recvd = 0;
+  if (!ese) {
     return -1;
-  while (1) {
-    if (ese->ops->transceive) {
-      recvd = ese->ops->transceive(ese, tx_buf, tx_len, rx_buf, rx_max);
-      break;
-    }
-    if (ese->ops->hw_transmit && ese->ops->hw_receive) {
-      ese->ops->hw_transmit(ese, tx_buf, tx_len, 1);
-      if (ese->error.is_err)
-        break;
-      recvd = ese->ops->hw_receive(ese, rx_buf, rx_max, 1);
-      break;
-    }
-    ese_set_error(ese, -1);
-    break;
   }
-  if (ese->error.is_err)
-    return -1;
-  return recvd;
+
+  if (ese->ops->transceive) {
+    recvd = ese->ops->transceive(ese, tx_buf, tx_len, rx_buf, rx_max);
+    return ese_error(ese) ? -1 : recvd;
+  }
+
+  if (ese->ops->hw_transmit && ese->ops->hw_receive) {
+    ese->ops->hw_transmit(ese, tx_buf, tx_len, 1);
+    if (!ese_error(ese)) {
+      recvd = ese->ops->hw_receive(ese, rx_buf, rx_max, 1);
+    }
+    return ese_error(ese) ? -1 : recvd;
+  }
+
+  ese_set_error(ese, kEseGlobalErrorNoTransceive);
+  return -1;
 }
 
-API int ese_close(struct EseInterface *ese) {
-  if (!ese)
-    return -1;
+API void ese_close(struct EseInterface *ese) {
+  if (!ese) {
+    return;
+  }
   ALOGV("closing interface '%s'", ese_name(ese));
-  if (!ese->ops->close)
-    return 0;
-  return ese->ops->close(ese);
+  if (!ese->ops->close) {
+    return;
+  }
+  ese->ops->close(ese);
 }
diff --git a/libese/include/ese/bit_spec.h b/libese/include/ese/bit_spec.h
new file mode 100644
index 0000000..4e6c848
--- /dev/null
+++ b/libese/include/ese/bit_spec.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef ESE_BIT_SPEC_H__
+#define ESE_BIT_SPEC_H__ 1
+
+struct bit_spec {
+  uint8_t value;
+  uint8_t shift;
+};
+
+static inline uint8_t bs_get(const struct bit_spec b, uint8_t var) {
+  return (var & (b.value << b.shift)) >> b.shift;
+}
+
+static inline uint8_t bs_mask(const struct bit_spec b, uint8_t value) {
+  return (value & b.value) << b.shift;
+}
+
+static inline uint8_t bs_clear(const struct bit_spec b) {
+ return (((uint8_t)~0) ^ bs_mask(b, b.value));
+}
+
+static inline uint8_t bs_set(uint8_t var, const struct bit_spec b, uint8_t value) {
+  return ((var) & bs_clear(b)) | bs_mask(b, value);
+}
+
+static inline void bs_assign(uint8_t *dst, const struct bit_spec b, uint8_t value) {
+  *dst =  bs_set(*dst, b, value);
+}
+
+
+#endif  /* ESE_BIT_SPEC_H__ */
+
+
diff --git a/libese/include/ese/ese.h b/libese/include/ese/ese.h
index 39827ed..f12ed03 100644
--- a/libese/include/ese/ese.h
+++ b/libese/include/ese/ese.h
@@ -17,10 +17,8 @@
 #ifndef ESE_H_
 #define ESE_H_ 1
 
-#include <stdint.h>
-#include <stdint.h>
-
 #include "ese_hw_api.h"
+#include "../../../libese-sysdeps/include/ese/sysdeps.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -68,17 +66,14 @@
 #define ESE_INITIALIZER __ESE_INITIALIZER
 #define ESE_INCLUDE_HW __ESE_INCLUDE_HW
 
-const char *ese_name(struct EseInterface *ese);
+const char *ese_name(const struct EseInterface *ese);
 int ese_open(struct EseInterface *ese, void *hw_opts);
-int ese_close(struct EseInterface *ese);
-int ese_transceive(struct EseInterface *ese, uint8_t *const tx_buf, size_t tx_len, uint8_t *rx_buf, size_t rx_max);
+void ese_close(struct EseInterface *ese);
+int ese_transceive(struct EseInterface *ese, const uint8_t *tx_buf, uint32_t tx_len, uint8_t *rx_buf, uint32_t rx_max);
 
-int ese_error(struct EseInterface *ese);
-const char *ese_error_message(struct EseInterface *ese);
-int ese_error_code(struct EseInterface *ese);
-
-/* Called by the implementations. */
-void ese_set_error(struct EseInterface *ese, int code);
+bool ese_error(const struct EseInterface *ese);
+const char *ese_error_message(const struct EseInterface *ese);
+int ese_error_code(const struct EseInterface *ese);
 
 #ifdef __cplusplus
 }  /* extern "C" */
diff --git a/libese/include/ese/ese_hw_api.h b/libese/include/ese/ese_hw_api.h
index dd76391..1d23768 100644
--- a/libese/include/ese/ese_hw_api.h
+++ b/libese/include/ese/ese_hw_api.h
@@ -17,8 +17,7 @@
 #ifndef ESE_HW_API_H_
 #define ESE_HW_API_H_ 1
 
-#include <stddef.h>
-#include <stdint.h>
+#include "../../../libese-sysdeps/include/ese/sysdeps.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -28,49 +27,109 @@
  * to make use of.
  */
 #define __ESE_INCLUDE_HW(name) \
-  extern const struct EseOperations * name## _ops; \
-  extern const char ** name##_errors; \
-  extern const size_t name##_errors_count
+  extern const struct EseOperations * name## _ops
 
 
 struct EseInterface;
-/* !! Note !!
- * Receive and transmit operations on SPI buses should ensure the CS
- * does not change between subsequent recieve (or transmit) calls unless
- * the |complete| argument is 1.
+
+/* ese_hw_receive_op_t: receives a buffer from the hardware.
+ * Args:
+ * - struct EseInterface *: session handle.
+ * - uint8_t *: pointer to the buffer to receive data into.
+ * - uint32_t: maximum length of the data to receive.
+ * - int: 1 or 0 indicating if it is a complete transaction. This allows the underlying
+ *       implementation to batch reads if needed by the underlying wire protocol or if
+ *       the hardware needs to be signalled explicitly.
  *
- * In practice, this should not require additional state tracking as entry
- * to each function can simply assert the CS state (even if unchanged) and
- * then check whether to unassert based on |complete|.
- *
- * Other communications backends may have different needs which may be solved
- * separately by minimally processing protocol headers.
- *
- * The other option is having a transactional view where we have an explicit
- * begin/end or claim/release.
+ * Returns:
+ * - uint32_t: bytes received.
  */
-typedef size_t (ese_hw_receive_op_t)(struct EseInterface *, uint8_t *, size_t, int);
-typedef size_t (ese_hw_transmit_op_t)(struct EseInterface *, const uint8_t *, size_t, int);
+typedef uint32_t (ese_hw_receive_op_t)(struct EseInterface *, uint8_t *, uint32_t, int);
+/* ese_hw_transmit_op_t: transmits a buffer over the hardware.
+ * Args:
+ * - struct EseInterface *: session handle.
+ * - uint8_t *: pointer to the buffer to transmit.
+ * - uint32_t: length of the data to transmit.
+ * - int: 1 or 0 indicating if it is a complete transaction.
+ *
+ * Returns:
+ * - uint32_t: bytes transmitted.
+ */
+typedef uint32_t (ese_hw_transmit_op_t)(struct EseInterface *, const uint8_t *, uint32_t, int);
+/* ese_hw_reset_op_t: resets the hardware in case of communication desynchronization.
+ * Args:
+ * - struct EseInterface *: session handle.
+ *
+ * Returns:
+ * - int: -1 on error, 0 on success.
+ */
 typedef int (ese_hw_reset_op_t)(struct EseInterface *);
-/* Implements wire protocol transceiving and will likely also then require locking. */
-typedef size_t (ese_transceive_op_t)(struct EseInterface *, const uint8_t *, size_t, uint8_t *, size_t);
-/* Returns 0 on timeout, 1 on byte seen, -1 on error. */
+/* ese_transceive_op_t:  fully contained transmission and receive operation.
+ *
+ * Must provide an implementation of the wire protocol necessary to transmit
+ * and receive an application payload to and from the eSE.  Normally, this
+ * implementation is built on the hw_{receive,transmit} operations also
+ * provided and often requires the poll operation below.
+ *
+ * Args:
+ * - struct EseInterface *: session handle.
+ * - const uint8_t *: pointer to the buffer to transmit.
+ * - uint32_t: length of the data to transmit.
+ * - uint8_t *: pointer to the buffer to receive into.
+ * - uint32_t: maximum length of the data to receive.
+ *
+ * Returns:
+ * - uint32_t: bytes received.
+ */
+typedef uint32_t (ese_transceive_op_t)(struct EseInterface *, const uint8_t *, uint32_t, uint8_t *, uint32_t);
+/* ese_poll_op_t: waits for the hardware to be ready to send data.
+ *
+ * Args:
+ * - struct EseInterface *: session handle.
+ * - uint8_t: byte to wait for. E.g., a start of frame byte.
+ * - float: time in seconds to wait.
+ * - int: whether to complete a transaction when polling іs complete.
+ *
+ * Returns:
+ * - int: On error or timeout, -1.
+ *        On success, 1 or 0 depending on if the found byte was consumed.
+ */
 typedef int (ese_poll_op_t)(struct EseInterface *, uint8_t, float, int);
+/* ese_hw_open_op_t: prepares the hardware for use.
+ *
+ * This function should prepare the hardware for use and attach
+ * any implementation specific state to the EseInterface handle such
+ * that it is accessible in the other calls.
+ *
+ * Args:
+ * - struct EseInterface *: freshly initialized session handle.
+ * - void *: implementation specific pointer from the libese client.
+ *
+ * Returns:
+ * - int: < 0 on error, 0 on success.
+ */
 typedef int (ese_open_op_t)(struct EseInterface *, void *);
-typedef int (ese_close_op_t)(struct EseInterface *);
+/* ese_hw_close_op_t: releases the hardware in use.
+ *
+ * This function should free any dynamic memory and release
+ * the claimed hardware.
+ *
+ * Args:
+ * - struct EseInterface *: freshly initialized session handle.
+ *
+ * Returns:
+ * - Nothing.
+ */
+typedef void (ese_close_op_t)(struct EseInterface *);
 
 #define __ESE_INITIALIZER(TYPE) \
 { \
   .ops = TYPE## _ops, \
-  .errors = TYPE## _errors, \
-  .errors_count = TYPE## _errors_count, \
   .pad =  { 0 }, \
 }
 
 #define __ese_init(_ptr, TYPE) {\
   _ptr->ops = TYPE## _ops; \
-  _ptr->errors = TYPE## _errors; \
-  _ptr->errors_count = TYPE## _errors_count; \
   _ptr->pad[0] = 0; \
 }
 
@@ -99,6 +158,10 @@
 
   /* Operational options */
   const void *const opts;
+
+  /* Operation error messages. */
+  const char **errors;
+  const uint32_t errors_count;
 };
 
 /* Maximum private stack storage on the interface instance. */
@@ -106,24 +169,32 @@
 struct EseInterface {
   const struct EseOperations * ops;
   struct {
-    int is_err;
+    bool is_err;
     int code;
     const char *message;
   } error;
-  const char **errors;
-  size_t errors_count;
   /* Reserved to avoid heap allocation requirement. */
   uint8_t pad[ESE_INTERFACE_STATE_PAD];
 };
 
+/*
+ * Provided by libese to manage exposing usable errors up the stack to the
+ * libese user.
+ */
+void ese_set_error(struct EseInterface *ese, int code);
 
-#define ESE_DEFINE_HW_ERRORS(name, ary) \
-  const char ** name##_errors = (ary); \
-  const size_t name##_errors_count = (sizeof(ary) / sizeof((ary)[0]))
+/*
+ * Global error enums.
+ */
+enum EseGlobalError {
+  kEseGlobalErrorNoTransceive = -1,
+  kEseGlobalErrorPollTimedOut = -2,
+};
 
 #define ESE_DEFINE_HW_OPS(name, obj) \
   const struct EseOperations * name##_ops = &obj
 
+
 #ifdef __cplusplus
 }  /* extern "C" */
 #endif
diff --git a/libese/include/ese/log.h b/libese/include/ese/log.h
index 150c562..8eebe23 100644
--- a/libese/include/ese/log.h
+++ b/libese/include/ese/log.h
@@ -19,6 +19,10 @@
 #ifndef ESE_LOG_H_
 #define ESE_LOG_H_ 1
 
+/* Uncomment when doing bring up or other messy debugging.
+ * #define LOG_NDEBUG 0
+ */
+
 #if defined(ESE_LOG_NONE) || defined(ESE_LOG_STDIO)
 #  define ESE_LOG_ANDROID 0
 #endif
diff --git a/libese/include/ese/sysdeps.h b/libese/include/ese/sysdeps.h
new file mode 100644
index 0000000..67f91f1
--- /dev/null
+++ b/libese/include/ese/sysdeps.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef ESE_PLATFORM_SYSDEPS_H__
+#define ESE_PLATFORM_SYSDEPS_H__ 1
+
+#include <stdint.h> /* uint*_t */
+
+extern void *ese_memcpy(void *__dest, const void *__src, uint64_t __n);
+extern void *ese_memset(void *__s, int __c, uint64_t __n);
+
+#endif  /* ESE_PLATFORM_SYSDEPS_H__ */
diff --git a/libese/tests/Android.bp b/libese/tests/Android.bp
index 29abee0..1239eb2 100644
--- a/libese/tests/Android.bp
+++ b/libese/tests/Android.bp
@@ -14,29 +14,13 @@
 // limitations under the License.
 //
 
-cc_defaults {
-    name: "libese_tests_default",
-
-    multilib: {
-        lib32: {
-            suffix: "32",
-        },
-        lib64: {
-            suffix: "64",
-        },
-    },
-}
-
-test_libraries = [
-    "libese",
-    "libese-hw-fake",
-    "liblog",
-]
-
 cc_test {
     name: "ese_unittests",
-    defaults: ["libese_tests_default"],
-    srcs: ["ese_unittests.cpp"],
+    srcs: ["ese_unittests.cpp", "bitspec_unittests.cpp"],
     host_supported: true,
-    shared_libs: test_libraries,
+    shared_libs: [
+        "libese",
+        "libese-hw-fake",
+        "liblog",
+    ],
 }
diff --git a/libese/tests/bitspec_unittests.cpp b/libese/tests/bitspec_unittests.cpp
new file mode 100644
index 0000000..1f2fc2a
--- /dev/null
+++ b/libese/tests/bitspec_unittests.cpp
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+
+#include <ese/bit_spec.h>
+#include <gtest/gtest.h>
+
+using ::testing::Test;
+
+struct TestSpec {
+  struct {
+    struct bit_spec bit[8];
+  } single;
+  struct {
+    struct bit_spec high;
+    struct bit_spec mid;
+    struct bit_spec low;
+  } adjacent;
+  struct {
+    struct bit_spec all8;
+    struct bit_spec upper6;
+    struct bit_spec lower6;
+  } overlap;
+};
+
+const struct TestSpec kTestSpec = {
+  .single = {
+    .bit = {
+      { .value = 1, .shift = 0 },
+      { .value = 1, .shift = 1 },
+      { .value = 1, .shift = 2 },
+      { .value = 1, .shift = 3 },
+      { .value = 1, .shift = 4 },
+      { .value = 1, .shift = 5 },
+      { .value = 1, .shift = 6 },
+      { .value = 1, .shift = 7 },
+    },
+  },
+  .adjacent = {
+    .high = { .value = 0x3, .shift = 6 },
+    .mid = { .value = 0xf, .shift = 2 },
+    .low = { .value = 0x3, .shift = 0 },
+  },
+  .overlap = {
+    .all8 = { .value = 0xff, .shift = 0 },
+    .upper6 = { .value = 0x3f, .shift = 2 },
+    .lower6 = { .value = 0x3f, .shift = 0 },
+  },
+};
+
+class BitSpecTest : public virtual Test {
+ public:
+  BitSpecTest() {
+  }
+  virtual ~BitSpecTest() { }
+  virtual void SetUp() { }
+  virtual void TearDown() { }
+};
+
+TEST_F(BitSpecTest, single_bits_assign_accrue) {
+  uint8_t byte = 0;
+  uint8_t expected_byte = 0;
+  // Accrue bits.
+  for (int bit = 0; bit < 8; ++bit) {
+    expected_byte |= (1 << bit);
+    bs_assign(&byte, kTestSpec.single.bit[bit], 1);
+    EXPECT_EQ(expected_byte, byte);
+    EXPECT_EQ(1, bs_get(kTestSpec.single.bit[bit], expected_byte));
+  }
+}
+
+TEST_F(BitSpecTest, single_bits_assign1_individual) {
+  // One bit at a time.
+  for (int bit = 0; bit < 8; ++bit) {
+    uint8_t expected_byte = (1 << bit);
+    uint8_t byte = bs_set(0, kTestSpec.single.bit[bit], 1);
+    EXPECT_EQ(expected_byte, byte);
+    EXPECT_EQ(1, bs_get(kTestSpec.single.bit[bit], expected_byte));
+  }
+}
+
+TEST_F(BitSpecTest, single_bits_assign0_individual) {
+  // One bit at a time.
+  for (int bit = 0; bit < 8; ++bit) {
+    uint8_t expected_byte = 0xff ^ (1 << bit);
+    uint8_t byte = bs_set(0xff, kTestSpec.single.bit[bit], 0);
+    EXPECT_EQ(expected_byte, byte);
+    EXPECT_EQ(0, bs_get(kTestSpec.single.bit[bit], expected_byte));
+  }
+}
+
+TEST_F(BitSpecTest, single_bits_clear_individual) {
+  // One bit at a time.
+  for (int bit = 0; bit < 8; ++bit) {
+    uint8_t byte = 0xff;
+    uint8_t expected_byte = 0xff ^ (1 << bit);
+    byte &= bs_clear(kTestSpec.single.bit[bit]);
+    EXPECT_EQ(expected_byte, byte);
+    EXPECT_EQ(0, bs_get(kTestSpec.single.bit[bit], expected_byte));
+  }
+}
+
+TEST_F(BitSpecTest, adjacent_bit_assign) {
+  uint8_t byte = 0;
+  EXPECT_EQ(0, bs_get(kTestSpec.adjacent.high, byte));
+  EXPECT_EQ(0, bs_get(kTestSpec.adjacent.mid, byte));
+  EXPECT_EQ(0, bs_get(kTestSpec.adjacent.low, byte));
+  byte = 0xff;
+  EXPECT_EQ(0x3, bs_get(kTestSpec.adjacent.high, byte));
+  EXPECT_EQ(0xf, bs_get(kTestSpec.adjacent.mid, byte));
+  EXPECT_EQ(0x3, bs_get(kTestSpec.adjacent.low, byte));
+  for (int i = 0; i < 0xf; ++i) {
+    bs_assign(&byte, kTestSpec.adjacent.mid, i);
+    EXPECT_EQ(0x3, bs_get(kTestSpec.adjacent.high, byte));
+    EXPECT_EQ(i, bs_get(kTestSpec.adjacent.mid, byte));
+    EXPECT_EQ(0x3, bs_get(kTestSpec.adjacent.low, byte));
+  }
+  byte = 0xff;
+  for (int i = 0; i < 0x3; ++i) {
+    bs_assign(&byte, kTestSpec.adjacent.low, i);
+    bs_assign(&byte, kTestSpec.adjacent.high, i);
+    EXPECT_EQ(i, bs_get(kTestSpec.adjacent.high, byte));
+    EXPECT_EQ(0xf, bs_get(kTestSpec.adjacent.mid, byte));
+    EXPECT_EQ(i, bs_get(kTestSpec.adjacent.low, byte));
+  }
+}
+
+TEST_F(BitSpecTest, overlap_bit_assign) {
+  uint8_t byte = 0;
+  EXPECT_EQ(0, bs_get(kTestSpec.overlap.upper6, byte));
+  EXPECT_EQ(0, bs_get(kTestSpec.overlap.lower6, byte));
+  EXPECT_EQ(0, bs_get(kTestSpec.overlap.all8, byte));
+  byte = 0xff;
+  EXPECT_EQ(0x3f, bs_get(kTestSpec.overlap.upper6, byte));
+  EXPECT_EQ(0x3f, bs_get(kTestSpec.overlap.lower6, byte));
+  EXPECT_EQ(0xff, bs_get(kTestSpec.overlap.all8, byte));
+
+  byte = 0;
+  for (int i = 0; i < 0x3f; ++i) {
+    bs_assign(&byte, kTestSpec.overlap.lower6, i);
+    EXPECT_EQ((i & (0x3f << 2)) >> 2, bs_get(kTestSpec.overlap.upper6, byte));
+    EXPECT_EQ(i, bs_get(kTestSpec.overlap.lower6, byte));
+    EXPECT_EQ(i, bs_get(kTestSpec.overlap.all8, byte));
+  }
+  byte = 0;
+  for (int i = 0; i < 0x3f; ++i) {
+    bs_assign(&byte, kTestSpec.overlap.upper6, i);
+    EXPECT_EQ(i, bs_get(kTestSpec.overlap.upper6, byte));
+    EXPECT_EQ((i << 2) & 0x3f, bs_get(kTestSpec.overlap.lower6, byte));
+    EXPECT_EQ(i << 2, bs_get(kTestSpec.overlap.all8, byte));
+  }
+  byte = 0;
+  for (int i = 0; i < 0xff; ++i) {
+    bs_assign(&byte, kTestSpec.overlap.all8, i);
+    EXPECT_EQ(i >> 2, bs_get(kTestSpec.overlap.upper6, byte));
+    EXPECT_EQ(i & 0x3f, bs_get(kTestSpec.overlap.lower6, byte));
+    EXPECT_EQ(i, bs_get(kTestSpec.overlap.all8, byte));
+  }
+}
diff --git a/libese/tests/ese_unittests.cpp b/libese/tests/ese_unittests.cpp
index dcfc7f8..c62b758 100644
--- a/libese/tests/ese_unittests.cpp
+++ b/libese/tests/ese_unittests.cpp
@@ -68,7 +68,7 @@
 };
 
 TEST_F(EseInterfaceTest, EseCloseNull) {
-  EXPECT_EQ(-1, ese_close(NULL));
+  ese_close(NULL);
 };
 
 TEST_F(EseInterfaceTest, EseCloseNoOp) {
@@ -79,32 +79,32 @@
     .ops = &dummy_ops
   };
   /* Will pass without an open first. */
-  EXPECT_EQ(0, ese_close(&dummy));
+  ese_close(&dummy);
 };
 
 TEST_F(EseInterfaceTest, EseCloseOk) {
   EXPECT_EQ(0, ese_open(&ese_, NULL));
-  EXPECT_EQ(0, ese_close(&ese_));
+  ese_close(&ese_);
 };
 
 TEST_F(EseInterfaceTest, EseClosePending) {
   EXPECT_EQ(0, ese_open(&ese_, NULL));
   ese_.ops->hw_transmit(&ese_, NULL, 0, 0);
-  EXPECT_EQ(-1, ese_close(&ese_));
+  ese_close(&ese_);
   EXPECT_EQ(0, ese_open(&ese_, NULL));
   ese_.ops->hw_transmit(&ese_, NULL, 0, 1);
   ese_.ops->hw_receive(&ese_, NULL, 0, 0);
-  EXPECT_EQ(-1, ese_close(&ese_));
+  ese_close(&ese_);
   EXPECT_EQ(0, ese_open(&ese_, NULL));
   ese_.ops->hw_receive(&ese_, NULL, 0, 1);
-  EXPECT_EQ(0, ese_close(&ese_));
+  ese_close(&ese_);
 };
 
 
 TEST_F(EseInterfaceTest, EseTransceiveSendNothing) {
   EXPECT_EQ(0, ese_open(&ese_, NULL));
   EXPECT_EQ(0, ese_transceive(&ese_, NULL, 0, NULL, 0));
-  EXPECT_EQ(0, ese_close(&ese_));
+  ese_close(&ese_);
 };
 
 TEST_F(EseInterfaceTest, EseTransceiveNoOps) {
diff --git a/tools/ese_relay/Android.bp b/tools/ese_relay/Android.bp
index bbb1dc7..329dabe 100644
--- a/tools/ese_relay/Android.bp
+++ b/tools/ese_relay/Android.bp
@@ -14,38 +14,39 @@
 // limitations under the License.
 //
 
-relay_shared_libraries = [
-    "liblog",
-    "libese",
-]
+cc_defaults {
+    name: "ese-relay-defaults",
+    host_supported: false,
+    target: {
+        darwin: {
+            enabled: false,
+        },
+    },
+    srcs: ["ese_relay.c"],
+    shared_libs: [
+        "liblog",
+        "libese",
+    ],
+}
 
 cc_binary {
-    name: "ese-relay-pn80t",
-    srcs: ["ese_relay.c", "ese_relay_pn80t.c"],
-    host_supported: false,
-    shared_libs: ["libese-hw-nxp-pn80t-spidev", "libese-teq1"] +
-                 relay_shared_libraries,
-    target: {
-      darwin: {
-        enabled: false,
-      },
-      windows: {
-        enabled: false,
-      },
-    },
+    name: "ese-relay-pn80t-nq-nci",
+    defaults: ["ese-relay-defaults"],
+    srcs: ["ese_relay_pn80t_nq_nci.c"],
+    shared_libs: ["libese-hw-nxp-pn80t-nq-nci", "libese-teq1"],
+}
+
+cc_binary {
+    name: "ese-relay-pn80t-spidev",
+    defaults: ["ese-relay-defaults"],
+    srcs: ["ese_relay_pn80t_spidev.c"],
+    shared_libs: ["libese-hw-nxp-pn80t-spidev", "libese-teq1"],
 }
 
 cc_binary {
     name: "ese-relay-fake",
-    srcs: ["ese_relay.c", "ese_relay_fake.c"],
+    defaults: ["ese-relay-defaults"],
+    srcs: ["ese_relay_fake.c"],
+    shared_libs: ["libese-hw-fake"],
     host_supported: true,
-    shared_libs: relay_shared_libraries + ["libese-hw-fake"],
-    target: {
-      darwin: {
-        enabled: false,
-      },
-      windows: {
-        enabled: false,
-      },
-    },
 }
diff --git a/tools/ese_relay/ese_relay_fake.c b/tools/ese_relay/ese_relay_fake.c
index 862e5c5..f336709 100644
--- a/tools/ese_relay/ese_relay_fake.c
+++ b/tools/ese_relay/ese_relay_fake.c
@@ -24,7 +24,4 @@
 const size_t kAtrLength = sizeof(kAtr);
 const void *kEseOpenData = NULL;
 
-void ese_relay_init(struct EseInterface *ese) {
-  const struct EseInterface kInterface = ESE_INITIALIZER(ESE_HW_FAKE);
-  memcpy(ese, &kInterface, sizeof(kInterface));
-}
+void ese_relay_init(struct EseInterface *ese) { ese_init(ese, ESE_HW_FAKE); }
diff --git a/tools/ese_relay/ese_relay_pn80t_nq_nci.c b/tools/ese_relay/ese_relay_pn80t_nq_nci.c
new file mode 100644
index 0000000..d9fbb25
--- /dev/null
+++ b/tools/ese_relay/ese_relay_pn80t_nq_nci.c
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Pull in NXP PN80T for use in the relay. */
+#include <ese/ese.h>
+ESE_INCLUDE_HW(ESE_HW_NXP_PN80T_NQ_NCI);
+
+/* From
+ * http://stackoverflow.com/questions/27074551/how-to-list-applets-on-jcop-cards
+ */
+static const uint8_t kAtrBytes[] = {
+    0x3B, 0xF8, 0x13, 0x00, 0x00, 0x81, 0x31, 0xFE, 0x45,
+    0x4A, 0x43, 0x4F, 0x50, 0x76, 0x32, 0x34, 0x31, 0xB7,
+};
+const uint8_t *kAtr = &kAtrBytes[0];
+const size_t kAtrLength = sizeof(kAtr);
+const void *kEseOpenData = NULL;
+
+void ese_relay_init(struct EseInterface *ese) {
+  ese_init(ese, ESE_HW_NXP_PN80T_NQ_NCI);
+}
diff --git a/tools/ese_relay/ese_relay_pn80t.c b/tools/ese_relay/ese_relay_pn80t_spidev.c
similarity index 100%
rename from tools/ese_relay/ese_relay_pn80t.c
rename to tools/ese_relay/ese_relay_pn80t_spidev.c