[sysinfo] Add ioctl_sysinfo_get_interrupt_controller_info()

Returns information about the interrupt controller. Currently the only
information returned is the type, which may be APIC, GICv2, or GICv3. In
the future we might want to pass through more info like whether MSIs are
used etc.

TEST=sysinfo test and manually verify the values are correct on vim2,
kvm, and x64.

Change-Id: Ic1c1f7b7e1f9bbb8ba1a6dbd9023c3061b781d0f
diff --git a/system/dev/bus/platform/platform-bus.cpp b/system/dev/bus/platform/platform-bus.cpp
index 1a8ec8d..4d76403 100644
--- a/system/dev/bus/platform/platform-bus.cpp
+++ b/system/dev/bus/platform/platform-bus.cpp
@@ -19,7 +19,9 @@
 #include <fbl/algorithm.h>
 #include <fbl/auto_lock.h>
 #include <fbl/unique_ptr.h>
+#include <zircon/boot/driver-config.h>
 #include <zircon/boot/image.h>
+#include <zircon/device/sysinfo.h>
 #include <zircon/process.h>
 #include <zircon/syscalls/iommu.h>
 
@@ -310,6 +312,7 @@
     }
 
     bool got_platform_id = false;
+    uint8_t interrupt_controller_type = INTERRUPT_CONTROLLER_TYPE_UNKNOWN;
     zx_off_t metadata_offset = 0;
     len = zbi_length;
     off = sizeof(header);
@@ -347,6 +350,12 @@
                 zxlogf(ERROR, "device_publish_metadata(board_name) failed: %d\n", status);
                 return status;
             }
+        } else if (header.type == ZBI_TYPE_KERNEL_DRIVER) {
+            if (header.extra == KDRV_ARM_GIC_V2) {
+                interrupt_controller_type = INTERRUPT_CONTROLLER_TYPE_GIC_V2;
+            } else if (header.extra == KDRV_ARM_GIC_V3) {
+                interrupt_controller_type = INTERRUPT_CONTROLLER_TYPE_GIC_V3;
+            }
         } else if (ZBI_TYPE_DRV_METADATA(header.type)) {
             status = zbi.read(metadata + metadata_offset, off, itemlen);
             if (status != ZX_OK) {
@@ -359,6 +368,15 @@
         len -= itemlen;
     }
 
+    // Publish interrupt controller type to sysinfo driver
+    status = device_publish_metadata(parent(), "/dev/misc/sysinfo",
+                                     DEVICE_METADATA_INTERRUPT_CONTROLLER_TYPE,
+                                     &interrupt_controller_type, sizeof(interrupt_controller_type));
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "device_publish_metadata(interrupt_controller_type) failed: %d\n", status);
+        return status;
+    }
+
     if (!got_platform_id) {
         zxlogf(ERROR, "platform_bus: ZBI_TYPE_PLATFORM_ID not found\n");
         return ZX_ERR_INTERNAL;
diff --git a/system/dev/misc/sysinfo/sysinfo.c b/system/dev/misc/sysinfo/sysinfo.c
index e8757c1..e0997a0 100644
--- a/system/dev/misc/sysinfo/sysinfo.c
+++ b/system/dev/misc/sysinfo/sysinfo.c
@@ -117,6 +117,27 @@
         *out_actual = sizeof(sysinfo->board_name);
         return ZX_OK;
     }
+    case IOCTL_SYSINFO_GET_INTERRUPT_CONTROLLER_INFO : {
+        interrupt_controller_info_t info = {};
+
+#if defined(__aarch64__)
+        size_t actual = 0;
+        zx_status_t status = device_get_metadata(sysinfo->zxdev,
+                                                 DEVICE_METADATA_INTERRUPT_CONTROLLER_TYPE,
+                                                 &info.type, sizeof(uint8_t), &actual);
+        if (status != ZX_OK) {
+            return status;
+        }
+#elif defined(__x86_64__)
+        info.type = INTERRUPT_CONTROLLER_TYPE_APIC;
+#else
+        info.type = INTERRUPT_CONTROLLER_TYPE_UNKNOWN;
+#endif
+
+        memcpy(reply, &info, sizeof(interrupt_controller_info_t));
+        *out_actual = sizeof(interrupt_controller_info_t);
+        return ZX_OK;
+    }
     default:
         return ZX_ERR_INVALID_ARGS;
     }
diff --git a/system/public/zircon/device/sysinfo.h b/system/public/zircon/device/sysinfo.h
index 2421723..85cc832 100644
--- a/system/public/zircon/device/sysinfo.h
+++ b/system/public/zircon/device/sysinfo.h
@@ -34,6 +34,23 @@
 #define IOCTL_SYSINFO_GET_BOARD_NAME \
     IOCTL(IOCTL_KIND_DEFAULT, IOCTL_FAMILY_SYSINFO, 4)
 
