[dartuino][bootloader] Get Bootloader USB working.

This commit is contained in:
Gurjant Kalsi
2016-03-09 15:23:25 -08:00
parent 1458330c8b
commit a8be9611cc
11 changed files with 955 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2016 Gurjant Kalsi <me@gurjantkalsi.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef APP_MOOT_MOOT_H_
#define APP_MOOT_MOOT_H_
typedef enum {
BOOT_NOW,
NEXT_BOOT_STRATEGY
} next_boot_action_t;
#endif // APP_MOOT_MOOT_H_

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) 2016 Gurjant Kalsi <me@gurjantkalsi.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef APP_MOOT_STUBS_H_
#define APP_MOOT_STUBS_H_
#include <sys/types.h>
typedef struct moot_sysinfo {
uintptr_t sys_base_addr; // Pointer to the base of the main system image.
size_t btldr_offset;
size_t bootloader_len;
size_t system_offset;
size_t system_len;
char *system_flash_name;
} moot_sysinfo_t;
// Must be implemented by the platform;
extern const moot_sysinfo_t moot_system_info;
// Returns NO_ERROR if it was successfully able to mount a secondary flash
// device. If NO_ERROR is returned, mount_path and device_name should also be
// populated to reflect the path at which the FS was mounted and the name of
// the BIO device that hosts the FS.
status_t moot_mount_default_fs(char **mount_path, char **device_name);
#endif // APP_MOOT_STUBS_H_

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) 2016 Gurjant Kalsi <me@gurjantkalsi.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef APP_MOOT_USB_H_
#define APP_MOOT_USB_H_
#include <stdbool.h>
// Initialize the USB stack / USB boot mechanisms.
void init_usb_boot(void);
// Allow the USB device to interrupt the boot sequence.
void attempt_usb_boot(void);
#endif // APP_MOOT_USB_H_

68
app/moot/moot.c Normal file
View File

