Added README.md and TO-DOs.

Signed-off-by: Yilin Sun <imi415@imi.moe>
This commit is contained in:
Yilin Sun 2023-01-03 00:01:31 +08:00
parent 53509e30ba
commit 35f33adb70
Signed by: imi415
GPG Key ID: 17F01E106F9F5E0A
7 changed files with 259 additions and 113 deletions

View File

@ -5,6 +5,7 @@ project(esp_nano_hosted)
set(NH_SOURCES
"proto/esp_hosted_config.pb.c"
"src/nh_ctrl_api.c"
"src/nh_shared_if.c"
)
set(NH_INCLUDES

85
README.md Normal file
View File

@ -0,0 +1,85 @@
# ESP Nano Hosted
## Introduction
This project is trying to interface with `esp-hosted` firmware using a MCU-friendly method.
## SPI Frame Format
* Each SPI transaction is fixed 1600 bytes
* Each transaction is prefixed by a 12-byte header, defined as follows:
```c
struct esp_payload_header {
uint8_t if_type:4;
uint8_t if_num:4;
uint8_t flags;
uint16_t len;
uint16_t offset;
uint16_t checksum;
uint16_t seq_num;
uint8_t reserved2;
union {
uint8_t reserved3;
uint8_t hci_pkt_type;
uint8_t priv_pkt_type;
};
} __attribute__((packed));
```
* The `if_type` field is one of the following enum:
```c
typedef enum {
ESP_STA_IF,
ESP_AP_IF,
ESP_SERIAL_IF,
ESP_HCI_IF,
ESP_PRIV_IF,
ESP_TEST_IF,
ESP_MAX_IF,
} ESP_INTERFACE_TYPE;
```
* Control requests and responses are handled through `ESP_SERIAL_IF`, which has the following TLV structure:
```c
/*
* TLV (Type - Length - Value) structure is as follows:
* --------------------------------------------------------------------------------------------
* Endpoint Type | Endpoint Length | Endpoint Value | Data Type | Data Length | Data Value |
* --------------------------------------------------------------------------------------------
*
* Bytes used per field as follows:
* --------------------------------------------------------------------------------------------
* 1 | 2 | Endpoint length | 1 | 2 | Data length |
* --------------------------------------------------------------------------------------------
*/
/* For type fields: */
#define PROTO_PSER_TLV_T_EPNAME 0x01
#define PROTO_PSER_TLV_T_DATA 0x02
/* Some stupid constraints in the original code expects the length should be same... even they don't have to */
#define CTRL_EP_NAME_RESP "ctrlResp"
#define CTRL_EP_NAME_EVENT "ctrlEvnt"
```
* The control messages are encapsulated in protobuf, in the data field of the above TLV.
* For Host-to-ESP messages, the endpoint name `CTRL_EP_NAME_RESP` is used.
* For ESP-to-Host messages, the endpoint name `CTRL_EP_NAME_EVENT` or `CTRL_EP_NAME_RESP` are used.
## Issues
Who owns the buffers? How many buffers should we allocate?
* Minimize the memory block operations (malloc and free)
* Control plane and data plane can be async
* Control API can be blocking until responded
* At least 2 full-sized buffers are required (for full duplex operations)
* To re-use the bus while a request is underway, control plane and data plane RX buffers should be copied
* Bus TX operation should be blocking, however the previous RX data (if any) can be received.
* The response for the request being transmitted will never arrive within the same transaction
## License
Not decided yet, please be patient. At least not before the project is usable.

34
include/nh_common.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef NH_COMMON_H
#define NH_COMMON_H
#include <stdbool.h>
#include <stdint.h>
typedef enum {
NH_RET_SUCCESS = 0,
NH_RET_FAIL = 1,
NH_RET_TIMEOUT = 2,
} nh_ret_t;
/* OSA */
typedef void *nh_osa_semaphore_t;
typedef nh_ret_t (*nh_osa_semaphore_create_t)(void *handle, nh_osa_semaphore_t *sem);
typedef nh_ret_t (*nh_osa_semaphore_take_t)(void *handle, nh_osa_semaphore_t sem, uint32_t timeout_msec);
typedef nh_ret_t (*nh_osa_semaphore_give_t)(void *handle, nh_osa_semaphore_t sem);
typedef nh_ret_t (*nh_osa_semaphore_destroy_t)(void *handle, nh_osa_semaphore_t sem);
typedef nh_ret_t (*nh_osa_buf_allocate_t)(void *handle, uint8_t **buf, uint32_t buf_size);
typedef nh_ret_t (*nh_osa_buf_free_t)(void *handle, uint8_t *buf);
typedef struct {
nh_osa_buf_allocate_t buf_allocate;
nh_osa_buf_free_t buf_free;
nh_osa_semaphore_create_t sem_create;
nh_osa_semaphore_take_t sem_take;
nh_osa_semaphore_give_t sem_give;
nh_osa_semaphore_destroy_t sem_destroy;
void *user_data;
} nh_osa_t;
#endif // NH_COMMON_H

View File

@ -1,28 +1,7 @@
#ifndef NH_CTRL_API_H
#define NH_CTRL_API_H
#include <stdbool.h>
#include <stdint.h>
typedef enum {
NH_RET_SUCCESS = 0,
NH_RET_FAIL = 1,
NH_RET_TIMEOUT = 2,
} nh_ret_t;
/* OSA */
typedef void *nh_osa_semaphore_t;
typedef nh_ret_t (*nh_osa_semaphore_create_t)(void *handle, nh_osa_semaphore_t *sem);
typedef nh_ret_t (*nh_osa_semaphore_take_t)(void *handle, nh_osa_semaphore_t sem, uint32_t timeout_msec);
typedef nh_ret_t (*nh_osa_semaphore_give_t)(void *handle, nh_osa_semaphore_t sem);
typedef nh_ret_t (*nh_osa_buf_allocate_t)(void *handle, uint8_t **buf, uint32_t buf_size);
typedef nh_ret_t (*nh_osa_buf_free_t)(void *handle, uint8_t *buf);
/* OPS */
typedef nh_ret_t (*nh_ops_spi_xfer_t)(void *handle, uint8_t *tx_data, uint16_t *rx_data, uint16_t len);
typedef nh_ret_t (*nh_ops_hs_poll_t)(void *handle);
typedef nh_ret_t (*nh_ops_drdy_read_t)(void *handle, bool *rdy);
#include "nh_shared_if.h"
/* Event callbacks */
typedef void (*nh_cb_init_t)(void *handle);
@ -30,20 +9,6 @@ typedef void (*nh_cb_heartbeat_t)(void *handle);
typedef void (*nh_cb_sta_disconn_from_ap_t)(void *handle);
typedef void (*nh_cb_sta_disconn_from_soft_ap_t)(void *handle);
typedef struct {
nh_osa_buf_allocate_t buf_allocate;
nh_osa_buf_free_t buf_free;
nh_osa_semaphore_create_t sem_create;
nh_osa_semaphore_take_t sem_take;
nh_osa_semaphore_give_t sem_give;
} nh_ctrl_api_osa_t;
typedef struct {
nh_ops_spi_xfer_t xfer;
nh_ops_hs_poll_t handshake_poll;
nh_ops_drdy_read_t data_ready_read;
} nh_ctrl_api_ops_t;
typedef struct {
nh_cb_init_t init;
nh_cb_heartbeat_t heartbeat;
@ -52,22 +17,14 @@ typedef struct {
} nh_ctrl_api_cb_t;
typedef struct {
nh_ctrl_api_osa_t osa;
nh_ctrl_api_ops_t ops;
nh_ctrl_api_cb_t cb;
void *user_data;
nh_shared_if_t *shared_if;
nh_osa_t *osa;
nh_ctrl_api_cb_t cb;
void *user_data;
/* Private states */
uint8_t *p_buf_tx;
uint8_t *p_buf_rx;
bool p_flag_request; /* !! Guarded by: p_semaphore_request */
nh_osa_semaphore_t p_semaphore_event;
nh_osa_semaphore_t p_semaphore_request;
nh_osa_semaphore_t p_semaphore_response;
} nh_ctrl_api_t;
nh_ret_t nh_ctrl_api_init(nh_ctrl_api_t *api);
void nh_ctrl_api_task(nh_ctrl_api_t *api);
void nh_ctrl_api_inject_data_ready(nh_ctrl_api_t *api);
#endif // NH_CTRL_API_H

41
include/nh_shared_if.h Normal file
View File

@ -0,0 +1,41 @@
#ifndef NH_SHARED_IF_H
#define NH_SHARED_IF_H
#include "nh_common.h"
/* OPS */
typedef nh_ret_t (*nh_shared_if_ops_spi_xfer_t)(void *handle, uint8_t *tx_data, uint8_t *rx_data, uint32_t len);
typedef nh_ret_t (*nh_shared_if_ops_reset_t)(void *handle);
typedef nh_ret_t (*nh_shared_if_ops_hs_poll_t)(void *handle);
typedef nh_ret_t (*nh_shared_if_ops_drdy_read_t)(void *handle, bool *rdy);
typedef struct {
nh_shared_if_ops_spi_xfer_t xfer;
nh_shared_if_ops_reset_t reset;
nh_shared_if_ops_hs_poll_t handshake_poll;
nh_shared_if_ops_drdy_read_t data_ready_read;
void *user_data;
} nh_shared_if_ops_t;
typedef struct {
nh_shared_if_ops_t ops;
nh_osa_t *osa;
/* Private states */
nh_osa_semaphore_t p_semaphore_xfer_req;
nh_osa_semaphore_t p_semaphore_tx;
uint8_t *p_buf_frame_tx; /* SPI TX frame, guarded */
uint8_t *p_buf_frame_rx; /* SPI RX frame, guarded */
uint8_t *p_buf_ctrl_tx; /* Control API TX payload */
uint8_t *p_buf_ctrl_rx; /* Control API RX payload */
} nh_shared_if_t;
nh_ret_t nh_shared_if_init(nh_shared_if_t *shared_if);
void nh_shared_if_task(nh_shared_if_t *shared_if);
void nh_shared_if_inject_data_ready(nh_shared_if_t *shared_if);
/* Internal APIs */
nh_ret_t nh_shared_if_ctrl_send(nh_shared_if_t *shared_if, uint8_t *tx_payload, uint32_t len, uint32_t timeout_ms);
nh_ret_t nh_shared_if_ctrl_recv(nh_shared_if_t *shared_if, uint8_t *rx_payload, uint32_t *len, uint32_t timeout_ms);
#endif // NH_SHARED_IF_H

View File

@ -1,5 +1,8 @@
#include "nh_ctrl_api.h"
#include "pb_encode.h"
#include "pb_decode.h"
#include "esp_hosted_config.pb.h"
#define NH_XFER_BUF_SIZE 1600
@ -7,81 +10,31 @@
#define NH_SEMAPHORE_EVENT_TIMEOUT 1000
#define NH_SEMAPHORE_REQ_TIMEOUT 5000
static nh_ret_t nh_ctrl_api_general_request(nh_ctrl_api_t *api, CtrlMsg *msg);
nh_ret_t nh_ctrl_api_init(nh_ctrl_api_t *api) {
nh_ret_t ret = NH_RET_SUCCESS;
/* Create semaphore for input events (Data Ready, Ctrl API call) */
ret = api->osa.sem_create(api->user_data, &api->p_semaphore_event);
if (ret != NH_RET_SUCCESS) {
return ret;
}
/* Create semaphore for control API requests, we can support maximum 1 control API request at any given time */
ret = api->osa.sem_create(api->user_data, &api->p_semaphore_request);
if (ret != NH_RET_SUCCESS) {
return ret;
}
/* Create semaphore for control API responses */
ret = api->osa.sem_create(api->user_data, &api->p_semaphore_response);
if (ret != NH_RET_SUCCESS) {
return ret;
}
/* TODO: Determine the initial value of each semaphores. */
/* Allocate TX and RX buffers, we should minimalize the malloc-free counts to reduce memory fragmentation */
ret = api->osa.buf_allocate(api->user_data, &api->p_buf_tx, NH_XFER_BUF_SIZE);
if (ret != NH_RET_SUCCESS) {
return ret;
}
ret = api->osa.buf_allocate(api->user_data, &api->p_buf_rx, NH_XFER_BUF_SIZE);
if (ret != NH_RET_SUCCESS) {
return ret;
}
return NH_RET_SUCCESS;
}
void nh_ctrl_api_task(nh_ctrl_api_t *api) {
nh_ret_t ret = NH_RET_SUCCESS;
ret = api->osa.sem_take(api->user_data, api->p_semaphore_event, NH_SEMAPHORE_EVENT_TIMEOUT);
if (ret != NH_RET_SUCCESS) {
/* Failed to acquire semaphore due to error or timed out */
return;
}
/* Acquired semaphore, could be an event or a request, or both. */
return ret;
}
nh_ret_t nh_ctrl_api_get_mac(nh_ctrl_api_t *api, uint8_t *mac) {
nh_ret_t ret;
ret = api->osa.sem_take(api->user_data, api->p_semaphore_request, NH_SEMAPHORE_REQ_TIMEOUT);
if (ret != NH_RET_SUCCESS) {
/* Failed to take the request semaphore in time, maybe due to another request in under way */
return ret;
}
/* TODO: Encode the request */
CtrlMsg_Req_GetMacAddress req = CtrlMsg_Req_GetMacAddress_init_zero;
req.mode = Ctrl_WifiMode_STA;
CtrlMsg req_msg = CtrlMsg_init_zero;
api->p_flag_request = true;
ret = api->osa.sem_give(api->user_data, api->p_semaphore_event);
if (ret != NH_RET_SUCCESS) {
goto give_sem_and_exit;
}
ret = api->osa.sem_take(api->user_data, api->p_semaphore_response, NH_SEMAPHORE_REQ_TIMEOUT);
if(ret != NH_RET_SUCCESS) {
goto give_sem_and_exit;
}
give_sem_and_exit:
api->osa.sem_give(api->user_data, api->p_semaphore_request);
req_msg.msg_type = CtrlMsgType_Req;
req_msg.msg_id = CtrlMsgId_Req_GetMACAddress;
req_msg.payload.req_get_mac_address.mode = Ctrl_WifiMode_STA;
ret = nh_ctrl_api_general_request(api, &req_msg);
return ret;
}
static nh_ret_t nh_ctrl_api_general_request(nh_ctrl_api_t *api, CtrlMsg *msg) {
nh_ret_t ret;
pb_ostream_t ostream = pb_ostream_from_buffer(api->p_buf_tx, NH_XFER_BUF_SIZE);
pb_encode(&ostream, CtrlMsg_fields, msg);
}

75
src/nh_shared_if.c Normal file
View File

@ -0,0 +1,75 @@
#include "nh_shared_if.h"
#define NH_XFER_REQ_TIMEOUT 5000
#define NH_XFER_MAX_SIZE 1600
nh_ret_t nh_shared_if_init(nh_shared_if_t *shared_if) {
nh_ret_t ret;
ret = shared_if->osa->sem_create(shared_if->osa->user_data, &shared_if->p_semaphore_xfer_req);
if (ret != NH_RET_SUCCESS) {
return ret;
}
ret = shared_if->osa->buf_allocate(shared_if->osa->user_data, &shared_if->p_buf_frame_tx, NH_XFER_MAX_SIZE);
if (ret != NH_RET_SUCCESS) {
goto err_free_sem_xfer;
}
ret = shared_if->osa->buf_allocate(shared_if->osa->user_data, &shared_if->p_buf_frame_rx, NH_XFER_MAX_SIZE);
if (ret != NH_RET_SUCCESS) {
goto err_free_tx_buf;
}
return ret;
err_free_tx_buf:
shared_if->osa->buf_free(shared_if->osa->user_data, shared_if->p_buf_frame_tx);
err_free_sem_xfer:
shared_if->osa->sem_destroy(shared_if->osa->user_data, shared_if->p_semaphore_xfer_req);
return ret;
}
void nh_shared_if_task(nh_shared_if_t *shared_if) {
nh_ret_t ret;
ret = shared_if->osa->sem_take(shared_if->osa->user_data, shared_if->p_semaphore_xfer_req, NH_XFER_REQ_TIMEOUT);
if (ret != NH_RET_SUCCESS) {
return;
}
/* Wait for device available */
ret = shared_if->ops.handshake_poll(shared_if->ops.user_data);
if(ret != NH_RET_SUCCESS) {
return;
}
/* Taken the xfer request semaphore. The possible reasons are:
* 1: Some data (could from any interfaces) needs to be sent
* 2: Data ready pin has been toggled, some data needs to be received
*/
bool rx_available = false;
/* Check if new data is available */
ret = shared_if->ops.data_ready_read(shared_if->ops.user_data, &rx_available);
if (ret != NH_RET_SUCCESS) {
return;
}
ret = shared_if->ops.xfer(shared_if->ops.user_data, shared_if->p_buf_frame_tx, shared_if->p_buf_frame_rx,
NH_XFER_MAX_SIZE);
if (ret != NH_RET_SUCCESS) {
return;
}
/* If we do not have RX data... */
if (!rx_available) {
return;
}
/* TODO: Check interface type... */
}