diff --git a/config_templates/BCT.ini b/config_templates/BCT.ini index 74dd6f648..b6193b9f4 100644 --- a/config_templates/BCT.ini +++ b/config_templates/BCT.ini @@ -10,3 +10,7 @@ stage2_entrypoint = 0xF0000000 ; To force-disable nogc, add nogc = 0 ; To opt out of using Atmosphere's NCM reimplementation, add disable_ncm = 1 + +[fastboot] +force_enable = 0 +button_timeout_ms = 0 diff --git a/fusee/fusee-primary/Makefile b/fusee/fusee-primary/Makefile index c471da0d4..4a764e7db 100644 --- a/fusee/fusee-primary/Makefile +++ b/fusee/fusee-primary/Makefile @@ -27,7 +27,7 @@ endif #--------------------------------------------------------------------------------- TARGET := $(notdir $(CURDIR)) BUILD := build -SOURCES := src src/sdmmc src/lib src/lib/fatfs src/display +SOURCES := src src/sdmmc src/lib src/lib/fatfs src/display src/fastboot DATA := data INCLUDES := include ../../libraries/libvapours/include @@ -39,11 +39,10 @@ DEFINES := -D__BPMP__ -DFUSEE_STAGE1_SRC -DATMOSPHERE_GIT_BRANCH=\"$(AMSBRANCH)\ CFLAGS := \ -g \ - -O2 \ + -Os \ -fomit-frame-pointer \ -ffunction-sections \ -fdata-sections \ - -std=gnu11 \ -Werror \ -Wall \ -fstrict-volatile-bitfields \ @@ -51,7 +50,7 @@ CFLAGS := \ CFLAGS += $(INCLUDE) -CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11 +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=c++2a ASFLAGS := -g $(ARCH) LDFLAGS = -specs=$(TOPDIR)/linker.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) diff --git a/fusee/fusee-primary/src/bct0.c b/fusee/fusee-primary/src/bct0.c index 523071685..2746a1d3f 100644 --- a/fusee/fusee-primary/src/bct0.c +++ b/fusee/fusee-primary/src/bct0.c @@ -20,12 +20,15 @@ #include +#define CONFIG_LOG_LEVEL_KEY "log_level" + #define STAGE2_NAME_KEY "stage2_path" #define STAGE2_MTC_NAME_KEY "stage2_mtc_path" #define STAGE2_ADDRESS_KEY "stage2_addr" #define STAGE2_ENTRYPOINT_KEY "stage2_entrypoint" -#define CONFIG_LOG_LEVEL_KEY "log_level" +#define FASTBOOT_FORCE_ENABLE_KEY "force_enable" +#define FASTBOOT_BUTTON_TIMEOUT_KEY "button_timeout_ms" static int bct0_ini_handler(void *user, const char *section, const char *name, const char *value) { bct0_t *bct0 = (bct0_t*) user; @@ -61,6 +64,18 @@ static int bct0_ini_handler(void *user, const char *section, const char *name, c } else { return 0; } + } else if (strcmp(section, "fastboot") == 0) { + if (strcmp(name, FASTBOOT_FORCE_ENABLE_KEY) == 0) { + int tmp = 0; + sscanf(value, "%d", &tmp); + bct0->fastboot_force_enable = (tmp != 0); + } else if (strcmp(name, FASTBOOT_BUTTON_TIMEOUT_KEY) == 0) { + int tmp = 0; + sscanf(value, "%d", &tmp); + bct0->fastboot_button_timeout = tmp; + } else { + return 0; + } } else { return 0; } @@ -77,5 +92,8 @@ int bct0_parse(const char *ini, bct0_t *out) { out->stage2_load_address = 0xf0000000; out->stage2_entrypoint = 0xf0000000; + out->fastboot_force_enable = false; + out->fastboot_button_timeout = 3000; + return ini_parse_string(ini, bct0_ini_handler, out); } diff --git a/fusee/fusee-primary/src/bct0.h b/fusee/fusee-primary/src/bct0.h index ebb86e2ee..948a49746 100644 --- a/fusee/fusee-primary/src/bct0.h +++ b/fusee/fusee-primary/src/bct0.h @@ -33,6 +33,10 @@ typedef struct { char stage2_mtc_path[0x100]; uintptr_t stage2_load_address; uintptr_t stage2_entrypoint; + + /* [fastboot] */ + bool fastboot_force_enable; + int fastboot_button_timeout; } bct0_t; int bct0_parse(const char *ini, bct0_t *out); diff --git a/fusee/fusee-primary/src/fastboot/fastboot.cpp b/fusee/fusee-primary/src/fastboot/fastboot.cpp new file mode 100644 index 000000000..4795452be --- /dev/null +++ b/fusee/fusee-primary/src/fastboot/fastboot.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "fastboot.h" +#include "fastboot_gadget.h" + +extern "C" { +#include "../lib/log.h" +#include "../timers.h" +#include "../utils.h" +#include "../btn.h" +} + +static ams::fastboot::FastbootGadget fastboot_gadget((uint8_t*) 0xf0000000, 1024 * 1024 * 256); // 256 MiB download buffer reaches end of address space + +extern "C" fastboot_return fastboot_enter(const bct0_t *bct0) { + bool should_enter = bct0->fastboot_force_enable; + + log_setup_display(); + + if (!should_enter) { + if (bct0->fastboot_button_timeout > 0) { + uint32_t timeout = bct0->fastboot_button_timeout; + uint32_t start_time = get_time_ms(); + uint32_t last_message_secs = 0; + while (get_time_ms() - start_time < timeout) { + uint32_t seconds_remaining = (start_time + timeout - get_time_ms() + 999) / 1000; + if (seconds_remaining != last_message_secs) { + print((ScreenLogLevel) (SCREEN_LOG_LEVEL_NONE | SCREEN_LOG_LEVEL_NO_PREFIX), "\rPress volume up in %d seconds to enter fastboot. ", seconds_remaining); + last_message_secs = seconds_remaining; + } + + if (btn_read() & BTN_VOL_UP) { + should_enter = true; + break; + } + } + print((ScreenLogLevel) (SCREEN_LOG_LEVEL_NONE | SCREEN_LOG_LEVEL_NO_PREFIX), "\n\n"); + } + } + + if (!should_enter) { + log_cleanup_display(); + return FASTBOOT_SKIPPED; + } + + print(SCREEN_LOG_LEVEL_DEBUG, "Entering fastboot.\n"); + ams::xusb::Initialize(); + print(SCREEN_LOG_LEVEL_DEBUG, "Finished initializing USB hardware.\n"); + ams::xusb::EnableDevice(fastboot_gadget); + + print((ScreenLogLevel) (SCREEN_LOG_LEVEL_NONE | SCREEN_LOG_LEVEL_NO_PREFIX), "Fastboot mode:\n"); + print((ScreenLogLevel) (SCREEN_LOG_LEVEL_NONE | SCREEN_LOG_LEVEL_NO_PREFIX), "-------------------------------\n"); + print((ScreenLogLevel) (SCREEN_LOG_LEVEL_NONE | SCREEN_LOG_LEVEL_NO_PREFIX), "Volume up: Reboot to RCM.\n"); + print((ScreenLogLevel) (SCREEN_LOG_LEVEL_NONE | SCREEN_LOG_LEVEL_NO_PREFIX), "Volume down: Boot normally.\n"); + print((ScreenLogLevel) (SCREEN_LOG_LEVEL_NONE | SCREEN_LOG_LEVEL_NO_PREFIX), "POWER: Reboot to fusee-primary.\n"); + + fastboot_return r = fastboot_gadget.Run(); + + log_cleanup_display(); + + return r; +} diff --git a/fusee/fusee-primary/src/fastboot/fastboot.h b/fusee/fusee-primary/src/fastboot/fastboot.h new file mode 100644 index 000000000..5c79444e1 --- /dev/null +++ b/fusee/fusee-primary/src/fastboot/fastboot.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "../bct0.h" + +enum fastboot_return { + FASTBOOT_INVALID, + FASTBOOT_SKIPPED, + FASTBOOT_LOAD_STAGE2, + FASTBOOT_CHAINLOAD, +}; + +enum fastboot_return fastboot_enter(const bct0_t *bct0); + +#ifdef __cplusplus +} +#endif diff --git a/fusee/fusee-primary/src/fastboot/fastboot_descriptors.inc b/fusee/fusee-primary/src/fastboot/fastboot_descriptors.inc new file mode 100644 index 000000000..3a0d988dc --- /dev/null +++ b/fusee/fusee-primary/src/fastboot/fastboot_descriptors.inc @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace ams { + + namespace fastboot { + + namespace descriptors { + + USB_DECLARE_STRING_DESCRIPTOR(langid, u"\x0409"); // English (US) + USB_DECLARE_STRING_DESCRIPTOR(manu, u"Atmosphère"); + USB_DECLARE_STRING_DESCRIPTOR(product, u"Fusée Fastboot Gadget"); + USB_DECLARE_STRING_DESCRIPTOR(serial, u"Serial Number"); + USB_DECLARE_STRING_DESCRIPTOR(configuration, u"Fastboot"); + USB_DECLARE_STRING_DESCRIPTOR(interface, u"Fastboot"); + + static constexpr usb::StringDescriptorIndexer sd_indexer; + + static constexpr usb::DeviceDescriptor device_descriptor = { + { + .bLength = sizeof(usb::DeviceDescriptor), + .bDescriptorType = usb::DescriptorType::DEVICE, + }, + { + .bcdUSB = 0x210, + .bDeviceClass = 0, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .bMaxPacketSize0 = 64, + .idVendor = 0x1209, + .idProduct = 0x8b01, + .bcdDevice = 0x100, + .iManufacturer = sd_indexer.manu, + .iProduct = sd_indexer.product, + .iSerialNumber = sd_indexer.serial, + .bNumConfigurations = 1, + } + }; + + static constexpr usb::DeviceQualifierDescriptor device_qualifier_descriptor = { + { + .bLength = sizeof(usb::DeviceQualifierDescriptor), + .bDescriptorType = usb::DescriptorType::DEVICE_QUALIFIER, + }, + { + .bcdUSB = 0x210, + .bDeviceClass = 0, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .bMaxPacketSize0 = 64, + .bNumConfigurations = 1, + .bReserved = 0, + } + }; + + static constexpr struct WholeConfigurationDescriptor { + usb::ConfigurationDescriptor configuration_descriptor = { + { + .bLength = sizeof(usb::ConfigurationDescriptor), + .bDescriptorType = usb::DescriptorType::CONFIGURATION, + }, + { + .wTotalLength = sizeof(WholeConfigurationDescriptor), + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = 4, + .bmAttributes = 0x80, + .bMaxPower = 0, + } + }; + usb::InterfaceDescriptor interface_descriptor { + { + .bLength = sizeof(usb::InterfaceDescriptor), + .bDescriptorType = usb::DescriptorType::INTERFACE, + }, + { + .bInterfaceNumber = 0, + .bAlternateSetting = 0, + .bNumEndpoints = 2, + .bInterfaceClass = 0xff, // Vendor Specific Class. + .bInterfaceSubClass = 0x42, // Fastboot. + .bInterfaceProtocol = 0x03, // Fastboot. + .iInterface = 5, + } + }; + usb::EndpointDescriptor endpoint_in_descriptor { + { + .bLength = sizeof(usb::EndpointDescriptor), + .bDescriptorType = usb::DescriptorType::ENDPOINT, + }, + { + .bEndpointAddress = 0x81, + .bmAttributes = 0x2, + .wMaxPacketSize = 512, + .bInterval = 1, + } + }; + usb::EndpointDescriptor endpoint_out_descriptor { + { + .bLength = sizeof(usb::EndpointDescriptor), + .bDescriptorType = usb::DescriptorType::ENDPOINT, + }, + { + .bEndpointAddress = 0x01, + .bmAttributes = 0x2, + .wMaxPacketSize = 512, + .bInterval = 1, + } + }; + } __attribute__((packed)) whole_configuration_descriptor; + + static const uint8_t bMS_VendorCode = 37; /* chosen by fair dice roll */ + + /* This helps Windows figure out that it should use the ADB driver with our device without having to get a driver package signed. */ + static constexpr struct { + usb::ms::os20::DescriptorSetHeader descriptor_set_header1 = { + .wLength = sizeof(usb::ms::os20::DescriptorSetHeader), + .wDescriptorType = usb::ms::os20::DescriptorType::SetHeaderDescriptor, + .dwWindowsVersion = usb::ms::NTDDI_WIN8_1, + .wTotalLength = sizeof(*this), + }; + + /* https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/automatic-installation-of-winusb#how-to-configure-a-winusb-device */ + usb::ms::os20::FeatureCompatibleID feature_compatible_id = usb::ms::os20::FeatureCompatibleID("WINUSB", ""); + + /* https://android.googlesource.com/platform/development/+/8c78ba643862731c603677284ae88089a959cc52/host/windows/usb/api/adb_api_extra.h#64 */ + usb::ms::os20::FeatureRegistryProperty feature_registry_device_interface_guid; + } __attribute__((packed)) ms_os20_descriptor; + + static constexpr auto bos_container = usb::make_bos_descriptor_container(usb::caps::USB_2_0_EXTENSION { + { + { + .bLength = sizeof(usb::caps::USB_2_0_EXTENSION), + .bDescriptorType = usb::DescriptorType::DEVICE_CAPABILITY, + }, + { + .bDevCapabilityType = usb::DeviceCapabilityType::USB_2_0_EXTENSION, + } + }, + { + .bmAttributes = 0, + } + }, usb::caps::SUPERSPEED { + { + { + .bLength = sizeof(usb::caps::SUPERSPEED), + .bDescriptorType = usb::DescriptorType::DEVICE_CAPABILITY, + }, + { + .bDevCapabilityType = usb::DeviceCapabilityType::SUPERSPEED_USB, + } + }, + { + .bmAttributes = 0, + .wSpeedsSupported = 0b1111, + .bFunctionalitySupport = 1, + .bU1DevExitLat = 0, + .wU2DevExitLat = 0 + } + }, usb::caps::make_platform_capability( + /* Indicate to Windows hosts that they can retrieve their OS-specific descriptor through a special vendor-specific request. */ + usb::ms::DescriptorSetInformation { + .dwWindowsVersion = usb::ms::NTDDI_WIN8_1, + .wMSOSDescriptorSetTotalLength = sizeof(ms_os20_descriptor), + .bMS_VendorCode = bMS_VendorCode, + .bAltEnumCode = 0, + }) + ); + + } // namespace descriptors + + } // namespace fastboot + +} // namespace ams diff --git a/fusee/fusee-primary/src/fastboot/fastboot_gadget.cpp b/fusee/fusee-primary/src/fastboot/fastboot_gadget.cpp new file mode 100644 index 000000000..a81616b56 --- /dev/null +++ b/fusee/fusee-primary/src/fastboot/fastboot_gadget.cpp @@ -0,0 +1,402 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "fastboot_gadget.h" + +#include "fastboot.h" + +#include "xusb.h" +#include "xusb_control.h" +#include "xusb_endpoint.h" + +extern "C" { +#include "../lib/log.h" +#include "../lib/vsprintf.h" +#include "../utils.h" +#include "../btn.h" +} + +#include "fastboot_descriptors.inc" + +#include +#include +#include + +namespace ams { + + namespace fastboot { + + namespace { + xusb::TRB in_ring[5]; + xusb::TRB out_ring[5]; + + namespace ep { + static constexpr auto out = xusb::endpoints[xusb::EpAddr {1, xusb::Dir::Out}]; + static constexpr auto in = xusb::endpoints[xusb::EpAddr {1, xusb::Dir::In}]; + } + + } // anonymous namespace + + FastbootGadget::FastbootGadget(uint8_t *download_buffer, size_t download_buffer_size) : + impl(*this), + download_buffer(download_buffer), + download_buffer_size(download_buffer_size) { + } + + fastboot_return FastbootGadget::Run() { + bool usb_error = false; + uint32_t last_button = btn_read(); + while (this->state < State::Exit || usb_error) { + if (!usb_error) { + xusb::Process(); + } + + if (this->state == State::UsbError && !usb_error) { + print((ScreenLogLevel) (SCREEN_LOG_LEVEL_NONE | SCREEN_LOG_LEVEL_NO_PREFIX), "\nFastboot encountered a USB error (0x%x).\nPress one of the buttons listed above to continue.\n", this->usb_error.GetValue()); + usb_error = true; + } + + uint32_t button = btn_read(); + if (button & BTN_VOL_UP && !(last_button & BTN_VOL_UP)) { + /* Reboot to RCM. */ + pmc_reboot(0); + return FASTBOOT_INVALID; + } + + if (button & BTN_VOL_DOWN && !(last_button & BTN_VOL_DOWN)) { + /* Boot normally. */ + print(SCREEN_LOG_LEVEL_INFO, "Booting normally.\n"); + return FASTBOOT_LOAD_STAGE2; + } + + if (button & BTN_POWER && !(last_button & BTN_POWER)) { + /* Reboot to fusee-primary. */ + reboot_to_self(); + return FASTBOOT_INVALID; + } + + last_button = button; + } + + switch(this->state) { + case State::Exit: + return FASTBOOT_LOAD_STAGE2; + case State::Reboot: + reboot_to_self(); + return FASTBOOT_INVALID; + case State::Chainload: + return FASTBOOT_CHAINLOAD; + default: + return FASTBOOT_INVALID; + } + } + + void FastbootGadget::FormatResponse(ResponseToken token, const char *fmt, std::va_list args) { + const char *token_str = nullptr; + + switch(token) { + case ResponseToken::OKAY: + token_str = "OKAY"; + break; + case ResponseToken::INFO: + token_str = "INFO"; + break; + case ResponseToken::FAIL: + token_str = "FAIL"; + break; + case ResponseToken::DATA: + token_str = "DATA"; + break; + } + + strcpy(this->response_buffer, token_str); + vsnprintf(this->response_buffer + 4, sizeof(this->response_buffer) - 4, fmt, args); + } + + Result FastbootGadget::SendResponse(ResponseDisposition disposition, ResponseToken token, const char *fmt, ...) { + std::va_list args; + va_start(args, fmt); + + FormatResponse(token, fmt, args); + + va_end(args); + + R_TRY(ep::in.TransferNormal((void*) this->response_buffer, strlen(this->response_buffer), &last_trb)); + + this->response_disposition = disposition; + this->state = State::SendingResponse; + + return ResultSuccess(); + } + + static const size_t max_trb_size = 0x10000; // 64 KiB + + Result FastbootGadget::PrepareDownload(size_t size) { + R_UNLESS(size <= this->download_buffer_size, xusb::ResultDownloadTooLarge()); + + this->download_head = 0; + this->download_size = size; + this->download_needs_zlp = (size % max_trb_size) == 0; + + return ResultSuccess(); + } + + size_t FastbootGadget::GetMaxDownloadSize() const { + return this->download_buffer_size; + } + + size_t FastbootGadget::GetLastDownloadSize() const { + return this->download_size; + } + + void *FastbootGadget::GetLastDownloadBuffer() const { + return this->download_buffer; + } + + Result FastbootGadget::ReadHostCommand() { + R_TRY(ep::out.TransferNormal((void*) this->command_buffer, 64, &last_trb)); + + this->state = State::WaitingForHostCommand; + + return ResultSuccess(); + } + + Result FastbootGadget::QueueTRBsForReceive() { + xusb::TRBBorrow trb; + + bool has_queued_any = false; + + while (ep::out.GetFreeTRBCount() > 0 && (this->download_head < this->download_size || this->download_needs_zlp)) { + size_t remaining_in_download = this->download_size - this->download_head; + size_t num_trbs_remaining = (remaining_in_download + max_trb_size - 1) / max_trb_size; + + if (this->download_needs_zlp) { + num_trbs_remaining++; + } + + size_t td_size = std::min(std::min(num_trbs_remaining, (size_t) 32), (size_t) 32) - 1; + + /* Attempt to queue a TRB. */ + + size_t attempted_size = std::min(remaining_in_download, max_trb_size); + + /* If TRBBorrow is already holding a TRB, it will be queued implicitly when EnqueueTRB overwrites it. */ + R_TRY(ep::out.EnqueueTRB(&trb, num_trbs_remaining > 0)); + + R_UNLESS(&*trb != nullptr, xusb::ResultTransferRingFull()); + + trb->transfer.InitializeNormal(download_buffer + download_head, attempted_size); + + if (td_size > 0) { + trb->transfer.chain_bit = 1; + trb->transfer.td_size = td_size; + } + + if (attempted_size == 0) { + this->download_needs_zlp = false; + } else { + this->download_head+= attempted_size; + } + + has_queued_any = true; + } + + if (has_queued_any) { + trb->transfer.interrupt_on_completion = true; + trb->transfer.interrupt_on_short_packet = true; + trb.Release(); + + if (this->download_head == this->download_size && !this->download_needs_zlp) { + last_trb = &*trb; + } + + this->state = State::DataPhaseReceive; + + ep::out.RingDoorbell(); + } + + return ResultSuccess(); + } + + /* xusb::Gadget implementation */ + + Result FastbootGadget::ProcessTransferEventImpl(xusb::TransferEventTRB *event, xusb::TRBBorrow transfer) { + if (event->completion_code != 1 && event->completion_code != 13) { // success or short packet + print(SCREEN_LOG_LEVEL_DEBUG, "failing due to transfer completion with bad code %d\n", event->completion_code); + + return xusb::ResultUnexpectedCompletionCode(); + } + + switch(this->state) { + case State::WaitingForHostCommand: + R_UNLESS(event->ep_id == ep::out.GetIndex(), xusb::ResultUnexpectedEndpoint()); + R_UNLESS(&*transfer == this->last_trb, xusb::ResultUnexpectedTRB()); + + { + size_t received_size = transfer->transfer.transfer_length - event->trb_transfer_length; + this->command_buffer[received_size] = 0; + + return this->impl.ProcessCommand(this->command_buffer); + } + + case State::SendingResponse: + R_UNLESS(event->ep_id == ep::in.GetIndex(), xusb::ResultUnexpectedEndpoint()); + R_UNLESS(&*transfer == this->last_trb, xusb::ResultUnexpectedTRB()); + + switch(this->response_disposition) { + case ResponseDisposition::ReadHostCommand: + return this->ReadHostCommand(); + case ResponseDisposition::Download: + this->last_trb = nullptr; + return this->QueueTRBsForReceive(); + case ResponseDisposition::Okay: + return this->SendResponse(ResponseDisposition::ReadHostCommand, ResponseToken::OKAY, ""); + case ResponseDisposition::Continue: + this->state = State::Continuing; + return this->impl.Continue(); + case ResponseDisposition::Reboot: + this->state = State::Reboot; + return ResultSuccess(); + case ResponseDisposition::Chainload: + this->state = State::Chainload; + return ResultSuccess(); + default: + return ResultSuccess(); + } + + case State::DataPhaseReceive: + R_UNLESS(event->ep_id == ep::out.GetIndex(), xusb::ResultUnexpectedEndpoint()); + + /* Release completed TRBs back to ring early so we can reuse them. */ + transfer.Release(); + + if (&*transfer == last_trb) { + return this->SendResponse(ResponseDisposition::ReadHostCommand, ResponseToken::OKAY, ""); + } else { + return this->QueueTRBsForReceive(); + } + + case State::DataPhaseTransmit: + /* TODO, if we ever implement a command that involves sending data. */ + return ResultSuccess(); + + case State::Continuing: + return this->impl.Continue(); + + default: + return ResultSuccess(); + } + } + + void FastbootGadget::ProcessTransferEvent(xusb::TransferEventTRB *trb, xusb::TRBBorrow transfer) { + Result r = this->ProcessTransferEventImpl(trb, std::move(transfer)); + + if (r.IsFailure()) { + print(SCREEN_LOG_LEVEL_ERROR, "fastboot error while processing transfer event: 0x%x\n", r.GetValue()); + this->state = State::UsbError; + this->usb_error = r; + } + } + + Result FastbootGadget::HandleSetupRequest(usb::SetupPacket &packet) { + if (packet.bmRequestType == usb::SetupPacket::PackRequestType( + usb::SetupPacket::RequestDirection::DeviceToHost, + usb::SetupPacket::RequestType::Vendor, + usb::SetupPacket::RequestRecipient::Device) && + packet.bRequest == descriptors::bMS_VendorCode) { + /* Retrieve MS OS 2.0 vendor-specific descriptor */ + R_UNLESS(packet.wValue == 0, xusb::ResultMalformedSetupRequest()); + R_UNLESS(packet.wIndex == usb::ms::MS_OS_20_DESCRIPTOR_INDEX, xusb::ResultMalformedSetupRequest()); + + uint16_t actual_length = sizeof(descriptors::ms_os20_descriptor); + if (actual_length > packet.wLength) { + actual_length = packet.wLength; + } + + return xusb::control::SendData((void*) &descriptors::ms_os20_descriptor, actual_length); + } + + return xusb::ResultUnknownSetupRequest(); + } + + Result FastbootGadget::GetDeviceDescriptor(uint8_t index, const usb::DeviceDescriptor **descriptor, uint16_t *length) { + R_UNLESS(index == 0, xusb::ResultInvalidDescriptorIndex()); + + *descriptor = &descriptors::device_descriptor; + *length = sizeof(descriptors::device_descriptor); + + return ResultSuccess(); + } + + Result FastbootGadget::GetDeviceQualifierDescriptor(uint8_t index, const usb::DeviceQualifierDescriptor **descriptor, uint16_t *length) { + R_UNLESS(index == 0, xusb::ResultInvalidDescriptorIndex()); + + *descriptor = &descriptors::device_qualifier_descriptor; + *length = sizeof(descriptors::device_qualifier_descriptor); + + return ResultSuccess(); + } + + Result FastbootGadget::GetConfigurationDescriptor(uint8_t index, const usb::ConfigurationDescriptor **descriptor, uint16_t *length) { + R_UNLESS(index == 0, xusb::ResultInvalidDescriptorIndex()); + + *descriptor = &descriptors::whole_configuration_descriptor.configuration_descriptor; + *length = sizeof(descriptors::whole_configuration_descriptor); + + return ResultSuccess(); + } + + Result FastbootGadget::GetBOSDescriptor(const usb::BOSDescriptor **descriptor, uint16_t *length) { + *descriptor = &descriptors::bos_container.bos_descriptor; + *length = sizeof(descriptors::bos_container); + + return ResultSuccess(); + } + + Result FastbootGadget::GetStringDescriptor(uint8_t index, uint16_t language, const usb::CommonDescriptor **descriptor, uint16_t *length) { + R_UNLESS(language == 0x0409 || (language == 0 && index == 0), xusb::ResultInvalidDescriptorIndex()); + R_UNLESS(descriptors::sd_indexer.GetStringDescriptor(index, language, descriptor, length), xusb::ResultInvalidDescriptorIndex()); + + return ResultSuccess(); + } + + Result FastbootGadget::SetConfiguration(uint16_t configuration) { + R_UNLESS(configuration <= 1, xusb::ResultInvalidConfiguration()); + + xusb::endpoints.ClearNonControlEndpoints(); + + ep::out.Initialize(descriptors::whole_configuration_descriptor.endpoint_out_descriptor, out_ring, std::size(out_ring)); + ep::in .Initialize(descriptors::whole_configuration_descriptor.endpoint_in_descriptor, in_ring, std::size(in_ring)); + + return ResultSuccess(); + } + + Result FastbootGadget::Deconfigure() { + ep::out.Disable(); + ep::in.Disable(); + + return ResultSuccess(); + } + + void FastbootGadget::PostConfigure() { + this->state = State::Invalid; + + ReadHostCommand(); + } + + } // namespace fastboot + +} // namespace ams diff --git a/fusee/fusee-primary/src/fastboot/fastboot_gadget.h b/fusee/fusee-primary/src/fastboot/fastboot_gadget.h new file mode 100644 index 000000000..d1f6c535b --- /dev/null +++ b/fusee/fusee-primary/src/fastboot/fastboot_gadget.h @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "xusb.h" +#include "fastboot.h" +#include "fastboot_impl.h" + +#include + +namespace ams { + + namespace fastboot { + + class FastbootGadget : public xusb::Gadget { + public: + FastbootGadget(uint8_t *download_buffer, size_t download_buffer_size); + + enum class ResponseDisposition { + ReadHostCommand, + + /* After this response completes, begin receive data phase. */ + Download, + /* After this response completes, send an empty OKAY response with + * ReadHostCommand disposition. */ + Okay, + /* After this response completes, call impl.Continue(). */ + Continue, + + Reboot, + Chainload, + } response_disposition; + + enum class ResponseToken { + OKAY, + INFO, + FAIL, + DATA, + }; + + fastboot_return Run(); + + Result SendResponse(ResponseDisposition disposition, ResponseToken token, const char *fmt, ...); + Result PrepareDownload(size_t size); + + size_t GetMaxDownloadSize() const; + size_t GetLastDownloadSize() const; + void *GetLastDownloadBuffer() const; + + private: + void FormatResponse(ResponseToken token, const char *fmt, std::va_list args); + Result ReadHostCommand(); + Result QueueTRBsForReceive(); + + FastbootImpl impl; + + enum class State { + Invalid, + WaitingForHostCommand, + SendingResponse, + DataPhaseReceive, + DataPhaseTransmit, + Continuing, + + Exit, + UsbError, + Reboot, + Chainload, + } state; + + Result usb_error; + + uint8_t *download_buffer; + size_t download_buffer_size; + size_t download_head; + size_t download_size; + bool download_needs_zlp; + int download_active_trbs; + + char command_buffer[65]; + char response_buffer[65]; + + xusb::TRB *last_trb = nullptr; + + private: + /* xusb::Gadget implementation */ + Result ProcessTransferEventImpl(xusb::TransferEventTRB *event, xusb::TRBBorrow transfer); + virtual void ProcessTransferEvent(xusb::TransferEventTRB *event, xusb::TRBBorrow transfer) override; + virtual Result HandleSetupRequest(usb::SetupPacket &packet) override; + virtual Result GetDeviceDescriptor(uint8_t index, const usb::DeviceDescriptor **descriptor, uint16_t *length) override; + virtual Result GetDeviceQualifierDescriptor(uint8_t index, const usb::DeviceQualifierDescriptor **descriptor, uint16_t *length) override; + virtual Result GetConfigurationDescriptor(uint8_t index, const usb::ConfigurationDescriptor **descriptor, uint16_t *length) override; + virtual Result GetBOSDescriptor(const usb::BOSDescriptor **descriptor, uint16_t *length) override; + virtual Result GetStringDescriptor(uint8_t index, uint16_t language, const usb::CommonDescriptor **descriptor, uint16_t *length) override; + virtual Result SetConfiguration(uint16_t configuration) override; + virtual Result Deconfigure() override; + virtual void PostConfigure() override; + }; + + } // namespace fastboot + +} // namespace ams diff --git a/fusee/fusee-primary/src/fastboot/fastboot_impl.cpp b/fusee/fusee-primary/src/fastboot/fastboot_impl.cpp new file mode 100644 index 000000000..29c82427c --- /dev/null +++ b/fusee/fusee-primary/src/fastboot/fastboot_impl.cpp @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "fastboot.h" +#include "fastboot_gadget.h" + +extern "C" { +#include "../lib/log.h" +#include "../utils.h" +#include "../chainloader.h" +#include "../lib/fatfs/ff.h" +} + +#include +#include + +uint32_t crc32(uint32_t crc, const uint8_t *buf, size_t len) { + int k; + + crc = ~crc; + while (len--) { + crc ^= *buf++; + for (k = 0; k < 8; k++) + crc = crc & 1 ? (crc >> 1) ^ 0xedb88320 : crc >> 1; + } + return ~crc; +} + +struct android_boot_image_v2 { + // version 0 + uint8_t magic[8]; + + uint32_t kernel_size; + uint32_t kernel_addr; + + uint32_t ramdisk_size; + uint32_t ramdisk_addr; + + uint32_t second_size; + uint32_t second_addr; + + uint32_t tags_addr; + uint32_t page_size; + + uint32_t header_version; + + uint32_t os_version; + + uint8_t name[16]; + uint8_t cmdline[512]; + uint32_t id[8]; + uint8_t extra_cmdline[1024]; + + // version 1 + uint32_t recovery_dtbo_size; + uint64_t recovery_dtbo_offset; + uint32_t header_size; + + // version 2 + uint32_t dtb_size; + uint64_t dtb_addr; +} __attribute__((packed)); + +namespace ams { + + namespace fastboot { + + using ResponseDisposition = FastbootGadget::ResponseDisposition; + using ResponseToken = FastbootGadget::ResponseToken; + + FastbootImpl::FastbootImpl(FastbootGadget &gadget) : gadget(gadget) { + } + + Result FastbootImpl::ProcessCommand(char *command_buffer) { + print(SCREEN_LOG_LEVEL_DEBUG, "got host command: '%s'\n", command_buffer); + + const char *argument = nullptr; + for (size_t i = 0; command_buffer[i] != 0; i++) { + if (command_buffer[i] == ':') { + command_buffer[i] = 0; + argument = command_buffer + i + 1; + break; + } + } + + if (!strcmp(command_buffer, "getvar")) { + return this->GetVar(argument); + } else if (!strcmp(command_buffer, "download")) { + return this->Download(argument); + } else if (!strcmp(command_buffer, "flash")) { + return this->Flash(argument); + } else if (!strcmp(command_buffer, "reboot")) { + return this->Reboot(argument); + } else if (!strcmp(command_buffer, "boot")) { + return this->Boot(argument); + } else if (!strcmp(command_buffer, "oem crc32")) { + return this->OemCRC32(argument); + } else { + return this->gadget.SendResponse(ResponseDisposition::ReadHostCommand, ResponseToken::FAIL, "unknown command: %s", command_buffer); + } + } + + Result FastbootImpl::GetVar(const char *argument) { + if (strcmp(argument, "version") == 0) { + return this->gadget.SendResponse(ResponseDisposition::ReadHostCommand, ResponseToken::OKAY, "0.4"); + } else if (strcmp(argument, "product") == 0) { + return this->gadget.SendResponse(ResponseDisposition::ReadHostCommand, ResponseToken::OKAY, "Fusée Fastboot"); + } else if (strcmp(argument, "max-download-size") == 0) { + return this->gadget.SendResponse(ResponseDisposition::ReadHostCommand, ResponseToken::OKAY, "%08X", this->gadget.GetMaxDownloadSize()); + } else { + return this->gadget.SendResponse(ResponseDisposition::ReadHostCommand, ResponseToken::FAIL, "unknown variable"); + } + } + + Result FastbootImpl::Download(const char *argument) { + bool parsed_ok = true; + uint32_t download_size = 0; + for (int i = 0; i < 8; i++) { + download_size<<= 4; + char ch = argument[i]; + if (ch >= '0' && ch <= '9') { + download_size|= ch - '0'; + } else if (ch >= 'a' && ch <= 'f') { + download_size|= (ch - 'a') + 0xa; + } else if (ch >= 'A' && ch <= 'F') { + download_size|= (ch - 'A') + 0xa; + } else { + parsed_ok = false; + break; + } + } + + if (!parsed_ok) { + return this->gadget.SendResponse(ResponseDisposition::ReadHostCommand, ResponseToken::FAIL, "failed to parse download size"); + } + + R_TRY_CATCH(gadget.PrepareDownload(download_size)) { + R_CATCH(xusb::ResultDownloadTooLarge) { + return this->gadget.SendResponse(ResponseDisposition::ReadHostCommand, ResponseToken::FAIL, "download size too large"); + } + } R_END_TRY_CATCH; + + return this->gadget.SendResponse(ResponseDisposition::Download, ResponseToken::DATA, "%08X", download_size); + } + + Result FastbootImpl::Flash(const char *argument) { + return this->gadget.SendResponse(ResponseDisposition::ReadHostCommand, ResponseToken::FAIL, "unknown partition"); + } + + Result FastbootImpl::Reboot(const char *argment) { + return this->gadget.SendResponse(ResponseDisposition::Reboot, ResponseToken::OKAY, ""); + } + + Result FastbootImpl::Boot(const char *argment) { + if (this->gadget.GetLastDownloadSize() == 0) { + return this->gadget.SendResponse(ResponseDisposition::ReadHostCommand, ResponseToken::FAIL, "no image has been downloaded"); + } + + /* Validate the boot image we have received. */ + if (this->gadget.GetLastDownloadSize() < sizeof(android_boot_image_v2)) { + return this->gadget.SendResponse(ResponseDisposition::ReadHostCommand, ResponseToken::FAIL, "received boot image is too small"); + } + + android_boot_image_v2 *bootimg = (android_boot_image_v2*) this->gadget.GetLastDownloadBuffer(); + if (memcmp(bootimg->magic, "ANDROID!", sizeof(bootimg->magic)) != 0) { + print(SCREEN_LOG_LEVEL_DEBUG, "invalid magic: '%s'\n", bootimg->magic); + return this->gadget.SendResponse(ResponseDisposition::ReadHostCommand, ResponseToken::FAIL, "received boot image has invalid magic"); + } + + if (bootimg->header_version != 2) { + print(SCREEN_LOG_LEVEL_DEBUG, "invalid version: %d\n", bootimg->header_version); + return this->gadget.SendResponse(ResponseDisposition::ReadHostCommand, ResponseToken::FAIL, "received boot image has invalid version"); + } + + if (bootimg->header_size != sizeof(*bootimg)) { + print(SCREEN_LOG_LEVEL_DEBUG, "invalid size: %d, expected %d\n", bootimg->header_size, sizeof(*bootimg)); + return this->gadget.SendResponse(ResponseDisposition::ReadHostCommand, ResponseToken::FAIL, "received boot image has invalid size"); + } + + if (bootimg->ramdisk_size != 0 || + bootimg->second_size != 0 || + bootimg->dtb_size != 0) { + return this->gadget.SendResponse(ResponseDisposition::ReadHostCommand, ResponseToken::FAIL, "received boot image has unexpected ramdisk, second stage bootloader, or dtb"); + } + + /* Setup chainloader. */ + + print(SCREEN_LOG_LEVEL_DEBUG, "loading kernel to 0x%lx\n", bootimg->kernel_addr); + + g_chainloader_num_entries = 1; + g_chainloader_entries[0].load_address = bootimg->kernel_addr; + g_chainloader_entries[0].src_address = (uintptr_t) this->gadget.GetLastDownloadBuffer() + bootimg->page_size; + g_chainloader_entries[0].size = bootimg->kernel_size; + g_chainloader_entries[0].num = 0; + g_chainloader_entrypoint = bootimg->kernel_addr; + + return this->gadget.SendResponse(ResponseDisposition::Chainload, ResponseToken::OKAY, ""); + } + + Result FastbootImpl::OemCRC32(const char *argument) { + uint32_t crc = crc32(0, (const uint8_t*) this->gadget.GetLastDownloadBuffer(), this->gadget.GetLastDownloadSize()); + + return this->gadget.SendResponse(ResponseDisposition::Okay, ResponseToken::INFO, "%08lx", crc); + } + + Result FastbootImpl::Continue() { + switch(this->current_action) { + default: + case Action::Invalid: + fatal_error("fastboot implementation has invalid current action"); + } + } + + } // namespace fastboot + +} // namespace ams diff --git a/fusee/fusee-primary/src/fastboot/fastboot_impl.h b/fusee/fusee-primary/src/fastboot/fastboot_impl.h new file mode 100644 index 000000000..c9013965d --- /dev/null +++ b/fusee/fusee-primary/src/fastboot/fastboot_impl.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include + +namespace ams { + + namespace fastboot { + + class FastbootGadget; + + class FastbootImpl { + public: + FastbootImpl(FastbootGadget &gadget); + + /* Implementation is allowed to modify the command buffer for string + parsing reasons. */ + Result ProcessCommand(char *command_buffer); + Result Continue(); + + private: + FastbootGadget &gadget; + + enum class Action { + Invalid, + } current_action; + + union { + /* Actions */ + }; + + /* Commands */ + Result GetVar(const char *arg); + Result Download(const char *arg); + Result Flash(const char *arg); + Result Reboot(const char *arg); + Result Boot(const char *arg); + Result OemCRC32(const char *arg); + + /* Continuations */ + }; + + } // namespace fastboot + +} // namespace ams diff --git a/fusee/fusee-primary/src/fastboot/fastboot_interface.h b/fusee/fusee-primary/src/fastboot/fastboot_interface.h new file mode 100644 index 000000000..3eed60f36 --- /dev/null +++ b/fusee/fusee-primary/src/fastboot/fastboot_interface.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "xusb_result.h" + +namespace fastboot { + + class FastbootInterface { + public: + virtual xusb::Result SendOkayResponse(const char *message="") = 0; + virtual xusb::Result SendInfoForOkayResponse(const char *message) = 0; + virtual xusb::Result SendInfoForFlasher(const char *message) = 0; + virtual xusb::Result SendFailResponse(const char *message) = 0; + }; + +} // namespace fastboot diff --git a/fusee/fusee-primary/src/fastboot/usb_types.h b/fusee/fusee-primary/src/fastboot/usb_types.h new file mode 100644 index 000000000..21d484015 --- /dev/null +++ b/fusee/fusee-primary/src/fastboot/usb_types.h @@ -0,0 +1,489 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include +#include + +namespace usb { + + enum class DescriptorType : uint8_t { + DEVICE = 1, + CONFIGURATION = 2, + STRING = 3, + INTERFACE = 4, + ENDPOINT = 5, + DEVICE_QUALIFIER = 6, + OTHER_SPEED_CONFIGURATION = 7, + INTERFACE_POWER = 8, + OTG = 9, + DEBUG = 10, + INTERFACE_ASSOCIATION = 11, + BOS = 15, + DEVICE_CAPABILITY = 16, + SUPERSPEED_USB_ENDPOINT_COMPANION = 48, + SUPERSPEEDPLUS_ISOCHRONOUS_ENDPOINT_COMPANION = 49, + }; + + enum class EndpointFeatureSelector : uint8_t { + ENDPOINT_HALT = 0, + }; + + enum class DeviceCapabilityType : uint8_t { + WIRELESS_USB = 0x1, + USB_2_0_EXTENSION = 0x2, + SUPERSPEED_USB = 0x3, + CONTAINER_ID = 0x4, + PLATFORM = 0x5, + POWER_DELIVERY_CAPABILITY = 0x6, + BATTERY_INFO_CAPABILITY = 0x7, + PD_CONSUMER_PORT_CAPABILITY = 0x8, + PD_PROVIDER_PORT_CAPABILITY = 0x9, + SUPERSPEED_PLUS = 0xa, + PRECISION_TIME_MEASUREMENT = 0xb, + WIRELESS_USB_EXT = 0xc, + BILLBOARD = 0xd, + AUTHENTICATION = 0xe, + BILLBOARD_EX = 0xf, + CONFIGURATION_SUMMARY = 0x10, + }; + + enum class EndpointType : uint8_t { + Control = 0, + Isochronous = 1, + Bulk = 2, + Interrupt = 3, + }; + + struct CommonDescriptor { + uint8_t bLength; + DescriptorType bDescriptorType; + } __attribute__((packed)); + + struct DeviceDescriptor : public CommonDescriptor { + struct { + uint16_t bcdUSB; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bMaxPacketSize0; + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + uint8_t iManufacturer; + uint8_t iProduct; + uint8_t iSerialNumber; + uint8_t bNumConfigurations; + } __attribute__((packed)); + } __attribute__((packed)); + + struct DeviceQualifierDescriptor : public CommonDescriptor { + struct { + uint16_t bcdUSB; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bMaxPacketSize0; + uint8_t bNumConfigurations; + uint8_t bReserved; + } __attribute__((packed)); + } __attribute__((packed)); + + struct ConfigurationDescriptor : public CommonDescriptor { + struct { + uint16_t wTotalLength; + uint8_t bNumInterfaces; + uint8_t bConfigurationValue; + uint8_t iConfiguration; + uint8_t bmAttributes; + uint8_t bMaxPower; + } __attribute__((packed)); + } __attribute__((packed)); + + static_assert(sizeof(ConfigurationDescriptor) == 9); + + struct InterfaceDescriptor : public CommonDescriptor { + struct { + uint8_t bInterfaceNumber; + uint8_t bAlternateSetting; + uint8_t bNumEndpoints; + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + uint8_t iInterface; + } __attribute__((packed)); + } __attribute__((packed)); + + static_assert(sizeof(InterfaceDescriptor) == 9); + + struct EndpointDescriptor : public CommonDescriptor { + struct { + uint8_t bEndpointAddress; + uint8_t bmAttributes; + uint16_t wMaxPacketSize; + uint8_t bInterval; + } __attribute__((packed)); + + EndpointType GetEndpointType() const { + return (EndpointType) (bmAttributes & 0b11); + } + } __attribute__((packed)); + + static_assert(sizeof(EndpointDescriptor) == 7); + + namespace detail { + + template + struct FixedString { + constexpr FixedString(char16_t const* s) { + for (size_t i = 0; i < N; i++) { + buffer[i] = s[i]; + } + } + static const size_t length = N; + char16_t buffer[N + 1]{}; + }; + template FixedString(char16_t const (&)[N]) -> FixedString; + + } // namespace detail + + template + struct StringDescriptor : public CommonDescriptor { + inline constexpr StringDescriptor() : bString {} { + this->bLength = (string.length * 2) + 2; + this->bDescriptorType = DescriptorType::STRING; + + for (size_t i = 0; i < string.length; i++) { + this->bString[i] = string.buffer[i]; + } + } + + char16_t bString[string.length]; + } __attribute__((packed)); + + namespace detail { + + template + struct StringDescriptorIndexer : public First::template WithIndex, public StringDescriptorIndexer { + using ThisDescriptor = First::template WithIndex; + using NextIndexer = StringDescriptorIndexer; + + bool GetStringDescriptor(uint8_t index, uint16_t language, const CommonDescriptor **descriptor, uint16_t *length) const { + if (index == I) { + *descriptor = &ThisDescriptor::_descriptor; + *length = sizeof(ThisDescriptor::_descriptor); + return true; + } else { + return NextIndexer::GetStringDescriptor(index, language, descriptor, length); + } + } + }; + + template + struct StringDescriptorIndexer : public First::template WithIndex { + using ThisDescriptor = First::template WithIndex; + + bool GetStringDescriptor(uint8_t index, uint16_t language, const CommonDescriptor **descriptor, uint16_t *length) const { + if (index == I) { + *descriptor = &ThisDescriptor::_descriptor; + *length = sizeof(ThisDescriptor::_descriptor); + return true; + } else { + return false; + } + } + }; + + } // namespace detail + + template + using StringDescriptorIndexer = detail::StringDescriptorIndexer<0, T...>; + + /* The wrapper type is here because we can't pass templates as template + * parameters directly. */ + +#define USB_DECLARE_STRING_DESCRIPTOR(id, english_value) struct SD_##id { \ + template \ + struct WithIndex { \ + static constexpr ::usb::StringDescriptor _descriptor = {}; \ + static constexpr size_t id = I; \ + }; \ + } + + struct BOSDescriptor : public CommonDescriptor { + struct { + uint16_t wTotalLength; + uint8_t bNumDeviceCaps; + } __attribute__((packed)); + } __attribute__((packed)); + + struct DeviceCapabilityDescriptor : public CommonDescriptor { + struct { + DeviceCapabilityType bDevCapabilityType; + } __attribute__((packed)); + } __attribute__((packed)); + + namespace caps { + + struct USB_2_0_EXTENSION : public DeviceCapabilityDescriptor { + struct { + uint32_t bmAttributes; + } __attribute__((packed)); + } __attribute__((packed)); + + struct SUPERSPEED : public DeviceCapabilityDescriptor { + struct { + uint8_t bmAttributes; + uint16_t wSpeedsSupported; + uint8_t bFunctionalitySupport; + uint8_t bU1DevExitLat; + uint16_t wU2DevExitLat; + } __attribute__((packed)); + } __attribute__((packed)); + + template + struct PLATFORM : public DeviceCapabilityDescriptor { + uint8_t bReserved; + uint8_t PlatformCapabilityUUID[16]; + PlatformCapability platform_cap; + + constexpr PLATFORM(PlatformCapability platform_cap) : + DeviceCapabilityDescriptor({ + { + .bLength = sizeof(*this), + .bDescriptorType = usb::DescriptorType::DEVICE_CAPABILITY, + }, + { + .bDevCapabilityType = usb::DeviceCapabilityType::PLATFORM + } + }), + bReserved(0), + platform_cap(platform_cap) { + static_assert(sizeof(PlatformCapability::UUID[0]) == 1); + static_assert(sizeof(PlatformCapability::UUID) == sizeof(PlatformCapabilityUUID)); + + for (size_t i = 0; i < sizeof(PlatformCapability::UUID); i++) { + PlatformCapabilityUUID[i] = PlatformCapability::UUID[i]; + } + } + } __attribute__((packed)); + + template + constexpr PLATFORM make_platform_capability(T cap) { + return PLATFORM(cap); + } + + } // namespace caps + + namespace ms { + inline const uint32_t NTDDI_WIN8_1 = 0x06030000; + inline const uint8_t MS_OS_20_DESCRIPTOR_INDEX = 0x7; + + struct DescriptorSetInformation { + static inline const uint8_t UUID[16] = {0xDF, 0x60, 0xDD, 0xD8, 0x89, 0x45, 0xC7, 0x4C, 0x9C, 0xD2, 0x65, 0x9D, 0x9E, 0x64, 0x8A, 0x9F}; + + uint32_t dwWindowsVersion; + uint16_t wMSOSDescriptorSetTotalLength; + uint8_t bMS_VendorCode; + uint8_t bAltEnumCode; + } __attribute__((packed)); + + namespace os20 { + + enum class DescriptorType : uint16_t { + SetHeaderDescriptor = 0, + SubsetHeaderConfiguration = 1, + SubsetHeaderFunction = 2, + FeatureCompatibleID = 3, + FeatureRegProperty = 4, + FeatureMinResumeTime = 5, + FeatureModelID = 6, + FeatureCCGPDevice = 7, + FeatureVendorRevision = 8 + }; + + struct DescriptorSetHeader { + uint16_t wLength; + DescriptorType wDescriptorType; + uint32_t dwWindowsVersion; + uint16_t wTotalLength; + } __attribute__((packed)); + + /* + struct ConfigurationSubsetHeader { + uint16_t wLength; + uint16_t wDescriptorType; + uint8_t bConfigurationValue; + uint8_t bReserved; + uint16_t wTotalLength; + } __attribute__((packed)); + + struct FunctionSubsetHeader { + uint16_t wLength; + uint16_t wDescriptorType; + uint8_t bFirstInterface; + uint8_t bReserved; + uint16_t wSubsetLength; + } __attribute__((packed)); + */ + + struct FeatureCompatibleID { + uint16_t wLength = sizeof(*this); + DescriptorType wDescriptorType = DescriptorType::FeatureCompatibleID; + uint8_t CompatibleID[8] = {0}; + uint8_t SubCompatibleID[8] = {0}; + + constexpr FeatureCompatibleID(const char CompatibleID[], const char SubCompatibleID[]) { + for (size_t i = 0; i < sizeof(this->CompatibleID) && CompatibleID[i] != '\0'; i++) { + this->CompatibleID[i] = CompatibleID[i]; + } + + for (size_t i = 0; i < sizeof(this->SubCompatibleID) && SubCompatibleID[i] != '\0'; i++) { + this->SubCompatibleID[i] = SubCompatibleID[i]; + } + } + } __attribute__((packed)); + + enum PropertyDataType : uint16_t { + REG_SZ = 1, + REG_EXPAND_SZ = 2, + REG_BINARY = 3, + REG_DWORD_LITTLE_ENDIAN = 4, + REG_DWORD_BIG_ENDIAN = 5, + REG_LINK = 6, + REG_MULTI_SZ = 7, + }; + + template + struct FeatureRegistryProperty { + uint16_t wLength = sizeof(*this); + DescriptorType wDescriptorType = DescriptorType::FeatureRegProperty; + uint16_t wPropertyDataType = property_data_type; + uint16_t wPropertyNameLength = (property_name.length + 1) * 2; + char16_t PropertyName[property_name.length + 1] = {0}; + uint16_t wPropertyDataLength = (property_data.length + 1) * 2; + char16_t PropertyData[property_data.length + 1] = {0}; + + constexpr FeatureRegistryProperty() { + for (size_t i = 0; i < property_name.length; i++) { + this->PropertyName[i] = property_name.buffer[i]; + } + for (size_t i = 0; i < property_data.length; i++) { + this->PropertyData[i] = property_data.buffer[i]; + } + } + } __attribute__((packed)); + + } // namespace os20 + + } // namespace ms + + namespace detail { + template + struct PackedTuple { + First first; + PackedTuple remaining; + + constexpr PackedTuple(First first, T... remaining) : + first(first), + remaining(remaining...) { + } + } __attribute__((packed)); + + template + struct PackedTuple { + First first; + + constexpr PackedTuple(First first) : first(first) { + } + } __attribute__((packed)); + } // namespace detail + + template + struct BOSDescriptorContainer { + BOSDescriptor bos_descriptor; + detail::PackedTuple tuple; + + constexpr BOSDescriptorContainer(T... members) : bos_descriptor { + { + .bLength = sizeof(BOSDescriptor), + .bDescriptorType = DescriptorType::BOS, + }, + { + .wTotalLength = sizeof(*this), + .bNumDeviceCaps = sizeof...(T), + } + }, tuple(members...) { + } + } __attribute__((packed)); + + template + constexpr BOSDescriptorContainer make_bos_descriptor_container(T... members) { + return BOSDescriptorContainer(members...); + } + + struct SetupPacket { + enum class StandardRequest : uint8_t { + GET_STATUS = 0, + CLEAR_FEATURE = 1, + // reserved + SET_FEATURE = 3, + // reserved + SET_ADDRESS = 5, + GET_DESCRIPTOR = 6, + SET_DESCRIPTOR = 7, + GET_CONFIGURATION = 8, + SET_CONFIGURATION = 9, + GET_INTERFACE = 10, + SET_INTERFACE = 11, + SYNCH_FRAME = 12 + }; + + enum class RequestDirection { + HostToDevice = 0, + DeviceToHost = 1, + }; + + enum class RequestType { + Standard = 0, + Class = 1, + Vendor = 2, + Reserved = 3, + }; + + enum class RequestRecipient { + Device = 0, + Interface = 1, + Endpoint = 2, + Other = 3 + }; + + static inline uint8_t PackRequestType(RequestDirection dir, RequestType type, RequestRecipient recipient) { + return ((uint8_t) dir << 7) | + ((uint8_t) type << 5) | + ((uint8_t) recipient << 0); + } + + uint8_t bmRequestType; + uint8_t bRequest; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; + } __attribute__((packed)); + +} // namespace usb diff --git a/fusee/fusee-primary/src/fastboot/xusb.cpp b/fusee/fusee-primary/src/fastboot/xusb.cpp new file mode 100644 index 000000000..801c35069 --- /dev/null +++ b/fusee/fusee-primary/src/fastboot/xusb.cpp @@ -0,0 +1,600 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "xusb.h" + +#include "xusb_event_ring.h" +#include "xusb_endpoint.h" + +#include "../reg/reg_apbdev_pmc.h" +#include "../reg/reg_car.h" +#include "../reg/reg_fuse.h" +#include "../reg/reg_xusb_padctl.h" +#include "../reg/reg_xusb_dev.h" + +extern "C" { +#include "../timers.h" +#include "../lib/log.h" +#include "../utils.h" +#include "../mc.h" +} + +#include + +namespace ams { + + namespace xusb { + + namespace impl { + + namespace padctl { + + void SetupPads(t210::CLK_RST_CONTROLLER::OscFreq osc_freq); + void SetupPllu(t210::CLK_RST_CONTROLLER::OscFreq osc_freq); + void SetupUTMIP(t210::CLK_RST_CONTROLLER::OscFreq osc_freq); + void ApplyFuseCalibration(); + void DisablePd(); + void SetupTracking(t210::CLK_RST_CONTROLLER::OscFreq osc_freq); + + } + + void ConfigureClockAndResetForDeviceMode(); + + EventRing event_ring; + TRB ep0_transfer_ring[16]; + + Gadget *current_gadget = nullptr; + } + + void Initialize() { + const bool use_bootrom = false; + + t210::CLK_RST_CONTROLLER + .CLK_OUT_ENB_W_0() + .CLK_ENB_XUSB.Enable() + .Commit() + .RST_DEVICES_W_0() + .SWR_XUSB_PADCTL_RST.Set(0) + .Commit(); + + udelay(2); + + t210::XUSB_PADCTL + .USB2_PAD_MUX_0() + .USB2_OTG_PAD_PORT0.SetXUSB() + .USB2_BIAS_PAD.SetXUSB() + .Commit(); + + if (use_bootrom) { + ((bool (*)()) (0x1104fd))(); + } else { + impl::padctl::SetupPads(t210::CLK_RST_CONTROLLER.OSC_CTRL_0().OSC_FREQ.Get()); + } + + t210::XUSB_PADCTL + .USB2_PORT_CAP_0() + .PORT0_CAP.SetDeviceOnly() + .Commit() + .SS_PORT_MAP_0() + .PORT0_MAP.SetUSB2Port0() + .Commit(); + + t210::APBDEV_PMC + .USB_AO_0() + .VBUS_WAKEUP_PD_P0.Set(0) + .ID_PD_P0.Set(0) + .Commit(); + + udelay(1); + + if (use_bootrom) { + ((void (*)()) (0x110227))(); + } else { + impl::ConfigureClockAndResetForDeviceMode(); + } + + impl::event_ring.Initialize(); + + endpoints[0].InitializeForControl(impl::ep0_transfer_ring, std::size(impl::ep0_transfer_ring)); + + t210::T_XUSB_DEV_XHCI + .ECPLO() + .ADDRLO.Set(endpoints.GetContextsForHardware()) + .Commit() + .ECPHI() + .ADDRHI.Set(0) + .Commit(); + + endpoints[0].Reload(); + + t210::T_XUSB_DEV_XHCI + .CTRL() + .LSE.Enable() + .IE.Enable() + .Commit(); + } + + void EnableDevice(Gadget &gadget) { + impl::current_gadget = &gadget; + + t210::XUSB_PADCTL + .ELPG_PROGRAM_0_0(0).Commit() + .ELPG_PROGRAM_1_0(0).Commit() + .USB2_VBUS_ID_0() + .VBUS_SOURCE_SELECT.SetOVERRIDE() + .ID_SOURCE_SELECT.SetOVERRIDE() + .Commit(); + + t210::T_XUSB_DEV_XHCI + .PORTHALT() + .HALT_LTSSM.Set(0) + .Commit() + .CTRL() + .ENABLE.Enable() + .Commit() + .CFG_DEV_FE() + .PORTREGSEL.SetHSFS() // PortSC accesses route to register for HS + .Commit() + .PORTSC() + .LWS.Set(1) + .PLS.SetRXDETECT() + .Commit() + .CFG_DEV_FE() + .PORTREGSEL.SetSS() // PortSC accesses route to register for SS + .Commit() + .PORTSC() + .LWS.Set(1) + .PLS.SetRXDETECT() + .Commit() + .CFG_DEV_FE() + .PORTREGSEL.SetINIT() // PortSC Accesses route to register based on current link speed + .Commit(); + + t210::XUSB_PADCTL + .USB2_VBUS_ID_0() + .VBUS_OVERRIDE.Set(1) + .ID_OVERRIDE.SetFLOAT() + .Commit(); + } + + void Process() { + impl::event_ring.Process(); + } + + void Finalize() { + mc_release_ahb_redirect(); + } + + Gadget *GetCurrentGadget() { + return impl::current_gadget; + } + + namespace impl { + + namespace padctl { + + void SetupPads(t210::CLK_RST_CONTROLLER::OscFreq osc_freq) { + constexpr bool use_bootrom = false; + + SetupPllu(osc_freq); + + if (use_bootrom) { + ((void (*)(t210::CLK_RST_CONTROLLER::OscFreq)) (0x11039b))(osc_freq); + ((void (*)()) (0x110357))(); + ((int (*)()) (0x110327))(); + ((bool (*)(t210::CLK_RST_CONTROLLER::OscFreq)) (0x1102cb))(osc_freq); + } else { + SetupUTMIP(osc_freq); + ApplyFuseCalibration(); + DisablePd(); + SetupTracking(osc_freq); + } + + udelay(30); + } + + void SetupPllu(t210::CLK_RST_CONTROLLER::OscFreq osc_freq) { + struct { + uint8_t divn, divm, divp; + } pllu_config; + + using OscFreq = t210::CLK_RST_CONTROLLER::OscFreq; + + switch(osc_freq) { + case OscFreq::OSC13: + pllu_config = {37, 1, 1}; + break; + case OscFreq::OSC16P8: + pllu_config = {28, 1, 1}; + break; + case OscFreq::OSC19P2: + pllu_config = {25, 1, 1}; + break; + case OscFreq::OSC38P4: + pllu_config = {25, 2, 1}; + break; + case OscFreq::OSC12: + pllu_config = {40, 1, 1}; + break; + case OscFreq::OSC48: + pllu_config = {40, 4, 1}; + break; + case OscFreq::OSC26: + pllu_config = {37, 2, 1}; + break; + default: + fatal_error("xusb initialization failed: invalid oscillator frequency"); + break; + } + + // reset PLLU + t210::CLK_RST_CONTROLLER + .PLLU_BASE_0() + .PLLU_ENABLE.Disable() + .PLLU_CLKENABLE_48M.Disable() + .PLLU_OVERRIDE.Set(1) + .PLLU_CLKENABLE_ICUSB.Disable() + .PLLU_CLKENABLE_HSIC.Disable() + .PLLU_CLKENABLE_USB.Disable() + .Commit(); + + udelay(100); + + t210::CLK_RST_CONTROLLER + .PLLU_MISC_0() + .PLLU_EN_LCKDET.Enable() + .Commit() + .PLLU_BASE_0() + .PLLU_DIVM.Set(pllu_config.divm) + .PLLU_DIVN.Set(pllu_config.divn) + .PLLU_DIVP.Set(pllu_config.divp) + .PLLU_OVERRIDE.Set(1) + .PLLU_ENABLE.Enable() + .Commit(); + + int i = 0; + + while (!t210::CLK_RST_CONTROLLER.PLLU_BASE_0().PLLU_LOCK && i < 100000) { + i++; + } + + if (t210::CLK_RST_CONTROLLER.PLLU_BASE_0().PLLU_LOCK) { + print(SCREEN_LOG_LEVEL_DEBUG, "got PLLU lock in %d iterations\n", i); + } else { + fatal_error("xusb initialization failed: PLLU lock not acquired in %d iterations\n", i); + } + + t210::CLK_RST_CONTROLLER + .PLLU_BASE_0() + .PLLU_CLKENABLE_USB.Enable() + .PLLU_CLKENABLE_HSIC.Enable() + .PLLU_CLKENABLE_48M.Enable() + .PLLU_CLKENABLE_ICUSB.Enable() + .Commit(); + } + + void SetupUTMIP(t210::CLK_RST_CONTROLLER::OscFreq osc_freq) { + t210::CLK_RST_CONTROLLER + .UTMIPLL_HW_PWRDN_CFG0_0() + .UTMIPLL_IDDQ_SWCTL.Set(1) // "IDDQ by software." + .UTMIPLL_IDDQ_OVERRIDE_VALUE.Set(0) // "PLL not in IDDQ mode... Override value used only when UTMIP_LL_IDDQ_SWCTL is set" + .Commit(); + + constexpr bool use_pllu = false; // works either way + + struct { + uint8_t ndiv, mdiv; + } pll_config; + + struct { + uint16_t enable_dly_count; + /* only used with PLLU reference source */ + uint16_t stable_count; + /* only used with PLLU reference source */ + uint16_t active_dly_count; + uint16_t xtal_freq_count; + } count_config; + + using OscFreq = t210::CLK_RST_CONTROLLER::OscFreq; + + switch(osc_freq) { + case OscFreq::OSC13: + pll_config = {74, 1}; + count_config = {2, 51, 9, 127}; + break; + case OscFreq::OSC16P8: + pll_config = {57, 1}; + count_config = {3, 66, 11, 165}; + break; + case OscFreq::OSC19P2: + pll_config = {50, 1}; + count_config = {3, 75, 12, 188}; + break; + case OscFreq::OSC38P4: + pll_config = {25, 1}; + count_config = {5, 150, 24, 375}; + break; + case OscFreq::OSC12: + pll_config = {80, 1}; + count_config = {2, 47, 8, 118}; + break; + case OscFreq::OSC48: + pll_config = {40, 2}; + count_config = {6, 188, 31, 469}; + break; + case OscFreq::OSC26: + pll_config = {74, 2}; + count_config = {4, 102, 17, 254}; + break; + default: + fatal_error("xusb initialization failed: invalid oscillator frequency"); + break; + } + + if (use_pllu) { + t210::CLK_RST_CONTROLLER + .UTMIP_PLL_CFG3_0() + .UTMIP_PLL_REF_SRC_SEL.Set(1) + .Commit(); + + /* 480 MHz * 30 / 15 = 960 MHz */ + pll_config = {30, 15}; + } else { + // TODECIDE: set PLL_REF_SRC_SEL? or assume correct from reset? + + /* don't need to wait for PLLU to become stable if we're not using it */ + count_config.stable_count = 0; + count_config.enable_dly_count = 0; + } + + t210::CLK_RST_CONTROLLER + .UTMIP_PLL_CFG0_0() + .UTMIP_PLL_NDIV.Set(pll_config.ndiv) + .UTMIP_PLL_MDIV.Set(pll_config.mdiv) + .Commit() + .UTMIP_PLL_CFG2_0() + .UTMIP_PHY_XTAL_CLOCKEN.Set(1) + .UTMIP_PLLU_STABLE_COUNT.Set(count_config.stable_count) + .UTMIP_PLL_ACTIVE_DLY_COUNT.Set(count_config.active_dly_count) + .Commit() + .UTMIP_PLL_CFG1_0() + .UTMIP_PLLU_ENABLE_DLY_COUNT.Set(count_config.enable_dly_count) + .UTMIP_XTAL_FREQ_COUNT.Set(count_config.xtal_freq_count) + .UTMIP_FORCE_PLL_ACTIVE_POWERDOWN.Set(0) + .UTMIP_FORCE_PLL_ENABLE_POWERUP.Set(1) + .UTMIP_FORCE_PLL_ENABLE_POWERDOWN.Set(0) + .Commit(); + + for (int i = 0; i < 100 && !t210::CLK_RST_CONTROLLER.UTMIPLL_HW_PWRDN_CFG0_0().UTMIPLL_LOCK; i++) { + udelay(1); + } + + if (t210::CLK_RST_CONTROLLER.UTMIPLL_HW_PWRDN_CFG0_0().UTMIPLL_LOCK) { + print(SCREEN_LOG_LEVEL_DEBUG, "got UTMIPLL lock\n"); + print(SCREEN_LOG_LEVEL_DEBUG, "UTMIP_PLL_CFG0_0: 0x%08x\n", t210::CLK_RST_CONTROLLER.UTMIP_PLL_CFG0_0().read_value); + print(SCREEN_LOG_LEVEL_DEBUG, "UTMIP_PLL_CFG1_0: 0x%08x\n", t210::CLK_RST_CONTROLLER.UTMIP_PLL_CFG1_0().read_value); + print(SCREEN_LOG_LEVEL_DEBUG, "UTMIP_PLL_CFG2_0: 0x%08x\n", t210::CLK_RST_CONTROLLER.UTMIP_PLL_CFG2_0().read_value); + } else { + /* There is some issue where occasionally after a reboot-to-self we + * fail to get a UTMIP lock. The BootROM ignores this failure and + * continues on. It seems that in these cases it eventually starts + * working, but may take a few seconds to do so. */ + print(SCREEN_LOG_LEVEL_ERROR, "failed to get UTMIP lock\n"); + print(SCREEN_LOG_LEVEL_DEBUG, "UTMIP_PLL_CFG0_0: 0x%08x\n", t210::CLK_RST_CONTROLLER.UTMIP_PLL_CFG0_0().read_value); + print(SCREEN_LOG_LEVEL_DEBUG, "UTMIP_PLL_CFG1_0: 0x%08x\n", t210::CLK_RST_CONTROLLER.UTMIP_PLL_CFG1_0().read_value); + print(SCREEN_LOG_LEVEL_DEBUG, "UTMIP_PLL_CFG2_0: 0x%08x\n", t210::CLK_RST_CONTROLLER.UTMIP_PLL_CFG2_0().read_value); + } + + t210::CLK_RST_CONTROLLER + .UTMIP_PLL_CFG2_0() + .UTMIP_FORCE_PD_SAMP_A_POWERDOWN.Set(0) + .UTMIP_FORCE_PD_SAMP_A_POWERUP.Set(1) + .UTMIP_FORCE_PD_SAMP_B_POWERDOWN.Set(0) + .UTMIP_FORCE_PD_SAMP_B_POWERUP.Set(1) + .UTMIP_FORCE_PD_SAMP_C_POWERDOWN.Set(0) + .UTMIP_FORCE_PD_SAMP_C_POWERUP.Set(1) + .UTMIP_FORCE_PD_SAMP_D_POWERDOWN.Set(0) + .UTMIP_FORCE_PD_SAMP_D_POWERUP.Set(1) + .Commit() + .UTMIP_PLL_CFG1_0() + .UTMIP_FORCE_PLL_ENABLE_POWERUP.Set(0) + .UTMIP_FORCE_PLL_ENABLE_POWERDOWN.Set(0) + .Commit() + .UTMIPLL_HW_PWRDN_CFG0_0() + .UTMIPLL_USE_LOCKDET.Set(1) + .UTMIPLL_CLK_ENABLE_SWCTL.Set(0) + .UTMIPLL_SEQ_ENABLE.Set(1) + .Commit(); + + udelay(2); + } + + void ApplyFuseCalibration() { + auto fuse = t210::FUSE.SKU_USB_CALIB(); + + t210::XUSB_PADCTL + .USB2_OTG_PAD0_CTL_0_0() + .HS_CURR_LEVEL.Set(fuse.HS_CURR_LEVEL.Get()) + .Commit() + .USB2_OTG_PAD0_CTL_1_0() + .TERM_RANGE_ADJ.Set(fuse.TERM_RANGE_ADJ.Get()) + .RPD_CTRL.Set(t210::FUSE.USB_CALIB_EXT().RPD_CTRL.Get()) + .Commit() + .USB2_BATTERY_CHRG_OTGPAD0_CTL1_0() + .VREG_FIX18.Set(0) + .VREG_LEV.Set(1) + .Commit(); + } + + void DisablePd() { + t210::XUSB_PADCTL + .USB2_OTG_PAD0_CTL_0_0() + .PD_ZI.Set(0) + .PD.Set(0) + .Commit() + .USB2_OTG_PAD0_CTL_1_0() + .PD_DR.Set(0) + .Commit() + .USB2_BATTERY_CHRG_OTGPAD0_CTL0_0() + .PD_CHG.Set(0) + .Commit() + .USB2_BIAS_PAD_CTL_0_0() + .PD.Set(0) + .Commit(); + } + + void SetupTracking(t210::CLK_RST_CONTROLLER::OscFreq osc_freq) { + uint8_t divisor; + + using OscFreq = t210::CLK_RST_CONTROLLER::OscFreq; + + switch(osc_freq) { + case OscFreq::OSC13: + divisor = 2; + break; + case OscFreq::OSC16P8: + divisor = 2; + break; + case OscFreq::OSC19P2: + divisor = 2; + break; + case OscFreq::OSC38P4: + divisor = 6; + break; + case OscFreq::OSC12: + divisor = 2; + break; + case OscFreq::OSC48: + divisor = 6; + break; + case OscFreq::OSC26: + divisor = 4; + break; + default: + fatal_error("xusb initialization failed: invalid oscillator frequency"); + break; + } + + t210::CLK_RST_CONTROLLER + .CLK_OUT_ENB_Y_0() + .CLK_ENB_USB2_TRK.Enable() + .Commit() + .CLK_SOURCE_USB2_HSIC_TRK_0() + .USB2_HSIC_TRK_CLK_DIVISOR.Set(divisor) + .Commit(); + + auto reg = t210::XUSB_PADCTL + .USB2_BIAS_PAD_CTL_1_0(0) + .TRK_DONE_RESET_TIMER.Set(10) + .TRK_START_TIMER.Set(30); + + reg.PD_TRK.Set(0).Commit(); + udelay(100); + reg.PD_TRK.Set(1).Commit(); + udelay(3); + reg.PD_TRK.Set(0).Commit(); + udelay(100); + + t210::XUSB_PADCTL + .USB2_BIAS_PAD_CTL_1_0() + .PD_TRK.Set(1) + .Commit(); + t210::CLK_RST_CONTROLLER + .CLK_OUT_ENB_Y_0() + .CLK_ENB_USB2_TRK.Disable() + .Commit(); + } + + } // namespace padctl + + void ConfigureClockAndResetForDeviceMode() { + t210::CLK_RST_CONTROLLER + .PLLU_OUTA_0() + .PLLU_OUT1_RSTN.Set(1) // RESET_DISABLE + .Commit(); + + udelay(2); + + t210::CLK_RST_CONTROLLER + .CLK_OUT_ENB_U_0() + .CLK_ENB_XUSB_DEV.Enable() + .Commit() + .CLK_SOURCE_XUSB_CORE_DEV_0() + .XUSB_CORE_DEV_CLK_SRC.SetPLLP_OUT0() + .XUSB_CORE_DEV_CLK_DIVISOR.Set(6) + .Commit(); + + udelay(2); + + t210::CLK_RST_CONTROLLER + .CLK_SOURCE_XUSB_FS_0() + .XUSB_FS_CLK_SRC.SetFO_48M() + .Commit() + .CLK_OUT_ENB_W_0() + .CLK_ENB_XUSB_SS.Enable() + .Commit() + .CLK_SOURCE_XUSB_SS_0() + .XUSB_SS_CLK_SRC.SetHSIC_480() + .XUSB_SS_CLK_DIVISOR.Set(6) + .Commit() + .RST_DEVICES_W_0() + .SWR_XUSB_SS_RST.Set(0) // DISABLE + .Commit() + .RST_DEVICES_U_0() + .SWR_XUSB_DEV_RST.Set(0) // DISABLE + .Commit(); + + udelay(2); + + mc_acquire_ahb_redirect(); + + t210::XUSB_DEV + .CONFIGURATION_0() + .EN_FPCI.Enable() + .Commit(); + + t210::T_XUSB_DEV + .CFG_1() + .IO_SPACE.Enable() + .MEMORY_SPACE.Enable() + .BUS_MASTER.Enable() + .Commit(); + + udelay(1); + + t210::T_XUSB_DEV + .CFG_4(0) + .BASE_ADDRESS.Set(t210::XUSB_DEV_BASE) + .PREFETCHABLE.SetNOT() + .ADDRESS_TYPE.Set32_BIT() + .SPACE_TYPE.SetMEMORY() + .Commit(); + + t210::XUSB_DEV + .INTR_MASK_0() + .IP_INT_MASK.Set(1) // "IP (SATA/AZ) interrupt to MPCORE gated by mask." + .Commit(); + } + + } // namespace impl + + Result xusb::Gadget::HandleSetupRequest(usb::SetupPacket &packet) { + return ResultUnknownSetupRequest(); + } + + } // namespace xusb + +} // namespace ams diff --git a/fusee/fusee-primary/src/fastboot/xusb.h b/fusee/fusee-primary/src/fastboot/xusb.h new file mode 100644 index 000000000..7b4edeb34 --- /dev/null +++ b/fusee/fusee-primary/src/fastboot/xusb.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "usb_types.h" +#include "xusb_trb.h" +#include "xusb_endpoint.h" + +#include + +#include + +namespace ams { + + namespace xusb { + + class Gadget { + public: + virtual Result HandleSetupRequest(usb::SetupPacket &packet); + virtual Result GetDeviceDescriptor(uint8_t index, const usb::DeviceDescriptor **descriptor, uint16_t *length) = 0; + virtual Result GetDeviceQualifierDescriptor(uint8_t index, const usb::DeviceQualifierDescriptor **descriptor, uint16_t *length) = 0; + virtual Result GetConfigurationDescriptor(uint8_t index, const usb::ConfigurationDescriptor **descriptor, uint16_t *length) = 0; + virtual Result GetBOSDescriptor(const usb::BOSDescriptor **descriptor, uint16_t *length) = 0; + virtual Result GetStringDescriptor(uint8_t index, uint16_t language, const usb::CommonDescriptor **descriptor, uint16_t *length) = 0; + + /* It is the implementor's responsibility to configure endpoints before this returns. */ + virtual Result SetConfiguration(uint16_t configuration) = 0; + virtual void PostConfigure() = 0; + virtual Result Deconfigure() = 0; + + virtual void ProcessTransferEvent(TransferEventTRB *event, TRBBorrow transfer) = 0; + }; + + void Initialize(); + void EnableDevice(Gadget &gadget); + void Process(); + void Finalize(); + + Gadget *GetCurrentGadget(); + + } // namespace xusb + +} // namespace ams diff --git a/fusee/fusee-primary/src/fastboot/xusb_control.cpp b/fusee/fusee-primary/src/fastboot/xusb_control.cpp new file mode 100644 index 000000000..cad345a70 --- /dev/null +++ b/fusee/fusee-primary/src/fastboot/xusb_control.cpp @@ -0,0 +1,414 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "xusb_control.h" + +#include "usb_types.h" +#include "xusb.h" +#include "xusb_endpoint.h" +#include "../reg/reg_xusb_dev.h" + +extern "C" { +#include "../timers.h" +#include "../lib/log.h" +} + +#include + +#include + +namespace ams { + + namespace xusb { + + namespace control { + + DeviceState device_state = DeviceState::Powered; + + namespace { + + enum class ControlEpState { + Idle, + DataInStage, + DataOutStage, + StatusStage, + } control_ep_state = ControlEpState::Idle; + + TRB *control_ep_last_trb = nullptr; + uint16_t control_ep_seq_num = 0; + + void ResetControlEndpoint() { + xusb::endpoints[0].Pause(); + xusb::endpoints[0].ResetAndReloadTransferRing(); + xusb::endpoints[0].ClearPause(); + + control_ep_state = ControlEpState::Idle; + control_ep_last_trb = nullptr; + } + + void ChangeDeviceState(DeviceState new_state) { + // if we are transitioning in or out of configured state, + if ((device_state == DeviceState::Configured) != (new_state == DeviceState::Configured)) { + t210::T_XUSB_DEV_XHCI.CTRL().RUN.Set(new_state == DeviceState::Configured).Commit(); + + if (new_state == DeviceState::Configured) { + t210::T_XUSB_DEV_XHCI.ST().RC.Clear().Commit(); + xusb::GetCurrentGadget()->PostConfigure(); + } + } + + switch(new_state) { + case DeviceState::Default: + device_state = DeviceState::Default; + + print(SCREEN_LOG_LEVEL_DEBUG, "entering default state, resetting control endpoint state...\n"); + ResetControlEndpoint(); + break; + case DeviceState::Address: + device_state = DeviceState::Address; + break; + case DeviceState::Configured: + device_state = DeviceState::Configured; + break; + default: + print(SCREEN_LOG_LEVEL_ERROR, "ChangeDeviceState(%d) state unknown\n", (int) new_state); + break; + } + } + + } // anonymous namespace + + Result SendData(void *data, size_t size) { + TRBBorrow trb; + + R_TRY(xusb::endpoints[0].EnqueueTRB(&trb)); + + trb->transfer.InitializeDataStage(true, (void*) data, size); + trb->transfer.interrupt_on_completion = true; + trb->transfer.interrupt_on_short_packet = true; + trb.Release(); + + udelay(1000); + + xusb::endpoints[0].RingDoorbell(control_ep_seq_num); + + control_ep_state = ControlEpState::DataInStage; + control_ep_last_trb = &*trb; + + return ResultSuccess(); + } + + Result SendStatus(bool direction) { + TRBBorrow trb; + + R_TRY(xusb::endpoints[0].EnqueueTRB(&trb)); + + trb->transfer.InitializeStatusStage(direction); + trb->transfer.interrupt_on_completion = true; + trb->transfer.interrupt_on_short_packet = true; + trb.Release(); + + udelay(1000); + + xusb::endpoints[0].RingDoorbell(control_ep_seq_num); + + control_ep_state = ControlEpState::StatusStage; + control_ep_last_trb = &*trb; + + return ResultSuccess(); + } + + namespace impl { + + Result SetAddress(uint16_t addr) { + print(SCREEN_LOG_LEVEL_DEBUG, "got SET_ADDRESS request(%d)\n", addr); + + R_UNLESS(addr <= 127, ams::xusb::ResultInvalidAddress()); + + t210::T_XUSB_DEV_XHCI + .CTRL() + .DEVADR.Set(addr) + .Commit(); + + xusb::endpoints.SetEP0DeviceAddress(addr); + + ChangeDeviceState(DeviceState::Address); + + return SendStatus(true); + } + + Result GetDescriptor(usb::DescriptorType type, uint8_t index, uint16_t language_id, uint16_t length) { + print(SCREEN_LOG_LEVEL_DEBUG, "got GET_DESCRIPTOR request(%d, %d, %d, %d)\n", type, index, language_id, length); + + uint16_t actual_length = 0; + const usb::CommonDescriptor *descriptor = nullptr; + + switch(type) { + case usb::DescriptorType::DEVICE: + R_UNLESS(language_id == 0, xusb::ResultMalformedSetupRequest()); + R_TRY(xusb::GetCurrentGadget()->GetDeviceDescriptor(index, (const usb::DeviceDescriptor**) &descriptor, &actual_length)); + break; + case usb::DescriptorType::CONFIGURATION: + R_UNLESS(language_id == 0, xusb::ResultMalformedSetupRequest()); + R_TRY(xusb::GetCurrentGadget()->GetConfigurationDescriptor(index, (const usb::ConfigurationDescriptor**) &descriptor, &actual_length)); + break; + case usb::DescriptorType::BOS: + R_UNLESS(index == 0, xusb::ResultInvalidDescriptorIndex()); + R_UNLESS(language_id == 0, xusb::ResultMalformedSetupRequest()); + R_TRY(xusb::GetCurrentGadget()->GetBOSDescriptor((const usb::BOSDescriptor**) &descriptor, &actual_length)); + break; + case usb::DescriptorType::STRING: + R_TRY(xusb::GetCurrentGadget()->GetStringDescriptor(index, language_id, &descriptor, &actual_length)); + break; + case usb::DescriptorType::DEVICE_QUALIFIER: + R_UNLESS(language_id == 0, xusb::ResultMalformedSetupRequest()); + R_TRY(xusb::GetCurrentGadget()->GetDeviceQualifierDescriptor(index, (const usb::DeviceQualifierDescriptor**) &descriptor, &actual_length)); + break; + default: + return xusb::ResultUnknownDescriptorType(); + } + + if (actual_length > length) { + actual_length = length; + } + + return SendData((void*) descriptor, actual_length); + } + + Result SetConfiguration(uint16_t config) { + print(SCREEN_LOG_LEVEL_DEBUG, "got SET_CONFIGURATION request(%d)\n", config); + + R_UNLESS(device_state == DeviceState::Address || device_state == DeviceState::Configured, xusb::ResultInvalidDeviceState()); + + // if we are already configured, deconfigure first. + if (device_state == DeviceState::Configured) { + R_TRY(xusb::GetCurrentGadget()->Deconfigure()); + ChangeDeviceState(DeviceState::Address); + } + + // if we need to reconfigure, do it. + if (config != 0) { + R_TRY(xusb::GetCurrentGadget()->SetConfiguration(config)); + ChangeDeviceState(DeviceState::Configured); + } + + return SendStatus(true); + } + + Result ClearEndpointHaltFeature(uint8_t index) { + xusb::endpoints[EpAddr::Decode(index)].ClearHalt(); + + return SendStatus(true); + } + + } // namespace impl + + Result ProcessEP0TransferEventImpl(TransferEventTRB *event, TRBBorrow transfer) { + if (event->completion_code == 1 || event->completion_code == 13) { + /* Success or short packet. */ + } else if (event->completion_code == 223) { // sequence number error + /* Sequence number error; skip to latest packet. */ + control_ep_state = ControlEpState::Idle; + } else { + /* Error. */ + control_ep_state = ControlEpState::Idle; + } + + if (&*transfer != control_ep_last_trb) { + /* This can happen when we skip packets. */ + print(SCREEN_LOG_LEVEL_ERROR, "got transfer event for wrong TRB (got %p, expected %p)\n", &*transfer, control_ep_last_trb); + return xusb::ResultUnexpectedTRB(); + } else { + control_ep_last_trb = nullptr; + + switch(control_ep_state) { + case ControlEpState::Idle: + return xusb::ResultInvalidControlEndpointState(); + case ControlEpState::DataInStage: + return SendStatus(false); // OUT + case ControlEpState::DataOutStage: + return SendStatus(false); // IN + case ControlEpState::StatusStage: + control_ep_state = ControlEpState::Idle; + return ResultSuccess(); + default: + return ResultSuccess(); + } + } + } + + void ProcessEP0TransferEvent(TransferEventTRB *event, TRBBorrow transfer) { + Result r = ProcessEP0TransferEventImpl(event, std::move(transfer)); + + if (r.IsFailure()) { + print(SCREEN_LOG_LEVEL_DEBUG, "failed to handle ep0 transfer event: 0x%x\n", r.GetValue()); + xusb::endpoints[0].Halt(); + } + } + + Result ProcessSetupPacketEventImpl(SetupPacketEventTRB *trb) { + R_UNLESS(trb->seq_num != 0xfffe, xusb::ResultInvalidSetupPacketSequenceNumber()); + R_UNLESS(trb->seq_num != 0xffff, xusb::ResultInvalidSetupPacketSequenceNumber()); + + R_UNLESS(device_state >= DeviceState::Default && device_state <= DeviceState::Configured, xusb::ResultInvalidDeviceState()); + + R_UNLESS(control_ep_state == ControlEpState::Idle, xusb::ResultControlEndpointBusy()); + + control_ep_seq_num = trb->seq_num; + xusb::endpoints[0].ClearHalt(); + + /* Let gadget try to service request first. */ + + do { + R_TRY_CATCH(xusb::GetCurrentGadget()->HandleSetupRequest(trb->packet)) { + R_CATCH(xusb::ResultUnknownSetupRequest) { break; } + } R_END_TRY_CATCH; + + return ResultSuccess(); + } while (0); + + /* If gadget did not recognize the request, handle it ourselves. */ + + switch((usb::SetupPacket::StandardRequest) trb->packet.bRequest) { + case usb::SetupPacket::StandardRequest::CLEAR_FEATURE: + if (trb->packet.bmRequestType == usb::SetupPacket::PackRequestType( + usb::SetupPacket::RequestDirection::HostToDevice, + usb::SetupPacket::RequestType::Standard, + usb::SetupPacket::RequestRecipient::Endpoint) && + trb->packet.wValue == (uint8_t) usb::EndpointFeatureSelector::ENDPOINT_HALT) { + R_UNLESS(trb->packet.wLength == 0, xusb::ResultMalformedSetupRequest()); + + return impl::ClearEndpointHaltFeature(trb->packet.wIndex); + } else { + return xusb::ResultUnknownSetupRequest(); + } + case usb::SetupPacket::StandardRequest::SET_ADDRESS: + if (trb->packet.bmRequestType != usb::SetupPacket::PackRequestType( + usb::SetupPacket::RequestDirection::HostToDevice, + usb::SetupPacket::RequestType::Standard, + usb::SetupPacket::RequestRecipient::Device) || + trb->packet.wIndex != 0 || + trb->packet.wLength != 0) { + return xusb::ResultMalformedSetupRequest(); + } + + if (device_state == DeviceState::Configured) { + return xusb::ResultInvalidDeviceState(); + } + + return impl::SetAddress(trb->packet.wValue); + + case usb::SetupPacket::StandardRequest::GET_DESCRIPTOR: + if (trb->packet.bmRequestType != usb::SetupPacket::PackRequestType( + usb::SetupPacket::RequestDirection::DeviceToHost, + usb::SetupPacket::RequestType::Standard, + usb::SetupPacket::RequestRecipient::Device)) { + return xusb::ResultMalformedSetupRequest(); + } + + return impl::GetDescriptor( + (usb::DescriptorType) (trb->packet.wValue >> 8), + trb->packet.wValue & 0xff, + trb->packet.wIndex, + trb->packet.wLength); + + case usb::SetupPacket::StandardRequest::SET_CONFIGURATION: + if (trb->packet.bmRequestType != usb::SetupPacket::PackRequestType( + usb::SetupPacket::RequestDirection::HostToDevice, + usb::SetupPacket::RequestType::Standard, + usb::SetupPacket::RequestRecipient::Device) || + trb->packet.wIndex != 0 || + trb->packet.wLength != 0) { + return xusb::ResultMalformedSetupRequest(); + } + + return impl::SetConfiguration(trb->packet.wValue); + + default: + return xusb::ResultUnknownSetupRequest(); + } + } + + void ProcessSetupPacketEvent(SetupPacketEventTRB *trb) { + Result r = ProcessSetupPacketEventImpl(trb); + + if (r.IsFailure()) { + print(SCREEN_LOG_LEVEL_WARNING, "error 0x%x while handling setup request\n", r.GetValue()); + print(SCREEN_LOG_LEVEL_WARNING, " bmRequestType: %d\n", trb->packet.bmRequestType); + print(SCREEN_LOG_LEVEL_WARNING, " bRequest: %d\n", trb->packet.bRequest); + print(SCREEN_LOG_LEVEL_WARNING, " wValue: %d\n", trb->packet.wValue); + print(SCREEN_LOG_LEVEL_WARNING, " wIndex: %d\n", trb->packet.wIndex); + print(SCREEN_LOG_LEVEL_WARNING, " wLenth: %d\n", trb->packet.wLength); + xusb::endpoints[0].Halt(); + } + } + + void ProcessPortStatusChangeEvent(AbstractTRB *trb) { + auto portsc = t210::T_XUSB_DEV_XHCI.PORTSC(); + auto porthalt = t210::T_XUSB_DEV_XHCI.PORTHALT(); + + print(SCREEN_LOG_LEVEL_DEBUG, "port status changed (0x%08x)\n", portsc.read_value); + + if (portsc.CSC) { // connect status change + int port_speed = portsc.PS.Get(); + print(SCREEN_LOG_LEVEL_DEBUG, "connect status changed (port speed %d)\n", port_speed); + portsc.CSC.Clear().Commit(); + } + + if (portsc.PRC.Get()) { // port reset change + print(SCREEN_LOG_LEVEL_DEBUG, "port reset changed (%d)\n", portsc.PR.Get()); + + if (!portsc.PR.Get()) { + ChangeDeviceState(DeviceState::Default); + } + + portsc.PRC.Clear().Commit(); + } + + if (portsc.WRC) { // warm port reset change + t210::T_XUSB_DEV_XHCI.PORTHALT().HALT_LTSSM.Set(0).Commit(); + + print(SCREEN_LOG_LEVEL_DEBUG, "warm port reset changed (%d)\n", portsc.WPR.Get()); + + portsc.WRC.Clear().Commit(); + } + + if (porthalt.STCHG_REQ) { + print(SCREEN_LOG_LEVEL_DEBUG, "porthalt STCHG_REQ\n"); + porthalt.HALT_LTSSM.Set(0).Commit(); + } + + if (portsc.PLC) { // port link state change + print(SCREEN_LOG_LEVEL_DEBUG, "port link state changed (%d)\n", portsc.PLS.Get()); + + if (portsc.PLS.Get() == 3) { // U3 + ChangeDeviceState(DeviceState::Default); + } + + portsc.PLC.Clear().Commit(); + } + + if (portsc.CEC) { // port configuration link error + print(SCREEN_LOG_LEVEL_DEBUG, "port configuration link error\n"); + portsc.CEC.Clear().Commit(); + } + } + + } // namespace control + + } // namespace xusb + +} // namespace ams diff --git a/fusee/fusee-primary/src/fastboot/xusb_control.h b/fusee/fusee-primary/src/fastboot/xusb_control.h new file mode 100644 index 000000000..e7fc75934 --- /dev/null +++ b/fusee/fusee-primary/src/fastboot/xusb_control.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "xusb_trb.h" +#include "xusb_endpoint.h" + +namespace ams { + + namespace xusb { + + namespace control { + + enum class DeviceState { + Powered, + Default, + Address, + Configured, + }; + + extern DeviceState state; + + Result SendData(void *data, size_t size); + Result SendStatus(bool direction); + + void ProcessEP0TransferEvent(TransferEventTRB *event, TRBBorrow transfer); + void ProcessSetupPacketEvent(SetupPacketEventTRB *trb); + void ProcessPortStatusChangeEvent(AbstractTRB *trb); + + } // namespace control + + } // namespace xusb + +} // namespace ams diff --git a/fusee/fusee-primary/src/fastboot/xusb_endpoint.cpp b/fusee/fusee-primary/src/fastboot/xusb_endpoint.cpp new file mode 100644 index 000000000..6b48e0844 --- /dev/null +++ b/fusee/fusee-primary/src/fastboot/xusb_endpoint.cpp @@ -0,0 +1,368 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "xusb_endpoint.h" + +#include "xusb.h" +#include "xusb_control.h" + +extern "C" { +#include "../lib/log.h" +} + +#include + +namespace ams { + + namespace xusb { + + void EndpointContext::InitializeForControl() { + memset((void*) this, 0, sizeof(*this)); + + this->ep_state = 1; + this->cerr = 3; + this->ep_type = 4; + this->max_packet_size = 64; + this->dcs = 1; + this->average_trb_length = 8; + this->cec = 3; + this->seqnum = 0; + } + + void EndpointContext::Initialize(const usb::EndpointDescriptor &desc) { + memset((void*) this, 0, sizeof(*this)); + + this->ep_state = 1; + + if (desc.GetEndpointType() != usb::EndpointType::Control && (desc.bEndpointAddress & 0x80)) { + /* In endpoint types are offset by 4. */ + this->ep_type = (uint8_t) desc.GetEndpointType() + 4; + } else { + this->ep_type = (uint8_t) desc.GetEndpointType(); + } + + this->cerr = 3; + this->max_packet_size = desc.wMaxPacketSize; + this->max_burst_size = 0; + + this->cec = 3; + } + + void EndpointAccessor::RingDoorbell() const { + t210::T_XUSB_DEV_XHCI.DB() + .TARGET.Set(this->no) + .Commit(); + } + + void EndpointAccessor::RingDoorbell(uint16_t stream_id) const { + t210::T_XUSB_DEV_XHCI.DB() + .STREAMID.Set(stream_id) + .TARGET.Set(this->no) + .Commit(); + } + + void EndpointAccessor::InitializeForControl(TRB *ring, size_t ring_size) const { + /* Initialize hardware endpoint context for control endpont. */ + this->GetContext().InitializeForControl(); + + /* Initialize transfer ring. */ + this->GetRingManager().Initialize(this->GetContext(), ring, ring_size); + } + + void EndpointAccessor::Initialize(const usb::EndpointDescriptor &desc, TRB *ring, size_t ring_size) const { + /* Initialize hardawre endpoint context for regular endpoint. */ + this->GetContext().Initialize(desc); + + /* Initialize transfer ring. */ + this->GetRingManager().Initialize(this->GetContext(), ring, ring_size); + + this->Reload(); + + t210::T_XUSB_DEV_XHCI.EP_PAUSE()[this->no].Set(0).Commit(); + t210::T_XUSB_DEV_XHCI.EP_HALT()[this->no].Set(0).Commit(); + } + + void EndpointAccessor::Disable() const { + this->GetContext().ep_state = 0; + this->Reload(); + this->ClearPause(); + this->ClearHalt(); + + auto stopped = t210::T_XUSB_DEV_XHCI.EP_STOPPED(); + if (stopped[this->no]) { + stopped[this->no].Clear().Commit(); + } + } + + Result EndpointAccessor::TransferNormal(void *data, size_t size, TRB **trb_out) const { + TRBBorrow trb; + + R_TRY(this->EnqueueTRB(&trb)); + + trb->transfer.InitializeNormal(data, size); + trb->transfer.interrupt_on_completion = true; + trb->transfer.interrupt_on_short_packet = true; + trb.Release(); + + this->RingDoorbell(); + + *trb_out = &*trb; + + return ResultSuccess(); + } + + void EndpointAccessor::Halt() const { + auto ep_halt = t210::T_XUSB_DEV_XHCI.EP_HALT(); + if (ep_halt[this->no]) { + return; + } + + (ep_halt[this->no] = 1).Commit(); + + /* BootROM polls EP_HALT register, but TRM says to poll STCHG instead. */ + while (!t210::T_XUSB_DEV_XHCI.EP_STCHG()[this->no]) { + } + + t210::T_XUSB_DEV_XHCI.EP_STCHG()[this->no].Clear().Commit(); + } + + void EndpointAccessor::ClearHalt() const { + auto ep_halt = t210::T_XUSB_DEV_XHCI.EP_HALT(); + if (!ep_halt[this->no]) { + return; + } + + (ep_halt[this->no] = 0).Commit(); + + /* Bootrom polls EP_HALT register, but TRM says to poll STCHG instead. */ + while (!t210::T_XUSB_DEV_XHCI.EP_STCHG()[this->no]) { + } + + t210::T_XUSB_DEV_XHCI.EP_STCHG()[this->no].Clear().Commit(); + } + + void EndpointAccessor::Pause() const { + auto ep_pause = t210::T_XUSB_DEV_XHCI.EP_PAUSE(); + if (ep_pause[this->no]) { + return; + } + + (ep_pause[this->no] = 1).Commit(); + + /* Bootrom polls EP_PAUSE register, but TRM says to poll STCHG instead. */ + while (!t210::T_XUSB_DEV_XHCI.EP_STCHG()[this->no]) { + } + + t210::T_XUSB_DEV_XHCI.EP_STCHG()[this->no].Clear().Commit(); + } + + void EndpointAccessor::ClearPause() const { + auto ep_pause = t210::T_XUSB_DEV_XHCI.EP_PAUSE(); + if (!ep_pause[this->no]) { + return; + } + + (ep_pause[this->no] = 0).Commit(); + + /* Bootrom polls EP_PAUSE register, but TRM says to poll STCHG instead. */ + while (!t210::T_XUSB_DEV_XHCI.EP_STCHG()[this->no]) { + } + + t210::T_XUSB_DEV_XHCI.EP_STCHG()[this->no].Clear().Commit(); + } + + void EndpointAccessor::Reload() const { + auto ep_reload = t210::T_XUSB_DEV_XHCI.EP_RELOAD(); + ep_reload[this->no] = 1; + ep_reload.Commit(); + + while (t210::T_XUSB_DEV_XHCI.EP_RELOAD()[this->no]) { + } + } + + TRBBorrow EndpointAccessor::AcceptCompletedTRB(TransferEventTRB *trb) const { + return TRBBorrow(&this->GetRingManager(), trb->GetSourceTRB()); + } + + void EndpointAccessor::ResetAndReloadTransferRing() const { + this->GetRingManager().Reset(this->GetContext()); + this->Reload(); + } + + void EndpointAccessor::DumpTransferRingForDebug() const { + this->GetRingManager().DumpTransferRingForDebug(); + } + + TRBBorrow::TRBBorrow(TRBBorrow &&other) : + ring(other.ring), + trb(other.trb), + state(other.state), + cycle_state(other.cycle_state) { + other.state = State::Empty; + } + + TRBBorrow &TRBBorrow::operator=(TRBBorrow &&other) { + this->Release(); + + this->ring = other.ring; + this->trb = other.trb; + this->state = other.state; + this->cycle_state = other.cycle_state; + + other.state = State::Empty; + + return *this; + } + + void TRBBorrow::Release() { + if (this->state == State::BorrowForEnqueue) { + this->trb->abstract.SetCycle(this->cycle_state); + this->state = State::Empty; + } else if (this->state == State::BorrowForRecycle) { + this->ring->RecycleTRB(this->trb); + this->state = State::Empty; + } + } + + void EndpointRingManager::Initialize(EndpointContext &ep_context, TRB *ring, size_t ring_size) { + this->ring = ring; + this->ring_size = ring_size; + + this->Reset(ep_context); + } + + void EndpointRingManager::Reset(EndpointContext &ep_context) { + memset(this->ring, 0, sizeof(TRB) * this->ring_size); + this->producer_cycle_state = true; + this->enqueue = &this->ring[0]; + this->dequeue = &this->ring[0]; + + ep_context.dcs = this->producer_cycle_state; + ep_context.tr_dequeue_ptr = ((uint64_t) &this->ring[0]) >> 4; + } + + size_t EndpointRingManager::GetFreeTRBCount() { + /* Last TRB is reserved for link. */ + size_t adjusted_ring_size = (this->ring_size - 1); + + if (this->ring_full) { + return 0; + } + + return adjusted_ring_size - ((this->enqueue + adjusted_ring_size - this->dequeue) % adjusted_ring_size); + } + + TRB *EndpointRingManager::EnqueueTRB(bool chain) { + if (ring_full) { + return nullptr; + } + + TRB *trb = this->enqueue++; + + if (this->enqueue == this->ring + (this->ring_size - 1)) { + this->enqueue->link.Initialize(&this->ring[0]); + this->enqueue->link.SetToggleCycle(true); + this->enqueue->link.SetChain(chain); + this->enqueue->abstract.SetCycle(this->producer_cycle_state); + + this->enqueue = &this->ring[0]; + this->producer_cycle_state = !this->producer_cycle_state; + } + + if (this->enqueue == this->dequeue) { + this->ring_full = true; + } + + trb->abstract.Clear(); + + return trb; + } + + void EndpointRingManager::RecycleTRB(TRB *trb) { + if (trb + 1 == &this->ring[this->ring_size-1]) { + this->dequeue = &this->ring[0]; + } else { + this->dequeue = trb + 1; + } + + this->ring_full = false; + } + + void EndpointRingManager::DebugState() { + ScreenLogLevel ll = (ScreenLogLevel) (SCREEN_LOG_LEVEL_DEBUG | SCREEN_LOG_LEVEL_NO_PREFIX); + print(ll, "%8s", "address "); + for (size_t i = 0; i < this->ring_size; i++) { + TRB *ptr = &this->ring[i]; + print(ll, "%x", ((uint32_t) ptr >> 4) & 0xf); + } + print(ll, "\n%8s", "cycle "); + for (size_t i = 0; i < this->ring_size; i++) { + TRB *ptr = &this->ring[i]; + print(ll, "%d", ptr->abstract.GetCycle()); + } + print(ll, "\n%8s", "deq/enq "); + for (size_t i = 0; i < this->ring_size; i++) { + TRB *ptr = &this->ring[i]; + if (this->dequeue == ptr && this->enqueue == ptr) { + print(ll, "!"); + } else if (this->dequeue == ptr) { + print(ll, "D"); + } else if (this->enqueue == ptr) { + print(ll, "E"); + } else if (ptr == &this->ring[this->ring_size-1]) { + print(ll, "L"); + } else { + print(ll, " "); + } + } + print(ll, "\n%8s", "ioc "); + for (size_t i = 0; i < this->ring_size; i++) { + TRB *ptr = &this->ring[i]; + print(ll, "%d", ptr->transfer.interrupt_on_completion); + } + print(ll, "\n"); + } + + void EndpointRingManager::DumpTransferRingForDebug() { + ScreenLogLevel ll = (ScreenLogLevel) (SCREEN_LOG_LEVEL_DEBUG | SCREEN_LOG_LEVEL_NO_PREFIX); + print(ll, " enq deq type \n"); + for (TRB *ptr = &this->ring[0]; ptr < &this->ring[this->ring_size]; ptr++) { + print(ll, " %3s %3s %3d %08x %08x %08x %08x\n", + ptr == this->enqueue ? "[E]" : "", + ptr == this->dequeue ? "[D]" : "", + ptr->transfer.trb_type, + ptr->abstract.field0, + ptr->abstract.field1, + ptr->abstract.field2, + ptr->abstract.field3); + } + print(ll, "pcs: %d\n", this->producer_cycle_state); + print(ll, "full: %s\n", this->ring_full ? "yes" : "no"); + } + + void Endpoints::ClearNonControlEndpoints() { + memset(&contexts[2], 0, sizeof(contexts) - 2*sizeof(contexts[0])); + } + + void Endpoints::SetEP0DeviceAddress(uint16_t addr) { + this->contexts[0].device_address = addr; + } + + Endpoints endpoints __attribute__((section(".dram"))); + + } // namespace xusb + +} // namespace ams diff --git a/fusee/fusee-primary/src/fastboot/xusb_endpoint.h b/fusee/fusee-primary/src/fastboot/xusb_endpoint.h new file mode 100644 index 000000000..6402f8be4 --- /dev/null +++ b/fusee/fusee-primary/src/fastboot/xusb_endpoint.h @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "xusb_trb.h" +#include "../reg/reg_xusb_dev.h" + +#include + +#include + +namespace ams { + + namespace xusb { + + struct EndpointContext { + uint32_t ep_state:3; + uint32_t :5; + uint32_t multi:2; + uint32_t max_p_streams:5; + uint32_t lsa:1; + uint32_t interval:8; + uint32_t :8; + + uint32_t :1; + uint32_t cerr:2; + uint32_t ep_type:3; + uint32_t :1; + uint32_t hid:1; + uint32_t max_burst_size:8; + uint32_t max_packet_size:16; + + uint64_t dcs:1; + uint64_t :3; + uint64_t tr_dequeue_ptr:60; + + uint32_t average_trb_length:16; + uint32_t max_esit_payload:16; + + uint32_t event_data_transfer_length_accumulator:24; + uint32_t :1; + uint32_t ptd:1; + uint32_t sxs:1; + uint32_t seqnum:5; + + uint32_t cprog:8; + uint32_t s_byte:7; + uint32_t tp:2; + uint32_t rec:1; + uint32_t cec:2; + uint32_t ced:1; + uint32_t hsp:1; + uint32_t rty:1; + uint32_t std:1; + uint32_t status:8; + + uint32_t data_offset:17; + uint32_t :4; + uint32_t lpa:1; + uint32_t num_trb:5; + uint32_t num_p:5; + + uint32_t scratchpad1; + + uint32_t scratchpad2; + + uint32_t cping:8; + uint32_t sping:8; + uint32_t tc:2; + uint32_t ns:1; + uint32_t ro:1; + uint32_t tlm:1; + uint32_t dlm:1; + uint32_t hsp2:1; + uint32_t rty2:1; + uint32_t stop_reclaim_request:8; + + uint32_t device_address:8; + uint32_t hub_address:8; + uint32_t root_port_number:8; + uint32_t slot_id:8; + + uint32_t routing_string:20; + uint32_t speed:4; + uint32_t lpu:1; + uint32_t myy:1; + uint32_t hub:1; + uint32_t dci:5; + + uint32_t tt_hub_slot_id:8; + uint32_t tt_port_num:8; + uint32_t ssf:4; + uint32_t sps:2; + uint32_t interrupt_target:10; + + uint64_t frz:1; + uint64_t end:1; + uint64_t elm:1; + uint64_t mrk:1; + uint64_t endpoint_link:60; + + void InitializeForControl(); + void Initialize(const usb::EndpointDescriptor &desc); + } __attribute__((aligned(64))); + + enum Dir { + Out, + In, + }; + + struct EpAddr { + static inline EpAddr Decode(uint8_t addr) { + return EpAddr { + .no = (uint8_t) (addr & 0b1111), + .direction = (addr >> 7) ? Dir::In : Dir::Out, + }; + } + + uint8_t no; + Dir direction; + }; + + class EndpointRingManager { + public: + void Initialize(EndpointContext &ep_context, TRB *ring, size_t ring_size); + void Reset(EndpointContext &ep_context); + + inline size_t GetRingUsableCount() { + return this->ring_size - 1; + } + size_t GetFreeTRBCount(); + + void DumpTransferRingForDebug(); // long form + void DebugState(); + + TRB *EnqueueTRB(bool chain); + void RecycleTRB(TRB *trb); + + inline bool GetProducerCycleState() { + return this->producer_cycle_state; + } + + private: + TRB *ring = nullptr; + size_t ring_size = 0; + bool ring_full = false; + + bool producer_cycle_state = true; + + TRB *dequeue = nullptr; + TRB *enqueue = nullptr; + }; + + /* TRB ownership: + Initially, all TRBs are owned by the transfer ring manager. + By calling EnqueueTRB(), you are borrowing a TRB from the ring. + By calling Release() or allowing TRBBorrow to be destructed, the borrow is transferred to the hardware. + When the hardware is finished with the TRB and interrupts for completion, the borrow is transferred back to the software. + By calling Release() or allowing TRBRecycleHelper to be destructed, the TRB is recycled to the transfer ring manager. + */ + + class EndpointAccessor; + + class TRBBorrow { + public: + inline TRBBorrow() : + state(State::Empty) { + } + + private: + friend class EndpointAccessor; + + inline TRBBorrow(EndpointRingManager *ring, TRB *trb) : + ring(ring), + trb(trb), + state(State::BorrowForRecycle) { + } + + inline TRBBorrow(EndpointRingManager *ring, TRB *trb, bool cycle_state) : + ring(ring), + trb(trb), + state(State::BorrowForEnqueue), + cycle_state(cycle_state) { + } + + public: + + inline ~TRBBorrow() { + Release(); + } + + TRBBorrow(TRBBorrow &) = delete; + TRBBorrow(TRBBorrow &&); + TRBBorrow &operator=(TRBBorrow &) = delete; + TRBBorrow &operator=(TRBBorrow &&); + + void Release(); + + inline TRB &operator*() { + return *this->trb; + } + + inline TRB *operator->() { + return this->trb; + } + + private: + + EndpointRingManager *ring; + TRB *trb; + + enum class State { + Empty, + BorrowForEnqueue, + BorrowForRecycle + } state; + + bool cycle_state; + }; + + class Endpoints; + + class EndpointAccessor { + public: + constexpr inline EndpointAccessor(uint8_t ep_index, Endpoints &eps) : + no(ep_index), + endpoints(eps) { + } + + void InitializeForControl(TRB *ring, size_t ring_size) const; + void Initialize(const usb::EndpointDescriptor &desc, TRB *ring, size_t ring_size) const; + void Disable() const; + + inline size_t GetRingUsableCount() const { + return this->GetRingManager().GetRingUsableCount(); + } + + inline size_t GetFreeTRBCount() const { + return this->GetRingManager().GetFreeTRBCount(); + } + + inline Result EnqueueTRB(TRBBorrow *borrow, bool chain=false) const { + // it is important to call this before Enqueue + bool cycle_state = this->GetRingManager().GetProducerCycleState(); + + TRB *trb = this->GetRingManager().EnqueueTRB(chain); + + R_UNLESS(trb != nullptr, xusb::ResultTransferRingFull()); + + *borrow = TRBBorrow( + &this->GetRingManager(), + trb, + cycle_state); + + return ResultSuccess(); + } + + Result TransferNormal(void *buffer, size_t size, TRB **trb_out) const; + + void Halt() const; + void ClearHalt() const; + + void Pause() const; + void ClearPause() const; + + void Reload() const; + void RingDoorbell() const; + void RingDoorbell(uint16_t stream_id) const; + TRBBorrow AcceptCompletedTRB(TransferEventTRB *trb) const; + + inline uint8_t GetIndex() const { + return this->no; + } + + void ResetAndReloadTransferRing() const; + + void DumpTransferRingForDebug() const; + + private: + inline EndpointContext &GetContext() const; + inline EndpointRingManager &GetRingManager() const; + + void Initialize(TRB *ring, size_t ring_size); + + uint8_t no; + Endpoints &endpoints; + }; + + class Endpoints { + public: + static const size_t NR_EPS = 32; + + private: + friend class EndpointAccessor; + // structure-of-arrays instead of array-of-structures + // to improve alignment. + EndpointRingManager rings[NR_EPS]; + EndpointContext contexts[NR_EPS]; + + public: + void ClearNonControlEndpoints(); + void SetEP0DeviceAddress(uint16_t addr); + + constexpr inline EndpointAccessor operator[](EpAddr addr) { + return EndpointAccessor(addr.no * 2 + (addr.direction == Dir::In ? 1 : 0), *this); + }; + + constexpr inline EndpointAccessor operator[](int no) { + return EndpointAccessor(no, *this); + }; + + inline uintptr_t GetContextsForHardware() { + return (uintptr_t) contexts; + } + }; + + inline EndpointContext &EndpointAccessor::GetContext() const { + return this->endpoints.contexts[this->no]; + } + + inline EndpointRingManager &EndpointAccessor::GetRingManager() const { + return this->endpoints.rings[this->no]; + } + + + extern Endpoints endpoints; + + static_assert(sizeof(EndpointContext) == 64, "Endpoint context should be 64 bytes"); + + } // namespace xusb + +} // namespace ams diff --git a/fusee/fusee-primary/src/fastboot/xusb_event_ring.cpp b/fusee/fusee-primary/src/fastboot/xusb_event_ring.cpp new file mode 100644 index 000000000..dbd6e33c8 --- /dev/null +++ b/fusee/fusee-primary/src/fastboot/xusb_event_ring.cpp @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "xusb_event_ring.h" + +#include "xusb.h" +#include "xusb_control.h" +#include "xusb_endpoint.h" + +#include "../reg/reg_xusb_dev.h" + +extern "C" { +#include "../lib/log.h" +#include "../utils.h" +} + +#include +#include + +namespace ams { + + namespace xusb { + + void EventRing::Initialize() { + memset((void*) &storage[0], 0, sizeof(storage)); + + if (((uintptr_t) &storage[0] & 0b1111) != 0) { + fatal_error("xusb initialization failed: event ring storage is misaligned\n"); + } + + this->consumer_dequeue_ptr = &this->storage[0]; + this->consumer_cycle_state = true; + + t210::T_XUSB_DEV_XHCI + .ERST0BALO(0).ADDRLO.Set((uint32_t) &this->storage[0]).Commit() + .ERST0BAHI(0).ADDRHI.Set(0).Commit() + .ERST1BALO(0).ADDRLO.Set((uint32_t) &this->storage[0x10]).Commit() + .ERST1BAHI(0).ADDRHI.Set(0).Commit() + .ERSTSZ(0) + .ERST0SZ.Set(0x10) + .ERST1SZ.Set(0x10) + .Commit() + .EREPLO(0) + .ADDRLO.Set((uint32_t) &this->storage[0]) + .ECS.Set(this->consumer_cycle_state) + .Commit() + .EREPHI(0).ADDRHI.Set(0).Commit() + .ERDPLO(0).ADDRLO.Set((uint32_t) &this->storage[0]).Commit() + .ERDPHI(0).ADDRHI.Set(0).Commit(); + } + + void EventRing::Process() { + auto st = t210::T_XUSB_DEV_XHCI.ST(); + if (st.IP) { + /* Clear interrupt pending. */ + st.IP.Clear().Commit(); + } + + while (this->consumer_dequeue_ptr->abstract.GetCycle() == this->consumer_cycle_state) { + switch(this->consumer_dequeue_ptr->abstract.GetType()) { + case TRBType::SetupPacketEvent: + control::ProcessSetupPacketEvent(&this->consumer_dequeue_ptr->setup_packet_event); + break; + case TRBType::PortStatusChangeEvent: + control::ProcessPortStatusChangeEvent(&this->consumer_dequeue_ptr->abstract); + break; + case TRBType::TransferEvent: { + TransferEventTRB *te = &this->consumer_dequeue_ptr->transfer_event; + TRBBorrow completed_transfer = xusb::endpoints[te->ep_id].AcceptCompletedTRB(te); + + if (te->ep_id == 0) { + xusb::control::ProcessEP0TransferEvent(te, std::move(completed_transfer)); + } else { + xusb::GetCurrentGadget()->ProcessTransferEvent(te, std::move(completed_transfer)); + } + + break; } + default: + print(SCREEN_LOG_LEVEL_WARNING, "got unrecognized TRB type on event ring: %d\n", (int) this->consumer_dequeue_ptr->abstract.GetType()); + break; + } + + if ((size_t) (this->consumer_dequeue_ptr - &this->storage[0]) == std::size(this->storage) - 1) { + this->consumer_dequeue_ptr = &this->storage[0]; + this->consumer_cycle_state = !this->consumer_cycle_state; + } else { + this->consumer_dequeue_ptr++; + } + + /* Some situations cause the event ring to continue filling up while + we're processing it, such as if we are submitting transfers and + they are completing before we fully yield out to the main + loop. */ + t210::T_XUSB_DEV_XHCI.ERDPLO() + .ADDRLO.Set((uint32_t) this->consumer_dequeue_ptr) + .Commit(); + } + + t210::T_XUSB_DEV_XHCI.ERDPLO() + .EHB.Clear() + .ADDRLO.Set((uint32_t) this->consumer_dequeue_ptr) + .Commit(); + } + + } // namespace xusb + +} // namespace ams diff --git a/fusee/fusee-primary/src/fastboot/xusb_event_ring.h b/fusee/fusee-primary/src/fastboot/xusb_event_ring.h new file mode 100644 index 000000000..341cb6276 --- /dev/null +++ b/fusee/fusee-primary/src/fastboot/xusb_event_ring.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "xusb_trb.h" + +namespace ams { + + namespace xusb { + + class EventRing { + public: + void Initialize(); + void Process(); + private: + TRB storage[0x20]; + + TRB *consumer_dequeue_ptr; + bool consumer_cycle_state; + } __attribute__((aligned(16))); + + } // namespace xusb + +} // namespace ams diff --git a/fusee/fusee-primary/src/fastboot/xusb_test_gadget.cpp b/fusee/fusee-primary/src/fastboot/xusb_test_gadget.cpp new file mode 100644 index 000000000..74d8329f7 --- /dev/null +++ b/fusee/fusee-primary/src/fastboot/xusb_test_gadget.cpp @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "xusb.h" +#include "xusb_control.h" +#include "xusb_endpoint.h" + +#include + +extern "C" { +#include "../lib/log.h" +#include "../utils.h" +#include "../btn.h" +} + +namespace ams { + + namespace xusb { + + namespace test { + + USB_DECLARE_STRING_DESCRIPTOR(langid, u"\x0409"); // English (US) + USB_DECLARE_STRING_DESCRIPTOR(manu, u"Test Manufacturer"); + USB_DECLARE_STRING_DESCRIPTOR(product, u"Test Product"); + USB_DECLARE_STRING_DESCRIPTOR(serial, u"Test Serial Number"); + USB_DECLARE_STRING_DESCRIPTOR(configuration, u"Test Configuration"); + USB_DECLARE_STRING_DESCRIPTOR(interface, u"Test Interface"); + + static constexpr usb::StringDescriptorIndexer sd_indexer; + + static xusb::TRB in_ring[5]; + static xusb::TRB out_ring[5]; + static char buffer[513] __attribute__((section(".dram"))) = {0}; + + class XusbTestGadget : public Gadget { + public: + static constexpr usb::DeviceDescriptor device_descriptor = { + { + .bLength = sizeof(usb::DeviceDescriptor), + .bDescriptorType = usb::DescriptorType::DEVICE, + }, + { + .bcdUSB = 0x210, + .bDeviceClass = 0, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .bMaxPacketSize0 = 64, + .idVendor = 0x1209, + .idProduct = 0x8b00, + .bcdDevice = 0x100, + .iManufacturer = sd_indexer.manu, + .iProduct = sd_indexer.product, + .iSerialNumber = sd_indexer.serial, + .bNumConfigurations = 1, + } + }; + + + static constexpr usb::DeviceQualifierDescriptor device_qualifier_descriptor = { + { + .bLength = sizeof(usb::DeviceQualifierDescriptor), + .bDescriptorType = usb::DescriptorType::DEVICE_QUALIFIER, + }, + { + .bcdUSB = 0x210, + .bDeviceClass = 0, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .bMaxPacketSize0 = 64, + .bNumConfigurations = 1, + .bReserved = 0, + } + }; + + static const struct WholeConfigurationDescriptor { + usb::ConfigurationDescriptor configuration_descriptor = { + { + .bLength = sizeof(usb::ConfigurationDescriptor), + .bDescriptorType = usb::DescriptorType::CONFIGURATION, + }, + { + .wTotalLength = sizeof(WholeConfigurationDescriptor), + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = 4, + .bmAttributes = 0x80, + .bMaxPower = 0, + } + }; + usb::InterfaceDescriptor interface_descriptor { + { + .bLength = sizeof(usb::InterfaceDescriptor), + .bDescriptorType = usb::DescriptorType::INTERFACE, + }, + { + .bInterfaceNumber = 0, + .bAlternateSetting = 0, + .bNumEndpoints = 2, + .bInterfaceClass = 0xff, + .bInterfaceSubClass = 0xff, + .bInterfaceProtocol = 0xff, + .iInterface = 5, + } + }; + usb::EndpointDescriptor endpoint_in_descriptor { + { + .bLength = sizeof(usb::EndpointDescriptor), + .bDescriptorType = usb::DescriptorType::ENDPOINT, + }, + { + .bEndpointAddress = 0x81, + .bmAttributes = 0x2, + .wMaxPacketSize = 512, + .bInterval = 1, + } + }; + usb::EndpointDescriptor endpoint_out_descriptor { + { + .bLength = sizeof(usb::EndpointDescriptor), + .bDescriptorType = usb::DescriptorType::ENDPOINT, + }, + { + .bEndpointAddress = 0x01, + .bmAttributes = 0x2, + .wMaxPacketSize = 512, + .bInterval = 1, + } + }; + } __attribute__((packed)) whole_configuration_descriptor; + + virtual Result GetDeviceDescriptor(uint8_t index, const usb::DeviceDescriptor **descriptor, uint16_t *length) override { + R_UNLESS(index == 0, xusb::ResultInvalidDescriptorIndex()); + + *descriptor = &device_descriptor; + *length = sizeof(device_descriptor); + + return ResultSuccess(); + } + + virtual Result GetDeviceQualifierDescriptor(uint8_t index, const usb::DeviceQualifierDescriptor **descriptor, uint16_t *length) override { + R_UNLESS(index == 0, xusb::ResultInvalidDescriptorIndex()); + + *descriptor = &device_qualifier_descriptor; + *length = sizeof(device_qualifier_descriptor); + + return ResultSuccess(); + } + + virtual Result GetConfigurationDescriptor(uint8_t index, const usb::ConfigurationDescriptor **descriptor, uint16_t *length) override { + R_UNLESS(index == 0, xusb::ResultInvalidDescriptorIndex()); + + *descriptor = &whole_configuration_descriptor.configuration_descriptor; + *length = sizeof(whole_configuration_descriptor); + + return ResultSuccess(); + } + + virtual Result GetBOSDescriptor(const usb::BOSDescriptor **descriptor, uint16_t *length) override { + return ResultUnknownDescriptorType(); + } + + virtual Result GetStringDescriptor(uint8_t index, uint16_t language, const usb::CommonDescriptor **descriptor, uint16_t *length) override { + R_UNLESS(language == 0x0409 || (language == 0 && index == 0), xusb::ResultInvalidDescriptorIndex()); + + R_UNLESS(sd_indexer.GetStringDescriptor(index, language, descriptor, length), xusb::ResultInvalidDescriptorIndex()); + + return ResultSuccess(); + } + + static constexpr auto out = xusb::endpoints[xusb::EpAddr {1, xusb::Dir::Out}]; + static constexpr auto in = xusb::endpoints[xusb::EpAddr {1, xusb::Dir::In}]; + + virtual Result SetConfiguration(uint16_t configuration) override { + R_UNLESS(configuration <= 1, xusb::ResultInvalidConfiguration()); + + xusb::endpoints.ClearNonControlEndpoints(); + + out.Initialize(whole_configuration_descriptor.endpoint_out_descriptor, out_ring, std::size(out_ring)); + in.Initialize(whole_configuration_descriptor.endpoint_in_descriptor, in_ring, std::size(in_ring)); + + print(SCREEN_LOG_LEVEL_DEBUG, "configured and enabled endpoints!\n"); + + return ResultSuccess(); + } + + virtual Result Deconfigure() override { + out.Disable(); + in.Disable(); + + return ResultSuccess(); + } + + TRB *last_out_trb = nullptr; + TRB *last_in_trb = nullptr; + + Result SubmitOutTRB() { + TRBBorrow trb; + + R_TRY(out.EnqueueTRB(&trb)); + + print(SCREEN_LOG_LEVEL_DEBUG, "submitting out trb %p\n", &*trb); + trb->transfer.InitializeNormal((void*) buffer, sizeof(buffer)-1); + trb->transfer.interrupt_on_completion = true; + trb->transfer.interrupt_on_short_packet = true; + trb.Release(); + + last_out_trb = &*trb; + + out.RingDoorbell(); + + return ResultSuccess(); + } + + Result SubmitInTRB(size_t length) { + TRBBorrow trb; + + R_TRY(in.EnqueueTRB(&trb)); + + print(SCREEN_LOG_LEVEL_DEBUG, "submitting in trb %p\n", &*trb); + trb->transfer.InitializeNormal((void*) buffer, length); + trb->transfer.interrupt_on_completion = true; + trb->transfer.interrupt_on_short_packet = true; + trb.Release(); + + last_in_trb = &*trb; + + in.RingDoorbell(); + + return ResultSuccess(); + } + + virtual void PostConfigure() override { + SubmitOutTRB(); + } + + virtual void ProcessTransferEvent(TransferEventTRB *event, TRBBorrow transfer) override { + print(SCREEN_LOG_LEVEL_DEBUG, "gadget got transfer event for ep %d\n", event->ep_id); + + if (event->ep_id == out.GetIndex()) { + if (&*transfer == last_out_trb) { + print(SCREEN_LOG_LEVEL_DEBUG, " out transfer completed: %d bytes remain\n", event->trb_transfer_length); + buffer[transfer->transfer.transfer_length - event->trb_transfer_length] = 0; + print(SCREEN_LOG_LEVEL_DEBUG, " \"%s\"\n", buffer); + + SubmitInTRB(transfer->transfer.transfer_length - event->trb_transfer_length); + } else { + print(SCREEN_LOG_LEVEL_DEBUG, " got completion for unexpected OUT trb (got %p, expected %p)\n", &*transfer, last_out_trb); + } + } else if (event->ep_id == in.GetIndex()) { + if (&*transfer == last_in_trb) { + SubmitOutTRB(); + } else { + print(SCREEN_LOG_LEVEL_DEBUG, " got completion for unexpected IN trb (got %p, expected %p)\n", &*transfer, last_out_trb); + } + } + } + }; + + static XusbTestGadget test_gadget; + + Gadget &GetTestGadget() { + return test_gadget; + } + + } // namespace test + + } // namespace xusb + +} // namespace ams diff --git a/fusee/fusee-primary/src/fastboot/xusb_test_gadget.h b/fusee/fusee-primary/src/fastboot/xusb_test_gadget.h new file mode 100644 index 000000000..c0571f48f --- /dev/null +++ b/fusee/fusee-primary/src/fastboot/xusb_test_gadget.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "xusb.h" + +namespace xusb { + + namespace test { + + Gadget &GetTestGadget(); + + } // namespace test + +} // namespace xusb diff --git a/fusee/fusee-primary/src/fastboot/xusb_trb.h b/fusee/fusee-primary/src/fastboot/xusb_trb.h new file mode 100644 index 000000000..97692709e --- /dev/null +++ b/fusee/fusee-primary/src/fastboot/xusb_trb.h @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "usb_types.h" + +#include +#include + +namespace ams { + + namespace xusb { + + enum class TRBType : uint8_t { + // transfer TRBs + Normal = 1, + SetupStage = 2, + DataStage = 3, + StatusStage = 4, + Isoch = 5, + Link = 6, + EventData = 7, + NoOp = 8, + + // event TRBs + TransferEvent = 32, + PortStatusChangeEvent = 34, + SetupPacketEvent = 63, // vendor defined + }; + + union TRB; + + struct AbstractTRB { + uint32_t field0; + uint32_t field1; + uint32_t field2; + uint32_t field3; + + inline void Clear() { + // leave cycle bit so hardware doesn't accidentlly consume this TRB + this->field0 = 0; + this->field1 = 0; + this->field2 = 0; + this->field3&= 1; + } + + inline bool GetCycle() { + return this->field3 & 1; + } + + inline void SetCycle(bool cycle) { + this->field3&= ~1; + this->field3|= cycle; + } + + inline TRBType GetType() { + return (TRBType) ((this->field3 >> 10) & 0b111111); + } + + inline void SetType(TRBType type) { + this->field3&= ~(0b111111 << 10); + this->field3|= (((uint32_t) type) & 0b111111) << 10; + } + } __attribute__((aligned(16))); + + struct LinkTRB : protected AbstractTRB { + inline void Initialize(TRB *link) { + AbstractTRB::SetType(TRBType::Link); + this->field0 = (uint32_t) link; + } + + inline void SetToggleCycle(bool tc) { + this->field3&= ~2; + this->field3|= tc << 1; + } + + inline void SetChain(bool chain) { + this->field3&= ~0x10; + this->field3|= chain << 4; + } + } __attribute__((aligned(16))); + + struct TransferTRB { + uint32_t data_ptr_lo; + uint32_t data_ptr_hi; + + uint32_t transfer_length:17; + uint32_t td_size:5; + uint32_t interrupter_target:10; + + uint32_t :0; + uint32_t cycle:1; + uint32_t evaluate_next_trb:1; + uint32_t interrupt_on_short_packet:1; + uint32_t no_snoop:1; + uint32_t chain_bit:1; + uint32_t interrupt_on_completion:1; + uint32_t immediate_data:1; + uint32_t :2; // reserved + uint32_t block_event_interrupt:1; + uint32_t trb_type:6; + uint32_t dir:1; + uint32_t :15; + + inline void InitializeNoOp() { + this->trb_type = (uint32_t) TRBType::NoOp; + } + + inline void InitializeNormal(void *data, size_t length) { + this->data_ptr_lo = (uint32_t) data; + this->transfer_length = length; + this->trb_type = (uint32_t) TRBType::Normal; + } + + inline void InitializeDataStage(bool dir, void *data, size_t length) { + this->data_ptr_lo = (uint32_t) data; + this->transfer_length = length; + this->trb_type = (uint32_t) TRBType::DataStage; + this->dir = dir; + } + + // see Table 4-7 on page 213 of XHCI spec + inline void InitializeStatusStage(bool dir) { + this->trb_type = (uint32_t) TRBType::StatusStage; + this->dir = dir; + } + } __attribute__((aligned(16))); + + struct TransferEventTRB { + uint32_t trb_ptr_lo; + uint32_t trb_ptr_hi; + uint32_t trb_transfer_length:24; + uint32_t completion_code:8; + uint32_t cycle:1; + uint32_t :1; + uint32_t ed:1; + uint32_t :7; + uint32_t trb_type:6; + uint32_t ep_id:5; + + inline TRB *GetSourceTRB() { + return (TRB*) trb_ptr_lo; + } + } __attribute__((aligned(16))); + + struct SetupPacketEventTRB { + usb::SetupPacket packet; + uint16_t seq_num; + uint16_t field2; + uint32_t field3; + + inline bool GetEventDataFlag() { + return (field3 & 0b100) != 0; + } + } __attribute__((aligned(16))); + + union TRB { + public: + AbstractTRB abstract; + LinkTRB link; + TransferTRB transfer; + + TransferEventTRB transfer_event; + SetupPacketEventTRB setup_packet_event; + } __attribute__((aligned(16))); + + static_assert(sizeof(TRB) == 16, "TRB should be 16 bytes"); + + } // namespace xusb + +} // namespace ams diff --git a/fusee/fusee-primary/src/main.c b/fusee/fusee-primary/src/main.c index d1ec3df03..7263f9eee 100644 --- a/fusee/fusee-primary/src/main.c +++ b/fusee/fusee-primary/src/main.c @@ -27,6 +27,7 @@ #include "lib/log.h" #include "lib/vsprintf.h" #include "display/video_fb.h" +#include "fastboot/fastboot.h" extern void (*__program_exit_callback)(int rc); @@ -43,7 +44,10 @@ static char g_bct0_buffer[BCTO_MAX_SIZE] __attribute__((section(".dram"))) = {0} "debugmode = 1\n"\ "debugmode_user = 0\n"\ "disable_user_exception_handlers = 0\n"\ -"[stratosphere]\n" +"[stratosphere]\n" \ +"[fastboot]\n" \ +"force_enable = 0\n" \ +"button_timeout_ms = 0\n" static const char *load_config(void) { if (!read_from_file(g_bct0_buffer, BCTO_MAX_SIZE, "atmosphere/config/BCT.ini")) { @@ -130,8 +134,18 @@ int main(void) { /* Assert that our configuration is sane. */ stage2_validate_config(&bct0); - /* Load the loader payload into DRAM. */ - stage2_load(&bct0); + /* Try to enter fastboot, if we are configured to. */ + switch(fastboot_enter(&bct0)) { + case FASTBOOT_INVALID: + case FASTBOOT_SKIPPED: + case FASTBOOT_LOAD_STAGE2: + /* Load the loader payload into DRAM. */ + stage2_load(&bct0); + break; + case FASTBOOT_CHAINLOAD: + print(SCREEN_LOG_LEVEL_DEBUG, "fastboot: chainloading\n"); + break; + } /* Setup argument data. */ strcpy(g_chainloader_arg_data, bct0.stage2_path); diff --git a/fusee/fusee-primary/src/reg/reg_apbdev_pmc.h b/fusee/fusee-primary/src/reg/reg_apbdev_pmc.h new file mode 100644 index 000000000..bae6007e9 --- /dev/null +++ b/fusee/fusee-primary/src/reg/reg_apbdev_pmc.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "reg_util.h" + +namespace t210 { + + const struct APBDEV_PMC { + static const uintptr_t base_addr = 0x7000e400; + using Peripheral = APBDEV_PMC; + + BEGIN_DEFINE_REGISTER(USB_AO_0, uint32_t, 0xf0) + DEFINE_RW_FIELD(ID_PD_P0, 3) + DEFINE_RW_FIELD(VBUS_WAKEUP_PD_P0, 2) + END_DEFINE_REGISTER(USB_AO_0) + } APBDEV_PMC; + +} // namespace t210 diff --git a/fusee/fusee-primary/src/reg/reg_car.h b/fusee/fusee-primary/src/reg/reg_car.h new file mode 100644 index 000000000..23e63aeff --- /dev/null +++ b/fusee/fusee-primary/src/reg/reg_car.h @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "reg_util.h" + +namespace t210 { + + const struct CLK_RST_CONTROLLER { + static const uintptr_t base_addr = 0x60006000; + using Peripheral = CLK_RST_CONTROLLER; + + enum class OscFreq { + OSC13 = 0, + OSC16P8 = 1, + OSC19P2 = 4, + OSC38P4 = 5, + OSC12 = 8, + OSC48 = 9, + OSC26 = 12, + }; + + BEGIN_DEFINE_REGISTER(RST_DEVICES_U_0, uint32_t, 0xc) + DEFINE_RW_FIELD(SWR_XUSB_DEV_RST, 31) + DEFINE_RW_FIELD(SWR_XUSB_HOST_RST, 25) + END_DEFINE_REGISTER(RST_DEVICES_U_0) + + BEGIN_DEFINE_REGISTER(CLK_OUT_ENB_U_0, uint32_t, 0x18) + DEFINE_RW_FIELD(CLK_ENB_XUSB_DEV, 31) + DEFINE_RW_FIELD(CLK_ENB_XUSB_HOST, 25) + END_DEFINE_REGISTER(CLK_OUT_ENB_U_0) + + BEGIN_DEFINE_REGISTER(OSC_CTRL_0, uint32_t, 0x50) + DEFINE_RW_FIELD(OSC_FREQ, 28, 31, OscFreq) + END_DEFINE_REGISTER(OSC_CTRL_0) + + BEGIN_DEFINE_REGISTER(PLLU_BASE_0, uint32_t, 0xc0) + DEFINE_RW_FIELD(PLLU_BYPASS, 31) + DEFINE_RW_FIELD(PLLU_ENABLE, 30) + DEFINE_RW_FIELD(PLLU_REF_DIS, 29) + DEFINE_RO_FIELD(PLLU_LOCK, 27) + DEFINE_RW_FIELD(PLLU_CLKENABLE_48M, 25) + DEFINE_RW_FIELD(PLLU_OVERRIDE, 24) + DEFINE_RW_FIELD(PLLU_CLKENABLE_ICUSB, 23) + DEFINE_RW_FIELD(PLLU_CLKENABLE_HSIC, 22) + DEFINE_RW_FIELD(PLLU_CLKENABLE_USB, 21) + DEFINE_RW_FIELD(PLLU_DIVP, 16, 20) + DEFINE_RW_FIELD(PLLU_DIVN, 8, 15) + DEFINE_RW_FIELD(PLLU_DIVM, 0, 7) + END_DEFINE_REGISTER(PLLU_BASE_0) + + BEGIN_DEFINE_REGISTER(PLLU_OUTA_0, uint32_t, 0xc4) + DEFINE_RW_FIELD(PLLU_OUT2_RATIO, 24, 21) + DEFINE_RW_FIELD(PLLU_OUT2_OVRRIDE, 18) + DEFINE_RW_FIELD(PLLU_OUT2_CLKEN, 17) + DEFINE_RW_FIELD(PLLU_OUT2_RSTEN, 16) + + DEFINE_RW_FIELD(PLLU_OUT1_RATIO, 8, 15) + DEFINE_RW_FIELD(PLLU_OUT1_OVRRIDE, 2) + DEFINE_RW_FIELD(PLLU_OUT1_CLKEN, 1) + DEFINE_RW_FIELD(PLLU_OUT1_RSTN, 0) + END_DEFINE_REGISTER(PLLU_OUTA_0) + + BEGIN_DEFINE_REGISTER(PLLU_MISC_0, uint32_t, 0xcc) + DEFINE_RW_FIELD(PLLU_IDDQ, 31) + DEFINE_RO_FIELD(PLLU_FREQLOCK, 30) + DEFINE_RW_FIELD(PLLU_EN_LCKDET, 29) + BEGIN_DEFINE_RW_SYMBOLIC_FIELD(PLLU_PTS, 27, 28) + DEFINE_RW_SYMBOLIC_VALUE(DISABLE, 0) + DEFINE_RW_SYMBOLIC_VALUE(VCO, 1) + DEFINE_RW_SYMBOLIC_VALUE(FO, 2) + DEFINE_RW_SYMBOLIC_VALUE(FO_ICUSB, 3) + END_DEFINE_SYMBOLIC_FIELD(PLLU_PTS) + DEFINE_RW_FIELD(PLLU_KCP, 25, 26) + DEFINE_RW_FIELD(PLLU_KVCO, 24) + DEFINE_RW_FIELD(PLLU_SETUP, 0, 23) + END_DEFINE_REGISTER(PLLU_MISC_0) + + BEGIN_DEFINE_REGISTER(CLK_OUT_ENB_Y_0, uint32_t, 0x298) + DEFINE_RW_FIELD(CLK_ENB_USB2_TRK, 18) + END_DEFINE_REGISTER(CLK_OUT_ENB_Y_0) + + BEGIN_DEFINE_REGISTER(RST_DEVICES_W_0, uint32_t, 0x35c) + DEFINE_RW_FIELD(SWR_XUSB_SS_RST, 28) + DEFINE_RW_FIELD(SWR_DVFS_RST, 27) + DEFINE_RW_FIELD(SWR_ENTROPY_RST, 21) + DEFINE_RW_FIELD(SWR_XUSB_PADCTL_RST, 14) + DEFINE_RW_FIELD(SWR_RESERVED10_RST, 13) + DEFINE_RW_FIELD(SWR_RESERVED9_RST, 12) + // SWR_RESERVED8_RST conspicuously missing from TRM + DEFINE_RW_FIELD(SWR_RESERVED7_RST, 10) + DEFINE_RW_FIELD(SWR_RESERVED6_RST, 9) + DEFINE_RW_FIELD(SWR_CEC_RST, 8) + DEFINE_RW_FIELD(SWR_RESERVED5_RST, 7) + DEFINE_RW_FIELD(SWR_RESERVED4_RST, 6) + DEFINE_RW_FIELD(SWR_RESERVED3_RST, 5) + DEFINE_RW_FIELD(SWR_RESERVED2_RST, 4) + DEFINE_RW_FIELD(SWR_RESERVED1_RST, 3) + DEFINE_RW_FIELD(SWR_RESERVED0_RST, 2) + DEFINE_RW_FIELD(SWR_SATACOLD_RST, 1) + END_DEFINE_REGISTER(RST_DEVICES_W_0) + + BEGIN_DEFINE_REGISTER(CLK_OUT_ENB_W_0, uint32_t, 0x364) + DEFINE_RW_FIELD(CLK_ENB_XUSB_SS, 28) + DEFINE_RW_FIELD(CLK_ENB_XUSB, 15) + END_DEFINE_REGISTER(CLK_OUT_ENB_W_0) + + BEGIN_DEFINE_REGISTER(UTMIP_PLL_CFG0_0, uint32_t, 0x480) + DEFINE_RW_FIELD(UTMIP_PLL_VREG_CTRL, 27, 28) + DEFINE_RW_FIELD(UTMIP_PLL_KCP, 25, 26) + DEFINE_RW_FIELD(UTMIP_PLL_NDIV, 16, 23) + DEFINE_RW_FIELD(UTMIP_PLL_MDIV, 8, 15) + DEFINE_RW_FIELD(UTMIP_PLL_LOCK_OVR, 2) + DEFINE_RW_FIELD(UTMIP_PLL_RESERVED2, 1) + DEFINE_RW_FIELD(UTMIP_PLL_EN_LCKDET, 0) + END_DEFINE_REGISTER(UTMIP_PLL_CFG0_0) + + BEGIN_DEFINE_REGISTER(UTMIP_PLL_CFG1_0, uint32_t, 0x484) + DEFINE_RW_FIELD(UTMIP_PLLU_ENABLE_DLY_COUNT, 27, 31) + DEFINE_RW_FIELD(UTMIP_PLL_RSVD, 18, 26) + DEFINE_RW_FIELD(UTMIP_FORCE_PLLU_POWERUP, 17) + DEFINE_RW_FIELD(UTMIP_FORCE_PLLU_POWERDOWN, 16) + DEFINE_RW_FIELD(UTMIP_FORCE_PLL_ENABLE_POWERUP, 15) + DEFINE_RW_FIELD(UTMIP_FORCE_PLL_ENABLE_POWERDOWN, 14) + DEFINE_RW_FIELD(UTMIP_FORCE_PLL_ACTIVE_POWERUP, 13) + DEFINE_RW_FIELD(UTMIP_FORCE_PLL_ACTIVE_POWERDOWN, 12) + DEFINE_RW_FIELD(UTMIP_XTAL_FREQ_COUNT, 0, 11) + END_DEFINE_REGISTER(UTMIP_PLL_CFG1_0) + + BEGIN_DEFINE_REGISTER(UTMIP_PLL_CFG2_0, uint32_t, 0x488) + DEFINE_RW_FIELD(UTMIP_PHY_XTAL_CLOCKEN, 30) + DEFINE_RW_FIELD(UTMIP_FORCE_PD_RESERVED, 26, 29) + DEFINE_RW_FIELD(UTMIP_FORCE_PD_SAMP_D_POWERUP, 25) + DEFINE_RW_FIELD(UTMIP_FORCE_PD_SAMP_D_POWERDOWN, 24) + DEFINE_RW_FIELD(UTMIP_PLL_ACTIVE_DLY_COUNT, 18, 23) + DEFINE_RW_FIELD(UTMIP_PLLU_STABLE_COUNT, 6, 17) + DEFINE_RW_FIELD(UTMIP_FORCE_PD_SAMP_C_POWERUP, 5) + DEFINE_RW_FIELD(UTMIP_FORCE_PD_SAMP_C_POWERDOWN, 4) + DEFINE_RW_FIELD(UTMIP_FORCE_PD_SAMP_B_POWERUP, 3) + DEFINE_RW_FIELD(UTMIP_FORCE_PD_SAMP_B_POWERDOWN, 2) + DEFINE_RW_FIELD(UTMIP_FORCE_PD_SAMP_A_POWERUP, 1) + DEFINE_RW_FIELD(UTMIP_FORCE_PD_SAMP_A_POWERDOWN, 0) + END_DEFINE_REGISTER(UTMIP_PLL_CFG2_0) + + BEGIN_DEFINE_REGISTER(UTMIP_PLL_CFG3_0, uint32_t, 0x4c0) + DEFINE_RW_FIELD(UTMIP_PLL_REF_SRC_SEL, 26) + DEFINE_RW_FIELD(UTMIP_PLL_REF_DIS, 25) + DEFINE_RW_FIELD(UTMIP_PLL_PTS, 24) + DEFINE_RW_FIELD(UTMIP_PLL_SETUP, 0, 23) // "Debug control bits." + END_DEFINE_REGISTER(UTMIP_PLL_CFG3_0) + + BEGIN_DEFINE_REGISTER(UTMIPLL_HW_PWRDN_CFG0_0, uint32_t, 0x52c) + DEFINE_RO_FIELD(UTMIPLL_LOCK, 31) + DEFINE_RO_FIELD(UTMIPLL_SEQ_STATE, 26, 27) + DEFINE_RW_FIELD(UTMIPLL_SEQ_START_STATE, 25) + DEFINE_RW_FIELD(UTMIPLL_SEQ_ENABLE, 24) + DEFINE_RW_FIELD(UTMIPLL_IDDQ_PD_INCLUDE, 7) + DEFINE_RW_FIELD(UTMIPLL_USE_LOCKDET, 6) + DEFINE_RW_FIELD(UTMIPLL_SEQ_RESET_INPUT_VALUE, 5) + DEFINE_RW_FIELD(UTMIPLL_SEQ_IN_SWCTL, 4) + DEFINE_RW_FIELD(UTMIPLL_CLK_ENABLE_OVERRIDE_VALUE, 3) + DEFINE_RW_FIELD(UTMIPLL_CLK_ENABLE_SWCTL, 2) + DEFINE_RW_FIELD(UTMIPLL_IDDQ_OVERRIDE_VALUE, 1) + DEFINE_RW_FIELD(UTMIPLL_IDDQ_SWCTL, 0) + END_DEFINE_REGISTER(UTMIPLL_HW_PWRDN_CFG0_0) + + BEGIN_DEFINE_REGISTER(CLK_SOURCE_XUSB_FS_0, uint32_t, 0x608) + BEGIN_DEFINE_RW_SYMBOLIC_FIELD(XUSB_FS_CLK_SRC, 29, 31) + DEFINE_RW_SYMBOLIC_VALUE(CLK_M, 0) + DEFINE_RW_SYMBOLIC_VALUE(FO_48M, 2) + DEFINE_RW_SYMBOLIC_VALUE(PLLP_OUT0, 4) + DEFINE_RW_SYMBOLIC_VALUE(HSIC_480, 6) + END_DEFINE_SYMBOLIC_FIELD(XUSB_FS_CLK_SRC) + + DEFINE_RW_FIELD(XUSB_FS_CLK_DIVISOR, 0, 7) + END_DEFINE_REGISTER(CLK_SOURCE_XUSB_FS_0) + + BEGIN_DEFINE_REGISTER(CLK_SOURCE_XUSB_CORE_DEV_0, uint32_t, 0x60c) + BEGIN_DEFINE_RW_SYMBOLIC_FIELD(XUSB_CORE_DEV_CLK_SRC, 29, 31) + DEFINE_RW_SYMBOLIC_VALUE(CLK_M, 0) + DEFINE_RW_SYMBOLIC_VALUE(PLLP_OUT0, 1) + DEFINE_RW_SYMBOLIC_VALUE(PLLREFE_CLKOUT, 5) + END_DEFINE_SYMBOLIC_FIELD(XUSB_CORE_DEV_CLK_SRC) + + DEFINE_RW_FIELD(XUSB_CORE_DEV_CLK_DIVISOR, 0, 7) + END_DEFINE_REGISTER(CLK_SOURCE_XUSB_CORE_DEV_0) + + BEGIN_DEFINE_REGISTER(CLK_SOURCE_XUSB_SS_0, uint32_t, 0x610) + BEGIN_DEFINE_RW_SYMBOLIC_FIELD(XUSB_SS_CLK_SRC, 29, 31) + DEFINE_RW_SYMBOLIC_VALUE(CLK_M, 0) + DEFINE_RW_SYMBOLIC_VALUE(PLLREFE_CLKOUT, 1) + DEFINE_RW_SYMBOLIC_VALUE(CLK_S, 2) + DEFINE_RW_SYMBOLIC_VALUE(HSIC_480, 3) + END_DEFINE_SYMBOLIC_FIELD(XUSB_SS_CLK_SRC) + + DEFINE_RW_FIELD(XUSB_HS_HSICP_CLK_SEL, 26) + DEFINE_RW_FIELD(XUSB_HS_CLK_BYPASS_SWITCH, 25) + DEFINE_RW_FIELD(XUSB_SS_CLK_BYPASS_SWITCH, 24) + DEFINE_RW_FIELD(XUSB_SS_CLK_DIVISOR, 0, 7) + END_DEFINE_REGISTER(CLK_SOURCE_XUSB_SS_0) + + BEGIN_DEFINE_REGISTER(CLK_SOURCE_USB2_HSIC_TRK_0, uint32_t, 0x6cc) + DEFINE_RW_FIELD(USB2_HSIC_TRK_CLK_DIVISOR, 0, 7) + END_DEFINE_REGISTER(CLK_SOURCE_USB2_HSIC_TRK_0) + } CLK_RST_CONTROLLER; + +} // namespace t210 diff --git a/fusee/fusee-primary/src/reg/reg_fuse.h b/fusee/fusee-primary/src/reg/reg_fuse.h new file mode 100644 index 000000000..19cfe5135 --- /dev/null +++ b/fusee/fusee-primary/src/reg/reg_fuse.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "reg_util.h" + +namespace t210 { + + const struct FUSE { + static const uintptr_t base_addr = 0x7000f800; + using Peripheral = FUSE; + + BEGIN_DEFINE_REGISTER(SKU_USB_CALIB, uint32_t, 0x1f0) + DEFINE_RO_FIELD(TERM_RANGE_ADJ, 7, 10) // TODO: check? + DEFINE_RO_FIELD(HS_CURR_LEVEL, 0, 5) // TODO: check? + END_DEFINE_REGISTER(SKU_USB_CALIB) + + BEGIN_DEFINE_REGISTER(USB_CALIB_EXT, uint32_t, 0x350) + DEFINE_RO_FIELD(RPD_CTRL, 0, 5) + END_DEFINE_REGISTER(USB_CALIB_EXT) + + } FUSE; + +} // namespace t210 diff --git a/fusee/fusee-primary/src/reg/reg_util.h b/fusee/fusee-primary/src/reg/reg_util.h new file mode 100644 index 000000000..5071e99a8 --- /dev/null +++ b/fusee/fusee-primary/src/reg/reg_util.h @@ -0,0 +1,495 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include + +template +class RegisterAccessor { + public: + using Base = _Base; + static const uintptr_t offset = _offset; + + __attribute__((always_inline)) RegisterAccessor(const Peripheral &p) : + peripheral(p), + ptr(reinterpret_cast(p.base_addr + _offset)), + read_value(*ptr), + write_value(read_value) { + } + + __attribute__((always_inline)) RegisterAccessor(const Peripheral &p, Base value) : + peripheral(p), + ptr(reinterpret_cast(p.base_addr + _offset)), + read_value(value), + write_value(value) { + } + + __attribute__((always_inline)) const Peripheral &Commit() { + *ptr = write_value; + return peripheral; + } + + const Peripheral &peripheral; + volatile Base *ptr; + Base read_value; + Base write_value; +}; + +struct rw1c_marker {}; + +template +class BitArrayRegisterAccessor { + public: + using Base = _Base; + + __attribute__((always_inline)) BitArrayRegisterAccessor(const Peripheral &p, uintptr_t offset) : + peripheral(p), + ptr(reinterpret_cast(p.base_addr + offset)), + read_value(*ptr), + write_value(read_value) { + } + + __attribute__((always_inline)) BitArrayRegisterAccessor(const Peripheral &p, uintptr_t offset, Base value) : + peripheral(p), + ptr(reinterpret_cast(p.base_addr + offset)), + read_value(value), + write_value(value) { + } + + __attribute__((always_inline)) BitArrayRegisterAccessor(const Peripheral &p, uintptr_t offset, rw1c_marker _marker) : + peripheral(p), + ptr(reinterpret_cast(p.base_addr + offset)), + read_value(*ptr), + write_value(0) { + } + + class BitAccessor { + public: + __attribute__((always_inline)) BitAccessor(BitArrayRegisterAccessor ®, int idx) : + reg(reg), + idx(idx) { + } + + __attribute__((always_inline)) bool Get() const { + return (reg.read_value >> idx) & 1; + } + + __attribute__((always_inline)) BitArrayRegisterAccessor &Set(bool value) { + reg.write_value&= ~(1 << idx); + reg.write_value|= (value << idx); + return reg; + } + + __attribute__((always_inline)) explicit operator bool() const { + return Get(); + } + + __attribute__((always_inline)) BitArrayRegisterAccessor &operator=(bool value) { + return Set(value); + } + + __attribute__((always_inline)) BitArrayRegisterAccessor &Clear() { + return Set(1); + } + + private: + BitArrayRegisterAccessor ® + int idx; + }; + + __attribute__((always_inline)) BitAccessor operator[](int idx) { + return BitAccessor(*this, idx); + } + + __attribute__((always_inline)) const Peripheral &Commit() { + *ptr = write_value; + read_value = write_value; + return peripheral; + } + + const Peripheral &peripheral; + volatile Base *ptr; + Base read_value; + Base write_value; +}; + +// [low, high] is an inclusive range, since that's the way these things are written in the TRM +template +class RegisterFieldAccessorBase { + public: + __attribute__((always_inline)) RegisterFieldAccessorBase(RegAcc &acc) : acc(acc) { } + + protected: + __attribute__((always_inline)) Type Get() { + return Type(((this->acc.read_value & mask) >> low) << internal_shift); + } + + __attribute__((always_inline)) RegAcc &Set(Type value) { + this->acc.write_value&= ~mask; + this->acc.write_value|= ((typename RegAcc::Base(value) >> internal_shift) << low) & mask; + return acc; + } + + public: + static const typename RegAcc::Base mask = ((typename RegAcc::Base(2) << (high-low)) - 1) << low; + protected: + using FieldType = Type; + using RegisterAccessor = RegAcc; + RegAcc &acc; +}; + +// base accessors + +template +class RegisterRWFieldAccessorBase : public RegisterFieldAccessorBase { + public: + __attribute__((always_inline)) RegisterRWFieldAccessorBase(RegAcc &acc) : RegisterFieldAccessorBase(acc) { + } + + __attribute__((always_inline)) Type Get() { + return RegisterFieldAccessorBase::Get(); + } + + __attribute__((always_inline)) explicit operator Type() { + return Get(); + } + + __attribute__((always_inline)) RegAcc &Set(Type value) { + return RegisterFieldAccessorBase::Set(value); + } +}; + +template +class RegisterROFieldAccessorBase : public RegisterFieldAccessorBase { + public: + __attribute__((always_inline)) RegisterROFieldAccessorBase(RegAcc &acc) : RegisterFieldAccessorBase(acc) { + } + + __attribute__((always_inline)) Type Get() { + return RegisterFieldAccessorBase::Get(); + } + + __attribute__((always_inline)) explicit operator Type() { + return Get(); + } +}; + +template +class RegisterWOFieldAccessorBase : public RegisterFieldAccessorBase { + public: + __attribute__((always_inline)) RegisterWOFieldAccessorBase(RegAcc &acc) : RegisterFieldAccessorBase(acc) { + } + + __attribute__((always_inline)) RegAcc &Set(Type value) { + return RegisterFieldAccessorBase::Set(value); + } +}; + +// generic accessors + +template +class RegisterRWFieldAccessor : public RegisterRWFieldAccessorBase { + public: + __attribute__((always_inline)) RegisterRWFieldAccessor(RegAcc &acc) : RegisterRWFieldAccessorBase(acc) { + } +}; + +template +class RegisterROFieldAccessor : public RegisterROFieldAccessorBase { + public: + __attribute__((always_inline)) RegisterROFieldAccessor(RegAcc &acc) : RegisterROFieldAccessorBase(acc) { + } +}; + +template +class RegisterWOFieldAccessor : public RegisterWOFieldAccessorBase { + public: + __attribute__((always_inline)) RegisterWOFieldAccessor(RegAcc &acc) : RegisterWOFieldAccessorBase(acc) { + } +}; + +// unusual accessors + +template +class RegisterRW1CFieldAccessor : public RegisterFieldAccessorBase { + public: + __attribute__((always_inline)) RegisterRW1CFieldAccessor(RegAcc &acc) : RegisterFieldAccessorBase(acc) { + acc.write_value&= ~RegisterFieldAccessorBase::mask; + } + + __attribute__((always_inline)) bool Get() { + return RegisterFieldAccessorBase::Get(); + } + + __attribute__((always_inline)) operator bool() { + return RegisterFieldAccessorBase::Get(); + } + + __attribute__((always_inline)) RegAcc &Clear() { + return RegisterFieldAccessorBase::Set(true); + } +}; + +// templated-specialized accessors + +template +class RegisterRWFieldAccessor : public RegisterRWFieldAccessorBase { + public: + __attribute__((always_inline)) RegisterRWFieldAccessor(RegAcc &acc) : RegisterRWFieldAccessorBase(acc) { + } + + __attribute__((always_inline)) RegAcc &Disable() { + return RegisterFieldAccessorBase::Set(false); + } + + __attribute__((always_inline)) RegAcc &Enable() { + return RegisterFieldAccessorBase::Set(true); + } + + __attribute__((always_inline)) operator bool() { + return RegisterFieldAccessorBase::Get(); + } +}; + +template +class RegisterROFieldAccessor : public RegisterROFieldAccessorBase { + public: + __attribute__((always_inline)) RegisterROFieldAccessor(RegAcc &acc) : RegisterROFieldAccessorBase(acc) { + } + + __attribute__((always_inline)) operator bool() { + return RegisterROFieldAccessorBase::Get(); + } +}; + +#define BEGIN_DEFINE_REGISTER(name, type, offset) \ + class name##_Accessor : public RegisterAccessor { \ + public: \ + using RegSelf = name##_Accessor; \ + __attribute__((always_inline)) name##_Accessor(const Peripheral &p) : RegisterAccessor(p) {} \ + __attribute__((always_inline)) name##_Accessor(const Peripheral &p, type value) : RegisterAccessor(p, value) {} + +#define BEGIN_DEFINE_WO_REGISTER(name, type, offset) \ + class name##_Accessor : public RegisterAccessor { \ + public: \ + using RegSelf = name##_Accessor; \ + __attribute__((always_inline)) name##_Accessor(const Peripheral &p) : RegisterAccessor(p, 0) {} + +#define DEFINE_RW_FIELD(name, ...) \ + RegisterRWFieldAccessor name = RegisterRWFieldAccessor(*this); + +#define DEFINE_RO_FIELD(name, ...) \ + RegisterROFieldAccessor name = RegisterROFieldAccessor(*this); + +#define DEFINE_WO_FIELD(name, ...) \ + RegisterWOFieldAccessor name = RegisterWOFieldAccessor(*this); + +#define DEFINE_RW1C_FIELD(name, ...) \ + RegisterRW1CFieldAccessor name = RegisterRW1CFieldAccessor(*this); + + +#define BEGIN_DEFINE_RO_SYMBOLIC_FIELD(name, low, high) \ + struct name##_Accessor : public RegisterROFieldAccessor {\ + using SymSelf = name##_Accessor; \ + __attribute__((always_inline)) name##_Accessor(RegSelf &acc) : RegisterROFieldAccessor(acc) { } + +#define BEGIN_DEFINE_RW_SYMBOLIC_FIELD(name, low, high) \ + struct name##_Accessor : public RegisterRWFieldAccessor {\ + using SymSelf = name##_Accessor; \ + __attribute__((always_inline)) name##_Accessor(RegSelf &acc) : RegisterRWFieldAccessor(acc) { } + +#define DEFINE_RO_SYMBOLIC_VALUE(name, value) \ + __attribute__((always_inline)) bool Is##name() { \ + return Get() == typename SymSelf::FieldType(value); \ + } + +#define DEFINE_RW_SYMBOLIC_VALUE(name, value) \ + __attribute__((always_inline)) RegisterAccessor &Set##name() {\ + return Set(typename SymSelf::FieldType(value)); \ + } \ + __attribute__((always_inline)) bool Is##name() { \ + return Get() == typename SymSelf::FieldType(value); \ + } + +#define END_DEFINE_SYMBOLIC_FIELD(name) \ + } name = name##_Accessor(*this); + +#define END_DEFINE_REGISTER(name) \ + }; \ + __attribute__((always_inline)) name##_Accessor name() const { \ + return name##_Accessor(*this); \ + } \ + __attribute__((always_inline)) name##_Accessor name(name##_Accessor::Base value) const { \ + return name##_Accessor(*this, value); \ + } + +#define END_DEFINE_WO_REGISTER(name) \ + }; \ + __attribute__((always_inline)) name##_Accessor name() const { \ + return name##_Accessor(*this); \ + } + +#define DEFINE_BIT_ARRAY_REGISTER(name, type, offset) \ + __attribute__((always_inline)) BitArrayRegisterAccessor name() const { \ + return BitArrayRegisterAccessor(*this, offset); \ + } \ + __attribute__((always_inline)) BitArrayRegisterAccessor name(type value) const { \ + return BitArrayRegisterAccessor(*this, offset, value); \ + } + +#define DEFINE_RW1C_BIT_ARRAY_REGISTER(name, type, offset) \ + __attribute__((always_inline)) BitArrayRegisterAccessor name() const { \ + return BitArrayRegisterAccessor(*this, offset, rw1c_marker {}); \ + } + +#if (0) +/* + This can look a little obtuse, so here's an example of how it's meant to be used: + */ + +namespace t210 { + +const struct T_XUSB_DEV_XHCI { + /* This field needs to have this name and this type, since it is used by the *_DEFINE_REGISTER macros. */ + /* However, it is allowed to be non-static, in case you have multiple instances of the same kind of peripheral. */ + static const uintptr_t base_addr = 0x7000d000; + + /* This using needs to have this name, since it is used by the macros. */ + using Peripheral = T_XUSB_DEV_XHCI; + + /* You can use your own types with register fields, as long as they can be casted to and from integer types. */ + enum class LinkSpeed { + /* ... */ + }; + + /* BEGIN_DEFINE_REGISTER(name, base type, offset from periphal base) + Defines a register. */ + BEGIN_DEFINE_REGISTER(EREPLO, uint32_t, 0x28) + /* DEFINE_RW_FIELD(name, lo, [hi], [type], [internal shift]) + Internal shift is used for fields that store values without low bits. + Here, we want to be able to access a pointer directly, but the low 4 bits + belong to a different field, so the internal shift automatically shifts + reads and writes for us. */ + DEFINE_RW_FIELD(ADDRLO, 4, 31, uintptr_t, 4) + DEFINE_RW_FIELD(SEGI, 1) + DEFINE_RW_FIELD(ECS, 0) + END_DEFINE_REGISTER(EREPLO) + + BEGIN_DEFINE_REGISTER(PORTSC, uint32_t, 0x3c) + /* DEFINE_RO_FIELD(name, lo, [hi], [base type], [internal shift]) + Same as DEFINE_RW_FIELD except it creates a different accessor type + that does not have methods for writing. */ + DEFINE_RO_FIELD(WPR, 30) + DEFINE_RO_FIELD(RSVD4, 28, 29) + + /* DEFINE_RW1C_FIELD(name, lo) + Defines a bit that is cleared when 1 is written to it, so we + need to be sure to not write it back if we don't mean to. Will not + write back unless you call .Clear() on it. + + Forced to be a single-bit boolean type. */ + DEFINE_RW1C_FIELD(CEC, 23) + DEFINE_RW1C_FIELD(PLC, 22) + DEFINE_RW1C_FIELD(PRC, 21) + DEFINE_RO_FIELD(RSVD3, 20) + DEFINE_RW1C_FIELD(WRC, 19) + DEFINE_RW1C_FIELD(CSC, 17) + DEFINE_RW_FIELD(LWS, 16) + DEFINE_RO_FIELD(RSVD2, 15) + + /* BEGIN_DEFINE_RO_SYMBOLIC_FIELD(name, lo, hi) + Defines an enumerated field without writer accessor methods. */ + BEGIN_DEFINE_RO_SYMBOLIC_FIELD(PS, 10, 13) + /* Defines an enumerated value without writer accessor methods. */ + DEFINE_RO_SYMBOLIC_VALUE(UNDEFINED, 0) + DEFINE_RO_SYMBOLIC_VALUE(FS, 1) + DEFINE_RO_SYMBOLIC_VALUE(LS, 2) + DEFINE_RO_SYMBOLIC_VALUE(HS, 3) + DEFINE_RO_SYMBOLIC_VALUE(SS, 4) + END_DEFINE_SYMBOLIC_FIELD(PS) + + DEFINE_RO_FIELD(LANE_POLARITY_VALUE, 9) + + BEGIN_DEFINE_RW_SYMBOLIC_FIELD(PLS, 5, 8) + DEFINE_RW_SYMBOLIC_VALUE(U0, 0) + DEFINE_RW_SYMBOLIC_VALUE(U1, 1) + DEFINE_RW_SYMBOLIC_VALUE(U2, 2) + DEFINE_RW_SYMBOLIC_VALUE(U3, 3) + DEFINE_RW_SYMBOLIC_VALUE(DISABLED, 4) + DEFINE_RW_SYMBOLIC_VALUE(RXDETECT, 5) + DEFINE_RW_SYMBOLIC_VALUE(INACTIVE, 6) + DEFINE_RW_SYMBOLIC_VALUE(POLLING, 7) + DEFINE_RW_SYMBOLIC_VALUE(RECOVERY, 8) + DEFINE_RW_SYMBOLIC_VALUE(HOTRESET, 9) + DEFINE_RW_SYMBOLIC_VALUE(COMPLIANCE, 0xa) + DEFINE_RW_SYMBOLIC_VALUE(LOOPBACK, 0xb) + DEFINE_RW_SYMBOLIC_VALUE(RESUME, 0xf) + END_DEFINE_SYMBOLIC_FIELD(PLS) + + DEFINE_RO_FIELD(PR, 4) + DEFINE_RW_FIELD(LANE_POLARITY_OVRD_VALUE, 3) + DEFINE_RW_FIELD(LANE_POLARITY_OVRD, 2) + DEFINE_RW_FIELD(PED, 1) + DEFINE_RW_FIELD(CCS, 0) + END_DEFINE_REGISTER(PORTSC) +} T_XUSB_DEV_XHCI; + +void chaining_example() { + t210::CLK_RST_CONTROLLER + .CLK_OUT_ENB_W_0() + .CLK_ENB_XUSB().Enable() + .Commit() + .RST_DEVICES_W_0() + .SWR_XUSB_SS_RST().Set(0) + .Commit(); + + t210::XUSB_PADCTL + .USB2_PAD_MUX_0() + .USB2_OTG_PAD_PORT0().SetXUSB() + .USB2_BIAS_PAD().SetXUSB() + .Commit(); +} + + void rw1c_example() { + auto portsc = t210::XUSB_DEV.PORTSC(); + + if (portsc.CEC) { + portsc.CEC.Clear(); + } + + portsc.PED.Set(true); + + portsc.Commit(); +} + +uintptr_t internal_shift_example() { + t210::XUSB_DEV + .EREPLO() + .ADDRLO.Set((uintptr_t) &internal_shift_example) + .Commit(); + + return t210::XUSB_DEV.EREPLO().ADDRLO.Get(); +} + +void no_read_example() { + t210::XUSB_DEV + .EREPLO(0) // does not read from register- instead assumes it contains 0 + .ADDRLO.Set((uintptr_t) &no_read_example) + .Commit(); +} + +#endif diff --git a/fusee/fusee-primary/src/reg/reg_xusb_dev.h b/fusee/fusee-primary/src/reg/reg_xusb_dev.h new file mode 100644 index 000000000..0df58679d --- /dev/null +++ b/fusee/fusee-primary/src/reg/reg_xusb_dev.h @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "reg_util.h" + +namespace t210 { + static const uintptr_t XUSB_DEV_BASE = 0x700d0000; + + const struct T_XUSB_DEV_XHCI { + static const uintptr_t base_addr = XUSB_DEV_BASE; + using Peripheral = T_XUSB_DEV_XHCI; + + BEGIN_DEFINE_WO_REGISTER(DB, uint32_t, 0x4) + DEFINE_WO_FIELD(STREAMID, 16, 31) + DEFINE_WO_FIELD(TARGET, 8, 15) + END_DEFINE_WO_REGISTER(DB) + + BEGIN_DEFINE_REGISTER(ERSTSZ, uint32_t, 0x8) + DEFINE_RW_FIELD(ERST1SZ, 16, 31) + DEFINE_RW_FIELD(ERST0SZ, 0, 15) + END_DEFINE_REGISTER(ERSTSZ) + + BEGIN_DEFINE_REGISTER(ERST0BALO, uint32_t, 0x10) + DEFINE_RW_FIELD(ADDRLO, 4, 31, uintptr_t, 4) + END_DEFINE_REGISTER(ERST0BALO) + + BEGIN_DEFINE_REGISTER(ERST0BAHI, uint32_t, 0x14) + DEFINE_RW_FIELD(ADDRHI, 0, 31) + END_DEFINE_REGISTER(ERST0BAHI) + + BEGIN_DEFINE_REGISTER(ERST1BALO, uint32_t, 0x18) + DEFINE_RW_FIELD(ADDRLO, 4, 31, uintptr_t, 4) + END_DEFINE_REGISTER(ERST1BALO) + + BEGIN_DEFINE_REGISTER(ERST1BAHI, uint32_t, 0x1c) + DEFINE_RW_FIELD(ADDRHI, 0, 31) + END_DEFINE_REGISTER(ERST1BAHI) + + BEGIN_DEFINE_REGISTER(ERDPLO, uint32_t, 0x20) + DEFINE_RW_FIELD(ADDRLO, 4, 31, uintptr_t, 4) + DEFINE_RW1C_FIELD(EHB, 3) + END_DEFINE_REGISTER(ERDPLO) + + BEGIN_DEFINE_REGISTER(ERDPHI, uint32_t, 0x24) + DEFINE_RW_FIELD(ADDRHI, 0, 31) + END_DEFINE_REGISTER(ERDPHI) + + BEGIN_DEFINE_REGISTER(EREPLO, uint32_t, 0x28) + DEFINE_RW_FIELD(ADDRLO, 4, 31, uintptr_t, 4) + DEFINE_RW_FIELD(SEGI, 1) + DEFINE_RW_FIELD(ECS, 0) + END_DEFINE_REGISTER(EREPLO) + + BEGIN_DEFINE_REGISTER(EREPHI, uint32_t, 0x2c) + DEFINE_RW_FIELD(ADDRHI, 0, 31) + END_DEFINE_REGISTER(EREPHI) + + BEGIN_DEFINE_REGISTER(CTRL, uint32_t, 0x30) + DEFINE_RW_FIELD(ENABLE, 31) + DEFINE_RW_FIELD(DEVADR, 24, 30) + DEFINE_RW_FIELD(RSVD0, 8, 23) + DEFINE_RW_FIELD(EWE, 7) + DEFINE_RW_FIELD(SMI_DSE, 6) + DEFINE_RW_FIELD(SMI_EVT, 5) + DEFINE_RW_FIELD(IE, 4) + DEFINE_RW_FIELD(RSVD1, 2, 3) + DEFINE_RW_FIELD(LSE, 1) + DEFINE_RW_FIELD(RUN, 0) + END_DEFINE_REGISTER(CTRL) + + BEGIN_DEFINE_REGISTER(ST, uint32_t, 0x34) + DEFINE_RO_FIELD(RSVD1, 6, 1) + DEFINE_RW1C_FIELD(DSE, 5) + DEFINE_RW1C_FIELD(IP, 4) + DEFINE_RO_FIELD(RSVD0, 1, 3) + DEFINE_RW1C_FIELD(RC, 0) + END_DEFINE_REGISTER(ST) + + BEGIN_DEFINE_REGISTER(PORTSC, uint32_t, 0x3c) + DEFINE_RW_FIELD(WPR, 30) + DEFINE_RW_FIELD(RSVD4, 28, 29) + DEFINE_RW1C_FIELD(CEC, 23) + DEFINE_RW1C_FIELD(PLC, 22) + DEFINE_RW1C_FIELD(PRC, 21) + DEFINE_RW_FIELD(RSVD3, 20) + DEFINE_RW1C_FIELD(WRC, 19) + DEFINE_RW1C_FIELD(CSC, 17) + DEFINE_RW_FIELD(LWS, 16) + DEFINE_RW_FIELD(RSVD2, 15) + BEGIN_DEFINE_RO_SYMBOLIC_FIELD(PS, 10, 13) + DEFINE_RO_SYMBOLIC_VALUE(UNDEFINED, 0) + DEFINE_RO_SYMBOLIC_VALUE(FS, 1) + DEFINE_RO_SYMBOLIC_VALUE(LS, 2) + DEFINE_RO_SYMBOLIC_VALUE(HS, 3) + DEFINE_RO_SYMBOLIC_VALUE(SS, 4) + END_DEFINE_SYMBOLIC_FIELD(PS) + DEFINE_RO_FIELD(LANE_POLARITY_VALUE, 9) + BEGIN_DEFINE_RW_SYMBOLIC_FIELD(PLS, 5, 8) + DEFINE_RW_SYMBOLIC_VALUE(U0, 0) + DEFINE_RW_SYMBOLIC_VALUE(U1, 1) + DEFINE_RW_SYMBOLIC_VALUE(U2, 2) + DEFINE_RW_SYMBOLIC_VALUE(U3, 3) + DEFINE_RW_SYMBOLIC_VALUE(DISABLED, 4) + DEFINE_RW_SYMBOLIC_VALUE(RXDETECT, 5) + DEFINE_RW_SYMBOLIC_VALUE(INACTIVE, 6) + DEFINE_RW_SYMBOLIC_VALUE(POLLING, 7) + DEFINE_RW_SYMBOLIC_VALUE(RECOVERY, 8) + DEFINE_RW_SYMBOLIC_VALUE(HOTRESET, 9) + DEFINE_RW_SYMBOLIC_VALUE(COMPLIANCE, 0xa) + DEFINE_RW_SYMBOLIC_VALUE(LOOPBACK, 0xb) + DEFINE_RW_SYMBOLIC_VALUE(RESUME, 0xf) + END_DEFINE_SYMBOLIC_FIELD(PLS) + DEFINE_RO_FIELD(PR, 4) + DEFINE_RW_FIELD(LANE_POLARITY_OVRD_VALUE, 3) + DEFINE_RW_FIELD(LANE_POLARITY_OVRD, 2) + DEFINE_RW1C_FIELD(PED, 1) // TODO: is this rw1c? + DEFINE_RW_FIELD(CCS, 0) + END_DEFINE_REGISTER(PORTSC) + + BEGIN_DEFINE_REGISTER(ECPLO, uint32_t, 0x40) + DEFINE_RW_FIELD(ADDRLO, 6, 31, uintptr_t, 6) + END_DEFINE_REGISTER(ECPLO) + + BEGIN_DEFINE_REGISTER(ECPHI, uint32_t, 0x44) + DEFINE_RW_FIELD(ADDRHI, 0, 31) + END_DEFINE_REGISTER(ECPHI) + + DEFINE_BIT_ARRAY_REGISTER(EP_HALT, uint32_t, 0x50) + DEFINE_BIT_ARRAY_REGISTER(EP_PAUSE, uint32_t, 0x54) + DEFINE_BIT_ARRAY_REGISTER(EP_RELOAD, uint32_t, 0x58) + DEFINE_RW1C_BIT_ARRAY_REGISTER(EP_STCHG, uint32_t, 0x5c) + + BEGIN_DEFINE_REGISTER(PORTHALT, uint32_t, 0x6c) + // TRM labels both bit 26 and bit 20 as STCHG_REQ, but Linux kernel sources disambiguate it. + // https://patchwork.kernel.org/patch/11129617/ + //DEFINE_RW_FIELD(???, 26) + DEFINE_RW_FIELD(STCHG_PME_EN, 25) + DEFINE_RW_FIELD(STCHG_INTR_EN, 24) + DEFINE_RW1C_FIELD(STCHG_REQ, 20) + DEFINE_RW_FIELD(STCHG_STATE, 16, 19) + DEFINE_RW1C_FIELD(HALT_REJECT, 1) // TODO: RW1C semantics + DEFINE_RW_FIELD(HALT_LTSSM, 0) + END_DEFINE_REGISTER(PORTHALT) + + DEFINE_RW1C_BIT_ARRAY_REGISTER(EP_STOPPED, uint32_t, 0x78) + + BEGIN_DEFINE_REGISTER(CFG_DEV_FE, uint32_t, 0x85c) + // "Note: Do not change the values in bits 31:2" + DEFINE_RW_FIELD(INFINITE_SS_RETRY, 29) + DEFINE_RW_FIELD(EN_PRIME_EVENT, 28) + DEFINE_RW_FIELD(FEATURE_LPM, 27) + DEFINE_RW_FIELD(EN_STALL_EVENT, 26) + DEFINE_RW_FIELD(PORTDISCON_RST_HW, 25) + DEFINE_RW_FIELD(CTX_RESTORE, 24) + DEFINE_RW_FIELD(MFCOUNT_MIN, 4, 23) + DEFINE_RW_FIELD(PORTRST_HW, 3) + DEFINE_RW_FIELD(SEQNUM_INIT, 2) + BEGIN_DEFINE_RW_SYMBOLIC_FIELD(PORTREGSEL, 0, 1) + DEFINE_RW_SYMBOLIC_VALUE(INIT, 0) + DEFINE_RW_SYMBOLIC_VALUE(SS, 1) + DEFINE_RW_SYMBOLIC_VALUE(HSFS, 2) + END_DEFINE_SYMBOLIC_FIELD(PORTREGSEL) + END_DEFINE_REGISTER(CFG_DEV_FE) + } T_XUSB_DEV_XHCI; + + const struct T_XUSB_DEV { + static const uintptr_t base_addr = XUSB_DEV_BASE + 0x8000; + using Peripheral = T_XUSB_DEV; + + BEGIN_DEFINE_REGISTER(CFG_1, uint32_t, 0x4) + DEFINE_RW_FIELD(IO_SPACE, 0) + DEFINE_RW_FIELD(MEMORY_SPACE, 1) + DEFINE_RW_FIELD(BUS_MASTER, 2) + END_DEFINE_REGISTER(CFG_1) + + BEGIN_DEFINE_REGISTER(CFG_4, uint32_t, 0x10) + DEFINE_RW_FIELD(BASE_ADDRESS, 15, 31, uintptr_t, 15) + BEGIN_DEFINE_RW_SYMBOLIC_FIELD(PREFETCHABLE, 3, 3) + DEFINE_RW_SYMBOLIC_VALUE(NOT, 0) + DEFINE_RW_SYMBOLIC_VALUE(MERGABLE, 1) + END_DEFINE_SYMBOLIC_FIELD(PREFETCHABLE) + BEGIN_DEFINE_RW_SYMBOLIC_FIELD(ADDRESS_TYPE, 1, 2) + DEFINE_RW_SYMBOLIC_VALUE(32_BIT, 0) + DEFINE_RW_SYMBOLIC_VALUE(64_BIT, 2) + END_DEFINE_SYMBOLIC_FIELD(ADDRESS_TYPE) + BEGIN_DEFINE_RW_SYMBOLIC_FIELD(SPACE_TYPE, 0, 0) + DEFINE_RW_SYMBOLIC_VALUE(MEMORY, 0) + DEFINE_RW_SYMBOLIC_VALUE(IO, 1) + END_DEFINE_SYMBOLIC_FIELD(SPACE_TYPE) + END_DEFINE_REGISTER(CFG_4) + } T_XUSB_DEV; + + const struct XUSB_DEV { + static const uintptr_t base_addr = XUSB_DEV_BASE + 0x9000; + using Peripheral = XUSB_DEV; + + BEGIN_DEFINE_REGISTER(CONFIGURATION_0, uint32_t, 0x180) + DEFINE_RW_FIELD(EN_FPCI, 0) + END_DEFINE_REGISTER(CONFIGURATION_0) + + BEGIN_DEFINE_REGISTER(INTR_MASK_0, uint32_t, 0x188) + DEFINE_RW_FIELD(IP_INT_MASK, 16) + DEFINE_RW_FIELD(MSI_MASK, 8) + DEFINE_RW_FIELD(INT_MASK, 0) + END_DEFINE_REGISTER(INTR_MASK_0) + } XUSB_DEV; +} // namespace t210 diff --git a/fusee/fusee-primary/src/reg/reg_xusb_padctl.h b/fusee/fusee-primary/src/reg/reg_xusb_padctl.h new file mode 100644 index 000000000..d0781f171 --- /dev/null +++ b/fusee/fusee-primary/src/reg/reg_xusb_padctl.h @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "reg_util.h" + +namespace t210 { + + const struct XUSB_PADCTL { + static const uintptr_t base_addr = 0x7009f000; + using Peripheral = XUSB_PADCTL; + + BEGIN_DEFINE_REGISTER(USB2_PAD_MUX_0, uint32_t, 0x4) + BEGIN_DEFINE_RW_SYMBOLIC_FIELD(USB2_OTG_PAD_PORT0, 0, 1) + DEFINE_RW_SYMBOLIC_VALUE(SNPS, 0) + DEFINE_RW_SYMBOLIC_VALUE(XUSB, 1) + DEFINE_RW_SYMBOLIC_VALUE(UART, 2) + END_DEFINE_SYMBOLIC_FIELD(USB2_OTG_PAD_PORT0) + + BEGIN_DEFINE_RW_SYMBOLIC_FIELD(USB2_BIAS_PAD, 18, 19) + DEFINE_RW_SYMBOLIC_VALUE(SNPS, 0) + DEFINE_RW_SYMBOLIC_VALUE(XUSB, 1) + DEFINE_RW_SYMBOLIC_VALUE(UART, 2) + END_DEFINE_SYMBOLIC_FIELD(USB2_BIAS_PAD) + END_DEFINE_REGISTER(USB2_PAD_MUX_0) + + BEGIN_DEFINE_REGISTER(USB2_PORT_CAP_0, uint32_t, 0x8) + BEGIN_DEFINE_RW_SYMBOLIC_FIELD(PORT0_CAP, 0, 1) + DEFINE_RW_SYMBOLIC_VALUE(Disabled, 0) + DEFINE_RW_SYMBOLIC_VALUE(HostOnly, 1) + DEFINE_RW_SYMBOLIC_VALUE(DeviceOnly, 2) + DEFINE_RW_SYMBOLIC_VALUE(OtgCap, 3) + END_DEFINE_SYMBOLIC_FIELD(PORT0_CAP) + END_DEFINE_REGISTER(USB2_PORT_CAP_0) + + BEGIN_DEFINE_REGISTER(SS_PORT_MAP_0, uint32_t, 0x14) + BEGIN_DEFINE_RW_SYMBOLIC_FIELD(PORT0_MAP, 0, 3) + DEFINE_RW_SYMBOLIC_VALUE(USB2Port0, 0) + DEFINE_RW_SYMBOLIC_VALUE(USB2Port1, 1) + DEFINE_RW_SYMBOLIC_VALUE(USB2Port2, 2) + DEFINE_RW_SYMBOLIC_VALUE(USB2Port3, 3) + DEFINE_RW_SYMBOLIC_VALUE(InitDisabled, 7) + END_DEFINE_SYMBOLIC_FIELD(PORT0_MAP) + END_DEFINE_REGISTER(SS_PORT_MAP_0) + + BEGIN_DEFINE_REGISTER(ELPG_PROGRAM_0_0, uint32_t, 0x20) + END_DEFINE_REGISTER(ELPG_PROGRAM_0_0) + + BEGIN_DEFINE_REGISTER(ELPG_PROGRAM_1_0, uint32_t, 0x24) + END_DEFINE_REGISTER(ELPG_PROGRAM_1_0) + + BEGIN_DEFINE_REGISTER(USB2_BATTERY_CHRG_OTGPAD0_CTL0_0, uint32_t, 0x80) + DEFINE_RW_FIELD(GENERATE_SRP, 31) + DEFINE_RW_FIELD(SRP_INTR_EN, 30) + DEFINE_RW_FIELD(SRP_DETECTED, 29) + DEFINE_RW_FIELD(SRP_DETECT_EN, 28) + DEFINE_RW_FIELD(DCD_INTR_EN, 27) + DEFINE_RW_FIELD(DCD_DETECTED, 26) + DEFINE_RW_FIELD(ZIN_FILTER_EN, 25) + DEFINE_RW_FIELD(ZIN_CHNG_INTR_EN, 24) + DEFINE_RW_FIELD(ZIN_ST_CHNG, 23) + DEFINE_RO_FIELD(ZIN, 22) + DEFINE_RW_FIELD(ZIP_FILTER_EN, 21) + DEFINE_RW_FIELD(ZIP_CHNG_INTER_EN, 20) + DEFINE_RW_FIELD(ZIP_ST_CHNG, 19) + DEFINE_RO_FIELD(ZIP, 18) + DEFINE_RW_FIELD(OP_I_SRC_EN, 13) + DEFINE_RW_FIELD(ON_SRC_EN, 12) + DEFINE_RW_FIELD(ON_SINK_EN, 11) + DEFINE_RW_FIELD(OP_SRV_EN, 10) + DEFINE_RW_FIELD(OP_SINK_EN, 9) + DEFINE_RW_FIELD(VDAT_DET_FILTER_EN, 8) + DEFINE_RW_FIELD(VDAT_DET_CHNG_INTER_EN, 7) + DEFINE_RW_FIELD(VDAT_DET_ST_CHNG, 6) + DEFINE_RO_FIELD(VDAT_DET, 5) + DEFINE_RW_FIELD(VDCD_DET_FILTER_EN, 4) + DEFINE_RW_FIELD(VDCD_DET_CHNG_INTR_EN, 3) + DEFINE_RW_FIELD(VDCD_DET_ST_CHNG, 2) + DEFINE_RO_FIELD(VDCD_DET, 1) + DEFINE_RW_FIELD(PD_CHG, 0) + END_DEFINE_REGISTER(USB2_BATTERY_CHRG_OTGPAD0_CTL0_0) + + BEGIN_DEFINE_REGISTER(USB2_BATTERY_CHRG_OTGPAD0_CTL1_0, uint32_t, 0x84) + DEFINE_RW_FIELD(USBON_RPU_OVRD_VAL, 23) + DEFINE_RW_FIELD(USBON_RPU_OVRD, 22) + DEFINE_RW_FIELD(USBON_RPD_OVRD_VAL, 21) + DEFINE_RW_FIELD(USBON_RPD_OVRD, 20) + DEFINE_RW_FIELD(USBOP_RPU_OVRD_VAL, 19) + DEFINE_RW_FIELD(USBOP_RPU_OVRD, 18) + DEFINE_RW_FIELD(USBOP_RPD_OVRD_VAL, 17) + DEFINE_RW_FIELD(USBOP_RPD_OVRD, 16) + DEFINE_RW_FIELD(VREG_DYN_DLY, 9, 10) + DEFINE_RW_FIELD(VREG_LEV, 7, 8) + DEFINE_RW_FIELD(VREG_FIX18, 6) + DEFINE_RW_FIELD(DIV_DET_EN, 4) + DEFINE_RO_FIELD(VOP_DIV2P7_DET, 3) + DEFINE_RO_FIELD(VOP_DIV2P0_DET, 2) + DEFINE_RO_FIELD(VON_DIV2P7_DET, 1) + DEFINE_RO_FIELD(VON_DIV2P0_DET, 0) + END_DEFINE_REGISTER(USB2_BATTERY_CHRG_OTGPAD0_CTL1_0) + + BEGIN_DEFINE_REGISTER(USB2_OTG_PAD0_CTL_0_0, uint32_t, 0x88) + DEFINE_RW_FIELD(PD_ZI, 29) + DEFINE_RW_FIELD(PD2_OVRD_EN, 28) + DEFINE_RW_FIELD(PD2, 27) + DEFINE_RW_FIELD(PD, 26) + DEFINE_RW_FIELD(TERM_SEL, 25) + DEFINE_RW_FIELD(LS_FSLEW, 21, 24) + DEFINE_RW_FIELD(LS_RSLEW, 17, 20) + DEFINE_RW_FIELD(FS_FSLEW, 13, 16) + DEFINE_RW_FIELD(FS_RSLEW, 9, 12) + DEFINE_RW_FIELD(HS_SLEW, 6, 8) + DEFINE_RW_FIELD(HS_CURR_LEVEL, 0, 5) + END_DEFINE_REGISTER(USB2_OTG_PAD0_CTL_0_0) + + BEGIN_DEFINE_REGISTER(USB2_OTG_PAD0_CTL_1_0, uint32_t, 0x8c) + DEFINE_RW_FIELD(RPD_CTRL, 26, 30) + DEFINE_RO_FIELD(RPU_STATUS_HIGH, 25) + DEFINE_RW_FIELD(RPU_SWITCH_LOW, 24) + DEFINE_RW_FIELD(RPU_SWITCH_OVRD, 23) + DEFINE_RW_FIELD(HS_LOOPBACK_OVRD_VAL, 22) + DEFINE_RW_FIELD(HS_LOOPBACK_OVRD_EN, 21) + DEFINE_RW_FIELD(PTERM_RANGE_ADJ, 17, 20) + DEFINE_RW_FIELD(PD_DISC_OVRD_VAL, 16) + DEFINE_RW_FIELD(PD_CHRP_OVRD_VAL, 15) + DEFINE_RW_FIELD(RPU_RANGE_ADJ, 13, 14) + DEFINE_RW_FIELD(HS_COUP_EN, 11, 12) + DEFINE_RW_FIELD(SPARE, 7, 10) + DEFINE_RW_FIELD(TERM_RANGE_ADJ, 3, 6) + DEFINE_RW_FIELD(PD_DR, 2) + DEFINE_RW_FIELD(PD_DISC_OVRD, 1) + DEFINE_RW_FIELD(PD_CHRP_OVRD, 0) + END_DEFINE_REGISTER(USB2_OTG_PAD0_CTL_1_0) + + BEGIN_DEFINE_REGISTER(USB2_BIAS_PAD_CTL_0_0, uint32_t, 0x284) + DEFINE_RW_FIELD(TRK_POWER_ENA, 29) + DEFINE_RW_FIELD(SPARE, 25, 28) + DEFINE_RW_FIELD(CHG_DIV, 21, 24) + DEFINE_RW_FIELD(TEMP_COEF, 18, 20) + DEFINE_RW_FIELD(VREF_CTRL, 15, 17) + DEFINE_RW_FIELD(ADJRPU, 12, 14) + DEFINE_RW_FIELD(PD, 11) + DEFINE_RW_FIELD(TERM_OFFSET, 8, 10) + DEFINE_RW_FIELD(HS_CHIRP_LEVEL, 6, 7) + DEFINE_RW_FIELD(HS_DISCON_LEVEL, 3, 5) + DEFINE_RW_FIELD(HS_SQUELCH_LEVEL, 0, 2) + END_DEFINE_REGISTER(USB2_BIAS_PAD_CTL_0_0) + + BEGIN_DEFINE_REGISTER(USB2_BIAS_PAD_CTL_1_0, uint32_t, 0x288) + DEFINE_RW_FIELD(FORCE_TRK_CLK_EN, 30) + DEFINE_RW_FIELD(TRK_SW_OVRF, 29) + DEFINE_RO_FIELD(TRK_DONE, 28) + DEFINE_RW_FIELD(TRK_START, 27) + DEFINE_RW_FIELD(PD_TRK, 26) + DEFINE_RW_FIELD(TRK_DONE_RESET_TIMER, 19, 25) + DEFINE_RW_FIELD(TRK_START_TIMER, 12, 18) + DEFINE_RO_FIELD(PCTRL, 6, 11) + DEFINE_RO_FIELD(TCTRL, 0, 5) + END_DEFINE_REGISTER(USB2_BIAS_PAD_CTL_1_0) + + BEGIN_DEFINE_REGISTER(USB2_VBUS_ID_0, uint32_t, 0xc60) + DEFINE_RW_FIELD(VBUS_WAKEUP_CHNG_INTR_EN, 24) + DEFINE_RW_FIELD(VBUS_WAKEUP_ST_CHNG, 23) + DEFINE_RO_FIELD(VBUS_WAKEUP, 22) + BEGIN_DEFINE_RW_SYMBOLIC_FIELD(ID_OVERRIDE, 18, 21) + DEFINE_RW_SYMBOLIC_VALUE(GND, 0) + DEFINE_RW_SYMBOLIC_VALUE(A, 4) + DEFINE_RW_SYMBOLIC_VALUE(B, 2) + DEFINE_RW_SYMBOLIC_VALUE(C, 1) + DEFINE_RW_SYMBOLIC_VALUE(FLOAT, 8) + END_DEFINE_SYMBOLIC_FIELD(ID_OVERRIDE) + BEGIN_DEFINE_RW_SYMBOLIC_FIELD(ID_SOURCE_SELECT, 16, 17) + DEFINE_RW_SYMBOLIC_VALUE(VGPIO, 0) + DEFINE_RW_SYMBOLIC_VALUE(OVERRIDE, 1) + END_DEFINE_SYMBOLIC_FIELD(ID_SOURCE_SELECT) + DEFINE_RW_FIELD(VBUS_WAKEUP_OVERRIDE, 15) + DEFINE_RW_FIELD(VBUS_OVERRIDE, 14) + BEGIN_DEFINE_RW_SYMBOLIC_FIELD(VBUS_SOURCE_SELECT, 12, 13) + DEFINE_RW_SYMBOLIC_VALUE(VGPIO, 0) + DEFINE_RW_SYMBOLIC_VALUE(OVERRIDE, 1) + END_DEFINE_SYMBOLIC_FIELD(VBUS_SOURCE_SELECT) + DEFINE_RW_FIELD(IDDIG_CHNG_INTR_EN, 11) + DEFINE_RW_FIELD(IDDIG_ST_CHNG, 10) + DEFINE_RO_FIELD(IDDIG_C, 9) + DEFINE_RO_FIELD(IDDIG_B, 8) + DEFINE_RO_FIELD(IDDIG_A, 7) + DEFINE_RO_FIELD(IDDIG, 6) + DEFINE_RW_FIELD(VBUS_VALID_CHNG_INTR_EN, 5) + DEFINE_RW_FIELD(VBUS_VALID_ST_CHNG, 4) + DEFINE_RO_FIELD(VBUS_VALID, 3) + DEFINE_RW_FIELD(OTG_VBUS_SESS_VLD_CHNG_INTR_EN, 2) + DEFINE_RW_FIELD(OTG_VBUS_SESS_VLD_ST_CHNG, 1) + DEFINE_RO_FIELD(OTG_VBUS_SESS_VLD, 0) + END_DEFINE_REGISTER(USB2_VBUS_ID_0) + } XUSB_PADCTL; + +} // namespace t210 diff --git a/fusee/fusee-primary/src/utils.h b/fusee/fusee-primary/src/utils.h index d3eaea88e..ef3c6cede 100644 --- a/fusee/fusee-primary/src/utils.h +++ b/fusee/fusee-primary/src/utils.h @@ -22,6 +22,8 @@ #include #include +#ifndef BIT + #define BIT(n) (1u << (n)) #define BITL(n) (1ull << (n)) #define MASK(n) (BIT(n) - 1) @@ -29,6 +31,8 @@ #define MASK2(a,b) (MASK(a) & ~MASK(b)) #define MASK2L(a,b) (MASKL(a) & ~MASKL(b)) +#endif + #define MAKE_REG32(a) (*(volatile uint32_t *)(a)) #define ALIGN(m) __attribute__((aligned(m))) @@ -108,12 +112,13 @@ static inline bool overlaps_a(const void *as, const void *ae, const void *bs, co static inline bool check_32bit_address_range_in_program(uintptr_t addr, size_t size) { extern uint8_t __chainloader_start__[], __chainloader_end__[]; extern uint8_t __stack_bottom__[], __stack_top__[]; + extern uint8_t __dram_start__[], __dram_end__[]; extern uint8_t __start__[], __end__[]; uint8_t *start = (uint8_t *)addr, *end = start + size; return overlaps_a(start, end, __chainloader_start__, __chainloader_end__) || overlaps_a(start, end, __stack_bottom__, __stack_top__) || - overlaps_a(start, end, (void *)0xC0000000, (void *)0xC03C0000) || /* framebuffer */ + overlaps_a(start, end, __dram_start__, __dram_end__) || overlaps_a(start, end, __start__, __end__); } diff --git a/libraries/libvapours/include/vapours/results.hpp b/libraries/libvapours/include/vapours/results.hpp index f10e5af32..6aa2a283a 100644 --- a/libraries/libvapours/include/vapours/results.hpp +++ b/libraries/libvapours/include/vapours/results.hpp @@ -63,3 +63,4 @@ /* Unofficial. */ #include +#include diff --git a/libraries/libvapours/include/vapours/results/xusb_gadget_results.hpp b/libraries/libvapours/include/vapours/results/xusb_gadget_results.hpp new file mode 100644 index 000000000..61ee4e025 --- /dev/null +++ b/libraries/libvapours/include/vapours/results/xusb_gadget_results.hpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include + +namespace ams::xusb { + + /* Please note: These results are all custom, and not official. */ + R_DEFINE_NAMESPACE_RESULT_MODULE(445); + + + /* Result 1-1000 reserved for Atmosphere. */ + + /* USB protocol-level results. */ + R_DEFINE_ERROR_RESULT(InvalidDeviceState, 1); + R_DEFINE_ERROR_RESULT(MalformedSetupRequest, 2); + R_DEFINE_ERROR_RESULT(UnknownSetupRequest, 3); + R_DEFINE_ERROR_RESULT(UnknownDescriptorType, 4); + R_DEFINE_ERROR_RESULT(InvalidDescriptorIndex, 5); + R_DEFINE_ERROR_RESULT(InvalidAddress, 6); + R_DEFINE_ERROR_RESULT(ControlEndpointBusy, 7); + R_DEFINE_ERROR_RESULT(InvalidSetupPacketSequenceNumber, 8); + R_DEFINE_ERROR_RESULT(InvalidControlEndpointState, 9); + R_DEFINE_ERROR_RESULT(InvalidConfiguration, 10); + R_DEFINE_ERROR_RESULT(TransferRingFull, 11); + + /* Gadget-level results. */ + R_DEFINE_ERROR_RESULT(UnexpectedCompletionCode, 101); + R_DEFINE_ERROR_RESULT(UnexpectedEndpoint, 102); + R_DEFINE_ERROR_RESULT(UnexpectedTRB, 103); + R_DEFINE_ERROR_RESULT(DownloadTooLarge, 104); + +}