Status: DRAFT
Author: jeffbrown@google.com
This document shows a sketch of how the Fuchsia Interface Definition Language (FIDL) v2.0 might be used to describe and implement an I/O protocol.
See FIDL 2.0: Overview for more information about FIDL's overall purpose, goals, and requirements, as well as links to related documents.
WORK IN PROGRESS
This example is a hypothetical extensible I/O protocol with device class specific sub-interfaces. Something similar might be used to define part of the Zircon I/O subsystem.
The example uses a C-like style to conform to Zircon conventions for the purpose of this exposition. (Actual style conventions TBD. Maybe it'll be Google C++. Maybe not.)
We declare several interfaces to represent an interface hierarchy. The goal here is to model the set of I/O operations in an extensible loosely object-oriented manner without relying on ioctl style trampolines.
library fdio; const uint32 FILE_CLASS = 0x1000; // Represents a file-like object whose contents can be read or written. interface file { // Gets the class of interface supported by this file. // Class constants are declared alongside their interfaces. 0x1001: get_class() -> (uint32 class); // Obtain backing VMO, or a null handle if not available. 0x1002: get_vmo() -> (uint32 status, handle<vmo>? vmo); // Generic read operation. 0x1003: read(uint32 size) -> (uint32 status, vector<uint8> buffer); // Generic write operation. 0x1004: write(vector<uint8> buffer) -> (uint32 status); };
library fdio; const uint32 DEVICE_CLASS = 0x2000; interface device : file { 0x2001: bind(string:400 device) -> (uint32 status); 0x2002: watch_dir() -> (uint32 status, watch_listener l); 0x2003: get_event_handle() -> (uint32 status, handle<event> event); 0x2004: get_driver_name() -> (uint32 status, string:32 name); 0x2005: get_device_name() -> (uint32 status, string:32 name); 0x2006: debug_suspend() -> (uint32 status); 0x2007: debug_resume() -> (uint32 status); 0x2008: sync() -> (uint32 status); }; interface watch_listener { 0x0001: notify(); };
library fdio; const uint32 ETHERNET_CLASS = 0x3000; interface ethernet : device { 0x3001: get_info() -> (uint32 status, ethernet_info info); 0x3002: get_fifos() -> (uint32 status, handle<fifo> tx, handle<fifo> rx, uint32 tx_depth, uint32 rx_depth); 0x3003: set_io_buffer(handle<vmo> vmo) -> (uint32 status); 0x3004: start() -> (uint32 status); 0x3005: stop() -> (uint32 status); 0x3006: tx_listen_start() -> (uint32 status); 0x3007: tx_listen_stop() -> (uint32 status); }; struct ethernet_info { uint32 feature; uint32 mtu; uint8[6] mac; };
##include <fidl-c.h> ##define FILE_CLASS 0x1000 enum { fdio_FILE_ORDINAL_GET_CLASS = 0x1001, fdio_FILE_ORDINAL_GET_VMO = 0x1002, fdio_FILE_ORDINAL_READ = 0x1003, fdio_FILE_ORDINAL_WRITE = 0x1004, }; typedef struct fdio_file_get_class_result { uint32_t status; } fdio_file_get_class_result_t; typedef struct fdio_file_get_vmo_result { uint32_t status; zx_handle_t vmo; } fdio_file_get_vmo_result_t; typedef struct fdio_file_read_args { uint32_t size; } fdio_file_read_args_t; typedef struct fdio_file_read_result { uint32_t status; fidl_vector_t buffer; } fdio_file_read_result_t; typedef struct fdio_file_write_args { fidl_vector_t buffer; } fdio_file_write_args_t; typedef struct fdio_file_write_result { uint32_t status; } fdio_file_write_result_t; /* Encoding and introspection tables go here... */
/* Similar to file.fidl-c.h */
/* Similar to device.fidl-c.h but we have a new struct */ typedef struct ethernet_info { uint32_t feature; uint32_t mtu; uint8_t[6] mac; uint8_t[2]; // padding } ethernet_info_t;
##include <fidl-cpp-native.h> ##include "file.fidl-c.h" namespace fdio { constexpr uint32_t FILE_CLASS = 0x1000; namespace file_native { enum class ordinal { GET_CLASS = fdio_FILE_ORDINAL_GET_CLASS, GET_VMO = fdio_FILE_ORDINAL_GET_VMO, READ = fdio_FILE_ORDINAL_READ, WRITE = fdio_FILE_ORDINAL_WRITE, }; struct get_class_result { uint32_t status; }; struct get_vmo_result { uint32_t status; zx::unowned_vmo vmo; }; struct read_args { uint32_t size; }; struct read_result { uint32_t status; fidl::vector<uint8_t> buffer; }; struct write_args { fidl::vector<uint8_t> buffer; }; struct write_result { uint32_t status; }; } // namespace file_native } // namespace fdio
/* Similar to file.fidl-cpp-native.h but we derive the interface */ namespace device_native { using namespace fidl_native; /* Declarations for derived interface go here... */ };
/* Similar to device.fidl-cpp-native.h but we have a new struct */ typedef struct ethernet_info_native { uint32_t feature; uint32_t mtu; uint8_t[6] mac; uint8_t[2]; // padding } ethernet_info_t;
##include <fidl-cpp.h> ##include "file.fidl-cpp-native.h" namespace fdio { class file { public: typedef std::function<void(uint32_t status)> get_class_callback; typedef std::function<void(uint32_t status, zx::vmo vmo)> get_vmo_callback; typedef std::function<void(uint32_t status, std::optional<std::vector<uint8_t>> buffer)> read_callback; typedef std::function<void(uint32_t status)> write_callback; virtual ~file() = 0; virtual void get_class(get_class_callback callback) = 0; virtual void get_vmo(get_vmo_callback callback) = 0; virtual void read(uint32_t size, read_callback callback) = 0; virtual void write(std::vector<uint8_t> buffer, write_callback callback callback) = 0; class proxy : public file { /* proxy stuff */ }; class stub : public file { /* stub stuff */ }; }; typedef fidl::interface_ptr<file> file_ptr; typedef fidl::interface_req<file> file_req; } // namespace fdio
##include "file.fidl-cpp.h" namespace fdio { /* definition of proxy and stub functions */ } // namespace fdio
/* Similar to file.fidl-cpp.h but we derive the interface */ class device : public file { /* Declarations for derived interface go here... */ };
/* Similar to file.fidl-cpp.cc */
/* Similar to device.fidl-cpp.h but we also have a new struct here */ typedef struct ethernet_info { uint32_t feature; uint32_t mtu; uint8_t[6] mac; uint8_t[2]; // padding } ethernet_info_t;
/* Similar to device.fidl-cpp.cc */
TBD
TBD
TBD
ZX_status_t read_bytes(zx_handle_t channel, size_t size, uint8_t* dest, size_t* out_actual_bytes) { uint8_t wbuf[sizeof(fidl_message_header_t) + sizeof(fdio_file_read_args_t)]; memset(wbufbuffer, 0, sizeof(wbuf)); fidl_message_header_t* header = (fidl_message_header_t*)wbuf; header->transaction_id = 1; header->ordinal = fdio_FILE_ORDINAL_READ; fdio_file_read_args_t* args = (fdio_file_read_args_t*)(header + 1); args->size = size; uint8_t rbuf[ZX_CHANNEL_MAX_MESSAGE]; ZX_channel_call_args_t call; call.wr_bytes = wbuf; call.wr_handles = NULL; call.wr_num_bytes = sizeof(wbuf); call.wr_num_handles = 0; call.rd_bytes = rbuf; call.rd_handles = NULL; call.rd_num_bytes = sizeof(rbuf); call.rd_num_handles = 0; size_t actual_bytes; ZX_status_t status = ZX_channel_call(channel, ZX_TIMEOUT_INFINITE, &call, &actual_bytes, NULL); if (status != ZX_NO_ERROR) return status; header = (fidl_message_header_t*)rbuf; if (actual_bytes < sizeof(fidl_message_header_t) || header->ordinal != fdio_FILE_ORDINAL_READ) return ZX_ERR_PROTOCOL_VIOLATION; fdio_file_read_result_t* result = (fdio_file_read_result_t*)(header + 1); status = fidl_object_decode(fdio_FILE_READ_RESULT_ENCODING, result, actual_bytes - sizeof(fidl_message_header_t), NULL, 0); if (status != ZX_NO_ERROR) return status; *out_actual_bytes = result->buffer.size; memcpy(dest, result->buffer.data, *out_actual_bytes); return ZX_NO_ERROR; }
TBD, similar overall shape to C code other than some library conveniences
void read_bytes_async(const fdio::file_ptr& file, size_t size, file::read_callback callback) { file->read(size, std::move(callback)); }