Subversion Repositories HelenOS

Compare Revisions

Ignore whitespace Rev 4376 → Rev 4377

/branches/tracing/kernel/generic/src/console/kconsole.c
31,10 → 31,11
*/
 
/**
* @file kconsole.c
* @brief Kernel console.
* @file kconsole.c
* @brief Kernel console.
*
* This file contains kernel thread managing the kernel console.
*
*/
 
#include <console/kconsole.h>
49,8 → 50,14
#include <macros.h>
#include <debug.h>
#include <func.h>
#include <string.h>
#include <macros.h>
#include <sysinfo/sysinfo.h>
#include <ddi/device.h>
#include <symtab.h>
#include <macros.h>
#include <errno.h>
#include <putchar.h>
#include <string.h>
 
/** Simple kernel console.
*
59,7 → 66,7
* but makes it possible for other kernel subsystems to
* register their own commands.
*/
 
/** Locking.
*
* There is a list of cmd_info_t structures. This list
74,33 → 81,36
* When locking two cmd info structures, structure with
* lower address must be locked first.
*/
SPINLOCK_INITIALIZE(cmd_lock); /**< Lock protecting command list. */
LIST_INITIALIZE(cmd_head); /**< Command list. */
 
static cmd_info_t *parse_cmdline(char *cmdline, size_t len);
static bool parse_argument(char *cmdline, size_t len, index_t *start,
index_t *end);
static char history[KCONSOLE_HISTORY][MAX_CMDLINE] = {};
SPINLOCK_INITIALIZE(cmd_lock); /**< Lock protecting command list. */
LIST_INITIALIZE(cmd_head); /**< Command list. */
 
/** Initialize kconsole data structures. */
static wchar_t history[KCONSOLE_HISTORY][MAX_CMDLINE] = {};
static count_t history_pos = 0;
 
