/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2013 by Amaury Pouly * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include "stddef.h" #include "config.h" #include "protocol.h" #include "logf.h" #include "usb_ch9.h" #include "usb_drv.h" #include "memory.h" #include "target.h" #include "system.h" extern unsigned char oc_codestart[]; extern unsigned char oc_codeend[]; extern unsigned char oc_stackstart[]; extern unsigned char oc_stackend[]; extern unsigned char oc_bufferstart[]; extern unsigned char oc_bufferend[]; #define oc_codesize ((size_t)(oc_codeend - oc_codestart)) #define oc_stacksize ((size_t)(oc_stackend - oc_stackstart)) #define oc_buffersize ((size_t)(oc_bufferend - oc_bufferstart)) static bool g_exit = false; /** * * USB stack * */ static struct usb_device_descriptor device_descriptor= { .bLength = sizeof(struct usb_device_descriptor), .bDescriptorType = USB_DT_DEVICE, .bcdUSB = 0x0200, .bDeviceClass = USB_CLASS_PER_INTERFACE, .bDeviceSubClass = 0, .bDeviceProtocol = 0, .bMaxPacketSize0 = 64, .idVendor = HWSTUB_USB_VID, .idProduct = HWSTUB_USB_PID, .bcdDevice = HWSTUB_VERSION_MAJOR << 8 | HWSTUB_VERSION_MINOR, .iManufacturer = 1, .iProduct = 2, .iSerialNumber = 0, .bNumConfigurations = 1 }; #define USB_MAX_CURRENT 200 static struct usb_config_descriptor config_descriptor = { .bLength = sizeof(struct usb_config_descriptor), .bDescriptorType = USB_DT_CONFIG, .wTotalLength = 0, /* will be filled in later */ .bNumInterfaces = 1, .bConfigurationValue = 1, .iConfiguration = 0, .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, .bMaxPower = (USB_MAX_CURRENT + 1) / 2, /* In 2mA units */ }; #define USB_HWSTUB_INTF 0 static struct usb_interface_descriptor interface_descriptor = { .bLength = sizeof(struct usb_interface_descriptor), .bDescriptorType = USB_DT_INTERFACE, .bInterfaceNumber = USB_HWSTUB_INTF, .bAlternateSetting = 0, .bNumEndpoints = 0, .bInterfaceClass = HWSTUB_CLASS, .bInterfaceSubClass = HWSTUB_SUBCLASS, .bInterfaceProtocol = HWSTUB_PROTOCOL, .iInterface = 3 }; static const struct usb_string_descriptor usb_string_iManufacturer = USB_STRING_INITIALIZER(u"Rockbox.org"); static const struct usb_string_descriptor usb_string_iProduct = USB_STRING_INITIALIZER(u"Rockbox hardware stub"); static const struct usb_string_descriptor usb_string_iInterface = USB_STRING_INITIALIZER(u"HWStub"); /* this is stringid #0: languages supported */ static const struct usb_string_descriptor lang_descriptor = USB_STRING_INITIALIZER(u"\x0409"); /* LANGID US English */ static struct hwstub_version_desc_t version_descriptor = { sizeof(struct hwstub_version_desc_t), HWSTUB_DT_VERSION, HWSTUB_VERSION_MAJOR, HWSTUB_VERSION_MINOR, HWSTUB_VERSION_REV }; static struct hwstub_layout_desc_t layout_descriptor = { sizeof(struct hwstub_layout_desc_t), HWSTUB_DT_LAYOUT, 0, 0, 0, 0, 0, 0 }; #define USB_NUM_STRINGS 5 static const struct usb_string_descriptor* const usb_strings[USB_NUM_STRINGS] = { &lang_descriptor, &usb_string_iManufacturer, &usb_string_iProduct, &usb_string_iInterface }; uint8_t *usb_buffer = oc_bufferstart; uint32_t usb_buffer_size = 0; static void fill_layout_info(void) { layout_descriptor.dCodeStart = (uint32_t)oc_codestart; layout_descriptor.dCodeSize = oc_codesize; layout_descriptor.dStackStart = (uint32_t)oc_stackstart; layout_descriptor.dStackSize = oc_stacksize; layout_descriptor.dBufferStart = (uint32_t)oc_bufferstart; layout_descriptor.dBufferSize = oc_buffersize; } static void handle_std_dev_desc(struct usb_ctrlrequest *req) { int size; void* ptr = NULL; unsigned index = req->wValue & 0xff; switch(req->wValue >> 8) { case USB_DT_DEVICE: ptr = &device_descriptor; size = sizeof(struct usb_device_descriptor); break; case USB_DT_OTHER_SPEED_CONFIG: case USB_DT_CONFIG: { /* int max_packet_size; */ /* config desc */ if((req->wValue >> 8) == USB_DT_CONFIG) { /* max_packet_size = (usb_drv_port_speed() ? 512 : 64); */ config_descriptor.bDescriptorType = USB_DT_CONFIG; } else { /* max_packet_size = (usb_drv_port_speed() ? 64 : 512); */ config_descriptor.bDescriptorType = USB_DT_OTHER_SPEED_CONFIG; } size = sizeof(struct usb_config_descriptor); /* interface desc */ memcpy(usb_buffer + size, (void *)&interface_descriptor, sizeof(interface_descriptor)); size += sizeof(interface_descriptor); /* hwstub version */ memcpy(usb_buffer + size, (void *)&version_descriptor, sizeof(version_descriptor)); size += sizeof(version_descriptor); /* hwstub layout */ fill_layout_info(); memcpy(usb_buffer + size, (void *)&layout_descriptor, sizeof(layout_descriptor)); size += sizeof(layout_descriptor); /* hwstub target */ fill_layout_info(); memcpy(usb_buffer + size, (void *)&target_descriptor, sizeof(target_descriptor)); size += sizeof(target_descriptor); /* target specific descriptors */ target_get_config_desc(usb_buffer + size, &size); /* fix config descriptor */ config_descriptor.wTotalLength = size; memcpy(usb_buffer, (void *)&config_descriptor, sizeof(config_descriptor)); ptr = usb_buffer; break; } case USB_DT_STRING: if(index < USB_NUM_STRINGS) { size = usb_strings[index]->bLength; ptr = (void *)usb_strings[index]; } else usb_drv_stall(EP_CONTROL, true, true); break; default: break; } if(ptr) { int length = MIN(size, req->wLength); if(ptr != usb_buffer) memcpy(usb_buffer, ptr, length); usb_drv_send(EP_CONTROL, usb_buffer, length); usb_drv_recv(EP_CONTROL, NULL, 0); } else usb_drv_stall(EP_CONTROL, true, true); } static void handle_std_dev_req(struct usb_ctrlrequest *req) { switch(req->bRequest) { case USB_REQ_GET_CONFIGURATION: usb_buffer[0] = 1; usb_drv_send(EP_CONTROL, usb_buffer, 1); usb_drv_recv(EP_CONTROL, NULL, 0); break; case USB_REQ_SET_CONFIGURATION: usb_drv_send(EP_CONTROL, NULL, 0); break; case USB_REQ_GET_DESCRIPTOR: handle_std_dev_desc(req); break; case USB_REQ_SET_ADDRESS: usb_drv_send(EP_CONTROL, NULL, 0); usb_drv_set_address(req->wValue); break; case USB_REQ_GET_STATUS: usb_buffer[0] = 0; usb_buffer[1] = 0; usb_drv_send(EP_CONTROL, usb_buffer, 2); usb_drv_recv(EP_CONTROL, NULL, 0); break; default: usb_drv_stall(EP_CONTROL, true, true); } } static void handle_std_intf_desc(struct usb_ctrlrequest *req) { int size; void* ptr = NULL; switch(req->wValue >> 8) { case HWSTUB_DT_VERSION: ptr = &version_descriptor; size = sizeof(version_descriptor); break; case HWSTUB_DT_LAYOUT: ptr = &layout_descriptor; size = sizeof(layout_descriptor); break; case HWSTUB_DT_TARGET: ptr = &target_descriptor; size = sizeof(target_descriptor); break; default: target_get_desc(req->wValue >> 8, &ptr); if(ptr != 0) size = ((struct usb_descriptor_header *)ptr)->bLength; break; } if(ptr) { int length = MIN(size, req->wLength); if(ptr != usb_buffer) memcpy(usb_buffer, ptr, length); usb_drv_send(EP_CONTROL, usb_buffer, length); usb_drv_recv(EP_CONTROL, NULL, 0); } else usb_drv_stall(EP_CONTROL, true, true); } static void handle_std_intf_req(struct usb_ctrlrequest *req) { unsigned intf = req->wIndex & 0xff; if(intf != USB_HWSTUB_INTF) return usb_drv_stall(EP_CONTROL, true, true); switch(req->bRequest) { case USB_REQ_GET_DESCRIPTOR: handle_std_intf_desc(req); break; default: usb_drv_stall(EP_CONTROL, true, true); } } static void handle_std_req(struct usb_ctrlrequest *req) { switch(req->bRequestType & USB_RECIP_MASK) { case USB_RECIP_DEVICE: return handle_std_dev_req(req); case USB_RECIP_INTERFACE: return handle_std_intf_req(req); default: usb_drv_stall(EP_CONTROL, true, true); } } static void handle_get_log(struct usb_ctrlrequest *req) { enable_logf(false); int length = logf_readback(usb_buffer, MIN(req->wLength, usb_buffer_size)); usb_drv_send(EP_CONTROL, usb_buffer, length); usb_drv_recv(EP_CONTROL, NULL, 0); enable_logf(true); } /* default implementation, relying on the compiler to produce correct code, * targets should reimplement this... */ uint8_t __attribute__((weak)) target_read8(const void *addr) { return *(volatile uint8_t *)addr; } uint16_t __attribute__((weak)) target_read16(const void *addr) { return *(volatile uint16_t *)addr; } uint32_t __attribute__((weak)) target_read32(const void *addr) { return *(volatile uint32_t *)addr; } void __attribute__((weak)) target_write8(void *addr, uint8_t val) { *(volatile uint8_t *)addr = val; } void __attribute__((weak)) target_write16(void *addr, uint16_t val) { *(volatile uint16_t *)addr = val; } void __attribute__((weak)) target_write32(void *addr, uint32_t val) { *(volatile uint32_t *)addr = val; } static bool read_atomic(void *dst, void *src, size_t sz) { switch(sz) { case 1: *(uint8_t *)dst = target_read8(src); return true; case 2: *(uint16_t *)dst = target_read16(src); return true; case 4: *(uint32_t *)dst = target_read32(src); return true; default: return false; } } static void *last_read_addr = 0; static uint16_t last_read_id = 0xffff; static size_t last_read_max_size = 0; static void handle_read(struct usb_ctrlrequest *req) { uint16_t id = req->wValue; if(req->bRequest == HWSTUB_READ) { int size = usb_drv_recv(EP_CONTROL, usb_buffer, req->wLength); if(size != sizeof(struct hwstub_read_req_t)) return usb_drv_stall(EP_CONTROL, true, true); asm volatile("nop" : : : "memory"); struct hwstub_read_req_t *read = (void *)usb_buffer; last_read_addr = (void *)read->dAddress; last_read_max_size = usb_buffer_size; last_read_id = id; usb_drv_send(EP_CONTROL, NULL, 0); } else { /* NOTE: READ2 is also called after a coprocessor operation */ if(id != last_read_id) return usb_drv_stall(EP_CONTROL, true, true); size_t len = MIN(req->wLength, last_read_max_size); int ret = set_exception_jmp(); if(ret == 0) { if(req->bRequest == HWSTUB_READ2_ATOMIC) { if(!read_atomic(usb_buffer, last_read_addr, len)) return usb_drv_stall(EP_CONTROL, true, true); } else { memcpy(usb_buffer, last_read_addr, len); asm volatile("nop" : : : "memory"); } } else { logf("trapped exception %d in read [0x%x,0x%x]\n", ret, last_read_addr, last_read_addr + len); return usb_drv_stall(EP_CONTROL, true, true); } usb_drv_send(EP_CONTROL, usb_buffer, len); usb_drv_recv(EP_CONTROL, NULL, 0); } } static bool write_atomic(void *dst, void *src, size_t sz) { switch(sz) { case 1: target_write8(dst, *(uint8_t *)src); return true; case 2: target_write16(dst, *(uint16_t *)src); return true; case 4: target_write32(dst, *(uint32_t *)src); return true; default: return false; } } static void handle_write(struct usb_ctrlrequest *req) { int size = usb_drv_recv(EP_CONTROL, usb_buffer, req->wLength); asm volatile("nop" : : : "memory"); struct hwstub_write_req_t *write = (void *)usb_buffer; int sz_hdr = sizeof(struct hwstub_write_req_t); if(size < sz_hdr) return usb_drv_stall(EP_CONTROL, true, true); int ret = set_exception_jmp(); if(ret == 0) { if(req->bRequest == HWSTUB_WRITE_ATOMIC) { if(!write_atomic((void *)write->dAddress, usb_buffer + sz_hdr, size - sz_hdr)) return usb_drv_stall(EP_CONTROL, true, true); } else { memcpy((void *)write->dAddress, usb_buffer + sz_hdr, size - sz_hdr); } } else { logf("trapped exception %d in write [0x%x,0x%x]\n", ret, write->dAddress, write->dAddress + size - sz_hdr); return usb_drv_stall(EP_CONTROL, true, true); } usb_drv_send(EP_CONTROL, NULL, 0); } static bool do_call(uint32_t addr) { /* trap exceptions */ int ret = set_exception_jmp(); if(ret == 0) { #if defined(CPU_ARM) /* in case of call, respond after return */ asm volatile("blx %0\n" : : "r"(addr) : "memory"); return true; #elif defined(CPU_MIPS) asm volatile("jalr %0\nnop\n" : : "r"(addr) : "memory"); return true; #else #warning call is unsupported on this platform return false; #endif } else { logf("trapped exception %d in call\n", ret); return false; } } static void do_jump(uint32_t addr) { #if defined(CPU_ARM) asm volatile("bx %0\n" : : "r" (addr) : "memory"); #elif defined(CPU_MIPS) asm volatile("jr %0\nnop\n" : : "r" (addr) : "memory"); #else #warning jump is unsupported on this platform #define NO_JUMP #endif } static void handle_exec(struct usb_ctrlrequest *req) { int size = usb_drv_recv(EP_CONTROL, usb_buffer, req->wLength); asm volatile("nop" : : : "memory"); struct hwstub_exec_req_t *exec = (void *)usb_buffer; if(size != sizeof(struct hwstub_exec_req_t)) return usb_drv_stall(EP_CONTROL, true, true); uint32_t addr = exec->dAddress; #if defined(CPU_ARM) if(exec->bmFlags & HWSTUB_EXEC_THUMB) addr |= 1; else addr &= ~1; #endif #ifdef CONFIG_FLUSH_CACHES target_flush_caches(); #endif if(exec->bmFlags & HWSTUB_EXEC_CALL) { if(do_call(addr)) usb_drv_send(EP_CONTROL, NULL, 0); else usb_drv_stall(EP_CONTROL, true, true); } else { #ifndef NO_JUMP /* in case of jump, respond immediately and disconnect usb */ usb_drv_send(EP_CONTROL, NULL, 0); usb_drv_exit(); do_jump(addr); #else usb_drv_stall(EP_CONTROL, true, true); #endif } } #ifdef CPU_MIPS static uint32_t rw_cp0_inst_buffer[3]; typedef uint32_t (*read_cp0_inst_buffer_fn_t)(void); typedef void (*write_cp0_inst_buffer_fn_t)(uint32_t); uint32_t mips_read_cp0(unsigned reg, unsigned sel) { /* ok this is tricky because the coprocessor read instruction encoding * contains the register and select, so we need to generate the instruction * on the fly, we generate a "function like" buffer with three instructions: * mfc0 v0, reg, sel * jr ra * nop */ rw_cp0_inst_buffer[0] = 0x40000000 | /*v0*/2 << 16 | (sel & 0x7) | (reg & 0x1f) << 11; rw_cp0_inst_buffer[1] = /*ra*/31 << 21 | 0x8; /* jr ra */ rw_cp0_inst_buffer[2] = 0; /* nop */ #ifdef CONFIG_FLUSH_CACHES target_flush_caches(); #endif read_cp0_inst_buffer_fn_t fn = (read_cp0_inst_buffer_fn_t)rw_cp0_inst_buffer; return fn(); } void mips_write_cp0(unsigned reg, unsigned sel, uint32_t val) { /* ok this is tricky because the coprocessor write instruction encoding * contains the register and select, so we need to generate the instruction * on the fly, we generate a "function like" buffer with three instructions: * mtc0 a0, reg, sel * jr ra * nop */ rw_cp0_inst_buffer[0] = 0x40800000 | /*a0*/4 << 16 | (sel & 0x7) | (reg & 0x1f) << 11; rw_cp0_inst_buffer[1] = /*ra*/31 << 21 | 0x8; /* jr ra */ rw_cp0_inst_buffer[2] = 0; /* nop */ #ifdef CONFIG_FLUSH_CACHES target_flush_caches(); #endif write_cp0_inst_buffer_fn_t fn = (write_cp0_inst_buffer_fn_t)rw_cp0_inst_buffer; fn(val); } #endif /* coprocessor read: return <0 on error (-2 for dull dump), or size to return * to host otherwise */ int cop_read(uint8_t args[HWSTUB_COP_ARGS], void *out_data, size_t out_max_sz) { /* virtually all targets do register-based operation, so 32-bit */ if(out_max_sz < 4) { logf("cop read failed: output buffer is too small\n"); return -1; } #ifdef CPU_MIPS if(args[HWSTUB_COP_MIPS_COP] != 0) { logf("cop read failed: only mips cp0 is supported\n"); return -2; } *(uint32_t *)out_data = mips_read_cp0(args[HWSTUB_COP_MIPS_REG], args[HWSTUB_COP_MIPS_SEL]); return 4; #else (void) args; (void) out_data; (void) out_max_sz; logf("cop read failed: unsupported cpu\n"); return -1; #endif } /* coprocessor write: return <0 on error (-2 for dull dump), or 0 on success */ int cop_write(uint8_t args[HWSTUB_COP_ARGS], const void *in_data, size_t in_sz) { /* virtually all targets do register-based operation, so 32-bit */ if(in_sz != 4) { logf("cop read failed: input buffer has wrong size\n"); return -1; } #ifdef CPU_MIPS if(args[HWSTUB_COP_MIPS_COP] != 0) { logf("cop read failed: only mips cp0 is supported\n"); return -2; } mips_write_cp0(args[HWSTUB_COP_MIPS_REG], args[HWSTUB_COP_MIPS_SEL], *(uint32_t *)in_data); return 0; #else (void) args; (void) in_data; (void) in_sz; logf("cop write failed: unsupported cpu\n"); return -1; #endif } /* return size to return to host or <0 on error */ int do_cop_op(struct hwstub_cop_req_t *cop, void *in_data, size_t in_sz, void *out_data, size_t out_max_sz) { int ret = -2; /* -2 means full debug dump */ /* handle operations */ if(cop->bOp == HWSTUB_COP_READ) { /* read cannot have extra data */ if(in_sz > 0) goto Lerr; ret = cop_read(cop->bArgs, out_data, out_max_sz); } else if(cop->bOp == HWSTUB_COP_WRITE) { ret = cop_write(cop->bArgs, in_data, in_sz); } Lerr: if(ret == -2) { /* debug output */ logf("invalid cop op: %d, ", cop->bOp); for(int i = 0; i < HWSTUB_COP_ARGS; i++) logf("%c0x%x", i == 0 ? '[' : ',', cop->bArgs[i]); logf("] in:%d\n", in_sz); } return ret; } static void handle_cop(struct usb_ctrlrequest *req) { int size = usb_drv_recv(EP_CONTROL, usb_buffer, req->wLength); int hdr_sz = sizeof(struct hwstub_cop_req_t); asm volatile("nop" : : : "memory"); struct hwstub_cop_req_t *cop = (void *)usb_buffer; /* request should at least contain the header */ if(size < hdr_sz) return usb_drv_stall(EP_CONTROL, true, true); /* perform coprocessor operation: put output buffer after the input one, * limit output buffer size to maximum buffer size */ uint8_t *in_buf = usb_buffer + hdr_sz; size_t in_sz = req->wLength - hdr_sz; uint8_t *out_buf = in_buf + in_sz; size_t out_max_sz = usb_buffer_size - req->wLength; int ret = do_cop_op(cop, in_buf, in_sz, out_buf, out_max_sz); /* STALL on error */ if(ret < 0) return usb_drv_stall(EP_CONTROL, true, true); /* acknowledge */ usb_drv_send(EP_CONTROL, NULL, 0); /* if there is a read stage, prepare everything for the READ2 */ if(ret > 0) { last_read_id = req->wValue; last_read_addr = out_buf; last_read_max_size = ret; } } static void handle_class_intf_req(struct usb_ctrlrequest *req) { unsigned intf = req->wIndex & 0xff; if(intf != USB_HWSTUB_INTF) return usb_drv_stall(EP_CONTROL, true, true); switch(req->bRequest) { case HWSTUB_GET_LOG: return handle_get_log(req); case HWSTUB_READ: case HWSTUB_READ2: case HWSTUB_READ2_ATOMIC: return handle_read(req); case HWSTUB_WRITE: case HWSTUB_WRITE_ATOMIC: return handle_write(req); case HWSTUB_EXEC: return handle_exec(req); case HWSTUB_COPROCESSOR_OP: return handle_cop(req); default: usb_drv_stall(EP_CONTROL, true, true); } } static void handle_class_req(struct usb_ctrlrequest *req) { switch(req->bRequestType & USB_RECIP_MASK) { case USB_RECIP_INTERFACE: return handle_class_intf_req(req); case USB_RECIP_DEVICE: //return handle_class_dev_req(req); default: usb_drv_stall(EP_CONTROL, true, true); } } /** * * Main * */ void main(uint32_t arg) { usb_buffer_size = oc_buffersize; logf("hwstub %d.%d.%d\n", HWSTUB_VERSION_MAJOR, HWSTUB_VERSION_MINOR, HWSTUB_VERSION_REV); logf("argument: 0x%08x\n", arg); target_init(); usb_drv_init(); while(!g_exit) { struct usb_ctrlrequest req; usb_drv_recv_setup(&req); switch(req.bRequestType & USB_TYPE_MASK) { case USB_TYPE_STANDARD: handle_std_req(&req); break; case USB_TYPE_CLASS: handle_class_req(&req); break; default: usb_drv_stall(EP_CONTROL, true, true); } } usb_drv_exit(); }