blob: 71f649e25b7670312bdb7139a6dd671373524e9d [file] [log] [blame]
// SPDX-License-Identifier: MIT
/*
* Copyright (c) 2016 MediaTek Inc.
*
* 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 <assert.h>
#include <debug.h>
#include <dev/udc.h>
#include <err.h>
#include <kernel/event.h>
#include <kernel/spinlock.h>
#include <kernel/thread.h>
#include <kernel/timer.h>
#include <lib/fastboot.h>
#include <list.h>
#include <platform/udc-common.h>
#include <stdlib.h>
#include <string.h>
#include <trace.h>
/* Current write cmd timeout of USB and PCIe are the same, so define it here.
* If the value of new added controller is different, then consider to extend
* the fastboot_controller_interface structure.
*/
#define LOCAL_TRACE 2
struct fastboot_cmd {
struct fastboot_cmd *next;
const char *prefix;
unsigned int prefix_len;
void (*handle)(const char *arg, void *data, unsigned int sz);
};
void *download_base;
unsigned int download_max;
unsigned int download_size;
unsigned int fastboot_state = STATE_OFFLINE;
struct fastboot_cmd *cmdlist;
struct fastboot_var *varlist;
struct fastboot_controller_interface *interface;
static struct {
struct list_node list;
spin_lock_t lock;
} interfaces = {
.list = LIST_INITIAL_VALUE(interfaces.list),
.lock = SPIN_LOCK_INITIAL_VALUE,
};
struct controller_interface {
struct list_node node;
struct fastboot_controller_interface *interface;
};
status_t register_fastboot_controller_interface(struct fastboot_controller_interface *interface)
{
spin_lock_saved_state_t state;
struct controller_interface
*ci = (struct controller_interface *)malloc(sizeof(struct controller_interface));
if (!ci)
return ERR_NO_MEMORY;
list_initialize(&ci->node);
ci->interface = interface;
spin_lock_irqsave(&interfaces.lock, state);
list_add_head(&interfaces.list, &ci->node);
spin_unlock_irqrestore(&interfaces.lock, state);
return NO_ERROR;
}
static void unregister_fastboot_controller_interfaces(void)
{
struct controller_interface *ci, *temp;
spin_lock_saved_state_t state;
spin_lock_irqsave(&interfaces.lock, state);
list_for_every_entry_safe(&interfaces.list, ci, temp, struct controller_interface, node) {
free(ci);
}
spin_unlock_irqrestore(&interfaces.lock, state);
}
__WEAK const char *get_fastboot_controller_interface_name(void)
{
static const char *name = "usb";
return name;
}
static struct fastboot_controller_interface *fastboot_get_controller_interface(const char *name)
{
struct controller_interface *ci, *temp;
spin_lock_saved_state_t state;
spin_lock_irqsave(&interfaces.lock, state);
list_for_every_entry_safe(&interfaces.list, ci, temp, struct controller_interface, node) {
if (strncmp(ci->interface->name, name, strlen(ci->interface->name)) == 0) {
spin_unlock_irqrestore(&interfaces.lock, state);
return ci->interface;
}
}
spin_unlock_irqrestore(&interfaces.lock, state);
return NULL;
}
struct fastboot_controller_interface *fastboot_get_active_controller_interface()
{
return interface;
}
unsigned int fastboot_get_state(void)
{
return fastboot_state;
}
void fastboot_set_state(unsigned int state)
{
fastboot_state = state;
}
void fastboot_unregister_all(void)
{
struct fastboot_cmd *cmd;
while (cmdlist != NULL) {
cmd = cmdlist;
cmdlist = cmd->next;
free(cmd);
}
}
void fastboot_register(const char *prefix,
void (*handle)(const char *arg, void *data, unsigned int sz))
{
struct fastboot_cmd *cmd;
cmd = malloc(sizeof(*cmd));
if (cmd) {
cmd->prefix = prefix;
cmd->prefix_len = strlen(prefix);
cmd->handle = handle;
cmd->next = cmdlist;
cmdlist = cmd;
}
}
void fastboot_publish(const char *name, const char *value)
{
struct fastboot_var *var;
var = malloc(sizeof(*var));
if (var) {
var->name = name;
var->value = value;
var->next = varlist;
varlist = var;
}
}
void fastboot_ack(const char *code, const char *reason)
{
char *response;
if (fastboot_state != STATE_COMMAND)
return;
if (reason == 0)
reason = "";
response = (char *)memalign(CACHE_LINE, MAX_RSP_SIZE);
if (!response) {
dprintf(CRITICAL, "fastboot: can't allocate memory\n");
return;
}
snprintf(response, MAX_RSP_SIZE, "%s%s", code, reason);
fastboot_state = STATE_COMPLETE;
interface->write_with_timeout(response, strlen(response), WRITE_CMD_TIMEOUT);
free(response);
}
void fastboot_info(const char *reason)
{
char *response;
if (fastboot_state != STATE_COMMAND)
return;
if (reason == 0)
return;
response = (char *)memalign(CACHE_LINE, MAX_RSP_SIZE);
if (!response) {
dprintf(CRITICAL, "fastboot: can't allocate memory\n");
return;
}
snprintf(response, MAX_RSP_SIZE, "INFO%s", reason);
interface->write_with_timeout(response, strlen(response), WRITE_CMD_TIMEOUT);
free(response);
}
void fastboot_fail(const char *reason)
{
fastboot_ack("FAIL", reason);
/* add debug log */
dprintf(ALWAYS, "%s\n", reason);
}
void fastboot_okay(const char *info)
{
fastboot_ack("OKAY", info);
}
static int fastboot_command_loop(void)
{
struct fastboot_cmd *cmd;
int r;
char *buffer = (char *)memalign(CACHE_LINE, MAX_RSP_SIZE);
if (!buffer) {
dprintf(CRITICAL, "fastboot: can't allocate memory\n");
return ERR_NO_MEMORY;
}
dprintf(ALWAYS,"fastboot: processing commands\n");
again:
while ((fastboot_state != STATE_ERROR) && (fastboot_state != STATE_RETURN)) {
r = interface->read(buffer, MAX_RSP_SIZE);
if ((strncmp(interface->name, "usb", strlen(interface->name)) == 0) && r < 0)
break; /* no input command */
buffer[r] = 0;
dprintf(ALWAYS, "fastboot: %s[len:%d]\n", buffer, r);
dprintf(ALWAYS, "fastboot:[download_base:%p][download_size:0x%x]\n",
download_base, (unsigned int)download_size);
/* Pick up matched command and handle it */
for (cmd = cmdlist; cmd; cmd = cmd->next) {
if (memcmp(buffer, cmd->prefix, cmd->prefix_len))
continue;
fastboot_state = STATE_COMMAND;
dprintf(ALWAYS,"fastboot:[cmd:%s]-[arg:%s]\n", cmd->prefix, buffer + cmd->prefix_len);
cmd->handle((const char *) buffer + cmd->prefix_len, (void *) download_base,
download_size);
if (fastboot_state == STATE_COMMAND)
fastboot_fail("unknown reason");
goto again;
}
dprintf(ALWAYS,"[unknown command]*[%s]*\n", buffer);
fastboot_fail("unknown command");
}
if (fastboot_state != STATE_RETURN)
fastboot_state = STATE_OFFLINE;
dprintf(ALWAYS,"fastboot: oops!\n");
free(buffer);
return fastboot_state;
}
static int fastboot_handler(void)
{
int status = 0;
while (status != STATE_RETURN) {
if (strncmp(interface->name, "usb", strlen(interface->name)) == 0)
interface->event_wait();
status = fastboot_command_loop();
}
return 0;
}
int fastboot_init(void *base, unsigned size)
{
const char *name;
LTRACEF("%s()\n", __func__);
download_base = base;
download_max = size;
name = get_fastboot_controller_interface_name();
ASSERT(name);
interface = fastboot_get_controller_interface(name);
ASSERT(interface);
if (interface->init() < 0)
return -1;
interface->start();
fastboot_handler();
interface->stop();
interface->fini();
unregister_fastboot_controller_interfaces();
return 0;
}