@@ -0,0 +1,68 @@
/*
* Copyright (c) 2016 Gurjant Kalsi <me@gurjantkalsi.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <app/moot/fsboot.h>
#include <app/moot/moot.h>
#include <app/moot/stubs.h>
#include <app/moot/usbboot.h>
#include <app.h>
#include <arch.h>
#include <assert.h>
#include <debug.h>
#include <err.h>
#include <kernel/event.h>
#include <lk/init.h>
#include <stdlib.h>
#include <trace.h>
static void do_boot(void)
{
arch_disable_ints();
arch_quiesce();
arch_chain_load((void *)(moot_system_info.sys_base_addr), 0, 0, 0, 0);
}
static void moot_init(const struct app_descriptor *app)
{
// Initialize our boot subsystems.
init_usb_boot();
}
static void moot_entry(const struct app_descriptor *app, void *args)
{
// Wait a few seconds for the host to try to talk to us over USB.
attempt_usb_boot();
// Check the SPIFlash for an upgrade image.
attempt_fs_boot();
// Boot the main system image.
do_boot();
}
APP_START(moot)
.init = moot_init,
.entry = moot_entry,
APP_END

15
app/moot/rules.mk Normal file
View File

@@ -0,0 +1,15 @@
LOCAL_DIR := $(GET_LOCAL_DIR)
MODULE := $(LOCAL_DIR)
MODULE_SRCS += \
$(LOCAL_DIR)/fsboot.c \
$(LOCAL_DIR)/moot.c \
$(LOCAL_DIR)/stubs.c \
$(LOCAL_DIR)/usbboot.c \
MODULE_DEPS += \
lib/bootimage
include make/module.mk

34
app/moot/stubs.c Normal file
View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) 2016 Gurjant Kalsi <me@gurjantkalsi.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <compiler.h>
#include <app/moot/stubs.h>
#include <err.h>
// Fail by default. System must override this.
__WEAK status_t moot_mount_default_fs(char **mount_path, char **device_name)
{
*device_name = NULL;
*mount_path = NULL;
return ERR_NOT_IMPLEMENTED;
}

344
app/moot/usbboot.c Normal file
View File

@@ -0,0 +1,344 @@
/*
* Copyright (c) 2016 Gurjant Kalsi <me@gurjantkalsi.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <app/moot/usbboot.h>
#include <app/moot/stubs.h>
#include <dev/udc.h>
#include <dev/usb.h>
#include <dev/usbc.h>
#include <err.h>
#include <kernel/event.h>
#include <kernel/thread.h>
#include <lib/bio.h>
#include <lib/buildsig.h>
#include <lib/version.h>
#include <platform.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <trace.h>
#define LOCAL_TRACE 0
#define COMMAND_MAGIC (0x4d4f4f54) // MOOT
#define RESP_MAGIC (0x52455350) // RESP
#define USB_XFER_SIZE (512)
#define W(w) (w & 0xff), (w >> 8)
#define W3(w) (w & 0xff), ((w >> 8) & 0xff), ((w >> 16) & 0xff)
// How long should we wait for activity on USB before we continue to boot?
#define USB_BOOT_TIMEOUT (3000)
static const uint8_t if_descriptor[] = {
0x09, /* length */
INTERFACE, /* type */
0x01, /* interface num */ // TODO(gkalsi)
0x00, /* alternates */
0x02, /* endpoint count */
0xff, /* interface class */
0x01, /* interface subclass */
0x00, /* interface protocol */
0x00, /* string index */
/* endpoint 1 IN */
0x07, /* length */
ENDPOINT, /* type */
0x1 | 0x80, /* address: 1 IN */ // TODO(gkalsi)
0x02, /* type: bulk */
W(64), /* max packet size: 64 */
00, /* interval */
/* endpoint 1 OUT */
0x07, /* length */
ENDPOINT, /* type */
0x1, /* address: 1 OUT */ // TODO(gkalsi)
0x02, /* type: bulk */
W(64), /* max packet size: 64 */
00, /* interval */
};
// Everything is okay.
#define USB_RESP_NO_ERROR (0x00)
#define USB_RESP_XMIT_READY (0x01)
#define USB_RESP_RECV_READY (0x02)
// Malformed reqeust
#define USB_RESP_BAD_DATA_LEN (0xAAA0)
#define USB_RESP_BAD_MAGIC (0xAAA1)
#define USB_RESP_UNKNOWN_COMMAND (0xAAA2)
// Device Side System Errors
#define USB_RESP_SYS_IMAGE_TOO_BIG (0xFFF1)
#define USB_RESP_ERR_OPEN_SYS_FLASH (0xFFF2)
#define USB_RESP_ERR_ERASE_SYS_FLASH (0xFFF3)
#define USB_RESP_ERR_WRITE_SYS_FLASH (0xFFF4)
#define USB_RESP_CANT_FIND_BUILDSIG (0xFFF5)
/* Bootloader commands */
#define USB_CMD_FLASH (0x01)
#define USB_CMD_BOOT (0x02)
#define USB_CMD_DEVINFO (0x03)
typedef struct cmd_header {
uint32_t magic;
uint32_t cmd;
uint32_t arg;
} cmd_header_t;
typedef struct cmd_response {
uint32_t magic;
uint32_t code;
uint32_t arg;
} cmd_response_t;
// USB Functions
static void usb_xmit(void *data, size_t len);
static status_t usb_recv(void *data, size_t len, lk_time_t timeout, size_t *actual);
static status_t usb_xmit_cplt_cb(ep_t endpoint, usbc_transfer_t *t);
static status_t usb_recv_cplt_cb(ep_t endpoint, usbc_transfer_t *t);
static status_t usb_register_cb(
void *cookie, usb_callback_op_t op, const union usb_callback_args *args);
static uint8_t buffer[4096];
static event_t txevt = EVENT_INITIAL_VALUE(txevt, 0, EVENT_FLAG_AUTOUNSIGNAL);
static event_t rxevt = EVENT_INITIAL_VALUE(rxevt, 0, EVENT_FLAG_AUTOUNSIGNAL);
static volatile bool online = false;
// Command processor that handles USB boot commands.
static bool handle(const void *data, const size_t n, cmd_response_t *resp)
{
DEBUG_ASSERT(resp);
resp->magic = RESP_MAGIC;
resp->code = USB_RESP_NO_ERROR;
resp->arg = 0;
// Make sure we have enough data.
if (n < sizeof(cmd_header_t)) {
resp->code = USB_RESP_BAD_DATA_LEN;
return false;
}
cmd_header_t *header = (cmd_header_t *)data;
if (header->magic != COMMAND_MAGIC) {
resp->code = USB_RESP_BAD_MAGIC;
return false;
}
ssize_t image_length;
const lk_version_t *version;
status_t st;
switch (header->cmd) {
case USB_CMD_FLASH:
image_length = header->arg;
if (image_length > (ssize_t)moot_system_info.system_len) {
resp->code = USB_RESP_SYS_IMAGE_TOO_BIG;
break;
}
// Make space on the flash for the data.
bdev_t *dev = bio_open(moot_system_info.system_flash_name);
if (!dev) {
resp->code = USB_RESP_ERR_OPEN_SYS_FLASH;
break;
}
ssize_t n_bytes_erased = bio_erase(dev, moot_system_info.system_offset, image_length);
if (n_bytes_erased < image_length) {
resp->code = USB_RESP_ERR_ERASE_SYS_FLASH;
break;
}
// Signal to the host to start sending the image over.
resp->code = USB_RESP_RECV_READY;
resp->arg = 0;
usb_xmit((void *)resp, sizeof(*resp));
off_t addr = moot_system_info.system_offset;
while (image_length > 0) {
ssize_t xfer = (image_length > (ssize_t)sizeof(buffer)) ?
(ssize_t)sizeof(buffer) : image_length;
size_t bytes_received;
usb_recv(buffer, xfer, INFINITE_TIME, &bytes_received);
ssize_t written = bio_write(dev, buffer, addr, bytes_received);
if (written != (ssize_t)bytes_received) {
resp->code = USB_RESP_ERR_WRITE_SYS_FLASH;
goto finish;
}
addr += written;
image_length -= written;
}
resp->code = USB_RESP_NO_ERROR;
break;
case USB_CMD_BOOT:
resp->code = USB_RESP_NO_ERROR;
resp->arg = 0;
return true;
break;
case USB_CMD_DEVINFO:
st = buildsig_search(
(void *)moot_system_info.sys_base_addr,
DEFAULT_BUILDSIG_SEARCH_LEN,
1024*1024,
&version
);
if (st != NO_ERROR) {
resp->code = USB_RESP_CANT_FIND_BUILDSIG;
break;
}
size_t buflen = sizeof(buffer);
snprintf((char *)buffer, buflen, "%s\n%s\n%s\n%s\n%s",
version->arch, version->platform, version->target,
version->project, version->buildid);
resp->code = USB_RESP_XMIT_READY;
resp->arg = strnlen((char *)buffer, buflen);
usb_xmit((void *)resp, sizeof(*resp));
usb_xmit((void *)buffer, resp->arg);
resp->arg = 0;
resp->code = USB_RESP_NO_ERROR;
break;
default:
resp->arg = 0;
resp->code = USB_RESP_UNKNOWN_COMMAND;
break;
}
finish:
return false;
}
void init_usb_boot(void)
{
usb_append_interface_lowspeed(if_descriptor, sizeof(if_descriptor));
usb_append_interface_highspeed(if_descriptor, sizeof(if_descriptor));
usb_register_callback(&usb_register_cb, NULL);
}
void attempt_usb_boot(void)
{
printf("attempting usb boot\n");
uint8_t *buf = malloc(USB_XFER_SIZE);
lk_time_t start = current_time();
lk_time_t timeout = USB_BOOT_TIMEOUT;
size_t bytes_received;
while (current_time() - start < timeout) {
if (!online) {
thread_yield();
continue;
}
status_t r = usb_recv(buf, USB_XFER_SIZE, timeout, &bytes_received);
if (r == ERR_TIMED_OUT) {
goto finish;
} else if (r == NO_ERROR) {
// Somebody tried to talk to us over USB, they own the boot now.
cmd_response_t response;
bool should_boot = handle(buf, bytes_received, &response);
usb_xmit((void *)&response, sizeof(response));
timeout = INFINITE_TIME;
if (should_boot) {
goto finish;
}
}
}
finish:
free(buf);
return;
}
static status_t usb_register_cb(
void *cookie,
usb_callback_op_t op,
const union usb_callback_args *args
)
{
if (op == USB_CB_ONLINE) {
usbc_setup_endpoint(1, USB_IN, 0x40);
usbc_setup_endpoint(1, USB_OUT, 0x40);
online = true;
}
return NO_ERROR;
}
static status_t usb_xmit_cplt_cb(ep_t endpoint, usbc_transfer_t *t)
{
event_signal(&txevt, false);
return 0;
}
static status_t usb_recv_cplt_cb(ep_t endpoint, usbc_transfer_t *t)
{
event_signal(&rxevt, false);
return 0;
}
static void usb_xmit(void *data, size_t len)
{
usbc_transfer_t transfer = {
.callback = &usb_xmit_cplt_cb,
.result = 0,
.buf = data,
.buflen = len,
.bufpos = 0,
.extra = 0,
};
usbc_queue_tx(1, &transfer);
event_wait(&txevt);
}
static status_t usb_recv(void *data, size_t len, lk_time_t timeout, size_t *actual)
{
usbc_transfer_t transfer = {
.callback = &usb_recv_cplt_cb,
.result = 0,
.buf = data,
.buflen = len,
.bufpos = 0,
.extra = 0,
};
usbc_queue_rx(1, &transfer);
status_t res = event_wait_timeout(&rxevt, timeout);
if (res != NO_ERROR) {
// TODO(gkalsi): Cancel the USB txn?
return res;
}
*actual = transfer.bufpos;
return NO_ERROR;
}

