diff options
Diffstat (limited to 'class/adb')
| -rw-r--r-- | class/adb/usbd_adb.c | 616 | ||||
| -rw-r--r-- | class/adb/usbd_adb.h | 68 | ||||
| -rw-r--r-- | class/adb/usbd_adb_cherrysh.c | 355 | ||||
| -rw-r--r-- | class/adb/usbd_adb_sync.c | 932 |
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"); + } +} |
