summaryrefslogtreecommitdiff
path: root/class/adb
diff options
context:
space:
mode:
Diffstat (limited to 'class/adb')
-rw-r--r--class/adb/usbd_adb.c616
-rw-r--r--class/adb/usbd_adb.h68
-rw-r--r--class/adb/usbd_adb_cherrysh.c355
-rw-r--r--class/adb/usbd_adb_sync.c932
4 files changed, 1758 insertions, 213 deletions
diff --git a/class/adb/usbd_adb.c b/class/adb/usbd_adb.c
index dc664309..b82ab998 100644
--- a/class/adb/usbd_adb.c
+++ b/class/adb/usbd_adb.c
@@ -1,203 +1,367 @@
/*
- * Copyright (c) 2024, sakumisu
+ * Copyright (c) 2024 ~ 2026, sakumisu
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "usbd_core.h"
#include "usbd_adb.h"
-#define ADB_OUT_EP_IDX 0
-#define ADB_IN_EP_IDX 1
+#undef USB_DBG_TAG
+#define USB_DBG_TAG "usbd_adb"
+#include "usb_log.h"
-#define ADB_STATE_READ_MSG 0
-#define ADB_STATE_READ_DATA 1
-#define ADB_STATE_WRITE_MSG 2
-#define ADB_STATE_WRITE_DATA 3
-#define ADB_STATE_AWRITE_MSG 4
-#define ADB_STATE_AWRITE_DATA 5
+#define ADB_OUT_EP_IDX 0u
+#define ADB_IN_EP_IDX 1u
-#define MAX_PAYLOAD_V1 (4 * 1024)
-#define MAX_PAYLOAD_V2 (256 * 1024)
-#define MAX_PAYLOAD MAX_PAYLOAD_V1
-#define A_VERSION 0x01000000
+#define ADB_RX_STATE_READ_MSG 0u /* armed, waiting for the 24-byte header */
+#define ADB_RX_STATE_READ_DATA 1u /* armed, waiting for the payload */
+#define ADB_RX_STATE_COMPLETE 2u /* full packet ready, ISR already notified */
-#define A_SYNC 0x434e5953
-#define A_CNXN 0x4e584e43
-#define A_OPEN 0x4e45504f
-#define A_OKAY 0x59414b4f
-#define A_CLSE 0x45534c43
-#define A_WRTE 0x45545257
-#define A_AUTH 0x48545541
+#define ADB_TX_STATE_MSG 0u /* 24-byte header in flight */
+#define ADB_TX_STATE_DATA 1u /* payload in flight */
+#define ADB_TX_STATE_COMPLETE 2u /* transfer finished, the next send may start */
-struct adb_msg {
- uint32_t command; /* command identifier constant (A_CNXN, ...) */
- uint32_t arg0; /* first argument */
- uint32_t arg1; /* second argument */
- uint32_t data_length; /* length of payload (0 is allowed) */
- uint32_t data_crc32; /* crc32 of data payload */
- uint32_t magic; /* command ^ 0xffffffff */
-};
+#define ADB_DAEMON_THREAD_STACK 4096u
+#define ADB_DAEMON_THREAD_PRIO 10u
-struct adb_packet {
- USB_MEM_ALIGNX struct adb_msg msg;
- USB_MEM_ALIGNX uint8_t payload[USB_ALIGN_UP(MAX_PAYLOAD, CONFIG_USB_ALIGN_SIZE)];
-};
+struct usbd_adb_priv {
+ struct usbd_endpoint ep_data[2];
+ uint8_t busid;
-struct usbd_adb {
- uint8_t state;
- uint8_t common_state;
- uint8_t write_state;
- bool writable;
- uint32_t localid;
- uint32_t shell_remoteid;
- uint32_t file_remoteid;
-} adb_client;
+ const struct adb_service *svc_tab[ADB_MAX_SERVICE];
+ uint32_t remote_id[ADB_MAX_SERVICE];
+ bool svc_opened[ADB_MAX_SERVICE];
-static struct usbd_endpoint adb_ep_data[2];
+ volatile uint8_t rx_state;
+ volatile uint8_t tx_state;
-USB_NOCACHE_RAM_SECTION struct adb_packet tx_packet;
-USB_NOCACHE_RAM_SECTION struct adb_packet rx_packet;
+ usb_osal_sem_t rx_sem;
+ usb_osal_sem_t tx_sem;
+ usb_osal_mutex_t tx_lock;
+ usb_osal_thread_t rx_thread;
+} g_usbd_adb;
-static inline uint32_t adb_packet_checksum(struct adb_packet *packet)
+USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX struct adb_packet adb_tx_packet;
+USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX struct adb_packet adb_rx_packet;
+
+static uint32_t adb_packet_checksum(const struct adb_packet *packet)
{
uint32_t sum = 0;
uint32_t i;
- for (i = 0; i < packet->msg.data_length; ++i) {
- sum += (uint32_t)(packet->payload[i]);
+ for (i = 0; i < packet->msg.data_length; i++) {
+ sum += (uint32_t)packet->payload[i];
}
return sum;
}
-static uint32_t usbd_adb_get_remoteid(uint32_t localid)
+static int adb_svc_slot(uint32_t localid)
{
- if (localid == ADB_SHELL_LOALID) {
- return adb_client.shell_remoteid;
- } else {
- return adb_client.file_remoteid;
+ uint32_t i;
+
+ for (i = 0; i < ADB_MAX_SERVICE; i++) {
+ if (g_usbd_adb.svc_tab[i] && (g_usbd_adb.svc_tab[i]->localid == localid)) {
+ return (int)i;
+ }
}
+
+ return -1;
}
-static void adb_send_msg(struct adb_packet *packet)
+int usbd_adb_service_register(const struct adb_service *svc)
{
- adb_client.common_state = ADB_STATE_WRITE_MSG;
+ uint32_t i;
+
+ if (!svc || !svc->name || (svc->localid == 0u)) {
+ return -1;
+ }
- packet->msg.data_crc32 = adb_packet_checksum(packet);
- packet->msg.magic = packet->msg.command ^ 0xffffffff;
+ if (adb_svc_slot(svc->localid) >= 0) {
+ return -1; /* local id already taken */
+ }
- usbd_ep_start_write(0, adb_ep_data[ADB_IN_EP_IDX].ep_addr, (uint8_t *)&packet->msg, sizeof(struct adb_msg));
+ for (i = 0; i < ADB_MAX_SERVICE; i++) {
+ if (g_usbd_adb.svc_tab[i] == NULL) {
+ g_usbd_adb.svc_tab[i] = svc;
+ g_usbd_adb.remote_id[i] = 0;
+ g_usbd_adb.svc_opened[i] = false;
+ return 0;
+ }
+ }
+
+ return -1; /* table full */
}
-static void adb_send_okay(struct adb_packet *packet, uint32_t localid)
+static uint32_t usbd_adb_service_get_remoteid(uint32_t localid)
{
- packet->msg.command = A_OKAY;
- packet->msg.arg0 = localid;
- packet->msg.arg1 = usbd_adb_get_remoteid(localid);
- packet->msg.data_length = 0;
+ int slot = adb_svc_slot(localid);
- adb_send_msg(&tx_packet);
+ return (slot >= 0) ? g_usbd_adb.remote_id[slot] : 0u;
}
-static void adb_send_close(struct adb_packet *packet, uint32_t localid, uint32_t remoteid)
+static bool usbd_adb_service_is_opened(uint32_t localid)
{
- packet->msg.command = A_CLSE;
- packet->msg.arg0 = localid;
- packet->msg.arg1 = remoteid;
- packet->msg.data_length = 0;
+ int slot = adb_svc_slot(localid);
- adb_send_msg(&tx_packet);
+ return (slot >= 0) ? g_usbd_adb.svc_opened[slot] : false;
}
-void usbd_adb_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes)
+static int usbd_adb_send_internal(uint32_t command,
+ uint32_t arg0,
+ uint32_t arg1,
+ const uint8_t *data,
+ uint32_t len)
{
- (void)ep;
+ int ret;
- if (adb_client.common_state == ADB_STATE_READ_MSG) {
- USB_ASSERT(nbytes == sizeof(struct adb_msg));
- USB_ASSERT(rx_packet.msg.data_length <= sizeof(rx_packet.payload));
+ if (len && !data) {
+ return -1;
+ }
- USB_LOG_DBG("command:%x arg0:%x arg1:%x len:%d\r\n",
- rx_packet.msg.command,
- rx_packet.msg.arg0,
- rx_packet.msg.arg1,
- rx_packet.msg.data_length);
+ /* one transmitter at a time */
+ usb_osal_mutex_take(g_usbd_adb.tx_lock);
- if (rx_packet.msg.data_length) {
- /* setup next out ep read transfer */
- adb_client.common_state = ADB_STATE_READ_DATA;
- usbd_ep_start_read(busid, adb_ep_data[ADB_OUT_EP_IDX].ep_addr, rx_packet.payload, rx_packet.msg.data_length);
- } else {
- if (rx_packet.msg.command == A_CLSE) {
- adb_client.writable = false;
- usbd_adb_notify_write_done();
- USB_LOG_INFO("Close remoteid:%x\r\n", rx_packet.msg.arg0);
- }
- adb_client.common_state = ADB_STATE_READ_MSG;
- /* setup first out ep read transfer */
- usbd_ep_start_read(busid, adb_ep_data[ADB_OUT_EP_IDX].ep_addr, (uint8_t *)&rx_packet.msg, sizeof(struct adb_msg));
+ adb_tx_packet.msg.command = command;
+ adb_tx_packet.msg.arg0 = arg0;
+ adb_tx_packet.msg.arg1 = arg1;
+ adb_tx_packet.msg.data_length = len;
+ if (len) {
+ memcpy(adb_tx_packet.payload, data, len);
+ }
+ adb_tx_packet.msg.data_crc32 = adb_packet_checksum(&adb_tx_packet);
+ adb_tx_packet.msg.magic = command ^ 0xffffffffu;
+
+ /* drop a stale completion left behind by a previous timeout */
+ usb_osal_sem_reset(g_usbd_adb.tx_sem);
+
+ g_usbd_adb.tx_state = ADB_TX_STATE_MSG;
+ usbd_ep_start_write(g_usbd_adb.busid, g_usbd_adb.ep_data[ADB_IN_EP_IDX].ep_addr,
+ (uint8_t *)&adb_tx_packet.msg, sizeof(struct adb_msg));
+
+ ret = usb_osal_sem_take(g_usbd_adb.tx_sem, ADB_TX_TIMEOUT_MS);
+ if (ret != 0) {
+ USB_LOG_ERR("tx timeout, cmd %08x len %u\r\n", (unsigned)command, (unsigned)len);
+ g_usbd_adb.tx_state = ADB_TX_STATE_COMPLETE;
+ ret = -2;
+ } else {
+ ret = 0;
+ }
+
+ usb_osal_mutex_give(g_usbd_adb.tx_lock);
+
+ return ret;
+}
+
+static void adb_reply_okay(uint32_t localid, uint32_t remoteid)
+{
+ usbd_adb_send_internal(A_OKAY, localid, remoteid, NULL, 0);
+}
+
+static void adb_reply_close(uint32_t localid, uint32_t remoteid)
+{
+ usbd_adb_send_internal(A_CLSE, localid, remoteid, NULL, 0);
+}
+
+static void adb_handle_cnxn(struct adb_packet *pkt)
+{
+ static const char support_feature[] =
+ "device::"
+ "ro.product.name=cherryadb;"
+ "ro.product.model=cherrysh;"
+ "ro.product.device=cherryadb;"
+ "features=cmd,shell_v1";
+
+ (void)pkt;
+
+ usbd_adb_send_internal(A_CNXN, ADB_VERSION, ADB_MAX_PAYLOAD,
+ (const uint8_t *)support_feature, strlen(support_feature));
+}
+
+static void adb_handle_open(struct adb_packet *pkt)
+{
+ uint32_t remoteid = pkt->msg.arg0;
+ char *dest = (char *)pkt->payload;
+ uint32_t i;
+
+ if ((pkt->msg.data_length == 0) || (pkt->msg.data_length >= ADB_MAX_PAYLOAD)) {
+ adb_reply_close(0, remoteid);
+ return;
+ }
+
+ /* the destination string is not guaranteed to be NUL terminated */
+ dest[pkt->msg.data_length] = '\0';
+
+ for (i = 0; i < ADB_MAX_SERVICE; i++) {
+ const struct adb_service *svc = g_usbd_adb.svc_tab[i];
+ size_t name_len;
+
+ if (!svc) {
+ continue;
+ }
+
+ name_len = strlen(svc->name);
+ if (strncmp(dest, svc->name, name_len) != 0) {
+ continue;
}
- } else if (adb_client.common_state == ADB_STATE_READ_DATA) {
- switch (rx_packet.msg.command) {
- case A_SYNC:
- break;
- case A_CNXN: /* CONNECT(version, maxdata, "system-id-string") */
- {
- char *support_feature = "device::"
- "ro.product.name=cherryadb;"
- "ro.product.model=cherrysh;"
- "ro.product.device=cherryadb;"
- "features=cmd,shell_v1";
+ g_usbd_adb.remote_id[i] = remoteid;
+ g_usbd_adb.svc_opened[i] = true;
+
+ if (svc->on_open) {
+ svc->on_open(remoteid);
+ }
+
+ adb_reply_okay(svc->localid, remoteid);
+ return;
+ }
+
+ USB_LOG_WRN("unsupported destination '%s'\r\n", dest);
+ adb_reply_close(0, remoteid);
+}
+
+static void adb_handle_write(struct adb_packet *pkt)
+{
+ uint32_t remoteid = pkt->msg.arg0;
+ uint32_t localid = pkt->msg.arg1;
+ int slot = adb_svc_slot(localid);
+
+ if ((slot < 0) || !g_usbd_adb.svc_opened[slot] || (g_usbd_adb.remote_id[slot] != remoteid)) {
+ adb_reply_close(0, remoteid);
+ return;
+ }
+
+ if (g_usbd_adb.svc_tab[slot]->on_write) {
+ g_usbd_adb.svc_tab[slot]->on_write(remoteid, pkt->payload, pkt->msg.data_length);
+ }
+
+ adb_reply_okay(localid, remoteid);
+}
+
+static void adb_handle_close(struct adb_packet *pkt)
+{
+ uint32_t remoteid = pkt->msg.arg0;
+ uint32_t localid = pkt->msg.arg1;
+ int slot = adb_svc_slot(localid);
+
+ if (slot < 0) {
+ return;
+ }
+
+ g_usbd_adb.svc_opened[slot] = false;
+
+ if (g_usbd_adb.svc_tab[slot]->on_close) {
+ g_usbd_adb.svc_tab[slot]->on_close(remoteid);
+ }
+
+ g_usbd_adb.remote_id[slot] = 0;
+}
+
+static void adb_dispatch(struct adb_packet *pkt)
+{
+ switch (pkt->msg.command) {
+ case A_CNXN:
+ adb_handle_cnxn(pkt);
+ break;
+ case A_OPEN:
+ adb_handle_open(pkt);
+ break;
+ case A_WRTE:
+ adb_handle_write(pkt);
+ break;
+ case A_CLSE:
+ adb_handle_close(pkt);
+ break;
+ case A_OKAY:
+ /* the host acknowledged one of our writes, nothing to answer */
+ break;
+ case A_SYNC:
+ case A_AUTH:
+ break;
+ default:
+ USB_LOG_WRN("unknown command 0x%08x\r\n", (unsigned)pkt->msg.command);
+ break;
+ }
+}
+
+static void adb_handle_packet(struct adb_packet *pkt)
+{
+ if (pkt->msg.magic != (pkt->msg.command ^ 0xffffffffu)) {
+ USB_LOG_ERR("bad magic 0x%08x for command 0x%08x\r\n",
+ (unsigned)pkt->msg.magic, (unsigned)pkt->msg.command);
+ return;
+ }
+
+ adb_dispatch(pkt);
+}
+
+static void adb_rx_thread_entry(CONFIG_USB_OSAL_THREAD_SET_ARGV)
+{
+ (void)argument;
- tx_packet.msg.command = A_CNXN;
- tx_packet.msg.arg0 = A_VERSION;
- tx_packet.msg.arg1 = MAX_PAYLOAD;
- tx_packet.msg.data_length = strlen(support_feature);
- memcpy(tx_packet.payload, support_feature, strlen(support_feature));
+ while (1) {
+ if (usb_osal_sem_take(g_usbd_adb.rx_sem, USB_OSAL_WAITING_FOREVER) != 0) {
+ continue;
+ }
- adb_send_msg(&tx_packet);
+ adb_handle_packet(&adb_rx_packet);
- adb_client.writable = false;
- } break;
- case A_OPEN: /* OPEN(local-id, 0, "destination") */
- rx_packet.payload[rx_packet.msg.data_length] = '\0';
+ /* the packet has been consumed, arm the OUT endpoint again */
+ g_usbd_adb.rx_state = ADB_RX_STATE_READ_MSG;
+ usbd_ep_start_read(g_usbd_adb.busid, g_usbd_adb.ep_data[ADB_OUT_EP_IDX].ep_addr,
+ (uint8_t *)&adb_rx_packet.msg, sizeof(struct adb_msg));
+ }
+}
- if (strncmp((const char *)rx_packet.payload, "shell:", 6) == 0) {
- adb_client.localid = ADB_SHELL_LOALID;
- adb_client.shell_remoteid = rx_packet.msg.arg0;
- adb_send_okay(&tx_packet, ADB_SHELL_LOALID);
+void usbd_adb_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes)
+{
+ (void)ep;
- USB_LOG_INFO("Open shell service, remoteid:%x\r\n", rx_packet.msg.arg0);
- } else if (strncmp((const char *)rx_packet.payload, "sync:", 5) == 0) {
- adb_client.localid = ADB_FILE_LOALID;
- adb_client.file_remoteid = rx_packet.msg.arg0;
- adb_send_okay(&tx_packet, ADB_FILE_LOALID);
- USB_LOG_INFO("Open file service, remoteid:%x\r\n", rx_packet.msg.arg0);
- }
- break;
- case A_OKAY:
+ if (nbytes == 0) {
+ g_usbd_adb.rx_state = ADB_RX_STATE_READ_MSG;
+ usbd_ep_start_read(busid, g_usbd_adb.ep_data[ADB_OUT_EP_IDX].ep_addr, (uint8_t *)&adb_rx_packet.msg,
+ sizeof(struct adb_msg));
+ return;
+ }
- break;
- case A_CLSE:
+ if (g_usbd_adb.rx_state == ADB_RX_STATE_READ_MSG) {
+ if (nbytes != sizeof(struct adb_msg)) {
+ USB_LOG_ERR("short header %u bytes\r\n", (unsigned)nbytes);
+ g_usbd_adb.rx_state = ADB_RX_STATE_READ_MSG;
+ usbd_ep_start_read(busid, g_usbd_adb.ep_data[ADB_OUT_EP_IDX].ep_addr, (uint8_t *)&adb_rx_packet.msg,
+ sizeof(struct adb_msg));
+ return;
+ }
- break;
- case A_WRTE: /* WRITE(local-id, remote-id, "data") */
- if ((rx_packet.msg.arg0 == adb_client.shell_remoteid) && (rx_packet.msg.arg1 == ADB_SHELL_LOALID)) {
- adb_send_okay(&tx_packet, rx_packet.msg.arg1);
- } else if ((rx_packet.msg.arg0 == adb_client.file_remoteid) && (rx_packet.msg.arg1 == ADB_FILE_LOALID)) {
- adb_send_okay(&tx_packet, rx_packet.msg.arg1);
- } else {
- adb_send_close(&tx_packet, 0, rx_packet.msg.arg0);
- }
- break;
- case A_AUTH:
+ if (adb_rx_packet.msg.data_length > ADB_MAX_PAYLOAD) {
+ USB_LOG_ERR("header length %u too large\r\n",
+ (unsigned)adb_rx_packet.msg.data_length);
+ g_usbd_adb.rx_state = ADB_RX_STATE_READ_MSG;
+ usbd_ep_start_read(busid, g_usbd_adb.ep_data[ADB_OUT_EP_IDX].ep_addr, (uint8_t *)&adb_rx_packet.msg,
+ sizeof(struct adb_msg));
+ return;
+ }
- break;
+ if (adb_rx_packet.msg.data_length) {
+ USB_LOG_DBG("rx cmd 0x%08x arg0 0x%x arg1 0x%x len %u\r\n",
+ (unsigned)adb_rx_packet.msg.command,
+ (unsigned)adb_rx_packet.msg.arg0,
+ (unsigned)adb_rx_packet.msg.arg1,
+ (unsigned)adb_rx_packet.msg.data_length);
- default:
- break;
+ g_usbd_adb.rx_state = ADB_RX_STATE_READ_DATA;
+ usbd_ep_start_read(busid, g_usbd_adb.ep_data[ADB_OUT_EP_IDX].ep_addr, adb_rx_packet.payload,
+ adb_rx_packet.msg.data_length);
+ } else {
+ /* no payload -> the packet is already complete */
+ g_usbd_adb.rx_state = ADB_RX_STATE_COMPLETE;
+ usb_osal_sem_give(g_usbd_adb.rx_sem);
}
+ } else if (g_usbd_adb.rx_state == ADB_RX_STATE_READ_DATA) {
+ /* payload complete -> the packet is complete */
+ g_usbd_adb.rx_state = ADB_RX_STATE_COMPLETE;
+ usb_osal_sem_give(g_usbd_adb.rx_sem);
+ } else {
}
}
@@ -206,104 +370,152 @@ void usbd_adb_bulk_in(uint8_t busid, uint8_t ep, uint32_t nbytes)
(void)ep;
(void)nbytes;
- if (adb_client.common_state == ADB_STATE_WRITE_MSG) {
- if (tx_packet.msg.data_length) {
- adb_client.common_state = ADB_STATE_WRITE_DATA;
- usbd_ep_start_write(busid, adb_ep_data[ADB_IN_EP_IDX].ep_addr, tx_packet.payload, tx_packet.msg.data_length);
- } else {
- if (rx_packet.msg.command == A_WRTE) {
- adb_client.writable = true;
- if (adb_client.localid == ADB_SHELL_LOALID) {
- usbd_adb_notify_shell_read(rx_packet.payload, rx_packet.msg.data_length);
- } else {
- }
- }
- adb_client.common_state = ADB_STATE_READ_MSG;
- /* setup first out ep read transfer */
- usbd_ep_start_read(busid, adb_ep_data[ADB_OUT_EP_IDX].ep_addr, (uint8_t *)&rx_packet.msg, sizeof(struct adb_msg));
- }
- } else if (adb_client.common_state == ADB_STATE_WRITE_DATA) {
- adb_client.common_state = ADB_STATE_READ_MSG;
- /* setup first out ep read transfer */
- usbd_ep_start_read(busid, adb_ep_data[ADB_OUT_EP_IDX].ep_addr, (uint8_t *)&rx_packet.msg, sizeof(struct adb_msg));
- } else if (adb_client.write_state == ADB_STATE_AWRITE_MSG) {
- if (tx_packet.msg.data_length) {
- adb_client.write_state = ADB_STATE_AWRITE_DATA;
- usbd_ep_start_write(busid, adb_ep_data[ADB_IN_EP_IDX].ep_addr, tx_packet.payload, tx_packet.msg.data_length);
+ if ((nbytes % usbd_get_ep_mps(busid, ep)) == 0 && nbytes) {
+ /* send zlp */
+ usbd_ep_start_write(busid, g_usbd_adb.ep_data[ADB_IN_EP_IDX].ep_addr, NULL, 0);
+ return;
+ }
+
+ if (g_usbd_adb.tx_state == ADB_TX_STATE_MSG) {
+ if (adb_tx_packet.msg.data_length) {
+ g_usbd_adb.tx_state = ADB_TX_STATE_DATA;
+ usbd_ep_start_write(busid, g_usbd_adb.ep_data[ADB_IN_EP_IDX].ep_addr,
+ adb_tx_packet.payload, adb_tx_packet.msg.data_length);
} else {
+ g_usbd_adb.tx_state = ADB_TX_STATE_COMPLETE;
+ usb_osal_sem_give(g_usbd_adb.tx_sem);
}
- } else if (adb_client.write_state == ADB_STATE_AWRITE_DATA) {
- usbd_adb_notify_write_done();
+ } else if (g_usbd_adb.tx_state == ADB_TX_STATE_DATA) {
+ g_usbd_adb.tx_state = ADB_TX_STATE_COMPLETE;
+ usb_osal_sem_give(g_usbd_adb.tx_sem);
}
}
-void adb_notify_handler(uint8_t busid, uint8_t event, void *arg)
+static void adb_notify_handler(uint8_t busid, uint8_t event, void *arg)
{
(void)arg;
switch (event) {
- case USBD_EVENT_INIT:
- break;
- case USBD_EVENT_DEINIT:
+ case USBD_EVENT_CONFIGURED:
+ g_usbd_adb.rx_state = ADB_RX_STATE_READ_MSG;
+ g_usbd_adb.tx_state = ADB_TX_STATE_COMPLETE;
+ usbd_ep_start_read(busid, g_usbd_adb.ep_data[ADB_OUT_EP_IDX].ep_addr,
+ (uint8_t *)&adb_rx_packet.msg, sizeof(struct adb_msg));
break;
+
case USBD_EVENT_RESET:
- break;
- case USBD_EVENT_CONFIGURED:
- adb_client.common_state = ADB_STATE_READ_MSG;
- /* setup first out ep read transfer */
- usbd_ep_start_read(busid, adb_ep_data[ADB_OUT_EP_IDX].ep_addr, (uint8_t *)&rx_packet.msg, sizeof(struct adb_msg));
+ for (uint32_t i = 0; i < ADB_MAX_SERVICE; i++) {
+ g_usbd_adb.svc_opened[i] = false;
+ g_usbd_adb.remote_id[i] = 0;
+ }
break;
+ case USBD_EVENT_INIT:
+ break;
+ case USBD_EVENT_DEINIT:
+ break;
default:
break;
}
}
-struct usbd_interface *usbd_adb_init_intf(uint8_t busid, struct usbd_interface *intf, uint8_t in_ep, uint8_t out_ep)
+/* -------------------------------------------------------------------------
+ * initialisation
+ * ---------------------------------------------------------------------- */
+struct usbd_interface *usbd_adb_init_intf(uint8_t busid, struct usbd_interface *intf,
+ uint8_t in_ep, uint8_t out_ep)
{
- (void)busid;
+ g_usbd_adb.busid = busid;
+
+ g_usbd_adb.ep_data[ADB_OUT_EP_IDX].ep_addr = out_ep;
+ g_usbd_adb.ep_data[ADB_OUT_EP_IDX].ep_cb = usbd_adb_bulk_out;
+ g_usbd_adb.ep_data[ADB_IN_EP_IDX].ep_addr = in_ep;
+ g_usbd_adb.ep_data[ADB_IN_EP_IDX].ep_cb = usbd_adb_bulk_in;
+
+ usbd_add_endpoint(busid, &g_usbd_adb.ep_data[ADB_OUT_EP_IDX]);
+ usbd_add_endpoint(busid, &g_usbd_adb.ep_data[ADB_IN_EP_IDX]);
intf->class_interface_handler = NULL;
intf->class_endpoint_handler = NULL;
intf->vendor_handler = NULL;
intf->notify_handler = adb_notify_handler;
- adb_ep_data[ADB_OUT_EP_IDX].ep_addr = out_ep;
- adb_ep_data[ADB_OUT_EP_IDX].ep_cb = usbd_adb_bulk_out;
- adb_ep_data[ADB_IN_EP_IDX].ep_addr = in_ep;
- adb_ep_data[ADB_IN_EP_IDX].ep_cb = usbd_adb_bulk_in;
+ g_usbd_adb.rx_sem = usb_osal_sem_create(0);
+ USB_ASSERT_MSG(g_usbd_adb.rx_sem, "adb rx_sem fail");
+ g_usbd_adb.tx_sem = usb_osal_sem_create(0);
+ USB_ASSERT_MSG(g_usbd_adb.tx_sem, "adb tx_sem fail");
+ g_usbd_adb.tx_lock = usb_osal_mutex_create();
+ USB_ASSERT_MSG(g_usbd_adb.tx_lock, "adb tx_lock fail");
- usbd_add_endpoint(busid, &adb_ep_data[ADB_OUT_EP_IDX]);
- usbd_add_endpoint(busid, &adb_ep_data[ADB_IN_EP_IDX]);
+ g_usbd_adb.rx_thread = usb_osal_thread_create("usbd_adb", ADB_DAEMON_THREAD_STACK,
+ ADB_DAEMON_THREAD_PRIO, adb_rx_thread_entry, NULL);
+ USB_ASSERT_MSG(g_usbd_adb.rx_thread, "adb rx_thread fail");
+ usbd_adb_shell_init();
+ usbd_adb_sync_init();
return intf;
}
-bool usbd_adb_can_write(void)
+int usbd_adb_write(uint32_t localid,
+ const uint8_t *data,
+ uint32_t len)
{
- return adb_client.writable;
-}
+ uint32_t sent = 0;
-int usbd_abd_write(uint32_t localid, const uint8_t *data, uint32_t len)
-{
- struct adb_packet *packet;
+ if (usbd_adb_service_is_opened(localid) == false) {
+ return -1;
+ }
- packet = &tx_packet;
- packet->msg.command = A_WRTE;
- packet->msg.arg0 = localid;
- packet->msg.arg1 = usbd_adb_get_remoteid(localid);
- packet->msg.data_length = len;
- memcpy(packet->payload, data, len);
+ uint32_t remoteid = usbd_adb_service_get_remoteid(localid);
+ if (!remoteid) {
+ return -1;
+ }
- packet->msg.data_crc32 = adb_packet_checksum(packet);
- packet->msg.magic = packet->msg.command ^ 0xffffffff;
+ if (!len) {
+ return 0;
+ }
+
+ while (sent < len) {
+ uint32_t chunk = len - sent;
+ int ret;
+
+ if (chunk > ADB_MAX_PAYLOAD) {
+ chunk = ADB_MAX_PAYLOAD;
+ }
+
+ ret = usbd_adb_send_internal(A_WRTE,
+ localid,
+ remoteid,
+ data + sent,
+ chunk);
+ if (ret != 0) {
+ return ret;
+ }
+
+ sent += chunk;
+ }
- adb_client.write_state = ADB_STATE_AWRITE_MSG;
- usbd_ep_start_write(0, adb_ep_data[ADB_IN_EP_IDX].ep_addr, (uint8_t *)&packet->msg, sizeof(struct adb_msg));
return 0;
}
void usbd_adb_close(uint32_t localid)
{
- adb_send_close(&tx_packet, 0, usbd_adb_get_remoteid(localid));
+ if (usbd_adb_service_is_opened(localid) == false) {
+ return;
+ }
+
+ uint32_t remoteid = usbd_adb_service_get_remoteid(localid);
+ if (!remoteid) {
+ return;
+ }
+
+ usbd_adb_send_internal(A_CLSE, localid, remoteid, NULL, 0);
+}
+
+__WEAK void usbd_adb_shell_init(void)
+{
+}
+
+__WEAK void usbd_adb_sync_init(void)
+{
} \ No newline at end of file
diff --git a/class/adb/usbd_adb.h b/class/adb/usbd_adb.h
index 98ec19ba..dae33236 100644
--- a/class/adb/usbd_adb.h
+++ b/class/adb/usbd_adb.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2024, sakumisu
+ * Copyright (c) 2024 ~ 2026, sakumisu
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -7,14 +7,59 @@
#define USBD_ADB_H
#include <stdint.h>
+#include "usb_vfs.h"
-#define ADB_SHELL_LOALID 0x01
-#define ADB_FILE_LOALID 0x02
+#define ADB_VERSION 0x01000000u
+#define ADB_MAX_PAYLOAD (4u * 1024u)
+#define ADB_TX_TIMEOUT_MS 5000u
+#define ADB_MAX_SERVICE 4u
+
+#define ADB_LOCALID_SHELL 0x01u
+#define ADB_LOCALID_SYNC 0x02u
+
+/* -------------------------------------------------------------------------
+ * adb packet commands
+ *
+ * The 4cc is stored as a little endian integer, exactly like the official
+ * adb implementation does (A_CNXN == 0x4e584e43 == "CNXN" on the wire).
+ * ---------------------------------------------------------------------- */
+#define ADB_FOURCC(c0, c1, c2, c3) ((uint32_t)(c0) | ((uint32_t)(c1) << 8) | \
+ ((uint32_t)(c2) << 16) | ((uint32_t)(c3) << 24))
+
+#define A_SYNC ADB_FOURCC('S', 'Y', 'N', 'C')
+#define A_CNXN ADB_FOURCC('C', 'N', 'X', 'N')
+#define A_OPEN ADB_FOURCC('O', 'P', 'E', 'N')
+#define A_OKAY ADB_FOURCC('O', 'K', 'A', 'Y')
+#define A_CLSE ADB_FOURCC('C', 'L', 'S', 'E')
+#define A_WRTE ADB_FOURCC('W', 'R', 'T', 'E')
+#define A_AUTH ADB_FOURCC('A', 'U', 'T', 'H')
+
+struct adb_msg {
+ uint32_t command; /* A_CNXN / A_OPEN / A_OKAY / A_CLSE / A_WRTE */
+ uint32_t arg0; /* first argument */
+ uint32_t arg1; /* second argument */
+ uint32_t data_length; /* length of the payload that follows */
+ uint32_t data_crc32; /* checksum of the payload (plain 32-bit sum) */
+ uint32_t magic; /* command ^ 0xffffffff */
+};
+
+struct adb_packet {
+ USB_MEM_ALIGNX struct adb_msg msg;
+ USB_MEM_ALIGNX uint8_t payload[ADB_MAX_PAYLOAD];
+};
+
+struct adb_service {
+ const char *name; /* destination prefix, e.g. "shell:" / "sync:" */
+ uint32_t localid; /* ADB_LOCALID_SHELL / ADB_LOCALID_FILE / ... */
+ void (*on_open)(uint32_t remoteid);
+ void (*on_close)(uint32_t remoteid);
+ void (*on_write)(uint32_t remoteid, const uint8_t *data, uint32_t len);
+};
// clang-format off
-#define ADB_DESCRIPTOR_INIT(bFirstInterface, in_ep, out_ep, wMaxPacketSize) \
+#define ADB_DESCRIPTOR_INIT(bFirstInterface, in_ep, out_ep, wMaxPacketSize) \
USB_INTERFACE_DESCRIPTOR_INIT(bFirstInterface, 0x00, 0x02, 0xff, 0x42, 0x01, 0x02), \
- USB_ENDPOINT_DESCRIPTOR_INIT(in_ep, 0x02, wMaxPacketSize, 0x00), \
+ USB_ENDPOINT_DESCRIPTOR_INIT(in_ep, 0x02, wMaxPacketSize, 0x00), \
USB_ENDPOINT_DESCRIPTOR_INIT(out_ep, 0x02, wMaxPacketSize, 0x00)
// clang-format on
@@ -23,14 +68,15 @@ extern "C" {
#endif
struct usbd_interface *usbd_adb_init_intf(uint8_t busid, struct usbd_interface *intf, uint8_t in_ep, uint8_t out_ep);
-
-void usbd_adb_notify_shell_read(uint8_t *data, uint32_t len);
-void usbd_adb_notify_file_read(uint8_t *data, uint32_t len);
-void usbd_adb_notify_write_done(void);
-bool usbd_adb_can_write(void);
-int usbd_abd_write(uint32_t localid, const uint8_t *data, uint32_t len);
+int usbd_adb_write(uint32_t localid,
+ const uint8_t *data,
+ uint32_t len);
void usbd_adb_close(uint32_t localid);
+int usbd_adb_service_register(const struct adb_service *svc);
+void usbd_adb_shell_init(void);
+void usbd_adb_sync_init(void);
+
#ifdef __cplusplus
}
#endif
diff --git a/class/adb/usbd_adb_cherrysh.c b/class/adb/usbd_adb_cherrysh.c
new file mode 100644
index 00000000..61ed3b73
--- /dev/null
+++ b/class/adb/usbd_adb_cherrysh.c
@@ -0,0 +1,355 @@
+/*
+ * Copyright (c) 2024 ~ 2026, sakumisu
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "usbd_core.h"
+#include "usbd_adb.h"
+#include "csh.h"
+
+#include "FreeRTOS.h"
+#include "task.h"
+#include "event_groups.h"
+
+#ifndef CHERRYSH_TASK_REPL_PRIORITY
+#define CHERRYSH_TASK_REPL_PRIORITY (2)
+#endif
+
+#ifndef CHERRYSH_TASK_EXEC_PRIORITY
+#define CHERRYSH_TASK_EXEC_PRIORITY (1)
+#endif
+
+#ifndef CHERRYSH_TASK_REPL_STACK_SIZE
+#define CHERRYSH_TASK_REPL_STACK_SIZE 1024U
+#endif
+
+#ifndef CHERRYSH_TASK_EXEC_STACK_SIZE
+#define CHERRYSH_TASK_EXEC_STACK_SIZE 1024U
+#endif
+
+static chry_shell_t csh;
+static volatile bool login = false;
+static usb_ringbuffer_t shell_rb;
+static uint8_t mempool[1024];
+
+static TaskHandle_t task_hdl_repl = NULL;
+static TaskHandle_t task_hdl_exec = NULL;
+
+static EventGroupHandle_t event_hdl;
+static StaticEventGroup_t event_grp;
+
+static int shell_init(bool need_login);
+
+static void adb_shell_on_open(uint32_t remoteid)
+{
+ USB_LOG_INFO("adb shell open, remoteid:%u\r\n", (unsigned)remoteid);
+}
+
+static void adb_shell_on_close(uint32_t remoteid)
+{
+ USB_LOG_INFO("adb shell close, remoteid:%u\r\n", (unsigned)remoteid);
+}
+
+static void adb_shell_on_write(uint32_t remoteid, const uint8_t *data, uint32_t len)
+{
+ usb_ringbuffer_write(&shell_rb, (uint8_t *)data, len);
+ xEventGroupSetBits(event_hdl, 0x10);
+}
+
+static const struct adb_service adb_shell_service = {
+ .name = "shell:",
+ .localid = ADB_LOCALID_SHELL,
+ .on_open = adb_shell_on_open,
+ .on_close = adb_shell_on_close,
+ .on_write = adb_shell_on_write,
+};
+
+void usbd_adb_shell_init(void)
+{
+ if (usbd_adb_service_register(&adb_shell_service) != 0) {
+ USB_LOG_ERR("adb shell: register failed\r\n");
+ }
+ shell_init(false);
+}
+
+static uint16_t csh_sput_cb(chry_readline_t *rl, const void *data, uint16_t size)
+{
+ (void)rl;
+
+ usbd_adb_write(ADB_LOCALID_SHELL, data, size);
+
+ return size;
+}
+
+static uint16_t csh_sget_cb(chry_readline_t *rl, void *data, uint16_t size)
+{
+ (void)rl;
+ return usb_ringbuffer_read(&shell_rb, data, size);
+}
+
+static void wait_char(void)
+{
+ EventBits_t event;
+wait:
+ /* In order to lock the log from being disrupted , wait for REPL task execution to complete */
+ event = xEventGroupWaitBits(event_hdl, (0x10 | 0x01 | 0x04), pdTRUE, pdFALSE, portMAX_DELAY);
+ if ((event & 0x10) == 0) {
+ if (event & 0x01) {
+ chry_readline_erase_line(&csh.rl);
+ xEventGroupSetBits(event_hdl, 0x02);
+ }
+ if (event & 0x04) {
+ chry_readline_edit_refresh(&csh.rl);
+ xEventGroupSetBits(event_hdl, 0x08);
+ }
+
+ goto wait;
+ }
+}
+
+static void task_repl(void *param)
+{
+ (void)param;
+ int ret;
+ volatile uint8_t *pexec = (void *)&csh.exec;
+
+ for (;;) {
+ restart:
+ if (login) {
+ goto repl;
+ } else {
+ }
+
+ ret = csh_login(&csh);
+ if (ret == 0) {
+ login = true;
+ } else if (ret == 1) {
+ /*!< no enough char */
+ wait_char();
+ continue;
+ } else {
+ continue;
+ }
+
+ repl:
+ ret = chry_shell_task_repl(&csh);
+
+ if (ret == -1) {
+ /*!< error */
+ goto restart;
+ } else if (ret == 1) {
+ /*!< no enough char */
+ wait_char();
+ } else {
+ /*!< restart */
+ }
+
+ /*!< check flag */
+ if (*pexec == CSH_STATUS_EXEC_DONE) {
+ *pexec = CSH_STATUS_EXEC_IDLE;
+ chry_readline_auto_refresh(&csh.rl, true);
+ chry_readline_ignore(&csh.rl, false);
+ chry_readline_edit_refresh(&csh.rl);
+ }
+
+ if (login == false) {
+ chry_readline_erase_line(&csh.rl);
+ csh.rl.noblock = false;
+ }
+ }
+}
+
+static void task_exec(void *param)
+{
+ (void)param;
+
+ /*!< execute shell command */
+ chry_shell_task_exec(&csh);
+
+ /*!< notify REPL task execute done */
+ xEventGroupSetBits(event_hdl, 0x10);
+
+ /*!< wait for REPL task delete */
+ vTaskSuspend(NULL);
+}
+
+int chry_shell_port_create_context(chry_shell_t *csh, int argc, const char **argv)
+{
+ volatile TaskHandle_t *p_task_hdl_exec = (void *)&task_hdl_exec;
+ (void)csh;
+ (void)argc;
+ (void)argv;
+
+ if (*p_task_hdl_exec != NULL) {
+ vTaskDelete(*p_task_hdl_exec);
+ }
+
+ *p_task_hdl_exec = NULL;
+ if (xTaskCreate(task_exec, "task_exec", CHERRYSH_TASK_EXEC_STACK_SIZE, NULL, CHERRYSH_TASK_EXEC_PRIORITY, (TaskHandle_t *)p_task_hdl_exec) != pdPASS) {
+ return -1;
+ }
+ return 0;
+}
+
+void chry_shell_port_default_handler(chry_shell_t *csh, int sig)
+{
+ volatile uint8_t *pexec = (void *)&csh->exec;
+ volatile TaskHandle_t *p_task_hdl_exec = (void *)&task_hdl_exec;
+
+ switch (sig) {
+ case CSH_SIGINT:
+ case CSH_SIGQUIT:
+ case CSH_SIGKILL:
+ case CSH_SIGTERM:
+ break;
+ default:
+ return;
+ }
+
+ /*!< force delete task */
+ if (*p_task_hdl_exec != NULL) {
+ vTaskDelete(task_hdl_exec);
+ *p_task_hdl_exec = NULL;
+ }
+
+ switch (sig) {
+ case CSH_SIGINT:
+ csh->rl.sput(&csh->rl, "^SIGINT" CONFIG_CSH_NEWLINE, sizeof("^SIGINT" CONFIG_CSH_NEWLINE) - 1);
+ break;
+ case CSH_SIGQUIT:
+ csh->rl.sput(&csh->rl, "^SIGQUIT" CONFIG_CSH_NEWLINE, sizeof("^SIGQUIT" CONFIG_CSH_NEWLINE) - 1);
+ break;
+ case CSH_SIGKILL:
+ csh->rl.sput(&csh->rl, "^SIGKILL" CONFIG_CSH_NEWLINE, sizeof("^SIGKILL" CONFIG_CSH_NEWLINE) - 1);
+ break;
+ case CSH_SIGTERM:
+ csh->rl.sput(&csh->rl, "^SIGTERM" CONFIG_CSH_NEWLINE, sizeof("^SIGTERM" CONFIG_CSH_NEWLINE) - 1);
+ break;
+ default:
+ return;
+ }
+
+ *pexec = CSH_STATUS_EXEC_IDLE;
+ chry_readline_auto_refresh(&csh->rl, true);
+ chry_readline_ignore(&csh->rl, false);
+ chry_readline_edit_refresh(&csh->rl);
+}
+
+static int shell_init(bool need_login)
+{
+ chry_shell_init_t csh_init = {0};
+
+ if (usb_ringbuffer_init(&shell_rb, mempool, sizeof(mempool))) {
+ return -1;
+ }
+
+ if (need_login) {
+ login = false;
+ } else {
+ login = true;
+ }
+
+ /*!< I/O callback */
+ csh_init.sput = csh_sput_cb;
+ csh_init.sget = csh_sget_cb;
+
+#if defined(CONFIG_CSH_SYMTAB) && CONFIG_CSH_SYMTAB
+#if defined(__GNUC__)
+ extern const int __fsymtab_start;
+ extern const int __fsymtab_end;
+ extern const int __vsymtab_start;
+ extern const int __vsymtab_end;
+
+ /*!< get table from ld symbol */
+ csh_init.command_table_beg = &__fsymtab_start;
+ csh_init.command_table_end = &__fsymtab_end;
+ csh_init.variable_table_beg = &__vsymtab_start;
+ csh_init.variable_table_end = &__vsymtab_end;
+#elif defined(__ICCARM__) || defined(__ICCRX__) || defined(__ICCRISCV__)
+#pragma section="FSymTab"
+#pragma section="VSymTab"
+ csh_init.command_table_beg = __section_begin("FSymTab");
+ csh_init.command_table_end = __section_end("FSymTab");
+ csh_init.variable_table_beg = __section_begin("VSymTab");
+ csh_init.variable_table_end = __section_end("VSymTab");
+#endif
+#endif
+
+#if defined(CONFIG_CSH_PROMPTEDIT) && CONFIG_CSH_PROMPTEDIT
+ static char csh_prompt_buffer[128];
+
+ /*!< set prompt buffer */
+ csh_init.prompt_buffer = csh_prompt_buffer;
+ csh_init.prompt_buffer_size = sizeof(csh_prompt_buffer);
+#endif
+
+#if defined(CONFIG_CSH_HISTORY) && CONFIG_CSH_HISTORY
+ static char csh_history_buffer[128];
+
+ /*!< set history buffer */
+ csh_init.history_buffer = csh_history_buffer;
+ csh_init.history_buffer_size = sizeof(csh_history_buffer);
+#endif
+
+#if defined(CONFIG_CSH_LNBUFF_STATIC) && CONFIG_CSH_LNBUFF_STATIC
+ static char csh_line_buffer[128];
+
+ /*!< set linebuffer */
+ csh_init.line_buffer = csh_line_buffer;
+ csh_init.line_buffer_size = sizeof(csh_line_buffer);
+#endif
+
+ csh_init.uid = 0;
+ csh_init.user[0] = "cherry";
+
+ /*!< The port hash function is required,
+ and the strcmp attribute is used weakly by default,
+ int chry_shell_port_hash_strcmp(const char *hash, const char *str); */
+ csh_init.hash[0] = "12345678"; /*!< If there is no password, set to NULL */
+ csh_init.host = BOARD_NAME;
+ csh_init.user_data = NULL;
+
+ int ret = chry_shell_init(&csh, &csh_init);
+ if (ret) {
+ return -1;
+ }
+
+ task_hdl_exec = NULL;
+ event_hdl = xEventGroupCreateStatic(&event_grp);
+ task_hdl_repl = NULL;
+ if (xTaskCreate(task_repl, "task_repl", CHERRYSH_TASK_REPL_STACK_SIZE, NULL, CHERRYSH_TASK_REPL_PRIORITY, (TaskHandle_t *)&task_hdl_repl) != pdPASS) {
+ return -1;
+ }
+
+ return 0;
+}
+
+void shell_lock(void)
+{
+ xEventGroupSetBits(event_hdl, 0x01);
+ xEventGroupWaitBits(event_hdl, 0x02, pdTRUE, pdTRUE, portMAX_DELAY);
+}
+
+void shell_unlock(void)
+{
+ xEventGroupSetBits(event_hdl, 0x04);
+ xEventGroupWaitBits(event_hdl, 0x08, pdTRUE, pdTRUE, portMAX_DELAY);
+}
+
+static int csh_exit(int argc, char **argv)
+{
+ (void)argc;
+ (void)argv;
+
+ usbd_adb_close(ADB_LOCALID_SHELL);
+ return 0;
+}
+CSH_SCMD_EXPORT_ALIAS(csh_exit, exit, );
+
+#define __ENV_PATH "/sbin:/bin"
+const char ENV_PATH[] = __ENV_PATH;
+CSH_RVAR_EXPORT(ENV_PATH, PATH, sizeof(__ENV_PATH));
+
+#define __ENV_ZERO ""
+const char ENV_ZERO[] = __ENV_ZERO;
+CSH_RVAR_EXPORT(ENV_ZERO, ZERO, sizeof(__ENV_ZERO));
diff --git a/class/adb/usbd_adb_sync.c b/class/adb/usbd_adb_sync.c
new file mode 100644
index 00000000..b18e2b09
--- /dev/null
+++ b/class/adb/usbd_adb_sync.c
@@ -0,0 +1,932 @@
+/*
+ * Copyright (c) 2024 ~ 2026, sakumisu
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "usbd_core.h"
+#include "usbd_adb.h"
+
+#include "stdarg.h"
+
+#define ID_STAT ADB_FOURCC('S', 'T', 'A', 'T') /* "lstat_v1": the v1 stat */
+#define ID_LIST ADB_FOURCC('L', 'I', 'S', 'T')
+#define ID_DENT ADB_FOURCC('D', 'E', 'N', 'T')
+#define ID_SEND ADB_FOURCC('S', 'E', 'N', 'D')
+#define ID_RECV ADB_FOURCC('R', 'E', 'C', 'V')
+#define ID_DONE ADB_FOURCC('D', 'O', 'N', 'E')
+#define ID_DATA ADB_FOURCC('D', 'A', 'T', 'A')
+#define ID_OKAY ADB_FOURCC('O', 'K', 'A', 'Y')
+#define ID_FAIL ADB_FOURCC('F', 'A', 'I', 'L')
+#define ID_QUIT ADB_FOURCC('Q', 'U', 'I', 'T')
+
+struct adb_sync_wire_hdr {
+ uint32_t id;
+ uint32_t len;
+};
+
+struct adb_sync_wire_stat {
+ uint32_t id;
+ uint32_t mode;
+ uint32_t size;
+ uint32_t mtime;
+};
+
+struct adb_sync_wire_dent {
+ uint32_t id;
+ uint32_t mode;
+ uint32_t size;
+ uint32_t mtime;
+ uint32_t namelen;
+};
+
+/* The one size a request has: the client refuses to send anything whose path is
+ * longer than this (SendRequest(), and the same check on the "path,mode" of a
+ * SEND), so a body arriving here cannot be longer, and a body is all a request
+ * carries. Every path this file keeps is held with one byte over, for its
+ * terminator. */
+#define ADB_SYNC_PATH_MAX 1024u
+/* How much is read from the card and sent in one DATA record. The client sends
+ * up to 64KiB at a time and this side reassembles whatever arrives in whatever
+ * pieces it arrives in, so this only decides how big the records are - and it is
+ * what sizes the payload of the one record buffer, see struct adb_sync_packet. */
+#define ADB_SYNC_DATA_MAX 2048u
+#define ADB_SYNC_NAME_MAX VFS_MAX_PATHNAME
+/* Room for the longest reason this file builds, which is a path and an errno. */
+#define ADB_SYNC_MSG_MAX 160u
+
+#define SYNC_RX_STATE_HEADER 0u
+#define SYNC_RX_STATE_BODY 1u
+#define SYNC_RX_STATE_DATA 2u
+#define SYNC_RX_STATE_SKIP 3u
+
+struct adb_sync_packet {
+ struct adb_sync_wire_hdr hdr;
+ uint8_t payload[ADB_SYNC_DATA_MAX];
+};
+
+struct adb_sync_priv {
+ uint32_t req;
+
+ uint8_t rx_state;
+ uint32_t hdr_len;
+ uint32_t body_len;
+ uint32_t rx_remain;
+
+ bool push_failed;
+ bool push_isdir;
+ int fd;
+
+ bool have_target;
+ bool target_isdir;
+ char target[ADB_SYNC_PATH_MAX + 1u];
+
+ char path[ADB_SYNC_PATH_MAX + 1u];
+
+ struct adb_sync_packet packet;
+} g_adb_sync;
+
+static void adb_sync_send(const void *data, uint32_t len)
+{
+ if (len) {
+ usbd_adb_write(ADB_LOCALID_SYNC, (const uint8_t *)data, (uint32_t)len);
+ }
+}
+
+static void adb_sync_send_record(uint32_t id, const void *payload, uint32_t len)
+{
+ g_adb_sync.packet.hdr.id = id;
+ g_adb_sync.packet.hdr.len = len;
+
+ if (len) {
+ memcpy(g_adb_sync.packet.payload, payload, len);
+ }
+
+ adb_sync_send(&g_adb_sync.packet, sizeof(g_adb_sync.packet.hdr) + len);
+}
+
+static void adb_sync_send_data_record(uint32_t len)
+{
+ g_adb_sync.packet.hdr.id = ID_DATA;
+ g_adb_sync.packet.hdr.len = len;
+
+ adb_sync_send(&g_adb_sync.packet, sizeof(g_adb_sync.packet.hdr) + len);
+}
+
+static void adb_sync_send_stat(uint32_t mode, uint32_t size, uint32_t mtime)
+{
+ struct adb_sync_wire_stat *st = (struct adb_sync_wire_stat *)&g_adb_sync.packet;
+
+ st->id = ID_STAT;
+ st->mode = mode;
+ st->size = size;
+ st->mtime = mtime;
+
+ adb_sync_send(st, (uint32_t)sizeof(*st));
+}
+
+static void adb_sync_send_dent(uint32_t mode, uint32_t size, uint32_t mtime,
+ const char *name, uint32_t namelen)
+{
+ struct adb_sync_wire_dent *dent = (struct adb_sync_wire_dent *)&g_adb_sync.packet;
+
+ dent->id = ID_DENT;
+ dent->mode = mode;
+ dent->size = size;
+ dent->mtime = mtime;
+ dent->namelen = namelen;
+
+ memcpy((uint8_t *)dent + sizeof(*dent), name, namelen);
+
+ adb_sync_send(dent, (uint32_t)sizeof(*dent) + namelen);
+}
+
+static void adb_sync_send_list_end(void)
+{
+ struct adb_sync_wire_dent *dent = (struct adb_sync_wire_dent *)&g_adb_sync.packet;
+
+ memset(dent, 0, sizeof(*dent));
+ dent->id = ID_DONE;
+
+ adb_sync_send(dent, (uint32_t)sizeof(*dent));
+}
+
+static void adb_sync_send_recv_end(void)
+{
+ adb_sync_send_record(ID_DONE, NULL, 0u);
+}
+
+static void adb_sync_fail(const char *fmt, ...)
+{
+ char msg[ADB_SYNC_MSG_MAX];
+ va_list ap;
+ int n;
+
+ va_start(ap, fmt);
+ n = vsnprintf(msg, sizeof(msg), fmt, ap);
+ va_end(ap);
+
+ if ((n < 0) || ((size_t)n >= sizeof(msg))) {
+ n = (int)sizeof(msg) - 1; /* the reason was long: it got cut off */
+ }
+ msg[n] = '\0';
+
+ USB_LOG_ERR("adb sync: %s\r\n", msg);
+
+ switch (g_adb_sync.req) {
+ /* A SEND has its payload and the DONE behind it still on their way, and
+ * a pushed file is answered exactly once: the rest of it is read and
+ * dropped, and only this FAIL goes back. adbd does the same, for the
+ * same reason. Only a SEND and a pull can read a FAIL at all. */
+ case ID_SEND:
+ case ID_RECV:
+ if (g_adb_sync.req == ID_SEND) {
+ g_adb_sync.push_failed = true;
+ g_adb_sync.push_isdir = false;
+ }
+
+ adb_sync_send_record(ID_FAIL, msg, (uint32_t)n);
+ break;
+
+ case ID_STAT:
+ /* A stat is a query and is answered with the record it asked for,
+ * with nothing in it: it is a valid answer, and the client reads a
+ * FAIL in its place as a protocol fault. */
+ adb_sync_send_stat(0u, 0u, 0u);
+ break;
+
+ case ID_LIST:
+ adb_sync_send_list_end();
+ break;
+
+ default:
+ break; /* no request behind it, so there is nothing to answer */
+ }
+}
+
+static bool adb_sync_path_is_root(const char *path)
+{
+ return strcmp(path, vfs_get_fs_root_path()) == 0;
+}
+
+static bool adb_sync_name_is_dir(const char *path)
+{
+ size_t i = strlen(path);
+
+ while ((i > 0u) && (path[i - 1u] != '/')) {
+ if (path[i - 1u] == '.') {
+ return false; /* it has an extension, so it names a file */
+ }
+ i--;
+ }
+
+ return true;
+}
+
+/* Where a pushed directory lands is decided by the client, and it says so in
+ * the path it sends: "src" at an existing "dst" arrives as "dst/Basename(src)/",
+ * while "src/." arrives as "dst/./" - whose "." is a component like any other
+ * here, so folding it away is exactly "the contents of src land in dst".
+ * Both spellings therefore already arrive as the path they mean, and this side
+ * never has to guess which of the two was asked for.
+ *
+ * A Windows host spells its local source with backslashes and the client takes
+ * that basename verbatim, so "adb push .\output /sdc" arrives as
+ * "/sdc/.\output/...". FatFs takes both separators alike, so the backslashes
+ * are folded into forward ones before any component is looked at - otherwise
+ * ".\output" stays a single, unmakable name and the push dies on the first
+ * file. The checks above run first, so this never turns a relative path into
+ * something that looks absolute. */
+static bool adb_sync_build_full_path(const char *root, char *path, bool *wants_dir)
+{
+ char *dst0;
+ char *dst;
+ char *src;
+ size_t len;
+ size_t root_len;
+ size_t i;
+
+ *wants_dir = false;
+
+ root_len = strlen(root);
+ len = strlen(path);
+
+ if ((root_len == 0u) || (len < root_len) || (strncmp(path, root, root_len) != 0)) {
+ return false;
+ }
+
+ if ((len > root_len) && (root[root_len - 1u] != '/') && (path[root_len] != '/')) {
+ return false;
+ }
+
+ for (i = 0u; i < len; i++) {
+ if (path[i] == '\\') {
+ path[i] = '/';
+ }
+ }
+
+ *wants_dir = (len > 1u) && (path[len - 1u] == '/');
+
+ dst0 = path;
+ dst = path;
+ src = path;
+
+ for (;;) {
+ char c = *src;
+
+ if (c == '.') {
+ if (src[1] == '\0') {
+ src++; /* "." and the path ends there */
+ } else if (src[1] == '/') {
+ src += 2; /* "./" */
+ while (*src == '/') {
+ src++;
+ }
+ continue;
+ } else if ((src[1] == '.') && ((src[2] == '\0') || (src[2] == '/'))) {
+ src += (src[2] == '/') ? 3 : 2;
+ while (*src == '/') {
+ src++;
+ }
+ goto up_one; /* ".." */
+ }
+ }
+
+ while (((c = *src++) != '\0') && (c != '/')) {
+ *dst++ = c;
+ }
+
+ if (c != '/') {
+ break; /* the path ends here */
+ }
+
+ *dst++ = '/';
+ while ((c = *src++) == '/') {
+ /* an empty component, "//" - nothing to copy */
+ }
+ src--;
+
+ continue;
+
+ up_one:
+ /* keep the topmost root directory: there is nothing above it */
+ if (((dst - dst0) != 1) || (dst[-1] != '/')) {
+ if (dst > dst0) {
+ dst--;
+ }
+ }
+ while ((dst0 < dst) && (dst[-1] != '/')) {
+ dst--;
+ }
+ }
+
+ *dst = '\0';
+
+ /* the separator that said "directory" is not part of the path */
+ if (((dst - dst0) > 1) && (dst[-1] == '/')) {
+ dst[-1] = '\0';
+ }
+
+ return true;
+}
+
+static bool adb_sync_path_join(const char *dir, const char *name, char *out, size_t out_size)
+{
+ size_t dir_len = strlen(dir);
+ int n;
+
+ if ((dir_len == 0u) || (dir[dir_len - 1u] == '/')) {
+ n = snprintf(out, out_size, "%s%s", dir, name);
+ } else {
+ n = snprintf(out, out_size, "%s/%s", dir, name);
+ }
+
+ return (n > 0) && ((size_t)n < out_size);
+}
+
+static int adb_sync_path_type(const char *path, struct vfs_stat *info)
+{
+ if (adb_sync_path_is_root(path)) {
+ memset(info, 0, sizeof(*info));
+ info->st_mode = VFS_S_IFDIR | VFS_S_IRWXU | VFS_S_IRWXG | VFS_S_IRWXO;
+ return 0;
+ }
+
+ return vfs_stat(path, info);
+}
+
+static int adb_sync_mkdir_one(const char *path)
+{
+ struct vfs_stat info;
+ bool there = (adb_sync_path_type(path, &info) == 0);
+ int ret;
+
+ if (there && ((info.st_mode & VFS_S_IFMT) == VFS_S_IFDIR)) {
+ return 0; /* already a directory: nothing to make, nothing to do */
+ }
+
+ if (there && (adb_sync_name_is_dir(path) == false)) {
+ return VFS_ERR_NOTDIR; /* it is a file, and it is meant to be one */
+ }
+
+ if (there) {
+ if (vfs_unlink(path) != 0) {
+ return VFS_ERR_NOTDIR; /* still in the way, and it could not be taken away */
+ }
+ }
+
+ ret = vfs_mkdir(path);
+ if (ret != 0) {
+ return ret; /* the port layer's own reason is the one worth passing on */
+ }
+
+ /* It says it worked, so say so too: which directories a push had to make is
+ * the first thing worth knowing when a tree lands one level off. */
+ USB_LOG_INFO("adb sync: mkdir '%s'\r\n", path);
+
+ if ((adb_sync_path_type(path, &info) != 0) ||
+ ((info.st_mode & VFS_S_IFMT) != VFS_S_IFDIR)) {
+ /* It says it worked and the directory cannot be found: believe neither,
+ * and let the caller report a failure rather than write into nothing. */
+ return VFS_ERR_NOENT;
+ }
+
+ return 0;
+}
+
+static int adb_sync_mkdirs(char *path, bool last_is_dir)
+{
+ size_t end = strlen(path);
+ size_t cut;
+ size_t i;
+ char saved;
+ int ret;
+
+ if (adb_sync_path_is_root(path)) {
+ return 0; /* the root of the volume is always there */
+ }
+
+ if (last_is_dir == false) {
+ for (cut = end; cut > 0u; cut--) {
+ if (path[cut - 1u] == '/') {
+ break;
+ }
+ }
+ if (cut <= 1u) {
+ return 0; /* the parent is the root, which always exists */
+ }
+ end = cut - 1u;
+ }
+
+ for (i = 1u; i < end; i++) {
+ if (path[i] != '/') {
+ continue;
+ }
+
+ path[i] = '\0';
+ ret = adb_sync_mkdir_one(path);
+ path[i] = '/';
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ saved = path[end];
+ path[end] = '\0'; /* in front of the file name, or of the trailing slash */
+ ret = adb_sync_mkdir_one(path);
+ path[end] = saved;
+
+ return ret;
+}
+
+static int adb_sync_write_err(const char *path, int err)
+{
+ struct vfs_stat info;
+
+ if ((err == VFS_ERR_ROFS) && (adb_sync_path_type(path, &info) == 0) &&
+ ((info.st_mode & VFS_S_IFMT) == VFS_S_IFDIR)) {
+ return VFS_ERR_ISDIR;
+ }
+
+ return err;
+}
+
+static void adb_sync_fail_write(char *path, int err)
+{
+ struct vfs_stat info;
+ char saved;
+ size_t cut;
+ size_t len = strlen(path);
+
+ if (err == VFS_ERR_NOENT) {
+ for (cut = 1u; cut < len; cut++) {
+ if (path[cut] != '/') {
+ continue;
+ }
+
+ saved = path[cut];
+ path[cut] = '\0';
+
+ if (adb_sync_path_type(path, &info) != 0) {
+ adb_sync_fail("cannot write into '%s': that directory is not there", path);
+ path[cut] = saved;
+ return;
+ }
+
+ if ((info.st_mode & VFS_S_IFMT) != VFS_S_IFDIR) {
+ adb_sync_fail("cannot write into '%s': a file is where a directory has to be", path);
+ path[cut] = saved;
+ return;
+ }
+
+ path[cut] = saved; /* an ancestor, and a directory as it should be */
+ }
+ }
+
+ adb_sync_fail("cannot write '%s': %s", path, vfs_strerror(adb_sync_write_err(path, err)));
+}
+
+static void adb_sync_remember_target(const char *path, bool is_dir)
+{
+ size_t len = strlen(path);
+
+ if (g_adb_sync.have_target) {
+ return;
+ }
+
+ if (len >= sizeof(g_adb_sync.target)) {
+ return;
+ }
+
+ memcpy(g_adb_sync.target, path, len);
+ g_adb_sync.target[len] = '\0';
+ g_adb_sync.target_isdir = is_dir;
+ g_adb_sync.have_target = true;
+}
+
+static void adb_sync_do_stat(const char *path, bool wants_dir)
+{
+ struct vfs_stat info;
+ bool as_dir = (wants_dir || adb_sync_name_is_dir(path));
+ bool is_dir;
+ int ret;
+
+ USB_LOG_INFO("adb sync: stat '%s'\r\n", path);
+
+ ret = adb_sync_path_type(path, &info);
+ is_dir = (ret == 0) && ((info.st_mode & VFS_S_IFMT) == VFS_S_IFDIR);
+
+ if (is_dir) {
+ adb_sync_remember_target(path, true);
+ adb_sync_send_stat(info.st_mode, (uint32_t)info.st_size, info.st_mtime);
+ } else if (as_dir) {
+ adb_sync_remember_target(path, true);
+ adb_sync_send_stat(VFS_S_IFDIR | VFS_S_IRWXU | VFS_S_IRWXG | VFS_S_IRWXO, 0u, 0u);
+ } else if (ret == 0) {
+ adb_sync_remember_target(path, false);
+ adb_sync_send_stat(info.st_mode, (uint32_t)info.st_size, info.st_mtime);
+ } else {
+ adb_sync_remember_target(path, false);
+ adb_sync_fail("'%s' does not exist", path);
+ }
+}
+
+static void adb_sync_do_list(char *path)
+{
+ struct vfs_dirent *de;
+ struct vfs_stat info;
+ VFS_DIR *dir;
+ uint32_t namelen;
+ char *entry = (char *)g_adb_sync.packet.payload;
+
+ USB_LOG_INFO("adb sync: listing '%s'\r\n", path);
+
+ dir = vfs_opendir(path);
+ if (!dir) {
+ adb_sync_fail("cannot read '%s' as a directory", path);
+ return;
+ }
+
+ while ((de = vfs_readdir(dir)) != NULL) {
+ namelen = de->d_namlen;
+
+ /* "." and ".." are never part of a listing */
+ if ((namelen == 0u) || (namelen > ADB_SYNC_NAME_MAX)) {
+ continue;
+ }
+ if ((strcmp(de->d_name, ".") == 0) || (strcmp(de->d_name, "..") == 0)) {
+ continue;
+ }
+
+ if (adb_sync_path_join(path, de->d_name, entry,
+ sizeof(g_adb_sync.packet.payload)) == false) {
+ continue;
+ }
+
+ if (vfs_stat(entry, &info) == 0) {
+ adb_sync_send_dent(info.st_mode, (uint32_t)info.st_size, info.st_mtime,
+ de->d_name, namelen);
+ }
+ }
+
+ vfs_closedir(dir);
+
+ adb_sync_send_list_end();
+}
+
+static void adb_sync_do_recv(const char *path)
+{
+ int fd;
+ int n;
+
+ USB_LOG_INFO("adb sync: reading '%s'\r\n", path);
+
+ fd = vfs_open(path, VFS_O_RDONLY);
+ if (fd < 0) {
+ adb_sync_fail("cannot open '%s' for reading: %s", path, vfs_strerror(fd));
+ return;
+ }
+
+ for (;;) {
+ n = vfs_read(fd, g_adb_sync.packet.payload, ADB_SYNC_DATA_MAX);
+ if (n < 0) {
+ vfs_close(fd);
+ adb_sync_fail("cannot read '%s': %s", path, vfs_strerror(n));
+ return; /* the FAIL is the answer; the client reads it as failure */
+ }
+
+ if (n == 0) {
+ break;
+ }
+
+ adb_sync_send_data_record((uint32_t)n);
+ }
+
+ vfs_close(fd);
+
+ adb_sync_send_recv_end();
+}
+
+static void adb_sync_do_send(char *path, bool wants_dir, uint32_t mode)
+{
+ int ret;
+
+ if ((mode & VFS_S_IFMT) == VFS_S_IFDIR) {
+ ret = adb_sync_mkdirs(path, true);
+ if (ret != 0) {
+ adb_sync_fail("cannot make '%s' a directory: %s", path, vfs_strerror(ret));
+ return;
+ }
+
+ g_adb_sync.push_isdir = true;
+ g_adb_sync.fd = -1;
+ return;
+ }
+
+ if (g_adb_sync.have_target && g_adb_sync.target_isdir &&
+ (strcmp(path, g_adb_sync.target) == 0)) {
+ ret = adb_sync_mkdirs(path, true);
+ if (ret != 0) {
+ adb_sync_fail("cannot make '%s' a directory: %s", path, vfs_strerror(ret));
+ } else {
+ adb_sync_fail("'%s' is a directory, so no file can be written at it", path);
+ }
+ return;
+ }
+
+ if (wants_dir) {
+ ret = adb_sync_mkdirs(path, true);
+ if (ret != 0) {
+ adb_sync_fail("cannot make '%s' a directory: %s", path, vfs_strerror(ret));
+ } else {
+ adb_sync_fail("'%s' is a directory, so no file can be written at it", path);
+ }
+ return;
+ }
+
+ USB_LOG_INFO("adb sync: writing '%s'\r\n", path);
+
+ g_adb_sync.fd = vfs_open(path, VFS_O_WRONLY);
+ if (g_adb_sync.fd == VFS_ERR_NOENT) {
+ ret = adb_sync_mkdirs(path, false);
+ if (ret != 0) {
+ adb_sync_fail_write(path, ret);
+ return;
+ }
+ g_adb_sync.fd = vfs_open(path, VFS_O_WRONLY);
+ }
+
+ if (g_adb_sync.fd < 0) {
+ ret = g_adb_sync.fd;
+ g_adb_sync.fd = -1;
+ adb_sync_fail_write(path, ret);
+ return;
+ }
+}
+
+static void adb_sync_do_send_done(void)
+{
+ if (g_adb_sync.fd >= 0) {
+ vfs_close(g_adb_sync.fd);
+ g_adb_sync.fd = -1;
+ }
+
+ if (g_adb_sync.push_isdir) {
+ g_adb_sync.push_isdir = false;
+ adb_sync_send_record(ID_OKAY, NULL, 0u);
+ return;
+ }
+
+ if (g_adb_sync.push_failed) {
+ g_adb_sync.push_failed = false;
+ return;
+ }
+
+ adb_sync_send_record(ID_OKAY, NULL, 0u);
+}
+
+static void adb_sync_data_write(const uint8_t *data, uint32_t len)
+{
+ int n;
+
+ if (g_adb_sync.fd < 0) {
+ return; /* refused, or a directory: the bytes are dropped */
+ }
+
+ n = vfs_write(g_adb_sync.fd, data, len);
+ if (n != (int)len) {
+ int err = (n < 0) ? n : VFS_ERR_IO; /* a short write counts as I/O */
+
+ vfs_close(g_adb_sync.fd);
+ g_adb_sync.fd = -1;
+
+ adb_sync_fail("cannot write '%s': %s", g_adb_sync.path, vfs_strerror(err));
+ }
+}
+
+static void adb_sync_reset(void)
+{
+ if (g_adb_sync.fd >= 0) {
+ vfs_close(g_adb_sync.fd);
+ g_adb_sync.fd = -1;
+ }
+
+ g_adb_sync.rx_state = SYNC_RX_STATE_HEADER;
+ g_adb_sync.hdr_len = 0;
+ g_adb_sync.body_len = 0;
+ g_adb_sync.rx_remain = 0;
+ g_adb_sync.req = 0;
+ g_adb_sync.push_failed = false;
+ g_adb_sync.push_isdir = false;
+ g_adb_sync.have_target = false;
+ g_adb_sync.target_isdir = false;
+}
+
+static void adb_sync_dispatch(uint32_t id, char *body)
+{
+ bool wants_dir = false;
+ uint32_t mode = 0u;
+ size_t len;
+
+ g_adb_sync.req = id;
+
+ if (id == ID_QUIT) {
+ adb_sync_reset();
+ return;
+ }
+
+ if ((id != ID_STAT) && (id != ID_LIST) && (id != ID_RECV) && (id != ID_SEND)) {
+ adb_sync_fail("unsupported request 0x%08x", (unsigned)id);
+ return;
+ }
+
+ if (id == ID_SEND) {
+ char *comma = strrchr(body, ',');
+
+ if (comma == NULL) {
+ adb_sync_fail("the SEND request came without a mode behind its path");
+ return;
+ }
+
+ mode = (uint32_t)strtoul(comma + 1, NULL, 0);
+ *comma = '\0';
+ }
+
+ len = strlen(body);
+ memcpy(g_adb_sync.path, body, len + 1u);
+
+ if (adb_sync_build_full_path(vfs_get_fs_root_path(), g_adb_sync.path, &wants_dir) == false) {
+ adb_sync_fail("the path has to be absolute, starting with '%s', not '%s'",
+ vfs_get_fs_root_path(), g_adb_sync.path);
+ return;
+ }
+
+ switch (id) {
+ case ID_STAT:
+ adb_sync_do_stat(g_adb_sync.path, wants_dir);
+ break;
+ case ID_LIST:
+ adb_sync_do_list(g_adb_sync.path);
+ break;
+ case ID_RECV:
+ adb_sync_do_recv(g_adb_sync.path);
+ break;
+ case ID_SEND:
+ adb_sync_do_send(g_adb_sync.path, wants_dir, mode);
+ break;
+ default:
+ break; /* not reachable: the four above are the only ones left */
+ }
+}
+
+static void adb_sync_header_ready(void)
+{
+ uint32_t id = g_adb_sync.packet.hdr.id;
+ uint32_t len = g_adb_sync.packet.hdr.len;
+
+ if (id == ID_DONE) {
+ adb_sync_do_send_done();
+ g_adb_sync.rx_state = SYNC_RX_STATE_HEADER;
+ return;
+ }
+
+ if (id == ID_DATA) {
+ g_adb_sync.rx_remain = len;
+ g_adb_sync.rx_state = len ? SYNC_RX_STATE_DATA : SYNC_RX_STATE_HEADER;
+ return;
+ }
+
+ if ((id == ID_STAT) || (id == ID_LIST) || (id == ID_RECV) || (id == ID_SEND)) {
+ if (len > ADB_SYNC_PATH_MAX) {
+ g_adb_sync.req = id;
+ g_adb_sync.rx_remain = len;
+ g_adb_sync.rx_state = SYNC_RX_STATE_SKIP; /* read it and drop it */
+ adb_sync_fail("a request of %u bytes is longer than the %u bytes a path may be",
+ (unsigned)len, (unsigned)ADB_SYNC_PATH_MAX);
+ return;
+ }
+
+ g_adb_sync.body_len = 0;
+ g_adb_sync.rx_remain = len;
+
+ if (len == 0) {
+ g_adb_sync.packet.payload[0] = '\0';
+ adb_sync_dispatch(id, (char *)g_adb_sync.packet.payload);
+ g_adb_sync.rx_state = SYNC_RX_STATE_HEADER;
+ } else {
+ g_adb_sync.rx_state = SYNC_RX_STATE_BODY;
+ }
+ return;
+ }
+
+ g_adb_sync.rx_remain = len;
+ g_adb_sync.rx_state = len ? SYNC_RX_STATE_SKIP : SYNC_RX_STATE_HEADER;
+ adb_sync_dispatch(id, (char *)g_adb_sync.packet.payload);
+}
+
+static void adb_sync_consume(const uint8_t *data, uint32_t len)
+{
+ uint8_t *body = g_adb_sync.packet.payload;
+ uint32_t take;
+
+ while (len) {
+ switch (g_adb_sync.rx_state) {
+ case SYNC_RX_STATE_HEADER:
+ take = sizeof(struct adb_sync_wire_hdr) - g_adb_sync.hdr_len;
+ if (take > len) {
+ take = len;
+ }
+ memcpy((uint8_t *)&g_adb_sync.packet.hdr + g_adb_sync.hdr_len, data, take);
+ g_adb_sync.hdr_len += take;
+ data += take;
+ len -= take;
+
+ if (g_adb_sync.hdr_len == sizeof(struct adb_sync_wire_hdr)) {
+ g_adb_sync.hdr_len = 0;
+ adb_sync_header_ready();
+ }
+ break;
+
+ case SYNC_RX_STATE_BODY:
+ take = (g_adb_sync.rx_remain < len) ? g_adb_sync.rx_remain : len;
+ memcpy(body + g_adb_sync.body_len, data, take);
+ g_adb_sync.body_len += take;
+ g_adb_sync.rx_remain -= take;
+ data += take;
+ len -= take;
+
+ if (g_adb_sync.rx_remain == 0) {
+ body[g_adb_sync.body_len] = '\0';
+ adb_sync_dispatch(g_adb_sync.packet.hdr.id, (char *)body);
+ g_adb_sync.rx_state = SYNC_RX_STATE_HEADER;
+ }
+ break;
+
+ case SYNC_RX_STATE_DATA:
+ take = (g_adb_sync.rx_remain < len) ? g_adb_sync.rx_remain : len;
+ adb_sync_data_write(data, take);
+ g_adb_sync.rx_remain -= take;
+ data += take;
+ len -= take;
+
+ if (g_adb_sync.rx_remain == 0) {
+ g_adb_sync.rx_state = SYNC_RX_STATE_HEADER;
+ }
+ break;
+
+ case SYNC_RX_STATE_SKIP:
+ default:
+ take = (g_adb_sync.rx_remain < len) ? g_adb_sync.rx_remain : len;
+ g_adb_sync.rx_remain -= take;
+ data += take;
+ len -= take;
+
+ if (g_adb_sync.rx_remain == 0) {
+ g_adb_sync.rx_state = SYNC_RX_STATE_HEADER;
+ }
+ break;
+ }
+ }
+}
+
+static void usbd_adb_sync_on_open(uint32_t remoteid)
+{
+ adb_sync_reset();
+ USB_LOG_INFO("adb sync open, remoteid:%u\r\n", (unsigned)remoteid);
+}
+
+static void usbd_adb_sync_on_close(uint32_t remoteid)
+{
+ (void)remoteid;
+
+ adb_sync_reset();
+ USB_LOG_INFO("adb sync close, remoteid:%u\r\n", (unsigned)remoteid);
+}
+
+static void usbd_adb_sync_on_write(uint32_t remoteid, const uint8_t *data, uint32_t len)
+{
+ (void)remoteid;
+
+ if (!data || !len) {
+ return;
+ }
+
+ adb_sync_consume(data, len);
+}
+
+static const struct adb_service adb_sync_service = {
+ .name = "sync:",
+ .localid = ADB_LOCALID_SYNC,
+ .on_open = usbd_adb_sync_on_open,
+ .on_close = usbd_adb_sync_on_close,
+ .on_write = usbd_adb_sync_on_write,
+};
+
+void usbd_adb_sync_init(void)
+{
+ g_adb_sync.fd = -1;
+ adb_sync_reset();
+
+ if (usbd_adb_service_register(&adb_sync_service) != 0) {
+ USB_LOG_ERR("adb sync: registering the service failed\r\n");
+ }
+}