/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2002 by Linus Nielsen Feltzing * * iPod driver based on code from the ipodlinux project - http://ipodlinux.org * Adapted for Rockbox in January 2006 * Original file: podzilla/usb.c * Copyright (C) 2005 Adam Johnston * * 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 "config.h" #include "cpu.h" #include "kernel.h" #include "thread.h" #include "system.h" #include "debug.h" #include "storage.h" #include "disk.h" #include "panic.h" #include "lcd.h" #include "usb.h" #include "button.h" #include "string.h" #ifdef HAVE_USBSTACK #include "usb_core.h" #endif #include "logf.h" #include "screendump.h" #include "powermgmt.h" #ifndef BOOTLOADER #include "misc.h" #include "gui/yesno.h" #include "settings.h" #include "lang_enum.h" #include "gui/skin_engine/skin_engine.h" #endif #if defined(HIBY_R3PROII) || defined(HIBY_R1) #include "usb-hiby-gadget.h" #endif #if defined(IPOD_ACCESSORY_PROTOCOL) #include "iap.h" #endif #if (!defined(BOOTLOADER) || defined(HAVE_BOOTLOADER_USB_MODE)) #define USB_FULL_INIT #endif /* USB detect debouncing interval (200ms taken from the usb polling code) */ #define USB_DEBOUNCE_POLL (200*HZ/1000) /* NOTE: "usb_dw_gonak_effective:failed!" *PANIC*s can be observed when disconnecting the FiiO M3K from USB with the debounce interval for USB_STATUS_BY_EVENT set to 200ms, as above (see e75a3fb). Adjusting the interval to 10ms reduces likelihood of a panic. */ #define USB_DEBOUNCE_TIME (10*HZ/1000) bool do_screendump_instead_of_usb = false; #if !defined(SIMULATOR) && !defined(USB_NONE) /* We assume that the USB cable is extracted */ static int usb_state = USB_EXTRACTED; #if (CONFIG_STORAGE & STORAGE_MMC) && defined(USB_FULL_INIT) && !defined(HAVE_USBSTACK) static int usb_mmc_countdown = 0; #endif #ifdef USB_FULL_INIT /* Make sure there's enough stack space for screendump */ #ifndef USB_EXTRA_STACK # define USB_EXTRA_STACK 0x0 /*Define in firmware/export/config/[target].h*/ #endif static long usb_stack[(DEFAULT_STACK_SIZE*4 + DUMP_BMP_LINESIZE + USB_EXTRA_STACK)/sizeof(long)]; static const char usb_thread_name[] = "usb"; static unsigned int usb_thread_entry = 0; static bool usb_monitor_enabled = false; static bool exclusive_storage_enabled = false; static bool exclusive_storage_requested = false; static struct event_queue usb_queue SHAREDBSS_ATTR; #ifdef USB_ENABLE_HID static bool usb_hid = true; #endif #ifdef USB_ENABLE_AUDIO static int usb_audio = 0; #endif static bool usb_host_present = false; static int usb_num_acks_to_expect = 0; static uint32_t usb_broadcast_seqnum = 0x80000000; #ifdef HAVE_USB_POWER static int usb_mode = USBMODE_DEFAULT; static bool usb_power_only = false; #endif #if defined(USB_FIREWIRE_HANDLING) static void try_reboot(void) { #ifdef HAVE_DISK_STORAGE storage_sleepnow(); /* Immediately spindown the disk. */ sleep(HZ*2); #endif #ifdef IPOD_ARCH /* The following code is based on ipodlinux */ #if CONFIG_CPU == PP5020 memcpy((void *)0x40017f00, "diskmode\0\0hotstuff\0\0\1", 21); #elif CONFIG_CPU == PP5022 memcpy((void *)0x4001ff00, "diskmode\0\0hotstuff\0\0\1", 21); #endif /* CONFIG_CPU */ #endif /* IPOD_ARCH */ #ifdef IPOD_NANO2G memcpy((void *)0x0002bf00, "diskmodehotstuff\1\0\0\0", 20); #endif system_reboot(); /* Reboot */ } #endif /* USB_FIRWIRE_HANDLING */ /* Screen dump */ static inline bool usb_do_screendump(void) { if(do_screendump_instead_of_usb) { screen_dump(); #ifdef HAVE_REMOTE_LCD remote_screen_dump(); #endif /* HAVE_REMOTE_LCD */ return true; } return false; } #ifdef HAVE_USB_POWER void usb_set_mode(int mode) { usb_mode = mode; #if defined(DX50) || defined(DX90) ibasso_set_usb_mode(mode); #elif defined(HIBY_R3PROII) || defined(HIBY_R1) hiby_set_usb_mode(mode); #endif } #endif #ifdef USB_FIREWIRE_HANDLING static inline bool usb_reboot_button(void) { #ifdef HAVE_USB_POWER return (button_status() & ~USBPOWER_BTN_IGNORE); #else return false; #endif } #endif /* USB_FIREWIRE_HANDLING */ /*--- Routines that differ depending upon the presence of a USB stack ---*/ #ifdef HAVE_USBSTACK /* Enable / disable USB when the stack is enabled - otherwise a noop */ static inline void usb_stack_enable(bool enable) { usb_enable(enable); } #ifdef HAVE_HOTSWAP static inline void usb_handle_hotswap(long id) { switch(id) { case SYS_HOTSWAP_INSERTED: case SYS_HOTSWAP_EXTRACTED: usb_core_hotswap_event(1, id == SYS_HOTSWAP_INSERTED); break; } /* Note: No MMC storage handling is needed with the stack atm. */ } #endif /* HAVE_HOTSWAP */ static inline void usb_configure_drivers(int for_state) { #ifdef USB_ENABLE_AUDIO // FIXME: doesn't seem to get set when loaded at boot... usb_audio = global_settings.usb_audio; #endif switch(for_state) { case USB_POWERED: #ifdef USB_ENABLE_STORAGE usb_core_enable_driver(USB_DRIVER_MASS_STORAGE, false); #endif #ifdef USB_ENABLE_HID #ifdef USB_ENABLE_CHARGING_ONLY usb_core_enable_driver(USB_DRIVER_HID, false); #else usb_core_enable_driver(USB_DRIVER_HID, true); #endif /* USB_ENABLE_CHARGING_ONLY */ #endif /* USB_ENABLE_HID */ #ifdef USB_ENABLE_AUDIO usb_core_enable_driver(USB_DRIVER_AUDIO, (usb_audio == 1) || (usb_audio == 2)); // while "always" or "only in charge-only mode" #endif /* USB_ENABLE_AUDIO */ #ifdef USB_ENABLE_CHARGING_ONLY usb_core_enable_driver(USB_DRIVER_CHARGING_ONLY, true); #endif usb_attach(); break; /* USB_POWERED: */ case USB_INSERTED: #ifdef USB_ENABLE_STORAGE usb_core_enable_driver(USB_DRIVER_MASS_STORAGE, true); #endif #ifdef USB_ENABLE_HID usb_core_enable_driver(USB_DRIVER_HID, usb_hid); #endif #ifdef USB_ENABLE_AUDIO usb_core_enable_driver(USB_DRIVER_AUDIO, (usb_audio == 1) || (usb_audio == 3)); // while "always" or "only in mass-storage mode" #endif /* USB_ENABLE_AUDIO */ #ifdef USB_ENABLE_CHARGING_ONLY usb_core_enable_driver(USB_DRIVER_CHARGING_ONLY, false); #endif usb_attach(); break; /* USB_INSERTED: */ case USB_EXTRACTED: /* do not call usb_release_exclusive_storage. * usb core handles it */ break; /* USB_EXTRACTED: */ } } static inline void usb_slave_mode(bool on) { int rc; if(on) { trigger_cpu_boost(); #ifdef HAVE_PRIORITY_SCHEDULING thread_set_priority(thread_self(), PRIORITY_REALTIME); #endif disk_unmount_all(); } else { #ifdef HAVE_PRIORITY_SCHEDULING thread_set_priority(thread_self(), PRIORITY_SYSTEM); #endif /* Entered exclusive mode */ rc = disk_mount_all(); if(rc <= 0) /* no partition */ panicf("mount: %d",rc); cancel_cpu_boost(); } } void usb_signal_transfer_completion( struct usb_transfer_completion_event_data* event_data) { queue_post(&usb_queue, USB_TRANSFER_COMPLETION, (intptr_t)event_data); } void usb_clear_pending_transfer_completion_events(void) { #ifdef HAVE_EXTENDED_MESSAGING_AND_NAME while (queue_peek_ex(&usb_queue, NULL, 1 | QPEEK_REMOVE_EVENTS, QPEEK_FILTER1(USB_TRANSFER_COMPLETION))); #endif } void usb_signal_notify(long id, intptr_t data) { queue_post(&usb_queue, id, data); } #else /* !HAVE_USBSTACK */ static inline void usb_stack_enable(bool enable) { (void)enable; } #ifdef HAVE_HOTSWAP static inline void usb_handle_hotswap(long id) { #if (CONFIG_STORAGE & STORAGE_MMC) switch(id) { case SYS_HOTSWAP_INSERTED: case SYS_HOTSWAP_EXTRACTED: if(usb_state == USB_INSERTED) { usb_enable(false); usb_mmc_countdown = HZ/2; /* re-enable after 0.5 sec */ } break; case USB_REENABLE: if(usb_state == USB_INSERTED) usb_enable(true); /* reenable only if still inserted */ break; } #endif /* STORAGE_MMC */ (void)id; } #endif /* HAVE_HOTSWAP */ static inline void usb_configure_drivers(int for_state) { switch(for_state) { case USB_POWERED: exclusive_storage_requested = false; break; case USB_INSERTED: usb_request_exclusive_storage(); break; case USB_EXTRACTED: usb_release_exclusive_storage(); break; } } static inline void usb_slave_mode(bool on) { int rc; if(on) { DEBUGF("Entering USB slave mode\n"); disk_unmount_all(); storage_soft_reset(); storage_init(); storage_enable(false); usb_enable(true); cpu_idle_mode(true); } else { DEBUGF("Leaving USB slave mode\n"); cpu_idle_mode(false); /* Let the ISDx00 settle */ sleep(HZ*1); usb_enable(false); rc = storage_init(); if(rc) panicf("storage: %d",rc); sleep(HZ/10); rc = disk_mount_all(); if(rc <= 0) /* no partition */ panicf("mount: %d",rc); } } #endif /* HAVE_USBSTACK */ static void usb_set_host_present(bool present) { if(usb_host_present == present) return; usb_host_present = present; if(!usb_host_present) { usb_configure_drivers(USB_EXTRACTED); return; } #ifdef HAVE_USB_POWER if (usb_power_only) { /* Only charging is desired */ usb_configure_drivers(USB_POWERED); return; } #endif usb_configure_drivers(USB_INSERTED); } /*--- General driver code ---*/ static void NORETURN_ATTR usb_thread(void) { struct queue_event ev; while(1) { queue_wait(&usb_queue, &ev); reset_poweroff_timer(); /* Any USB event counts */ switch(ev.id) { /*** Main USB thread duties ***/ #ifdef HAVE_USBSTACK case USB_NOTIFY_SET_ADDR: case USB_NOTIFY_SET_CONFIG: case USB_NOTIFY_BUS_RESET: case USB_NOTIFY_CLASS_DRIVER: if(usb_state <= USB_EXTRACTED) break; usb_core_handle_notify(ev.id, ev.data); break; case USB_TRANSFER_COMPLETION: if(usb_state <= USB_EXTRACTED) break; #ifdef USB_DETECT_BY_REQUEST usb_state = USB_INSERTED; usb_set_host_present(true); #endif usb_core_handle_transfer_completion( (struct usb_transfer_completion_event_data*)ev.data); break; #endif /* HAVE_USBSTACK */ case USB_INSERTED: if(usb_state != USB_EXTRACTED) break; if(usb_do_screendump()) { usb_state = USB_SCREENDUMP; break; } usb_state = USB_POWERED; usb_stack_enable(true); #ifndef BOOTLOADER #ifndef HAVE_USB_POWER int usb_mode = -1; #endif send_event(SYS_EVENT_USB_INSERTED, &usb_mode); #endif #ifdef HAVE_USB_POWER /* Power (charging-only) button */ usb_power_only = usb_mode != USB_MODE_MASS_STORAGE; if(button_status() & ~USBPOWER_BTN_IGNORE) { usb_power_only = !usb_power_only; } #endif #ifndef USB_DETECT_BY_REQUEST usb_state = USB_INSERTED; usb_set_host_present(true); #endif break; /* USB_INSERTED */ case SYS_USB_CONNECTED_ACK: if((uint32_t)ev.data != usb_broadcast_seqnum) { DEBUGF("usb: late ack %lX < %lX", ev.data, usb_broadcast_seqnum); break; } if(usb_num_acks_to_expect == 0) { DEBUGF("usb: unexpected ack"); break; } if(--usb_num_acks_to_expect > 0) { DEBUGF("usb: got ack, %d to go...\n", usb_num_acks_to_expect); break; } DEBUGF("usb: all threads have acknowledged the connect.\n"); if(usb_host_present && exclusive_storage_requested) { usb_slave_mode(true); exclusive_storage_enabled = true; } break; /* SYS_USB_CONNECTED_ACK */ case USB_EXTRACTED: if(usb_state == USB_EXTRACTED) break; if(usb_state == USB_POWERED || usb_state == USB_INSERTED) usb_stack_enable(false); #ifdef IPOD_ACCESSORY_PROTOCOL iap_reset_state(IF_IAP_MP(0)); #endif usb_state = USB_EXTRACTED; #ifndef BOOTLOADER send_event(SYS_EVENT_USB_EXTRACTED, NULL); #endif usb_set_host_present(false); break; /* USB_EXTRACTED: */ /*** Miscellaneous USB thread duties ***/ /* HOTSWAP */ #ifdef HAVE_HOTSWAP case SYS_HOTSWAP_INSERTED: case SYS_HOTSWAP_EXTRACTED: #if (CONFIG_STORAGE & STORAGE_MMC) case USB_REENABLE: #endif usb_handle_hotswap(ev.id); break; #endif /* FIREWIRE */ #ifdef USB_FIREWIRE_HANDLING case USB_REQUEST_REBOOT: if(usb_reboot_button()) try_reboot(); break; #endif /* CHARGING */ #if defined(HAVE_USB_CHARGING_ENABLE) && defined(HAVE_USBSTACK) case USB_CHARGER_UPDATE: usb_charging_maxcurrent_change(usb_charging_maxcurrent()); break; #endif /* CLOSE */ #ifdef USB_DRIVER_CLOSE case USB_QUIT: thread_exit(); break; #endif } /* switch */ } /* while */ } #if defined(HAVE_USB_CHARGING_ENABLE) && defined(HAVE_USBSTACK) void usb_charger_update(void) { queue_post(&usb_queue, USB_CHARGER_UPDATE, 0); } #endif #ifdef USB_STATUS_BY_EVENT static int usb_status_tmo_callback(struct timeout* tmo) { if(usb_monitor_enabled) { int current_status = usb_detect(); int* last_status = (int*)tmo->data; if(current_status != *last_status) { /* Signal changed during the timeout; wait longer */ *last_status = current_status; return USB_DEBOUNCE_TIME; } /* Signal is stable, post the event. The thread will deal with * any spurious transitions (like inserted -> inserted). */ queue_post(&usb_queue, current_status, 0); } return 0; } void usb_status_event(int current_status) { static struct timeout tmo; static int last_status = USB_EXTRACTED; /* Caller isn't expected to filter for changes in status. * current_status: * USB_INSERTED, USB_EXTRACTED */ if(usb_monitor_enabled) { int oldstatus = disable_irq_save(); /* Dual-use function */ last_status = current_status; timeout_register(&tmo, usb_status_tmo_callback, USB_DEBOUNCE_TIME, (intptr_t)&last_status); restore_irq(oldstatus); } } void usb_start_monitoring(void) { int oldstatus = disable_irq_save(); /* Sync to event */ int status = usb_detect(); usb_monitor_enabled = true; /* An event may have been missed because it was sent before monitoring * was enabled due to the connector already having been inserted before * before or during boot. */ usb_status_event(status); #ifdef USB_FIREWIRE_HANDLING if(firewire_detect()) usb_firewire_connect_event(); #endif restore_irq(oldstatus); } #ifdef USB_FIREWIRE_HANDLING void usb_firewire_connect_event(void) { queue_post(&usb_queue, USB_REQUEST_REBOOT, 0); } #endif /* USB_FIREWIRE_HANDLING */ #else /* !USB_STATUS_BY_EVENT */ static void usb_tick(void) { static int usb_countdown = -1; static int last_usb_status = USB_EXTRACTED; #ifdef USB_FIREWIRE_HANDLING static int firewire_countdown = -1; static int last_firewire_status = false; #endif if(usb_monitor_enabled) { #ifdef USB_FIREWIRE_HANDLING int current_firewire_status = firewire_detect(); if(current_firewire_status != last_firewire_status) { last_firewire_status = current_firewire_status; firewire_countdown = USB_DEBOUNCE_POLL; } else { /* Count down until it gets negative */ if(firewire_countdown >= 0) firewire_countdown--; /* Report status when the signal has been stable long enough */ if(firewire_countdown == 0) { queue_post(&usb_queue, USB_REQUEST_REBOOT, 0); } } #endif /* USB_FIREWIRE_HANDLING */ int current_status = usb_detect(); /* Only report when the status has changed */ if(current_status != last_usb_status) { last_usb_status = current_status; usb_countdown = USB_DEBOUNCE_POLL; } else { /* Count down until it gets negative */ if(usb_countdown >= 0) usb_countdown--; /* Report status when the signal has been stable long enough */ if(usb_countdown == 0) { queue_post(&usb_queue, current_status, 0); } } } #if (CONFIG_STORAGE & STORAGE_MMC) if(usb_mmc_countdown > 0) { usb_mmc_countdown--; if(usb_mmc_countdown == 0) queue_post(&usb_queue, USB_REENABLE, 0); } #endif } void usb_start_monitoring(void) { usb_monitor_enabled = true; } #endif /* USB_STATUS_BY_EVENT */ void usb_acknowledge(long id, intptr_t seqnum) { queue_post(&usb_queue, id, seqnum); } #endif /* USB_FULL_INIT */ void usb_init(void) { /* Do required hardware inits first. For software USB the driver has * to make sure this won't trigger a transfer completion before the * queue and thread are created. */ usb_init_device(); #ifdef USB_FULL_INIT usb_enable(false); queue_init(&usb_queue, true); usb_thread_entry = create_thread(usb_thread, usb_stack, sizeof(usb_stack), 0, usb_thread_name IF_PRIO(, PRIORITY_SYSTEM) IF_COP(, CPU)); #ifndef USB_STATUS_BY_EVENT tick_add_task(usb_tick); #endif #endif /* USB_FULL_INIT */ } void usb_wait_for_disconnect(struct event_queue *q) { #ifdef USB_FULL_INIT struct queue_event ev; /* Don't return until we get SYS_USB_DISCONNECTED */ while(1) { queue_wait(q, &ev); if(ev.id == SYS_USB_DISCONNECTED) return; } #endif /* USB_FULL_INIT */ (void)q; } int usb_wait_for_disconnect_w_tmo(struct event_queue *q, int ticks) { #ifdef USB_FULL_INIT struct queue_event ev; /* Don't return until we get SYS_USB_DISCONNECTED or SYS_TIMEOUT */ while(1) { queue_wait_w_tmo(q, &ev, ticks); switch(ev.id) { case SYS_USB_DISCONNECTED: return 0; case SYS_TIMEOUT: return 1; } } #endif /* USB_FULL_INIT */ (void)q; (void)ticks; return 0; } #ifdef USB_DRIVER_CLOSE void usb_close(void) { unsigned int thread = usb_thread_entry; usb_thread_entry = 0; if(thread == 0) return; #ifndef USB_STATUS_BY_EVENT tick_remove_task(usb_tick); #endif usb_monitor_enabled = false; queue_post(&usb_queue, USB_QUIT, 0); thread_wait(thread); } #endif /* USB_DRIVER_CLOSE */ bool usb_inserted(void) { return usb_state == USB_INSERTED || usb_state == USB_POWERED; } #if defined(USB_FULL_INIT) bool usb_exclusive_storage(void) { /* Storage isn't actually exclusive until slave mode has been entered */ return exclusive_storage_enabled; } /* exclusive storage mode transision * HAVE_USBSTACK: * (inserted) * usb_set_host_present(true) * usb_configure_drivers(USB_INSERTED) * ... * (SET_CONFIG(n) which requires exclusive storage) * usb_core_do_set_config(n) * usb_request_exclusive_storage() * exclusive_storage_requested = true * ... * (all threads acked) * usb_slave_mode(true) * disk_unmount_all() * exclusive_storage_enabled = true * (exclusive mode done) * ... * (extracted, or SET_CONFIG(m) which does not require exclusive storage) * usb_core_do_set_config(m) * usb_release_exclusive_storage() * exclusive_storage_requested = false * exclusive_storage_enabled = false * usb_slave_mode(false) * disk_mount_all() * * !HAVE_USBSTACK: * (inserted) * usb_set_host_present(true) * usb_configure_drivers(USB_INSERTED) * usb_request_exclusive_storage() * exclusive_storage_requested = true * ... * (all threads acked) * usb_slave_mode(true) * disk_unmount_all() * exclusive_storage_enabled = true * ... * (extracted) * usb_set_host_present(false) * usb_configure_drivers(USB_EXTRACTED) * usb_release_exclusive_storage() * .. * */ void usb_request_exclusive_storage(void) { exclusive_storage_requested = true; usb_broadcast_seqnum += 1; usb_num_acks_to_expect = queue_broadcast(SYS_USB_CONNECTED, usb_broadcast_seqnum) - 1; DEBUGF("usb: waiting for %d acks...\n", usb_num_acks_to_expect); } void usb_release_exclusive_storage(void) { int bccount; if(!exclusive_storage_requested) { return; } exclusive_storage_requested = false; if(exclusive_storage_enabled) { usb_slave_mode(false); } exclusive_storage_enabled = false; /* Tell all threads that we are back in business */ bccount = queue_broadcast(SYS_USB_DISCONNECTED, 0) - 1; #ifdef DEBUG DEBUGF("USB extracted. Broadcast to %d threads...\n", bccount); #else (void)bccount; #endif return; } #ifdef USB_ENABLE_HID void usb_set_hid(bool enable) { usb_hid = enable; usb_core_enable_driver(USB_DRIVER_HID, usb_hid); } #endif /* USB_ENABLE_HID */ #ifdef USB_ENABLE_AUDIO void usb_set_audio(int value) { usb_audio = value; } #endif /* USB_ENABLE_AUDIO */ #ifdef HAVE_USB_POWER bool usb_powered_only(void) { return usb_power_only; } #endif /* HAVE_USB_POWER */ #endif /* HAVE_USBSTACK && defined(USB_FULL_INIT) */ #elif defined(USB_NONE) /* Dummy functions for USB_NONE */ bool usb_inserted(void) { return false; } void usb_acknowledge(long id, intptr_t seqnum) { (void)id; (void)seqnum; } void usb_init(void) { } void usb_start_monitoring(void) { } int usb_detect(void) { return USB_EXTRACTED; } void usb_wait_for_disconnect(struct event_queue *q) { (void)q; } #endif /* USB_NONE */