View File

@@ -5,6 +5,7 @@ ENTRY(_start)
SECTIONS
{
. = %ROMBASE%;
__rom_start = .;
/* text/read-only data */
.text : {
@@ -61,6 +62,7 @@ SECTIONS
.dummy_post_rodata : {
/* end of rodata, start of data area */
__rodata_end = . ;
__rom_end = . ;
__data_start_rom = .;
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) 2016 Gurjant Kalsi <me@gurjantkalsi.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
static char bootloader_primary_flash_name[] = "flash0";
static char bootloader_secondary_flash_name[] = "qspi-flash";
static char bootloader_mount_point[] = "/spifs";
#include <lib/fs.h>
#include <err.h>
#include <app/moot/stubs.h>
#include <stdio.h>
#define BOOTLOADER_LENGTH_KB (128)
status_t moot_mount_default_fs(char **mount_path, char **device_name)
{
*mount_path = bootloader_mount_point;
*device_name = bootloader_secondary_flash_name;
return NO_ERROR;
}
const moot_sysinfo_t moot_system_info = {
.sys_base_addr = 0x00220000,
.btldr_offset = 0x0,
.bootloader_len = 1024 * BOOTLOADER_LENGTH_KB,
.system_offset = 1024 * BOOTLOADER_LENGTH_KB,
.system_len = (1024 * (1024 - BOOTLOADER_LENGTH_KB)),
.system_flash_name = bootloader_primary_flash_name,
};

View File

@@ -0,0 +1,12 @@
LOCAL_DIR := $(GET_LOCAL_DIR)
MODULE := $(LOCAL_DIR)
MODULE_SRCS += \
$(LOCAL_DIR)/bootloader_stubs.c
MODULE_DEPS += \
lib/fs
include make/module.mk

314
tools/moot/mtldr.py Normal file
View File

@@ -0,0 +1,314 @@
"""
Copyright (c) 2016 Gurjant Kalsi <me@gurjantkalsi.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
import argparse
import binascii
import logging
import struct
import time
import usb.core
import usb.util
class Command:
flash = 0x01
boot = 0x02
devinfo = 0x03
class DataPhaseType:
none = 0
host_to_device = 1
device_to_host = 2
class Retcode:
# Normal operation
no_error = (0x00)
xmit_ready = (0x01)
recv_ready = (0x02)
# Malformed reqeust
bad_data_len = (0xAAA0)
bad_magic = (0xAAA1)
unknown_command = (0xAAA2)
# Device side system error.
sys_image_too_big = (0xFFF1)
err_open_sys_flash = (0xFFF2)
err_erase_sys_flash = (0xFFF3)
err_write_sys_flash = (0xFFF4)
cant_find_buildsig = (0xFFF5)
class CommandParam:
def __init__(self, data_phase_type):
self.data_phase_type = data_phase_type
VENDOR_ID = 0x9999
PRODUCT_ID = 0x9999
CLASS_VENDOR_SPECIFIC = 0xFF
SUBCLASS_MTLDR_DEBUG = 0x01
# create logger
logger = logging.getLogger('mtldr')
logger.setLevel(logging.WARN)
class FindByDeviceClass(object):
# Callable object that selects a USB device by a Sub/Device class pair
def __init__(self, device, subdevice):
self._device_class = device
self._subdevice_class = subdevice
def __call__(self, device):
for cfg in device:
intf = usb.util.find_descriptor(
cfg, bInterfaceClass=self._device_class,
bInterfaceSubClass=self._subdevice_class)
if intf:
return True
return False
class CommandDispatcher:
header_struct = struct.Struct("< i i i")
header_struct_len = 12 # bytes
response_struct = struct.Struct("< i i i")
response_struct_len = 12 # bytes
cmd_magic = 0x4d4f4f54
resp_magic = 0x52455350
data_phase_timeout = 10000 # ms
CommandParams = {
Command.flash: CommandParam(DataPhaseType.host_to_device),
Command.boot: CommandParam(DataPhaseType.none),
Command.devinfo: CommandParam(DataPhaseType.device_to_host),
}
def __init__(self, ep_in, ep_out):
self._ep_in = ep_in
self._ep_out = ep_out
def __read_response(self, timeout=None):
"""
reads a standard response from the USB device.
"""
if timeout:
resp = self._ep_in.read(CommandDispatcher.response_struct_len, timeout=timeout).tostring()
else:
resp = self._ep_in.read(CommandDispatcher.response_struct_len).tostring()
logger.debug(
("Read %d bytes: " % CommandDispatcher.response_struct_len) +
str(binascii.hexlify(resp))
)
resp = CommandDispatcher.response_struct.unpack(resp)
if resp[0] != CommandDispatcher.resp_magic:
raise("Device responded with an unexpected magic value.")
logger.debug(
("Read Response - retcode = %d, nbytes = %d" % (resp[1], resp[2]))
)
return (resp[1], int(resp[2]))
def __dispatch_no_data(self, command):
"""
Dispatches a command that has no data phase.
"""
logger.debug("Write %d bytes, command = %d" % (0, command))
command = CommandDispatcher.header_struct.pack(CommandDispatcher.cmd_magic, command, 0)
self._ep_out.write(command)
retcode, datalen = self.__read_response()
assert datalen == 0 # A command with no data can't have a datalen
return (retcode, list())
def __dispatch_device_to_host(self, command):
"""
Dispatches a command that has a device to host data phase.
"""
logger.debug("Write %d bytes, command = %d" % (0, command))
command = CommandDispatcher.header_struct.pack(CommandDispatcher.cmd_magic, command, 0)
self._ep_out.write(command)
retcode, datalen = self.__read_response()
if retcode != Retcode.xmit_ready:
return (retcode, list())
logger.debug("Read %d bytes, retcode = %d" % (int(datalen), retcode))
resp = self._ep_in.read(int(datalen), timeout=CommandDispatcher.data_phase_timeout)
retcode, datalen = self.__read_response()
return (retcode, resp)
def __dispatch_host_to_device(self, command, data):
"""
Dispatches a command that has a host to device data phase.
"""
logger.debug("Write %d bytes, command = %d" % (len(data), command))
# Tell the device that we're about to send it data. Also mention how
# much data we're about to send.
command = CommandDispatcher.header_struct.pack(CommandDispatcher.cmd_magic, command, len(data))
self._ep_out.write(command)
# The device will signal back to us that it's ready to read data.
retcode, datalen = self.__read_response(CommandDispatcher.data_phase_timeout)
assert datalen == 0
if retcode != Retcode.recv_ready:
# The device experienced an error and is not ready to receive data.
return (retcode, list())
# Write the data back to the device.
self._ep_out.write(data, timeout=CommandDispatcher.data_phase_timeout)
# The device will reply to us to let us know whether or not it received
# our data correctly.
retcode, datalen = self.__read_response()
return (retcode, list())
def dispatch(self, command, data=None):
"""
Dispatches a command to the connected USB device.
A command is composed of a command phase followed by an optional data
phase. If the data parameter is specified, dispatch(...) will also
attempt to send data to the device.
Returns a 2-Tuple as follows: (command result, optional data). If the
device returned data during the data phase, optional data will contain
that data, otherwise it will be None.
"""
# Make sure the command actually exists.
params = CommandDispatcher.CommandParams.get(command)
if not params:
raise("Command " + str(command) + " does not exist.")
if params.data_phase_type == DataPhaseType.none:
result = self.__dispatch_no_data(command)
elif params.data_phase_type == DataPhaseType.host_to_device:
result = self.__dispatch_host_to_device(command, data)
elif params.data_phase_type == DataPhaseType.device_to_host:
result = self.__dispatch_device_to_host(command)
return result
def cmd_devinfo(dispatcher, args):
retcode, data = dispatcher.dispatch(Command.devinfo)
if retcode != Retcode.no_error:
print ("Error %d while reading devinfo" % retcode)
else:
print data.tostring()
def cmd_flash(dispatcher, args):
with open(args.bin, 'rb') as file:
binary = file.read()
retcode, data = dispatcher.dispatch(Command.flash, binary)
if retcode != Retcode.no_error:
print ("Error %d while flashing device" % retcode)
def cmd_boot(dispatcher, args):
retcode, data = dispatcher.dispatch(Command.boot)
if retcode != Retcode.no_error:
print ("Error %d while booting device" % retcode)
def main():
# Setup the Logger
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
logger.addHandler(ch)
# Setup the argument parser
parser = argparse.ArgumentParser(prog='PROG')
subparsers = parser.add_subparsers(help='sub-command help')
devinfo_parser = subparsers.add_parser('devinfo', help='a help')
devinfo_parser.set_defaults(func=cmd_devinfo)
flash_parser = subparsers.add_parser('flash', help='b help')
flash_parser.add_argument('bin', help="Path to the LK Binary to flash")
flash_parser.set_defaults(func=cmd_flash)
boot_parser = subparsers.add_parser('boot', help='b help')
boot_parser.set_defaults(func=cmd_boot)
args = parser.parse_args()
logger.info("Waiting for device (vid=0x%04x, pid=0x%04x, "
"class=0x%02x, subclass=0x%02x)" %
(VENDOR_ID, PRODUCT_ID, CLASS_VENDOR_SPECIFIC,
SUBCLASS_MTLDR_DEBUG))
while True:
dev = usb.core.find(
idVendor=VENDOR_ID,
idProduct=PRODUCT_ID,
custom_match=FindByDeviceClass(CLASS_VENDOR_SPECIFIC,
SUBCLASS_MTLDR_DEBUG))
if dev:
break
time.sleep(0.5)
logger.info("Found USB Device!")
dev.set_configuration()
cfg = dev.get_active_configuration()
intf = usb.util.find_descriptor(
cfg, bInterfaceClass=CLASS_VENDOR_SPECIFIC,
bInterfaceSubClass=SUBCLASS_MTLDR_DEBUG)
ep_out = usb.util.find_descriptor(
intf,
custom_match=lambda e:
usb.util.endpoint_direction(e.bEndpointAddress) ==
usb.util.ENDPOINT_OUT)
ep_in = usb.util.find_descriptor(
intf,
custom_match=lambda e:
usb.util.endpoint_direction(e.bEndpointAddress) ==
usb.util.ENDPOINT_IN)
dispatcher = CommandDispatcher(ep_in, ep_out)
args.func(dispatcher, args)
if __name__ == "__main__":
main()