+// Return interrupt controller information
+//   in: none
+//   out: interrupt_controller_info_t
+#define IOCTL_SYSINFO_GET_INTERRUPT_CONTROLLER_INFO \
+    IOCTL(IOCTL_KIND_DEFAULT, IOCTL_FAMILY_SYSINFO, 5)
+
+enum {
+    INTERRUPT_CONTROLLER_TYPE_UNKNOWN = 0,
+    INTERRUPT_CONTROLLER_TYPE_APIC = 1,
+    INTERRUPT_CONTROLLER_TYPE_GIC_V2 = 2,
+    INTERRUPT_CONTROLLER_TYPE_GIC_V3 = 3,
+};
+
+typedef struct interrupt_controller_info_t {
+    uint8_t type;
+} interrupt_controller_info_t;
+
 // ssize_t ioctl_sysinfo_get_root_job(int fd, zx_handle_t* out);
 IOCTL_WRAPPER_OUT(ioctl_sysinfo_get_root_job, IOCTL_SYSINFO_GET_ROOT_JOB, zx_handle_t);
 
@@ -45,3 +62,7 @@
 
 // ssize_t ioctl_sysinfo_get_board_name(int fd, char* out, size_t out_len);
 IOCTL_WRAPPER_VAROUT(ioctl_sysinfo_get_board_name, IOCTL_SYSINFO_GET_BOARD_NAME, char);
+
+// ssize_t ioctl_sysinfo_get_gic_version(int fd, uint8_t* out);
+IOCTL_WRAPPER_OUT(ioctl_sysinfo_get_interrupt_controller_info,
+                  IOCTL_SYSINFO_GET_INTERRUPT_CONTROLLER_INFO, interrupt_controller_info_t);
\ No newline at end of file
diff --git a/system/ulib/ddk/include/ddk/metadata.h b/system/ulib/ddk/include/ddk/metadata.h
index 6aae412..9d0b304 100644
--- a/system/ulib/ddk/include/ddk/metadata.h
+++ b/system/ulib/ddk/include/ddk/metadata.h
@@ -11,28 +11,32 @@
 
 // MAC Address for Ethernet, Wifi, Bluetooth, etc.
 // Content: uint8_t[] (variable length based on type of MAC address)
-#define DEVICE_METADATA_MAC_ADDRESS         0x43414D6D // mMAC
+#define DEVICE_METADATA_MAC_ADDRESS               0x43414D6D // mMAC
 static_assert(DEVICE_METADATA_MAC_ADDRESS == ZBI_TYPE_DRV_MAC_ADDRESS, "");
 
 // Partition map for raw block device.
 // Content: bootdata_partition_map_t
-#define DEVICE_METADATA_PARTITION_MAP       0x5452506D // mPRT
+#define DEVICE_METADATA_PARTITION_MAP             0x5452506D // mPRT
 static_assert(DEVICE_METADATA_PARTITION_MAP == ZBI_TYPE_DRV_PARTITION_MAP, "");
 
 // maximum size of DEVICE_METADATA_PARTITION_MAP data
-#define METADATA_PARTITION_MAP_MAX          4096
+#define METADATA_PARTITION_MAP_MAX                4096
 
 // Initial USB mode
 // type: usb_mode_t
-#define DEVICE_METADATA_USB_MODE            0x4D425355 // USBM
+#define DEVICE_METADATA_USB_MODE                  0x4D425355 // USBM
 
 // Serial port info
 // type: serial_port_info_t
-#define DEVICE_METADATA_SERIAL_PORT_INFO    0x4D524553 // SERM
+#define DEVICE_METADATA_SERIAL_PORT_INFO          0x4D524553 // SERM
 
 // Platform board name (for sysinfo driver)
 // type: char[ZBI_BOARD_NAME_LEN]
-#define DEVICE_METADATA_BOARD_NAME          0x4E524F42 // BORN
+#define DEVICE_METADATA_BOARD_NAME                0x4E524F42 // BORN
+
+// Interrupt controller type (for sysinfo driver)
+// type: uint8_t
+#define DEVICE_METADATA_INTERRUPT_CONTROLLER_TYPE 0x43544E49 // INTC
 
 // Metadata types that have least significant byte set to lowercase 'd'
 // signify private driver data.
diff --git a/system/utest/sysinfo/main.cpp b/system/utest/sysinfo/main.cpp
index 65b06de..8e92249 100644
--- a/system/utest/sysinfo/main.cpp
+++ b/system/utest/sysinfo/main.cpp
@@ -59,9 +59,28 @@
     END_TEST;
 }
 
+bool get_interrupt_controller_info_succeeds() {
+    BEGIN_TEST;
+
+    // Get the resource handle from the driver.
+    int fd = open(SYSINFO_PATH, O_RDWR);
+    ASSERT_GE(fd, 0, "Can't open sysinfo");
+
+    // Test ioctl_sysinfo_get_board_name().
+    interrupt_controller_info_t info;
+    ssize_t n = ioctl_sysinfo_get_interrupt_controller_info(fd, &info);
+    ASSERT_EQ(n, sizeof(info), "ioctl_sysinfo_get_interrupt_controller_info failed");
+    EXPECT_NE(info.type, INTERRUPT_CONTROLLER_TYPE_UNKNOWN, "interrupt controller type is unknown");
+
+    close(fd);
+
+    END_TEST;
+}
+
 BEGIN_TEST_CASE(sysinfo_tests)
 RUN_TEST(get_root_resource_succeeds)
 RUN_TEST(get_board_name_succeeds)
+RUN_TEST(get_interrupt_controller_info_succeeds)
 END_TEST_CASE(sysinfo_tests)
 
 int main(int argc, char** argv) {