blob: 3ef13c6488acb76a0dfd4f57cadb5ab29da30af5 [file] [log] [blame]
/*
* Copyright (c) 2018 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 <err.h>
#include <kernel/mutex.h>
#include <kernel/thread.h>
#include <lib/kcmdline.h>
#include <libfdt.h>
#include <list.h>
#include <malloc.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <trace.h>
#if LK_DEBUGLEVEL > 0
#define LOCAL_TRACE 1
#else
#define LOCAL_TRACE 0
#endif
#define CMDLINE_OVERFLOW_STR "[ERROR] CMDLINE overflow"
struct subst_entry {
struct list_node node;
char *old_arg;
char *new_arg;
};
/* variable for keeping substition arg */
struct list_node subst_list = LIST_INITIAL_VALUE(subst_list);
/* variable for keeping append arg */
static char *cmdline_buf;
static char *cmdline_tail;
static char *cmdline_end;
static mutex_t lock = MUTEX_INITIAL_VALUE(lock);
static inline void validate_cmdline_boundary(const char *tail, const char *end)
{
if (tail >= end) {
dprintf(CRITICAL, CMDLINE_OVERFLOW_STR"\n");
panic(CMDLINE_OVERFLOW_STR);
}
}
static void dump_cmdline(void *fdt)
{
int len;
int chosen_node_offset;
const char *cmdline;
chosen_node_offset = fdt_path_offset(fdt, "/chosen");
if (chosen_node_offset < 0) {
LTRACEF("can't find chosen node.\n");
return;
}
cmdline = fdt_getprop(fdt, chosen_node_offset, "bootargs", &len);
if (!cmdline) {
LTRACEF("fdt_getprop bootargs failed.\n");
return;
}
LTRACEF("cmdline len=%zd, str=\"%s\"\n", strlen(cmdline), cmdline);
}
int kcmdline_init(void)
{
if (cmdline_buf)
return ERR_ALREADY_EXISTS;
cmdline_buf = (char *)malloc(CMDLINE_LEN);
if (!cmdline_buf)
return ERR_NO_MEMORY;
memset(cmdline_buf, 0, CMDLINE_LEN);
cmdline_tail = cmdline_buf;
cmdline_end = cmdline_buf + CMDLINE_LEN;
return NO_ERROR;
}
int kcmdline_finalized(void *fdt, size_t size)
{
int ret;
int len;
int offset;
const void *fdt_bootargs;
char *temp_ptr = NULL;
size_t append_arg_len = 0;
struct subst_entry *entry;
struct subst_entry *temp;
if (!cmdline_buf)
return ERR_NOT_READY;
if (!fdt || !size) {
LTRACEF("Invalid args: fdt(%p), size(%zd)\n", fdt, size);
return ERR_INVALID_ARGS;
}
ret = NO_ERROR;
mutex_acquire(&lock);
if ((*cmdline_buf == 0x0) && list_is_empty(&subst_list))
goto exit;
/* cmdline_buf is filled with bootargs before kcmdline_finalize is called */
if (*cmdline_buf != 0x0) {
append_arg_len = strlen(cmdline_buf);
temp_ptr = (char *)malloc(append_arg_len + 1);
if (!temp_ptr) {
ret = ERR_NO_MEMORY;
goto exit;
}
snprintf(temp_ptr, (append_arg_len + 1), "%s", cmdline_buf);
LTRACEF("append_arg_len:%zu cmdline_buf:%s\n",
append_arg_len, cmdline_buf);
}
ret = fdt_open_into(fdt, fdt, size);
if (ret) {
LTRACEF("fdt_open_into failed\n");
goto exit;
}
ret = fdt_check_header(fdt);
if (ret) {
LTRACEF("fdt_check_header failed\n");
goto exit;
}
/* Reset cmdline_tail */
cmdline_tail = cmdline_buf;
offset = fdt_path_offset(fdt, "/chosen");
fdt_bootargs = fdt_getprop(fdt, offset, "bootargs", &len);
if (!fdt_bootargs) {
ret = ERR_NOT_FOUND;
goto exit;
}
/* add appended string to final string */
validate_cmdline_boundary(cmdline_tail + append_arg_len +
strlen(fdt_bootargs) + 2, cmdline_end);
cmdline_tail += snprintf(cmdline_tail, CMDLINE_LEN, "%s",
(char *)fdt_bootargs);
if (temp_ptr) {
cmdline_tail += snprintf(cmdline_tail, cmdline_end - cmdline_tail,
" %s", temp_ptr);
}
/* subst string in final string */
list_for_every_entry_safe(&subst_list, entry, temp,
struct subst_entry, node) {
char *pos = strstr(cmdline_buf, entry->old_arg);
size_t old_len = strlen(entry->old_arg);
if (pos && ((*(pos + old_len) == ' ') || (*(pos + old_len) == '\0'))) {
size_t new_len = strlen(entry->new_arg);
char *p;
/* erase old arg with space */
memset(pos, ' ', old_len);
if (old_len >= new_len) {
p = pos; /* replace old arg with new arg */
} else {
/* append new arg in the end of cmdline */
validate_cmdline_boundary(cmdline_tail + new_len + 2,
cmdline_end);
p = cmdline_tail;
cmdline_tail += (new_len + 1);
*p++ = ' ';
}
memcpy(p, entry->new_arg, new_len);
}
/* free memory and delete node */
free(entry->old_arg);
free(entry->new_arg);
list_delete(&entry->node);
free(entry);
entry = NULL;
}
ret = fdt_setprop(fdt, offset, "bootargs", cmdline_buf,
strlen(cmdline_buf) + 1);
if (ret != 0) {
dprintf(CRITICAL, "fdt_setprop error.\n");
ret = ERR_GENERIC;
goto exit;
}
ret = fdt_pack(fdt);
if (ret != 0) {
dprintf(CRITICAL, "fdt_pack error.\n");
ret = ERR_GENERIC;
goto exit;
}
free(cmdline_buf);
cmdline_buf = NULL;
#if LOCAL_TRACE
dump_cmdline(fdt);
#endif
exit:
free(temp_ptr);
mutex_release(&lock);
return ret;
}
void kcmdline_print(void)
{
struct subst_entry *entry;
if (!cmdline_buf)
return;
mutex_acquire(&lock);
LTRACEF("append cmdline: %s\n", cmdline_buf);
LTRACEF("append cmdline size: %zd\n", strlen(cmdline_buf));
LTRACEF("subst list:\n");
/* traverse list to show subst_list */
list_for_every_entry(&subst_list, entry, struct subst_entry, node) {
LTRACEF("old_arg: %s, new_arg: %s\n", entry->old_arg, entry->new_arg);
}
mutex_release(&lock);
}
int kcmdline_append(const char *append_arg)
{
size_t append_arg_len;
if (!cmdline_buf)
return ERR_NOT_READY;
if (!append_arg)
return ERR_INVALID_ARGS;
mutex_acquire(&lock);
append_arg_len = strlen(append_arg);
validate_cmdline_boundary(cmdline_tail + append_arg_len + 1, cmdline_end);
cmdline_tail += snprintf(cmdline_tail, cmdline_end - cmdline_tail, " %s",
append_arg);
mutex_release(&lock);
return NO_ERROR;
}
int kcmdline_subst(const char *old_arg, const char *new_arg)
{
struct subst_entry *entry;
if (!cmdline_buf)
return ERR_NOT_READY;
if (!old_arg || !new_arg) {
LTRACEF("Invalid args: old_arg(%p), new_arg(%p)\n", old_arg, new_arg);
return ERR_INVALID_ARGS;
}
entry = (struct subst_entry *)malloc(sizeof(struct subst_entry));
if (!entry)
return ERR_NO_MEMORY;
memset(entry, 0, sizeof(struct subst_entry));
entry->old_arg = strdup(old_arg);
entry->new_arg = strdup(new_arg);
if (!entry->old_arg || !entry->new_arg) {
free(entry->old_arg);
free(entry->new_arg);
free(entry);
return ERR_NO_MEMORY;
}
mutex_acquire(&lock);
list_add_tail(&subst_list, &entry->node);
mutex_release(&lock);
return NO_ERROR;
}