From a508e5ebe533282dc1db1159c1394773579e048b Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Thu, 6 Jun 2019 18:00:08 -0700 Subject: [PATCH] [spinel] add framing and data type docs in header file (#3903) This commit also adds the SPI framing protocol documentation in `ncp_spi.h`. --- src/ncp/ncp_spi.hpp | 82 +++++++++++++ src/ncp/spinel.h | 278 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 353 insertions(+), 7 deletions(-) diff --git a/src/ncp/ncp_spi.hpp b/src/ncp/ncp_spi.hpp index 4bcb34784..b2f542048 100644 --- a/src/ncp/ncp_spi.hpp +++ b/src/ncp/ncp_spi.hpp @@ -37,6 +37,88 @@ #include "ncp/ncp_base.hpp" +/* + * SPI Framing Protocol + * + * Each SPI frame starts with a 5-byte frame header: + * + * +---------+-----+----------+----------+ + * | Octets: | 1 | 2 | 2 | + * +---------+-----+----------+----------+ + * | Fields: | HDR | RECV_LEN | DATA_LEN | + * +---------+-----+----------+----------+ + * + * - "HDR": The first byte is the header byte (defined below) + * - "RECV_LEN": The second and third bytes indicate the largest frame + * size that that device is ready to receive. If zero, then the + * other device must not send any data. (Little endian) + * - "DATA_LEN": The fourth and fifth bytes indicate the size of the + * pending data frame to be sent to the other device. If this value + * is equal-to or less-than the number of bytes that the other device + * is willing to receive, then the data of the frame is immediately + * after the header. (Little Endian) + * + * The "HDR" byte is defined as: + * + * 0 1 2 3 4 5 6 7 + * +---+---+---+---+---+---+---+---+ + * |RST|CRC|CCF| RESERVED |PATTERN| + * +---+---+---+---+---+---+---+---+ + * + * - "RST": This bit is set when that device has been reset since the + * last time `CS` (chip select) was asserted. + * - "CRC": This bit is set when that device supports writing a 16-bit + * CRC at the end of the data. The CRC length is NOT included in + * DATA_LEN. + * - "CCF": "CRC Check Failure". Set if the CRC check on the last + * received frame failed, cleared to zero otherwise. This bit is + * only used if both sides support CRC. + * - "RESERVED": These bits are all reserved for future used. They + * MUST be cleared to zero and MUST be ignored if set. + * - "PATTERN": These bits are set to a fixed value to help distinguish + * valid SPI frames from garbage (by explicitly making "0xFF" and + * "0x00" invalid values). Bit 6 MUST be set to be one and bit 7 + * MUST be cleared (0). A frame received that has any other values + * for these bits MUST be dropped. + * + * Prior to a sending or receiving a frame, the master MAY send a + * 5-octet frame with zeros for both the max receive frame size and the + * the contained frame length. This will induce the slave device to + * indicate the length of the frame it wants to send (if any) and + * indicate the largest frame it is capable of receiving at the moment. + * This allows the master to calculate the size of the next transaction. + * Alternatively, if the master has a frame to send it can just go ahead + * and send a frame of that length and determine if the frame was + * accepted by checking that the "RECV_LEN" from the slave frame is + * larger than the frame the master just tried to send. If the + * "RECV_LEN" is smaller then the frame wasn't accepted and will need to + * be transmitted again. + * + * This protocol can be used either unidirectionally or bidirectionally, + * determined by the behavior of the master and the slave. + * + * If the the master notices "PATTERN" is not set correctly, the master + * should consider the transaction to have failed and try again after 10 + * milliseconds, retrying up to 200 times. After unsuccessfully trying + * 200 times in a row, the master MAY take appropriate remedial action + * (like a NCP hardware reset, or indicating a communication failure to + * a user interface). + * + * At the end of the data of a frame is an optional 16-bit CRC, support + * for which is indicated by the "CRC" bit of the "HDR" byte being set. + * If these bits are set for both the master and slave frames, then CRC + * checking is enabled on both sides, effectively requiring that frame + * sizes be two bytes longer than would be otherwise required. The CRC + * is calculated using the same mechanism used for the CRC calculation + * in HDLC-Lite (See Appendix A.1.2). When both of the "CRC" bits are + * set, both sides must verify that the "CRC" is valid before accepting + * the frame. If not enough bytes were clocked out for the CRC to be + * read, then the frame must be ignored. If enough bytes were clocked + * out to perform a CRC check, but the CRC check fails, then the frame + * must be rejected and the "CRC_FAIL" bit on the next frame (and ONLY + * the next frame) MUST be set. + */ + namespace ot { namespace Ncp { diff --git a/src/ncp/spinel.h b/src/ncp/spinel.h index f47c68fce..4f6a840b8 100644 --- a/src/ncp/spinel.h +++ b/src/ncp/spinel.h @@ -27,21 +27,285 @@ /** * @file - * This file contains definitions of spinel API. + * This file contains definitions of spinel. */ #ifndef SPINEL_HEADER_INCLUDED #define SPINEL_HEADER_INCLUDED 1 /* - * Spinel definition guideline: + * Spinel is a host-controller protocol designed to enable + * inter-operation over simple serial connections between general purpose + * device operating systems (OS) host and network co-processors (NCP) for + * the purpose of controlling and managing the NCP. * - * New NCP firmware should work with an older host driver, i.e., NCP implementation should remain backward compatible. + * --------------------------------------------------------------------------- * - * - Existing fields in the format of an already implemented spinel property or command cannot change. - * - New fields may be appended at the end of the format (or the end of a struct) as long as the NCP implementation - * treats the new fields as optional (i.e., a driver not aware of and therefore not using the new fields should - * continue to function as before). + * Frame Format + * + * A frame is defined simply as the concatenation of + * + * - A header byte + * - A command (up to three bytes) + * - An optional command payload + * + * +---------+--------+-----+-------------+ + * | Octets: | 1 | 1-3 | n | + * +---------+--------+-----+-------------+ + * | Fields: | HEADER | CMD | CMD_PAYLOAD | + * +---------+--------+-----+-------------+ + * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * + * Header Format + * + * The header byte is broken down as follows: + * + * 0 1 2 3 4 5 6 7 + * +---+---+---+---+---+---+---+---+ + * | FLG | IID | TID | + * +---+---+---+---+---+---+---+---+ + * + * + * The flag field of the header byte ("FLG") is always set to the value + * two (or "10" in binary). Any frame received with these bits set to + * any other value else MUST NOT be considered a Spinel frame. + * + * This convention allows Spinel to be line compatible with BTLE HCI. + * By defining the first two bit in this way we can disambiguate between + * Spinel frames and HCI frames (which always start with either "0x01" + * or "0x04") without any additional framing overhead. + * + * The Interface Identifier (IID) is a number between 0 and 3, which + * is associated by the OS with a specific NCP. This allows the protocol + * to support up to 4 NCPs under same connection. + * + * The least significant bits of the header represent the Transaction + * Identifier (TID). The TID is used for correlating responses to the + * commands which generated them. + * + * When a command is sent from the host, any reply to that command sent + * by the NCP will use the same value for the TID. When the host + * receives a frame that matches the TID of the command it sent, it can + * easily recognize that frame as the actual response to that command. + * + * The TID value of zero (0) is used for commands to which a correlated + * response is not expected or needed, such as for unsolicited update + * commands sent to the host from the NCP. + * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * + * The command identifier is a 21-bit unsigned integer encoded in up to + * three bytes using the packed unsigned integer format described below. + * Depending on the semantics of the command in question, a payload MAY + * be included in the frame. The exact composition and length of the + * payload is defined by the command identifier. + * + * --------------------------------------------------------------------------- + * + * Data Packing + * + * Data serialization for properties is performed using a light-weight + * data packing format which was loosely inspired by D-Bus. The format + * of a serialization is defined by a specially formatted string. + * + * This packing format is used for notational convenience. While this + * string-based data-type format has been designed so that the strings + * may be directly used by a structured data parser, such a thing is not + * required to implement Spinel. + * + * Goals: + * + * - Be lightweight and favor direct representation of values. + * - Use an easily readable and memorable format string. + * - Support lists and structures. + * - Allow properties to be appended to structures while maintaining + * backward compatibility. + * + * Each primitive data-type has an ASCII character associated with it. + * Structures can be represented as strings of these characters. For + * example: + * + * - "C": A single unsigned byte. + * - "C6U": A single unsigned byte, followed by a 128-bit IPv6 address, + * followed by a zero-terminated UTF8 string. + * - "A(6)": An array of concatenated IPv6 addresses + * + * In each case, the data is represented exactly as described. For + * example, an array of 10 IPv6 address is stored as 160 bytes. + * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * + * Primitive Types + * + * +----------+----------------------+---------------------------------+ + * | Char | Name | Description | + * +----------+----------------------+---------------------------------+ + * | "." | DATATYPE_VOID | Empty data type. Used | + * | | | internally. | + * | "b" | DATATYPE_BOOL | Boolean value. Encoded in | + * | | | 8-bits as either 0x00 or 0x01. | + * | | | All other values are illegal. | + * | "C" | DATATYPE_UINT8 | Unsigned 8-bit integer. | + * | "c" | DATATYPE_INT8 | Signed 8-bit integer. | + * | "S" | DATATYPE_UINT16 | Unsigned 16-bit integer. | + * | "s" | DATATYPE_INT16 | Signed 16-bit integer. | + * | "L" | DATATYPE_UINT32 | Unsigned 32-bit integer. | + * | "l" | DATATYPE_INT32 | Signed 32-bit integer. | + * | "i" | DATATYPE_UINT_PACKED | Packed Unsigned Integer. See | + * | | | description below | + * | "6" | DATATYPE_IPv6ADDR | IPv6 Address. (Big-endian) | + * | "E" | DATATYPE_EUI64 | EUI-64 Address. (Big-endian) | + * | "e" | DATATYPE_EUI48 | EUI-48 Address. (Big-endian) | + * | "D" | DATATYPE_DATA | Arbitrary data. See related | + * | | | section below for details. | + * | "d" | DATATYPE_DATA_WLEN | Arbitrary data with prepended | + * | | | length. See below for details | + * | "U" | DATATYPE_UTF8 | Zero-terminated UTF8-encoded | + * | | | string. | + * | "t(...)" | DATATYPE_STRUCT | Structured datatype with | + * | | | prepended length. | + * | "A(...)" | DATATYPE_ARRAY | Array of datatypes. Compound | + * | | | type. | + * +----------+----------------------+---------------------------------+ + * + * All multi-byte values are little-endian unless explicitly stated + * otherwise. + * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * + * Packed Unsigned Integer + * + * For certain types of integers, such command or property identifiers, + * usually have a value on the wire that is less than 127. However, in + * order to not preclude the use of values larger than 255, we would + * need to add an extra byte. Doing this would add an extra byte to the + * majority of instances, which can add up in terms of bandwidth. + * + * The packed unsigned integer format is based on the unsigned integer + * format in EXI, except that we limit the maximum value to the + * largest value that can be encoded into three bytes (2,097,151). + * + * For all values less than 127, the packed form of the number is simply + * a single byte which directly represents the number. For values + * larger than 127, the following process is used to encode the value: + * + * 1. The unsigned integer is broken up into _n_ 7-bit chunks and + * placed into _n_ octets, leaving the most significant bit of each + * octet unused. + * 2. Order the octets from least-significant to most-significant. + * (Little-endian) + * 3. Clear the most significant bit of the most significant octet. + * Set the least significant bit on all other octets. + * + * Where `n` is the smallest number of 7-bit chunks you can use to + * represent the given value. + * + * Take the value 1337, for example: + * + * 1337 => 0x0539 + * => [39 0A] + * => [B9 0A] + * + * To decode the value, you collect the 7-bit chunks until you find an + * octet with the most significant bit clear. + * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * + * Data Blobs + * + * There are two types for data blobs: "d" and "D". + * + * - "d" has the length of the data (in bytes) prepended to the data + * (with the length encoded as type "S"). The size of the length + * field is not included in the length. + * - "D" does not have a prepended length: the length of the data is + * implied by the bytes remaining to be parsed. It is an error for + * "D" to not be the last type in a type in a type signature. + * + * This dichotomy allows for more efficient encoding by eliminating + * redundancy. If the rest of the buffer is a data blob, encoding the + * length would be redundant because we already know how many bytes are + * in the rest of the buffer. + * + * In some cases we use "d" even if it is the last field in a type + * signature. We do this to allow for us to be able to append + * additional fields to the type signature if necessary in the future. + * This is usually the case with embedded structs, like in the scan + * results. + * + * For example, let's say we have a buffer that is encoded with the + * datatype signature of "CLLD". In this case, it is pretty easy to + * tell where the start and end of the data blob is: the start is 9 + * bytes from the start of the buffer, and its length is the length of + * the buffer minus 9. (9 is the number of bytes taken up by a byte and + * two longs) + * + * The datatype signature "CLLDU" is illegal because we can't determine + * where the last field (a zero-terminated UTF8 string) starts. But the + * datatype "CLLdU" is legal, because the parser can determine the + * exact length of the data blob-- allowing it to know where the start + * of the next field would be. + * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * + * Structured Data + * + * The structure data type ("t(...)") is a way of bundling together + * several fields into a single structure. It can be thought of as a + * "d" type except that instead of being opaque, the fields in the + * content are known. This is useful for things like scan results where + * you have substructures which are defined by different layers. + * + * For example, consider the type signature "Lt(ES)t(6C)". In this + * hypothetical case, the first struct is defined by the MAC layer, and + * the second struct is defined by the PHY layer. Because of the use of + * structures, we know exactly what part comes from that layer. + * Additionally, we can add fields to each structure without introducing + * backward compatability problems: Data encoded as "Lt(ESU)t(6C)" + * (Notice the extra "U") will decode just fine as "Lt(ES)t(6C)". + * Additionally, if we don't care about the MAC layer and only care + * about the network layer, we could parse as "Lt()t(6C)". + * + * Note that data encoded as "Lt(ES)t(6C)" will also parse as "Ldd", + * with the structures from both layers now being opaque data blobs. + * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * + * Arrays + * + * An array is simply a concatenated set of _n_ data encodings. For + * example, the type "A(6)" is simply a list of IPv6 addresses---one + * after the other. The type "A(6E)" likewise a concatenation of IPv6- + * address/EUI-64 pairs. + * + * If an array contains many fields, the fields will often be surrounded + * by a structure ("t(...)"). This effectively prepends each item in + * the array with its length. This is useful for improving parsing + * performance or to allow additional fields to be added in the future + * in a backward compatible way. If there is a high certainty that + * additional fields will never be added, the struct may be omitted + * (saving two bytes per item). + * + * This specification does not define a way to embed an array as a field + * alongside other fields. + * + * --------------------------------------------------------------------------- + * + * Spinel definition guideline: + * + * New NCP firmware should work with an older host driver, i.e., NCP + * implementation should remain backward compatible. + * + * - Existing fields in the format of an already implemented spinel + * property or command cannot change. + * + * - New fields may be appended at the end of the format (or the end of + * a struct) as long as the NCP implementation treats the new fields as + * optional (i.e., a driver not aware of and therefore not using the + * new fields should continue to function as before). + * + * --------------------------------------------------------------------------- */ #ifdef SPINEL_PLATFORM_HEADER