blob: b377b20f2d09d532cd10042e670f0f35cf3d5b05 [file] [log] [blame]
/* Copyright 2015 Google Inc. All Rights Reserved.
*
* Note: flash_erase usage in here sends start and end as the same address.
* This is because the size of each sysvar env is a single erase-block (64k).
* The flash_erase function includes the addresses under the "last" param as
* part of the erase section, meaning inclusion of 0x8 as an address will erase
* 0x8 up to 0x9 (64k). 0x9 cannot be included as the end address because it is
* not in a sector boundary.
*/
#include <sysvar.h>
#include <asm/io.h>
#include <common.h>
#include <config.h>
#include <command.h>
#include <flash.h>
struct sysvar_uboot {
const char *sysvar_name;
const char *uboot_name;
};
static const struct sysvar_uboot su_mappings[] = {
{
.sysvar_name = ACTIVATED_KERNEL_NAME_SV,
.uboot_name = ACTIVATED_KERNEL_NAME_UB,
},
{
.sysvar_name = MAC_ADDR1_SV,
.uboot_name = MAC_ADDR1_UB,
},
};
// Some bit twiddling for erasing the correct sector on Atheros flash. This
// only erases multiples of 64kb.
#define __SYSVAR_ERASE_SECTOR(x) (((x) >> 16) & 0xff)
// Board-based implementation supplies pointer to flash info.
extern flash_info_t flash_info[];
struct sysvar_buf ro_buf;
struct sysvar_buf rw_buf;
const long sysvar_offset[SYSVAR_SPI_BLOCK] = {
SYSVAR_RW_OFFSET0, SYSVAR_RW_OFFSET1, SYSVAR_RO_OFFSET0, SYSVAR_RO_OFFSET1
};
/*
* str_to_int - Converts a string into an int.
*
* Returns an error by setting "err" to something non-zero.
*/
static int str_to_int(const char *str, int *err) {
int res = 0;
char *p;
for (p = str; *p != '\0'; ++p) {
switch (*p) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
res = res * 10 + (*p - '0');
break;
default:
if (err) {
*err = 1;
}
return res;
}
}
return res;
}
static void print_err(int code, void *arg) {
switch (code) {
case SYSVAR_CRC_ERR:
printf("## Error: CRC check failure on index %d\n", *(int*)arg);
break;
case SYSVAR_DELETE_ERR:
printf("## Error: failed to delete '%s'\n", (char*)arg);
break;
case SYSVAR_ERASE_ERR:
printf("## Error: erase failure on index %d\n", *(int*)arg);
break;
case SYSVAR_EXISTED_ERR:
printf("## Error: '%s' is already a read/write variable\n", (char*)arg);
break;
case SYSVAR_INDEX_ERR:
printf("## Error: invalid SPI flash device %d\n", *(int*)arg);
break;
case SYSVAR_READONLY_ERR:
printf("## Error: '%s' is a read only variable\n", (char*)arg);
break;
case SYSVAR_SET_ERR:
printf("## Error: '%s' not found\n", (char*)arg);
break;
case SYSVAR_SUCCESS:
break;
case SYSVAR_WRITE_ERR:
printf("## Error: unable to write to index %d\n", *(int*)arg);
break;
default:
/* Unknown error code. */
printf("## Error: code %d\n", code);
break;
}
}
/*
* data_recovery - system variables recovering routine
*/
int data_recovery(struct sysvar_buf *buf, int idx) {
int i, j, ret = SYSVAR_SUCCESS, sector;
/* load the system variables */
for (i = idx, j = idx + 1; i < idx + 2; i++, j--) {
/* read the data from SPI flash */
if (read_buff(flash_info, buf->data, sysvar_offset[i], buf->data_len))
continue;
/* check crc32 and wc32 (write count) */
if (check_var(buf, SYSVAR_LOAD_MODE) == SYSVAR_SUCCESS) {
/* erase SPI flash */
sector = __SYSVAR_ERASE_SECTOR(sysvar_offset[j]);
ret = flash_erase(flash_info, sector, sector);
if (ret) {
ret = SYSVAR_ERASE_ERR;
goto recovery_err;
}
/* check crc32 and wc32 (write count) */
if (check_var(buf, SYSVAR_SAVE_MODE)) {
ret = SYSVAR_CRC_ERR;
goto recovery_err;
}
/* write system variables(RW) to SPI flash */
ret = write_buff(flash_info, buf->data, sysvar_offset[j] + CFG_FLASH_BASE,
buf->data_len);
if (ret) {
ret = SYSVAR_WRITE_ERR;
goto recovery_err;
}
buf->loaded = true;
return SYSVAR_SUCCESS;
}
}
recovery_err:
clear_buf(buf);
return ret;
}
/*
* data_load - load the data from SPI flash to data buffer
*/
int data_load(struct sysvar_buf *buf, int idx) {
int i, j;
/* load the system variables */
for (i = idx, j = 0; i < idx + 2; i++, j++) {
buf->failed[j] = false;
/* read the data from SPI flash */
if (read_buff(flash_info, buf->data, sysvar_offset[i], buf->data_len)) {
buf->failed[j] = true;
}
/* check crc32 and wc32 (write count) */
if (check_var(buf, SYSVAR_LOAD_MODE))
buf->failed[j] = true;
}
load_err:
if (buf->failed[0] || buf->failed[1]) {
return SYSVAR_LOAD_ERR;
}
return SYSVAR_SUCCESS;
}
/*
* data_save - save the data from data buffer to SPI flash
*/
int data_save(struct sysvar_buf *buf, int *idx) {
int i, j, ret, sector;
/* save the system variables */
for (j = 0; j < 2; j++) {
i = idx[j];
sector = __SYSVAR_ERASE_SECTOR(sysvar_offset[i]);
ret = flash_erase(flash_info, sector, sector);
if (ret) {
return SYSVAR_SAVE_ERR;
}
/* check crc32 and wc32 (write count) */
if (check_var(buf, SYSVAR_SAVE_MODE)) {
return SYSVAR_CRC_ERR;
}
/* write system variables to SPI flash */
ret = write_buff(flash_info, buf->data, sysvar_offset[i] + CFG_FLASH_BASE,
buf->data_len);
if (ret) {
return SYSVAR_WRITE_ERR;
}
}
return SYSVAR_SUCCESS;
}
/*
* sysvar_dump - dump the data buffer in binary/ascii format
*/
void sysvar_dump(struct sysvar_buf *buf, int idx, bool load) {
extern char console_buffer[];
int start = 0;
printf("System Variables(%s):\n", (idx < SYSVAR_RO_BUF) ? "RW" : "RO");
printf("offset : 0x%08lx\n", sysvar_offset[idx]);
printf("size : %d bytes\n", buf->data_len);
printf("total : %d bytes\n", buf->total_len);
printf("used : %d bytes\n", buf->used_len);
printf("wc32 : 0x%08lx\n", get_wc32(buf));
printf("crc32 : 0x%08lx\n", get_crc32(buf));
while (1) {
/* dump one page data in data buffer */
dump_buf(buf, start, PAGE_SIZE);
/* continue to dump...? */
readline("(n)ext, (p)rev, (f)irst, (l)ast ? >> ");
if (strcmp(console_buffer, "n") == 0) {
start += PAGE_SIZE; /* go to next page */
if (start >= buf->data_len)
return;
} else if (strcmp(console_buffer, "p") == 0) {
start -= PAGE_SIZE; /* go to previous page */
if (start < 0)
return;
} else if (strcmp(console_buffer, "f") == 0) {
if (start == 0)
return;
start = 0; /* go to first page */
} else if (strcmp(console_buffer, "l") == 0) {
if (start == buf->data_len - PAGE_SIZE)
return;
start = buf->data_len - PAGE_SIZE; /* go to last page */
} else {
return;
}
}
}
/*
* sysvar_io - SPI flash IO operations
*/
int sysvar_io(int argc, char *argv[]) {
struct sysvar_buf *buf;
int i, idx, sector, ret = 0, str_to_int_err = 0;
idx = str_to_int(argv[1], &str_to_int_err);
if (str_to_int_err || idx < 0 || idx > 3) {
return SYSVAR_INDEX_ERR;
}
if (idx < 2) {
buf = &rw_buf;
} else {
buf = &ro_buf;
}
if (strcmp(argv[0], "write") == 0) {
/* fill data to data buffer */
for (i = 0; i < buf->data_len; i++)
buf->data[i] = i;
/* write the data buffer to spi_flash */
ret = write_buff(flash_info, buf->data, sysvar_offset[idx] + CFG_FLASH_BASE,
buf->data_len);
if (ret) {
ret = SYSVAR_WRITE_ERR;
}
} else if (strcmp(argv[0], "erase") == 0) {
/* erase spi_flash */
sector = __SYSVAR_ERASE_SECTOR(sysvar_offset[idx]);
ret = flash_erase(flash_info, sector, sector);
if (ret) {
ret = SYSVAR_ERASE_ERR;
}
}
if (ret == 0) {
ret = read_buff(flash_info, buf->data, sysvar_offset[idx], buf->data_len);
if (ret == 0)
sysvar_dump(buf, idx, false);
}
if (ret != 0)
printf("## Error: SPI flash %s failed at 0x%08lx\n",
argv[0], sysvar_offset[idx]);
rw_buf.loaded = false;
ro_buf.loaded = false;
return ret;
}
////////////////////////////////////////////////////////////////////////////////
// Sysvar/Uboot Integration
////////////////////////////////////////////////////////////////////////////////
/*
* sysvar_buf_init - Initializes a sysvar_buf struct.
*/
static int sysvar_buf_init(struct sysvar_buf *buf, bool is_ro) {
if (buf == NULL) {
return SYSVAR_PARAM_ERR;
}
memset(buf, 0, sizeof(*buf));
buf->list = (struct sysvar_list *)malloc(sizeof(struct sysvar_list));
if (buf->list == NULL) {
printf("list allocation ");
return SYSVAR_MEMORY_ERR;
}
buf->data = (uchar *) map_physmem(is_ro ? SYSVAR_RO_MEM : SYSVAR_RW_MEM);
if (buf->data == NULL) {
printf("data allocation ");
free(buf->list);
return SYSVAR_MEMORY_ERR;
}
buf->data_len = SYSVAR_BLOCK_SIZE;
buf->total_len = SYSVAR_BLOCK_SIZE - SYSVAR_HEAD;
buf->free_len = buf->total_len;
buf->readonly = is_ro;
strncpy(buf->list->name, is_ro ? "ro" : "rw", SYSVAR_NAME);
buf->list->value = NULL;
buf->list->len = SYSVAR_NAME + 2;
buf->list->next = NULL;
buf->loaded = false;
return SYSVAR_SUCCESS;
}
/*
* sysvar_env_clear_shared - Takes sysvar buf and clears anything that may be
* mapped to the uboot env. Does not delete any sysvar variables.
*/
static void sysvar_env_clear_shared(struct sysvar_buf *buf) {
struct sysvar_list *curr = buf->list->next;
while (curr != NULL) {
/* Only clear env variables for shared vars. */
set_sysvar_uboot(curr->name, NULL);
curr = curr->next;
}
}
/*
* sysvar_uboot_env_clear - Clears all potential uboot shared variables
* from the environment.
*/
void sysvar_uboot_env_clear(void) {
int i;
for (i = 0; i < ARRAY_SIZE(su_mappings); ++i) {
setenv(su_mappings[i].uboot_name, NULL);
}
}
/* Helper function for sysvar_uboot_env_load. */
static void _sysvar_uboot_env_load(struct sysvar_buf *buf) {
struct sysvar_list *curr = buf->list->next;
while (curr != NULL) {
/* Should only set env variables for special vars. */
set_sysvar_uboot(curr->name, curr->value);
curr = curr->next;
}
}
/*
* sysvar_uboot_env_load - Loads sysvar/uboot shared variables to the
* uboot environment.
*/
void sysvar_uboot_env_load(void) {
_sysvar_uboot_env_load(&ro_buf);
_sysvar_uboot_env_load(&rw_buf);
}
int sysvar_init(void) {
printf("sysvar_init. . . ");
if (sysvar_buf_init(&rw_buf, false) || sysvar_buf_init(&ro_buf, true) ||
sf_loadvar(&rw_buf, SYSVAR_RW_BUF) ||
sf_loadvar(&ro_buf, SYSVAR_RO_BUF)) {
printf("init failure!\n");
return SYSVAR_MEMORY_ERR;
}
sysvar_uboot_env_load();
printf("success!\n");
return SYSVAR_SUCCESS;
}
/*
* get_sysvar_shared - Determines if there's a mapping from sysvar -> uboot or
* vice versa.
*
* If there is a mapping:
* If given a sysvar name, returns the uboot env name that the sysvar name
* maps to. If given a uboot env name, returns the sysvar name that it maps
* to.
*
* If there is no mapping, returns NULL.
*/
const char *get_sysvar_shared(const char *var) {
int i;
for (i = 0; i < ARRAY_SIZE(su_mappings); ++i) {
if (strcmp(var, su_mappings[i].uboot_name) == 0) {
return su_mappings[i].sysvar_name;
}
if (strcmp(var, su_mappings[i].sysvar_name) == 0) {
return su_mappings[i].uboot_name;
}
}
return NULL;
}
const char *get_uboot_name(const char *sysvar_name) {
int i;
for (i = 0; i < ARRAY_SIZE(su_mappings); ++i) {
if (strcmp(sysvar_name, su_mappings[i].sysvar_name) == 0) {
return su_mappings[i].uboot_name;
}
}
return NULL;
}
/*
* set_sysvar_uboot - Sets the given variable to the value specified in the
* uboot env if sysvar name is shared with uboot.
*/
void set_sysvar_uboot(const char *var, const char *value) {
char *uboot_name = get_uboot_name(var);
if (uboot_name) {
printf(">>> mapping SV:'%s' to UB:'%s' . . .\n", var, uboot_name);
setenv(uboot_name, value);
}
}
////////////////////////////////////////////////////////////////////////////////
// Sysvar Flash Impl Functions
////////////////////////////////////////////////////////////////////////////////
/*
* sf_loadvar - load the data from SPI flash to data buffer
*/
int sf_loadvar(struct sysvar_buf *buf, int idx) {
if (data_load(buf, idx)) {
/* TODO(awdavies): Inform the user of potential data recovery failure. */
data_recovery(buf, idx);
}
/* move the data from data buffer to data list */
if (load_var(buf)) {
return SYSVAR_LOAD_ERR;
}
buf->loaded = true;
return SYSVAR_SUCCESS;
}
/*
* sf_savevar - save the data from data buffer to SPI flash
*/
int sf_savevar(struct sysvar_buf *buf, int idx) {
int ret;
int save_idx[2];
/* move the data from data list to data buffer */
if (save_var(buf))
return SYSVAR_SAVE_ERR;
/* erase failed partition first
* part0 part1 erase
* ----- ----- -----
* ok ok 0, 1
* failed ok 0, 1
* ok failed 1, 0
* failed failed 0, 1
*/
if (buf->failed[1]) {
save_idx[0] = idx + 1;
save_idx[1] = idx;
} else {
save_idx[0] = idx;
save_idx[1] = idx + 1;
}
/* save the data from data buffer to SPI flash */
if ((ret = data_save(buf, save_idx)))
return ret;
return SYSVAR_SUCCESS;
}
/*
* sf_getvar - get or print the system variable from data list
*/
int sf_getvar(char *name, char *value, int len) {
struct sysvar_list *var = NULL;
if (name == NULL) {
/* print all system variables(RO) */
print_var(&ro_buf);
/* print all system variables(RW) */
print_var(&rw_buf);
return SYSVAR_SUCCESS;
}
/* find the system variable(RO) */
var = find_var(&ro_buf, name);
if (var != NULL)
goto get_data;
/* find the system variable(RW) */
var = find_var(&rw_buf, name);
if (var != NULL)
goto get_data;
/* system variable not found */
return SYSVAR_GET_ERR;
get_data:
return get_var(var, name, value, len);
}
/*
* sf_setvar - add or delete the system variable in data list
*/
int sf_setvar(struct sysvar_buf *buf, int idx, char *name, char *value) {
struct sysvar_list *var = NULL;
int ret = 0;
if (name != NULL) {
if (idx < SYSVAR_RO_BUF) {
/* read only variable? */
var = find_var(&ro_buf, name);
} else {
/* variable existed? */
var = find_var(&rw_buf, name);
}
if (var != NULL) {
if (idx < SYSVAR_RO_BUF) {
return SYSVAR_READONLY_ERR;
} else {
return SYSVAR_EXISTED_ERR;
}
}
var = find_var(buf, name);
if (var != NULL) {
/* delete system variable */
ret = delete_var(buf, var);
if (ret != SYSVAR_SUCCESS) {
return SYSVAR_DELETE_ERR;
}
/* add system variable */
if (value != NULL) {
ret = set_var(buf, name, value);
}
} else {
/* add system variable */
if (value != NULL) {
ret = set_var(buf, name, value);
} else {
ret = SYSVAR_SET_ERR;
}
}
} else {
/* delete all of system variables */
ret = clear_var(buf);
}
return ret;
}
////////////////////////////////////////////////////////////////////////////////
// Uboot Commands
////////////////////////////////////////////////////////////////////////////////
/*
* do_flloadvar - load system variables from SPI flash
*
* sf_loadvar command:
* sf_loadvar - load system variables from persistent storage
*/
int do_flloadvar(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) {
/* load system variables from SPI flash */
int res, idx;
idx = SYSVAR_RW_BUF;
res = sf_loadvar(&rw_buf, idx);
print_err(res, &idx);
if (res != SYSVAR_SUCCESS) {
return res;
}
idx = SYSVAR_RO_BUF;
res = sf_loadvar(&ro_buf, idx);
print_err(res, &idx);
return res;
}
U_BOOT_CMD(
loadvar, 1, 0, do_flloadvar,
"loadvar - load system variables\n",
" - load system variables from SPI flash\n"
);
/*
* do_flsavevar - save system variables(RW) to SPI flash
*/
int do_flsavevar(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
/* save system variables(RW) */
printf("SV: Saving. . .\n");
int idx = SYSVAR_RW_BUF;
int res = sf_savevar(&rw_buf, idx);
print_err(res, &idx);
return res;
}
U_BOOT_CMD(
savevar, 1, 0, do_flsavevar,
"savevar - save system variables(RW)\n",
" - save system variables(RW) to SPI flash\n"
);
/*
* do_flprintvar - print system variables
*
* printvar command:
* printvar name - print system variable with name
* printvar - print all system variables
*/
int do_flprintvar(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) {
char value[SYSVAR_VALUE];
if (argv[1] == NULL) {
/* print all system variables */
sf_getvar(NULL, NULL, 0);
printf("\nSV: System Variables(RO): %d/%d bytes\n",
ro_buf.used_len, ro_buf.total_len);
printf("SV: System Variables(RW): %d/%d bytes\n",
rw_buf.used_len, rw_buf.total_len);
} else {
/* get a system variable */
if (sf_getvar(argv[1], value, SYSVAR_VALUE) == 0) {
puts(argv[1]);
putc('=');
/* puts value in case CONFIG_SYS_PBSIZE < SYSVAR_VALUE */
puts(value);
putc('\n');
printf("\nSV: System Variable: %d bytes\n",
(int)(SYSVAR_NAME + 2 + strlen(value)));
} else {
printf("## SYSVAR: '%s' not found\n", argv[1]);
}
}
return SYSVAR_SUCCESS;
}
U_BOOT_CMD(
printvar, 2, 0, do_flprintvar,
"printvar - print system variables\n",
" - print values of all system variables\n"
"printvar name ...\n"
" - print value of system variable 'name'\n"
);
/*
* do_flsetvar - add or delete system variables(RW)
*
* setvar command:
* setvar name value - add system variable with name:value
* setvar name - delete system variable with name
* setvar - delete all system variables
*/
int do_flsetvar(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) {
int ret = 0;
if (argc == 3) {
/* add a system variable(RW) */
printf("SV: adding %s\n", argv[1]);
ret = sf_setvar(&rw_buf, SYSVAR_RW_BUF, argv[1], argv[2]);
if (ret == SYSVAR_SUCCESS) {
set_sysvar_uboot(argv[1], argv[2]);
}
} else if (argc == 2) {
/* delete a system variable(RW) */
printf("SV: deleting %s\n", argv[1]);
ret = sf_setvar(&rw_buf, SYSVAR_RW_BUF, argv[1], NULL);
if (ret == SYSVAR_SUCCESS) {
set_sysvar_uboot(argv[1], NULL);
}
} else {
/* delete all system variables(RW) */
printf("SV: deleting all RW vars. . .\n");
sysvar_env_clear_shared(&rw_buf);
ret = sf_setvar(&rw_buf, SYSVAR_RW_BUF, NULL, NULL);
}
print_err(ret, argc > 1 ? argv[1] : NULL);
return ret;
}
U_BOOT_CMD(
setvar, 3, 0, do_flsetvar,
"setvar - set system variables(RW)\n",
"setvar name value ...\n"
" - set system variable(RW) 'name' to 'value ...'\n"
"setvar name\n"
" - delete system variable(RW) 'name'\n"
"setvar\n"
" - delete all system variables(RW)\n"
);
/*
* do_flsysvar - system variable debug functions
*/
int do_flsysvar(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) {
int ret = 0;
int idx;
void *err_arg = NULL;
if (argc < 2)
goto usage;
if (strcmp(argv[1], "set") == 0) {
if (argc == 4) {
/* add a system variable(RO) */
printf("SV: adding %s\n", argv[2]);
ret = sf_setvar(&ro_buf, SYSVAR_RO_BUF, argv[2], argv[3]);
if (ret == SYSVAR_SUCCESS) {
set_sysvar_uboot(argv[2], argv[3]);
}
} else if (argc == 3) {
/* delete a system variable(RO) */
printf("SV: deleting %s\n", argv[2]);
ret = sf_setvar(&ro_buf, SYSVAR_RO_BUF, argv[2], NULL);
if (ret == SYSVAR_SUCCESS) {
set_sysvar_uboot(argv[2], NULL);
}
} else if (argc == 2) {
/* delete all system variables(RO) */
printf("SV: deleting all RO vars. . .\n");
sysvar_env_clear_shared(&ro_buf);
ret = sf_setvar(&ro_buf, SYSVAR_RO_BUF, NULL, NULL);
} else {
goto usage;
}
if (argc > 2) {
err_arg = argv[2];
}
goto err_handle;
}
if (strcmp(argv[1], "save") == 0 && argc == 2) {
/* save system variables(RO) */
printf("SV: saving RO vars. . .\n");
idx = SYSVAR_RO_BUF;
ret = sf_savevar(&ro_buf, idx);
err_arg = &idx;
goto err_handle;
}
if (strcmp(argv[1], "dump") == 0 && argc == 3) {
if (strcmp(argv[2], "rw") == 0) {
/* dump data in data buffer(RW) */
sysvar_dump(&rw_buf, SYSVAR_RW_BUF, true);
} else if (strcmp(argv[2], "ro") == 0) {
/* dump data in data buffer(RO) */
sysvar_dump(&ro_buf, SYSVAR_RO_BUF, true);
} else {
goto usage;
}
return SYSVAR_SUCCESS;
}
if ((strcmp(argv[1], "read") == 0 && argc == 3) ||
(strcmp(argv[1], "write") == 0 && argc == 3) ||
(strcmp(argv[1], "erase") == 0 && argc == 3)) {
ret = sysvar_io(argc - 1, argv + 1);
idx = str_to_int(argv[2], NULL);
err_arg = &idx;
}
err_handle:
print_err(ret, err_arg);
return ret;
usage:
printf("Usage:\n%s\n", cmdtp->usage);
return 1;
}
U_BOOT_CMD(
sysvar, 4, 0, do_flsysvar,
"sysvar - system variable debug functions\n",
"set name value\n"
" - set system variable(RO) 'name' to 'value ...'\n"
"sysvar set name\n"
" - delete system variable(RO) 'name'\n"
"sysvar set\n"
" - delete all system variables(RO)\n"
"sysvar save\n"
" - save system variables(RO) to SPI flash\n"
"sysvar dump rw|ro\n"
" - dump data in data buffer\n"
"sysvar read 0|1|2|3\n"
" - read data from SPI flash 0|1|2|3 to data buffer\n"
"sysvar write 0|1|2|3\n"
" - write data from data buffer to SPI flash 0|1|2|3\n"
"sysvar erase 0|1|2|3\n"
" - erase data on SPI flash 0|1|2|3\n"
);