[ulib][zircon] Add ZX_CACHE_FLUSH_INVALIDATE flag

This can be OR'd in with ZX_CACHE_FLUSH_DATA to force
an invalidation after the writeback.

Also add the missing documentation for zx_cache_flush.

Change-Id: I3ad031541e7851118f73f4c030368b2df82ded8d
diff --git a/docs/syscalls/cache_flush.md b/docs/syscalls/cache_flush.md
new file mode 100644
index 0000000..5247ec7
--- /dev/null
+++ b/docs/syscalls/cache_flush.md
@@ -0,0 +1,51 @@
+# zx_cache_flush
+
+## NAME
+
+zx_cache_flush - Flush CPU data and/or instruction caches
+
+## SYNOPSIS
+
+```
+#include <zircon/syscalls.h>
+
+zx_status_t zx_cache_flush(const void* addr, size_t len, uint32_t flags);
+```
+
+## DESCRIPTION
+
+**zx_cache_flush**() flushes CPU caches covering memory in the given
+virtual address range.  If that range of memory is not readable, then
+the thread may fault as it would for a data read.
+
+*flags* is a bitwise OR of:
+
+ * **ZX_CACHE_FLUSH_DATA**
+
+   Clean (write back) data caches, so previous writes on this CPU are
+   visible in main memory.
+
+ * **ZX_CACHE_FLUSH_INVALIDATE**
+   (valid only when combined with **ZX_CACHE_FLUSH_DATA**)
+
+   Clean (write back) data caches and then invalidate data caches, so
+   previous writes on this CPU are visible in main memory and future
+   reads on this CPU see external changes to main memory.
+
+ * **ZX_CACHE_FLUSH_INSN**
+
+   Synchronize instruction caches with data caches, so previous writes
+   on this CPU are visible to instruction fetches.  If this is combined
+   with **ZX_CACHE_FLUSH_DATA**, then previous writes will be visible to
+   main memory as well as to instruction fetches.
+
+At least one of **ZX_CACHE_FLUSH_DATA** and **ZX_CACHE_FLUSH_INSN**
+must be included in *flags*.
+
+## RETURN VALUE
+
+**zx_cache_flush**() returns **ZX_OK** on success, or an error code on failure.
+
+## ERRORS
+
+**ZX_ERR_INVALID_ARGS** *flags* is invalid.
diff --git a/system/public/zircon/types.h b/system/public/zircon/types.h
index 24d8b69..cdf2f3f 100644
--- a/system/public/zircon/types.h
+++ b/system/public/zircon/types.h
@@ -307,6 +307,7 @@
 // Flag bits for zx_cache_flush.
 #define ZX_CACHE_FLUSH_INSN         (1u << 0)
 #define ZX_CACHE_FLUSH_DATA         (1u << 1)
+#define ZX_CACHE_FLUSH_INVALIDATE   (1u << 2)
 
 // Timer options.
 #define ZX_TIMER_SLACK_CENTER       0u
diff --git a/system/ulib/zircon/zx_cache_flush.cpp b/system/ulib/zircon/zx_cache_flush.cpp
index 5bc6748..d180802 100644
--- a/system/ulib/zircon/zx_cache_flush.cpp
+++ b/system/ulib/zircon/zx_cache_flush.cpp
@@ -32,8 +32,20 @@
 }  // anonymous namespace
 
 zx_status_t _zx_cache_flush(const void* addr, size_t len, uint32_t flags) {
-    if (flags == 0 || (flags & ~(ZX_CACHE_FLUSH_INSN | ZX_CACHE_FLUSH_DATA)))
+    switch (flags) {
+    case ZX_CACHE_FLUSH_INSN:
+        break;
+    case ZX_CACHE_FLUSH_DATA:
+        break;
+    case ZX_CACHE_FLUSH_DATA | ZX_CACHE_FLUSH_INSN:
+        break;
+    case ZX_CACHE_FLUSH_DATA | ZX_CACHE_FLUSH_INVALIDATE:
+        break;
+    case ZX_CACHE_FLUSH_DATA | ZX_CACHE_FLUSH_INVALIDATE | ZX_CACHE_FLUSH_INSN:
+        break;
+    default:
         return ZX_ERR_INVALID_ARGS;
+    }
 
 #if defined(__x86_64__)
 
@@ -42,10 +54,17 @@
 #elif defined(__aarch64__)
 
     if (flags & ZX_CACHE_FLUSH_DATA) {
-        for_each_dcache_line(addr, len, [](uintptr_t p) {
-                // Clean data cache (dc) to point of coherency (cvac).
-                __asm__ volatile("dc cvac, %0" :: "r"(p));
-            });
+        if (flags & ZX_CACHE_FLUSH_INVALIDATE) {
+            for_each_dcache_line(addr, len, [](uintptr_t p) {
+                    // Clean and invalidate data cache to point of coherency.
+                    __asm__ volatile("dc civac, %0" :: "r"(p));
+                });
+        } else {
+            for_each_dcache_line(addr, len, [](uintptr_t p) {
+                    // Clean data cache (dc) to point of coherency (cvac).
+                    __asm__ volatile("dc cvac, %0" :: "r"(p));
+                });
+        }
     }
 
     if (flags & ZX_CACHE_FLUSH_INSN) {