tcti-device: enable nonblocking mode on tcti-device

Enable non-blocking mode on tcti-device. This new feature
requires the TPM driver to support asynchronous mode of operation.
(As of kernel v3.18 the driver doesn't support it)

It is therefor configure time enabled by a new flag called
enable-tcti-device-async (off by default)

There are two places where the new flag changes the behavior:
1. In tcti_device_get_poll_handles(), where if the flag is enabled,
   the function will populate handles with the tcti_dev->fd,
   set num_handles to 1, and return TSS2_RC_SUCCESS.
   If the flag is not enabled the function returns
   TSS2_TCTI_RC_NOT_IMPLEMENTED.
2. In tcti_device_receive(), where if the flag is not enabled
   the only acceptable timeout value is TSS2_TCTI_TIMEOUT_BLOCK,
   to enforce synchronous mode. If the flag is enabled the valid
   timeout values are: -1 to block forever, 0 for nonblocking,
   and any positive value as the actual timeout value in milliseconds.

The device interface will always be open in non-blocking mode
as the flag is ignored by the driver and invoking poll() currently
always returns with the POLLIN flag set.

When this feature will be supported by the driver and the
enable-tcti-device-async flag is not enabled the behavior
doesn't change because it it enforced to be synchronous
in tcti_device_get_poll_handles(), but if the flag is enabled
it will enable the asynchronous behavior of the tcti-device module.

Signed-off-by: Tadeusz Struk <tadeusz.struk@intel.com>
diff --git a/configure.ac b/configure.ac
index 38ab4da..dc33d0e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -70,6 +70,15 @@
     [enable_esapi="yes"])
 AM_CONDITIONAL(ESAPI, test "x$enable_esapi" = "xyes")
 
+AC_ARG_ENABLE([tcti-device-async],
+    AS_HELP_STRING([--enable-tcti-device-async],
+	           [Enable asynchronus operation on TCTI device
+		    (note: This needs to be supported by the kernel driver). default is no]),
+    [enable_tcti_device_async=$enableval],
+    [enable_tcti_device_async=no])
+AS_IF([test "x$enable_tcti_device_async" = "xyes"],
+	AC_DEFINE([TCTI_ASYNC],[1]))
+
 AC_ARG_WITH([crypto],
             [AS_HELP_STRING([--with-crypto={gcrypt,ossl}],
                             [sets the ESAPI crypto backend (default is gcrypt)])],
@@ -305,4 +314,5 @@
     debug:              $enable_debug
     maxloglevel:        $with_maxloglevel
     doxygen:            $DX_FLAG_doc $enable_doxygen_doc
+    tcti-device-async:  $enable_tcti_device_async
 ])
diff --git a/src/tss2-tcti/tcti-device.c b/src/tss2-tcti/tcti-device.c
index a5d0f47..5b5da40 100644
--- a/src/tss2-tcti/tcti-device.c
+++ b/src/tss2-tcti/tcti-device.c
@@ -12,6 +12,7 @@
 #include <string.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <poll.h>
 #include <unistd.h>
 
 #include "tss2_tcti.h"
@@ -118,7 +119,9 @@
     TSS2_TCTI_DEVICE_CONTEXT *tcti_dev = tcti_device_context_cast (tctiContext);
     TSS2_TCTI_COMMON_CONTEXT *tcti_common = tcti_device_down_cast (tcti_dev);
     TSS2_RC rc = TSS2_RC_SUCCESS;
