Added README.md and TO-DOs.
Signed-off-by: Yilin Sun <imi415@imi.moe>
This commit is contained in:
parent
53509e30ba
commit
35f33adb70
|
@ -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
|
||||
|
|
|
@ -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.
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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... */
|
||||
}
|
Loading…
Reference in New Issue