/** Initialize kconsole data structures
*
* This is the most basic initialization, almost no
* other kernel subsystem is ready yet.
*
*/
void kconsole_init(void)
{
int i;
 
unsigned int i;
cmd_init();
for (i = 0; i < KCONSOLE_HISTORY; i++)
history[i][0] = '\0';
history[i][0] = 0;
}
 
 
/** Register kconsole command.
*
* @param cmd Structure describing the command.
*
* @return 0 on failure, 1 on success.
* @return False on failure, true on success.
*
*/
int cmd_register(cmd_info_t *cmd)
bool cmd_register(cmd_info_t *cmd)
{
link_t *cur;
110,16 → 120,14
* Make sure the command is not already listed.
*/
for (cur = cmd_head.next; cur != &cmd_head; cur = cur->next) {
cmd_info_t *hlp;
cmd_info_t *hlp = list_get_instance(cur, cmd_info_t, link);
hlp = list_get_instance(cur, cmd_info_t, link);
 
if (hlp == cmd) {
/* The command is already there. */
spinlock_unlock(&cmd_lock);
return 0;
return false;
}
 
/* Avoid deadlock. */
if (hlp < cmd) {
spinlock_lock(&hlp->lock);
128,13 → 136,13
spinlock_lock(&cmd->lock);
spinlock_lock(&hlp->lock);
}
if ((strncmp(hlp->name, cmd->name, max(strlen(cmd->name),
strlen(hlp->name))) == 0)) {
if (str_cmp(hlp->name, cmd->name) == 0) {
/* The command is already there. */
spinlock_unlock(&hlp->lock);
spinlock_unlock(&cmd->lock);
spinlock_unlock(&cmd_lock);
return 0;
return false;
}
spinlock_unlock(&hlp->lock);
147,294 → 155,274
list_append(&cmd->link, &cmd_head);
spinlock_unlock(&cmd_lock);
return 1;
return true;
}
 
/** Print count times a character */
static void rdln_print_c(char ch, int count)
static void print_cc(wchar_t ch, count_t count)
{
int i;
count_t i;
for (i = 0; i < count; i++)
putchar(ch);
}
 
/** Insert character to string */
static void insert_char(char *str, char ch, int pos)
/** Try to find a command beginning with prefix */
static const char *cmdtab_search_one(const char *name, link_t **startpos)
{
int i;
count_t namelen = str_length(name);
for (i = strlen(str); i > pos; i--)
str[i] = str[i - 1];
str[pos] = ch;
}
 
/** Try to find a command beginning with prefix */
static const char *cmdtab_search_one(const char *name,link_t **startpos)
{
size_t namelen = strlen(name);
const char *curname;
 
spinlock_lock(&cmd_lock);
 
if (!*startpos)
if (*startpos == NULL)
*startpos = cmd_head.next;
 
for (; *startpos != &cmd_head; *startpos = (*startpos)->next) {
cmd_info_t *hlp;
hlp = list_get_instance(*startpos, cmd_info_t, link);
 
curname = hlp->name;
if (strlen(curname) < namelen)
cmd_info_t *hlp = list_get_instance(*startpos, cmd_info_t, link);
const char *curname = hlp->name;
if (str_length(curname) < namelen)
continue;
if (strncmp(curname, name, namelen) == 0) {
spinlock_unlock(&cmd_lock);
return curname+namelen;
if (str_lcmp(curname, name, namelen) == 0) {
spinlock_unlock(&cmd_lock);
return (curname + str_lsize(curname, namelen));
}
}
spinlock_unlock(&cmd_lock);
spinlock_unlock(&cmd_lock);
return NULL;
}
 
 
/** Command completion of the commands
/** Command completion of the commands
*
* @param name - string to match, changed to hint on exit
* @return number of found matches
* @param name String to match, changed to hint on exit
* @param size Input buffer size
*
* @return Number of found matches
*
*/
static int cmdtab_compl(char *name)
static int cmdtab_compl(char *input, size_t size)
{
static char output[MAX_SYMBOL_NAME + 1];
link_t *startpos = NULL;
const char *foundtxt;
int found = 0;
int i;
 
output[0] = '\0';
while ((foundtxt = cmdtab_search_one(name, &startpos))) {
startpos = startpos->next;
if (!found)
strncpy(output, foundtxt, strlen(foundtxt) + 1);
else {
for (i = 0; output[i] && foundtxt[i] &&
output[i] == foundtxt[i]; i++)
;
output[i] = '\0';
}
const char *name = input;
count_t found = 0;
link_t *pos = NULL;
const char *hint;
char output[MAX_CMDLINE];
output[0] = 0;
while ((hint = cmdtab_search_one(name, &pos))) {
if ((found == 0) || (str_length(output) > str_length(hint)))
str_cpy(output, MAX_CMDLINE, hint);
pos = pos->next;
found++;
}
if (!found)
return 0;
 
if (found > 1 && !strlen(output)) {
if ((found > 1) && (str_length(output) != 0)) {
printf("\n");
startpos = NULL;
while ((foundtxt = cmdtab_search_one(name, &startpos))) {
cmd_info_t *hlp;
hlp = list_get_instance(startpos, cmd_info_t, link);
printf("%s - %s\n", hlp->name, hlp->description);
startpos = startpos->next;
pos = NULL;
while ((hint = cmdtab_search_one(name, &pos))) {
cmd_info_t *hlp = list_get_instance(pos, cmd_info_t, link);
printf("%s (%s)\n", hlp->name, hlp->description);
pos = pos->next;
}
}
strncpy(name, output, MAX_SYMBOL_NAME);
if (found > 0)
str_cpy(input, size, output);
return found;
}
 
static char *clever_readline(const char *prompt, chardev_t *input)
static wchar_t *clever_readline(const char *prompt, indev_t *indev)
{
static int histposition = 0;
 
static char tmp[MAX_CMDLINE + 1];
int curlen = 0, position = 0;
char *current = history[histposition];
int i;
char mod; /* Command Modifier */
char c;
 
printf("%s> ", prompt);
while (1) {
c = _getc(input);
if (c == '\n') {
putchar(c);
count_t position = 0;
wchar_t *current = history[history_pos];
current[0] = 0;
while (true) {
wchar_t ch = indev_pop_character(indev);
if (ch == '\n') {
/* Enter */
putchar(ch);
break;
}
if (c == '\b') { /* Backspace */
if (ch == '\b') {
/* Backspace */
if (position == 0)
continue;
for (i = position; i < curlen; i++)
current[i - 1] = current[i];
curlen--;
position--;
putchar('\b');
for (i = position; i < curlen; i++)
putchar(current[i]);
putchar(' ');
rdln_print_c('\b', curlen - position + 1);
continue;
if (wstr_remove(current, position - 1)) {
position--;
putchar('\b');
printf("%ls ", current + position);
print_cc('\b', wstr_length(current) - position + 1);
continue;
}
}
if (c == '\t') { /* Tabulator */
int found;
 
if (ch == '\t') {
/* Tab completion */
/* Move to the end of the word */
for (; position < curlen && current[position] != ' ';
for (; (current[position] != 0) && (!isspace(current[position]));
position++)
putchar(current[position]);
/* Copy to tmp last word */
for (i = position - 1; i >= 0 && current[i] != ' '; i--)
;
/* If word begins with * or &, skip it */
if (tmp[0] == '*' || tmp[0] == '&')
for (i = 1; tmp[i]; i++)
tmp[i - 1] = tmp[i];
i++; /* I is at the start of the word */
strncpy(tmp, current + i, position - i + 1);
 
if (i == 0) { /* Command completion */
found = cmdtab_compl(tmp);
} else { /* Symtab completion */
found = symtab_compl(tmp);
if (position == 0)
continue;
/* Find the beginning of the word
and copy it to tmp */
count_t beg;
for (beg = position - 1; (beg > 0) && (!isspace(current[beg]));
beg--);
if (isspace(current[beg]))
beg++;
char tmp[STR_BOUNDS(MAX_CMDLINE)];
wstr_nstr(tmp, current + beg, position - beg + 1);
int found;
if (beg == 0) {
/* Command completion */
found = cmdtab_compl(tmp, STR_BOUNDS(MAX_CMDLINE));
} else {
/* Symbol completion */
found = symtab_compl(tmp, STR_BOUNDS(MAX_CMDLINE));
}
 
if (found == 0)
if (found == 0)
continue;
for (i = 0; tmp[i] && curlen < MAX_CMDLINE;
i++, curlen++)
insert_char(current, tmp[i], i + position);
 
if (strlen(tmp) || found == 1) { /* If we have a hint */
for (i = position; i < curlen; i++)
putchar(current[i]);
position += strlen(tmp);
/* Add space to end */
if (found == 1 && position == curlen &&
curlen < MAX_CMDLINE) {
current[position] = ' ';
curlen++;
if (found > 1) {
/* No unique hint, list was printed */
printf("%s> ", prompt);
printf("%ls", current);
print_cc('\b', wstr_length(current) - position);
continue;
}
/* We have a hint */
size_t off = 0;
count_t i = 0;
while ((ch = str_decode(tmp, &off, STR_NO_LIMIT)) != 0) {
if (!wstr_linsert(current, ch, position + i, MAX_CMDLINE))
break;
i++;
}
printf("%ls", current + position);
position += str_length(tmp);
print_cc('\b', wstr_length(current) - position);
if (position == wstr_length(current)) {
/* Insert a space after the last completed argument */
if (wstr_linsert(current, ' ', position, MAX_CMDLINE)) {
printf("%ls", current + position);
position++;
putchar(' ');
}
} else { /* No hint, table was printed */
printf("%s> ", prompt);
for (i = 0; i < curlen; i++)
putchar(current[i]);
position += strlen(tmp);
}
rdln_print_c('\b', curlen - position);
continue;
}
if (c == 0x1b) { /* Special command */
mod = _getc(input);
c = _getc(input);
 
if (mod != 0x5b && mod != 0x4f)
continue;
 
if (c == 0x33 && _getc(input) == 0x7e) {
/* Delete */
if (position == curlen)
continue;
for (i = position + 1; i < curlen; i++) {
putchar(current[i]);
current[i - 1] = current[i];
}
putchar(' ');
rdln_print_c('\b', curlen - position);
curlen--;
} else if (c == 0x48) { /* Home */
rdln_print_c('\b', position);
position = 0;
} else if (c == 0x46) { /* End */
for (i = position; i < curlen; i++)
putchar(current[i]);
position = curlen;
} else if (c == 0x44) { /* Left */
if (position > 0) {
putchar('\b');
position--;
}
continue;
} else if (c == 0x43) { /* Right */
if (position < curlen) {
putchar(current[position]);
position++;
}
continue;
} else if (c == 0x41 || c == 0x42) {
/* Up, down */
rdln_print_c('\b', position);
rdln_print_c(' ', curlen);
rdln_print_c('\b', curlen);
if (c == 0x41) /* Up */
histposition--;
if (ch == U_LEFT_ARROW) {
/* Left */
if (position > 0) {
putchar('\b');
position--;
}
continue;
}
if (ch == U_RIGHT_ARROW) {
/* Right */
if (position < wstr_length(current)) {
putchar(current[position]);
position++;
}
continue;
}
if ((ch == U_UP_ARROW) || (ch == U_DOWN_ARROW)) {
/* Up, down */
print_cc('\b', position);
print_cc(' ', wstr_length(current));
print_cc('\b', wstr_length(current));
if (ch == U_UP_ARROW) {
/* Up */
if (history_pos == 0)
history_pos = KCONSOLE_HISTORY - 1;
else
histposition++;
if (histposition < 0) {
histposition = KCONSOLE_HISTORY - 1;
} else {
histposition =
histposition % KCONSOLE_HISTORY;
}
current = history[histposition];
printf("%s", current);
curlen = strlen(current);
position = curlen;
history_pos--;
} else {
/* Down */
history_pos++;
history_pos = history_pos % KCONSOLE_HISTORY;
}
current = history[history_pos];
printf("%ls", current);
position = wstr_length(current);
continue;
}
if (ch == U_HOME_ARROW) {
/* Home */
print_cc('\b', position);
position = 0;
continue;
}
if (ch == U_END_ARROW) {
/* End */
printf("%ls", current + position);
position = wstr_length(current);
continue;
}
if (ch == U_DELETE) {
/* Delete */
if (position == wstr_length(current))
continue;
if (wstr_remove(current, position)) {
printf("%ls ", current + position);
print_cc('\b', wstr_length(current) - position + 1);
}
continue;
}
if (curlen >= MAX_CMDLINE)
continue;
 
insert_char(current, c, position);
 
curlen++;
for (i = position; i < curlen; i++)
putchar(current[i]);
position++;
rdln_print_c('\b',curlen - position);
}
if (curlen) {
histposition++;
histposition = histposition % KCONSOLE_HISTORY;
if (wstr_linsert(current, ch, position, MAX_CMDLINE)) {
printf("%ls", current + position);
position++;
print_cc('\b', wstr_length(current) - position);
}
}
current[curlen] = '\0';
if (wstr_length(current) > 0) {
history_pos++;
history_pos = history_pos % KCONSOLE_HISTORY;
}
return current;
}
 
/** Kernel console managing thread.
*
* @param prompt Kernel console prompt (e.g kconsole/panic).
*/
void kconsole(void *prompt)
bool kconsole_check_poll(void)
{
cmd_info_t *cmd_info;
count_t len;
char *cmdline;
 
if (!stdin) {
printf("%s: no stdin\n", __func__);
return;
}
while (true) {
cmdline = clever_readline((char *) prompt, stdin);
len = strlen(cmdline);
if (!len)
continue;
cmd_info = parse_cmdline(cmdline, len);
if (!cmd_info)
continue;
if (strncmp(cmd_info->name, "exit",
min(strlen(cmd_info->name), 5)) == 0)
break;
(void) cmd_info->func(cmd_info->argv);
}
return check_poll(stdin);
}
 
static int parse_int_arg(char *text, size_t len, unative_t *result)
static bool parse_int_arg(const char *text, size_t len, unative_t *result)
{
static char symname[MAX_SYMBOL_NAME];
uintptr_t symaddr;
bool isaddr = false;
bool isptr = false;
448,63 → 436,113
text++;
len--;
}
if (text[0] < '0' || text[0] > '9') {
strncpy(symname, text, min(len + 1, MAX_SYMBOL_NAME));
symaddr = get_symbol_addr(symname);
if (!symaddr) {
if ((text[0] < '0') || (text[0] > '9')) {
char symname[MAX_SYMBOL_NAME];
str_ncpy(symname, MAX_SYMBOL_NAME, text, len + 1);
uintptr_t symaddr;
int rc = symtab_addr_lookup(symname, &symaddr);
switch (rc) {
case ENOENT:
printf("Symbol %s not found.\n", symname);
return -1;
}
if (symaddr == (uintptr_t) -1) {
return false;
case EOVERFLOW:
printf("Duplicate symbol %s.\n", symname);
symtab_print_search(symname);
return -1;
return false;
case ENOTSUP:
printf("No symbol information available.\n");
return false;
}
if (isaddr)
*result = (unative_t)symaddr;
*result = (unative_t) symaddr;
else if (isptr)
*result = **((unative_t **)symaddr);
*result = **((unative_t **) symaddr);
else
*result = *((unative_t *)symaddr);
} else { /* It's a number - convert it */
*result = *((unative_t *) symaddr);
} else {
/* It's a number - convert it */
*result = atoi(text);
if (isptr)
*result = *((unative_t *)*result);
*result = *((unative_t *) *result);
}
return true;
}
 
return 0;
/** Parse argument.
*
* Find start and end positions of command line argument.
*
* @param cmdline Command line as read from the input device.
* @param size Size (in bytes) of the string.
* @param start On entry, 'start' contains pointer to the offset
* of the first unprocessed character of cmdline.
* On successful exit, it marks beginning of the next argument.
* @param end Undefined on entry. On exit, 'end' is the offset of the first
* character behind the next argument.
*
* @return False on failure, true on success.
*
*/
static bool parse_argument(const char *cmdline, size_t size, size_t *start, size_t *end)
{
ASSERT(start != NULL);
ASSERT(end != NULL);
bool found_start = false;
size_t offset = *start;
size_t prev = *start;
wchar_t ch;
while ((ch = str_decode(cmdline, &offset, size)) != 0) {
if (!found_start) {
if (!isspace(ch)) {
*start = prev;
found_start = true;
}
} else {
if (isspace(ch))
break;
}
prev = offset;
}
*end = prev;
return found_start;
}
 
/** Parse command line.
*
* @param cmdline Command line as read from input device.
* @param len Command line length.
* @param cmdline Command line as read from input device.
* @param size Size (in bytes) of the string.
*
* @return Structure describing the command.
*
*/
cmd_info_t *parse_cmdline(char *cmdline, size_t len)
static cmd_info_t *parse_cmdline(const char *cmdline, size_t size)
{
index_t start = 0, end = 0;
cmd_info_t *cmd = NULL;
link_t *cur;
count_t i;
int error = 0;
if (!parse_argument(cmdline, len, &start, &end)) {
size_t start = 0;
size_t end = 0;
if (!parse_argument(cmdline, size, &start, &end)) {
/* Command line did not contain alphanumeric word. */
return NULL;
}
 
spinlock_lock(&cmd_lock);
cmd_info_t *cmd = NULL;
link_t *cur;
for (cur = cmd_head.next; cur != &cmd_head; cur = cur->next) {
cmd_info_t *hlp;
hlp = list_get_instance(cur, cmd_info_t, link);
cmd_info_t *hlp = list_get_instance(cur, cmd_info_t, link);
spinlock_lock(&hlp->lock);
if (strncmp(hlp->name, &cmdline[start], max(strlen(hlp->name),
end - start + 1)) == 0) {
if (str_lcmp(hlp->name, cmdline + start,
max(str_length(hlp->name),
str_nlength(cmdline + start, (count_t) (end - start) - 1))) == 0) {
cmd = hlp;
break;
}
512,7 → 550,7
spinlock_unlock(&hlp->lock);
}
spinlock_unlock(&cmd_lock);
spinlock_unlock(&cmd_lock);
if (!cmd) {
/* Unknown command. */
519,7 → 557,7
printf("Unknown command.\n");
return NULL;
}
 
/* cmd == hlp is locked */
/*
528,52 → 566,54
* converted to those specified in the cmd info
* structure.
*/
 
bool error = false;
count_t i;
for (i = 0; i < cmd->argc; i++) {
char *buf;
start = end + 1;
if (!parse_argument(cmdline, len, &start, &end)) {
start = end;
if (!parse_argument(cmdline, size, &start, &end)) {
printf("Too few arguments.\n");
spinlock_unlock(&cmd->lock);
return NULL;
}
error = 0;
char *buf;
switch (cmd->argv[i].type) {
case ARG_TYPE_STRING:
buf = (char *) cmd->argv[i].buffer;
strncpy(buf, (const char *) &cmdline[start],
min((end - start) + 2, cmd->argv[i].len));
buf[min((end - start) + 1, cmd->argv[i].len - 1)] =
'\0';
str_ncpy(buf, cmd->argv[i].len, cmdline + start,
end - start);
break;
case ARG_TYPE_INT:
if (parse_int_arg(cmdline + start, end - start + 1,
case ARG_TYPE_INT:
if (!parse_int_arg(cmdline + start, end - start,
&cmd->argv[i].intval))
error = 1;
error = true;
break;
case ARG_TYPE_VAR:
if (start != end && cmdline[start] == '"' &&
cmdline[end] == '"') {
buf = (char *) cmd->argv[i].buffer;
strncpy(buf, (const char *) &cmdline[start + 1],
min((end-start), cmd->argv[i].len));
buf[min((end - start), cmd->argv[i].len - 1)] =
'\0';
cmd->argv[i].intval = (unative_t) buf;
cmd->argv[i].vartype = ARG_TYPE_STRING;
} else if (!parse_int_arg(cmdline + start,
end - start + 1, &cmd->argv[i].intval)) {
if ((start < end - 1) && (cmdline[start] == '"')) {
if (cmdline[end - 1] == '"') {
buf = (char *) cmd->argv[i].buffer;
str_ncpy(buf, cmd->argv[i].len,
cmdline + start + 1,
(end - start) - 1);
cmd->argv[i].intval = (unative_t) buf;
cmd->argv[i].vartype = ARG_TYPE_STRING;
} else {
printf("Wrong synxtax.\n");
error = true;
}
} else if (parse_int_arg(cmdline + start,
end - start, &cmd->argv[i].intval)) {
cmd->argv[i].vartype = ARG_TYPE_INT;
} else {
printf("Unrecognized variable argument.\n");
error = 1;
error = true;
}
break;
case ARG_TYPE_INVALID:
default:
printf("invalid argument type\n");
error = 1;
printf("Invalid argument type\n");
error = true;
break;
}
}
583,8 → 623,8
return NULL;
}
start = end + 1;
if (parse_argument(cmdline, len, &start, &end)) {
start = end;
if (parse_argument(cmdline, size, &start, &end)) {
printf("Too many arguments.\n");
spinlock_unlock(&cmd->lock);
return NULL;
594,42 → 634,55
return cmd;
}
 
/** Parse argument.
/** Kernel console prompt.
*
* Find start and end positions of command line argument.
* @param prompt Kernel console prompt (e.g kconsole/panic).
* @param msg Message to display in the beginning.
* @param kcon Wait for keypress to show the prompt
* and never exit.
*
* @param cmdline Command line as read from the input device.
* @param len Number of characters in cmdline.
* @param start On entry, 'start' contains pointer to the index
* of first unprocessed character of cmdline.
* On successful exit, it marks beginning of the next argument.
* @param end Undefined on entry. On exit, 'end' points to the last character
* of the next argument.
*
* @return false on failure, true on success.
*/
bool parse_argument(char *cmdline, size_t len, index_t *start, index_t *end)
void kconsole(char *prompt, char *msg, bool kcon)
{
index_t i;
bool found_start = false;
if (!stdin) {
LOG("No stdin for kernel console");
return;
}
ASSERT(start != NULL);
ASSERT(end != NULL);
if (msg)
printf("%s", msg);
for (i = *start; i < len; i++) {
if (!found_start) {
if (isspace(cmdline[i]))
(*start)++;
else
found_start = true;
} else {
if (isspace(cmdline[i]))
break;
}
if (kcon)
indev_pop_character(stdin);
else
printf("Type \"exit\" to leave the console.\n");
while (true) {
wchar_t *tmp = clever_readline((char *) prompt, stdin);
count_t len = wstr_length(tmp);
if (!len)
continue;
char cmdline[STR_BOUNDS(MAX_CMDLINE)];
wstr_nstr(cmdline, tmp, STR_BOUNDS(MAX_CMDLINE));
if ((!kcon) && (len == 4) && (str_lcmp(cmdline, "exit", 4) == 0))
break;
cmd_info_t *cmd_info = parse_cmdline(cmdline, STR_BOUNDS(MAX_CMDLINE));
if (!cmd_info)
continue;
(void) cmd_info->func(cmd_info->argv);
}
*end = i - 1;
}
 
return found_start;
/** Kernel console managing thread.
*
*/
void kconsole_thread(void *data)
{
kconsole("kconsole", "Kernel console ready (press any key to activate)\n", true);
}
 
/** @}