-    ssize_t  size;
+    ssize_t size = 0;
+    struct pollfd fds;
+    int rc_poll, nfds = 1;
 
     if (tcti_dev == NULL) {
         return TSS2_TCTI_RC_BAD_CONTEXT;
@@ -127,12 +130,19 @@
     if (rc != TSS2_RC_SUCCESS) {
         return rc;
     }
+#ifndef TCTI_ASYNC
+    /* For async the valid timeout values are -1 - block forever,
+     * 0 - nonblocking, and any positive value - the actual timeout
+     * value in millisec.
+     * For sync the only valid value is -1 - block forever.
+     */
     if (timeout != TSS2_TCTI_TIMEOUT_BLOCK) {
         LOG_WARNING ("The underlying IPC mechanism does not support "
                      "asynchronous I/O. The 'timeout' parameter must be "
                      "TSS2_TCTI_TIMEOUT_BLOCK");
         return TSS2_TCTI_RC_BAD_VALUE;
     }
+#endif
     if (response_buffer == NULL) {
         LOG_DEBUG ("Caller queried for size but linux kernel doesn't allow this. "
                    "Returning 4k which is the max size for a response buffer.");
@@ -148,11 +158,24 @@
      * operation. If we try to read again before sending another command
      * the kernel will close the file descriptor and we'll get an EOF.
      */
-    TEMP_RETRY (size, read (tcti_dev->fd, response_buffer, *response_size));
-    if (size < 0) {
-        LOG_ERROR ("Failed to read response from fd %d, got errno %d: %s",
+    fds.fd = tcti_dev->fd;
+    fds.events = POLLIN;
+
+    rc_poll = poll(&fds, nfds, timeout);
+    if (rc_poll < 0) {
+        LOG_ERROR ("Failed to poll for response from fd %d, got errno %d: %s",
                    tcti_dev->fd, errno, strerror (errno));
         return TSS2_TCTI_RC_IO_ERROR;
+    } else if (rc_poll == 0) {
+        LOG_INFO ("Poll timed out on fd %d.", tcti_dev->fd);
+        return TSS2_TCTI_RC_TRY_AGAIN;
+    } else if (fds.revents == POLLIN) {
+        TEMP_RETRY (size, read(tcti_dev->fd, response_buffer, *response_size));
+        if (size < 0) {
+            LOG_ERROR ("Failed to read response from fd %d, got errno %d: %s",
+               tcti_dev->fd, errno, strerror (errno));
+            return TSS2_TCTI_RC_IO_ERROR;
+        }
     }
     if (size == 0) {
         LOG_WARNING ("Got EOF instead of response.");
@@ -221,11 +244,26 @@
     TSS2_TCTI_POLL_HANDLE *handles,
     size_t *num_handles)
 {
-    /* Linux driver doesn't support polling. */
+#ifdef TCTI_ASYNC
+    TSS2_TCTI_DEVICE_CONTEXT *tcti_dev = tcti_device_context_cast (tctiContext);
+
+    if (tcti_dev == NULL) {
+        return TSS2_TCTI_RC_BAD_CONTEXT;
+    }
+
+    if (handles == NULL || num_handles == NULL) {
+        return TSS2_TCTI_RC_BAD_REFERENCE;
+    }
+
+    *num_handles = 1;
+    handles->fd = tcti_dev->fd;
+    return TSS2_RC_SUCCESS;
+#else
     (void)(tctiContext);
     (void)(handles);
     (void)(num_handles);
     return TSS2_TCTI_RC_NOT_IMPLEMENTED;
+#endif
 }
 
 TSS2_RC
@@ -275,7 +313,7 @@
     memset (&tcti_common->header, 0, sizeof (tcti_common->header));
     tcti_common->locality = 3;
 
-    tcti_dev->fd = open (dev_path, O_RDWR);
+    tcti_dev->fd = open (dev_path, O_RDWR | O_NONBLOCK);
     if (tcti_dev->fd < 0) {
         LOG_ERROR ("Failed to open device file %s: %s",
                    dev_path, strerror (errno));
diff --git a/test/unit/tcti-device.c b/test/unit/tcti-device.c
index 4e0a945..770c4a7 100644
--- a/test/unit/tcti-device.c
+++ b/test/unit/tcti-device.c
@@ -123,7 +123,12 @@
     TSS2_RC rc;
 
     rc = Tss2_Tcti_GetPollHandles (ctx, handles, &num_handles);
+#ifdef TCTI_ASYNC
+    assert_int_equal (rc, TSS2_RC_SUCCESS);
+    assert_int_equal (num_handles, 1);
+#else
     assert_int_equal (rc, TSS2_TCTI_RC_NOT_IMPLEMENTED);
+#endif
 }
 /*
  */