/trunk/kernel/genarch/src/fb/fb.c |
---|
184,12 → 184,13 |
/** Hide logo and refresh screen |
* |
*/ |
static void logo_hide(void) |
static void logo_hide(bool silent) |
{ |
ylogo = 0; |
ytrim = yres; |
rowtrim = rows; |
fb_redraw(); |
if (!silent) |
fb_redraw(); |
} |
196,7 → 197,7 |
/** Draw character at given position |
* |
*/ |
static void glyph_draw(uint8_t glyph, unsigned int col, unsigned int row) |
static void glyph_draw(uint8_t glyph, unsigned int col, unsigned int row, bool silent) |
{ |
unsigned int x = COL2X(col); |
unsigned int y = ROW2Y(row); |
203,13 → 204,15 |
unsigned int yd; |
if (y >= ytrim) |
logo_hide(); |
logo_hide(silent); |
backbuf[BB_POS(col, row)] = glyph; |
for (yd = 0; yd < FONT_SCANLINES; yd++) |
memcpy(&fb_addr[FB_POS(x, y + yd + ylogo)], |
&glyphs[GLYPH_POS(glyph, yd)], glyphscanline); |
if (!silent) { |
for (yd = 0; yd < FONT_SCANLINES; yd++) |
memcpy(&fb_addr[FB_POS(x, y + yd + ylogo)], |
&glyphs[GLYPH_POS(glyph, yd)], glyphscanline); |
} |
} |
217,39 → 220,41 |
* |
* |
*/ |
static void screen_scroll(void) |
static void screen_scroll(bool silent) |
{ |
if (ylogo > 0) { |
logo_hide(); |
logo_hide(silent); |
return; |
} |
unsigned int row; |
for (row = 0; row < rows; row++) { |
unsigned int y = ROW2Y(row); |
unsigned int yd; |
if (!silent) { |
unsigned int row; |
for (yd = 0; yd < FONT_SCANLINES; yd++) { |
unsigned int x; |
unsigned int col; |
for (row = 0; row < rows; row++) { |
unsigned int y = ROW2Y(row); |
unsigned int yd; |
for (col = 0, x = 0; col < cols; col++, |
x += FONT_WIDTH) { |
uint8_t glyph; |
for (yd = 0; yd < FONT_SCANLINES; yd++) { |
unsigned int x; |
unsigned int col; |
if (row < rows - 1) { |
if (backbuf[BB_POS(col, row)] == |
backbuf[BB_POS(col, row + 1)]) |
continue; |
for (col = 0, x = 0; col < cols; col++, |
x += FONT_WIDTH) { |
uint8_t glyph; |
glyph = backbuf[BB_POS(col, row + 1)]; |
} else |
glyph = 0; |
memcpy(&fb_addr[FB_POS(x, y + yd)], |
&glyphs[GLYPH_POS(glyph, yd)], |
glyphscanline); |
if (row < rows - 1) { |
if (backbuf[BB_POS(col, row)] == |
backbuf[BB_POS(col, row + 1)]) |
continue; |
glyph = backbuf[BB_POS(col, row + 1)]; |
} else |
glyph = 0; |
memcpy(&fb_addr[FB_POS(x, y + yd)], |
&glyphs[GLYPH_POS(glyph, yd)], |
glyphscanline); |
} |
} |
} |
} |
259,15 → 264,15 |
} |
static void cursor_put(void) |
static void cursor_put(bool silent) |
{ |
glyph_draw(CURSOR, position % cols, position / cols); |
glyph_draw(CURSOR, position % cols, position / cols, silent); |
} |
static void cursor_remove(void) |
static void cursor_remove(bool silent) |
{ |
glyph_draw(0, position % cols, position / cols); |
glyph_draw(0, position % cols, position / cols, silent); |
} |
276,44 → 281,45 |
* Emulate basic terminal commands. |
* |
*/ |
static void fb_putchar(chardev_t *dev, char ch) |
static void fb_putchar(chardev_t *dev, char ch, bool silent) |
{ |
spinlock_lock(&fb_lock); |
switch (ch) { |
case '\n': |
cursor_remove(); |
cursor_remove(silent); |
position += cols; |
position -= position % cols; |
break; |
case '\r': |
cursor_remove(); |
cursor_remove(silent); |
position -= position % cols; |
break; |
case '\b': |
cursor_remove(); |
cursor_remove(silent); |
if (position % cols) |
position--; |
break; |
case '\t': |
cursor_remove(); |
cursor_remove(silent); |
do { |
glyph_draw((uint8_t) ' ', position % cols, |
position / cols); |
position / cols, silent); |
position++; |
} while ((position % 8) && (position < cols * rows)); |
break; |
default: |
glyph_draw((uint8_t) ch, position % cols, position / cols); |
glyph_draw((uint8_t) ch, position % cols, |
position / cols, silent); |
position++; |
} |
if (position >= cols * rows) { |
position -= cols; |
screen_scroll(); |
screen_scroll(silent); |
} |
cursor_put(); |
cursor_put(silent); |
spinlock_unlock(&fb_lock); |
} |
/trunk/kernel/genarch/src/drivers/ega/ega.c |
---|
62,54 → 62,8 |
static uint8_t *backbuf; |
static ioport_t ega_base; |
static void ega_putchar(chardev_t *d, const char ch); |
chardev_t ega_console; |
static chardev_operations_t ega_ops = { |
.write = ega_putchar |
}; |
static void ega_move_cursor(void); |
void ega_init(ioport_t base, uintptr_t videoram_phys) |
{ |
/* Initialize the software structure. */ |
ega_base = base; |
backbuf = (uint8_t *) malloc(SCREEN * 2, 0); |
if (!backbuf) |
panic("Unable to allocate backbuffer."); |
videoram = (uint8_t *) hw_map(videoram_phys, SCREEN * 2); |
/* Clear the screen and set the cursor position. */ |
memsetw(videoram, SCREEN, 0x0720); |
memsetw(backbuf, SCREEN, 0x0720); |
ega_move_cursor(); |
chardev_initialize("ega_out", &ega_console, &ega_ops); |
stdout = &ega_console; |
ega_parea.pbase = videoram_phys; |
ega_parea.vbase = (uintptr_t) videoram; |
ega_parea.frames = 1; |
ega_parea.cacheable = false; |
ddi_parea_register(&ega_parea); |
sysinfo_set_item_val("fb", NULL, true); |
sysinfo_set_item_val("fb.kind", NULL, 2); |
sysinfo_set_item_val("fb.width", NULL, ROW); |
sysinfo_set_item_val("fb.height", NULL, ROWS); |
sysinfo_set_item_val("fb.blinking", NULL, true); |
sysinfo_set_item_val("fb.address.physical", NULL, videoram_phys); |
} |
static void ega_display_char(char ch) |
{ |
videoram[ega_cursor * 2] = ch; |
backbuf[ega_cursor * 2] = ch; |
} |
/* |
* This function takes care of scrolling. |
*/ |
127,13 → 81,29 |
ega_cursor = ega_cursor - ROW; |
} |
void ega_putchar(chardev_t *d __attribute__((unused)), const char ch) |
static void ega_move_cursor(void) |
{ |
outb(ega_base + EGA_INDEX_REG, 0xe); |
outb(ega_base + EGA_DATA_REG, (uint8_t) ((ega_cursor >> 8) & 0xff)); |
outb(ega_base + EGA_INDEX_REG, 0xf); |
outb(ega_base + EGA_DATA_REG, (uint8_t) (ega_cursor & 0xff)); |
} |
static void ega_display_char(char ch, bool silent) |
{ |
backbuf[ega_cursor * 2] = ch; |
if (!silent) |
videoram[ega_cursor * 2] = ch; |
} |
static void ega_putchar(chardev_t *d __attribute__((unused)), const char ch, bool silent) |
{ |
ipl_t ipl; |
ipl = interrupts_disable(); |
spinlock_lock(&egalock); |
switch (ch) { |
case '\n': |
ega_cursor = (ega_cursor + ROW) - ega_cursor % ROW; |
146,23 → 116,54 |
ega_cursor--; |
break; |
default: |
ega_display_char(ch); |
ega_display_char(ch, silent); |
ega_cursor++; |
break; |
} |
ega_check_cursor(); |
ega_move_cursor(); |
if (!silent) |
ega_move_cursor(); |
spinlock_unlock(&egalock); |
interrupts_restore(ipl); |
} |
void ega_move_cursor(void) |
static chardev_operations_t ega_ops = { |
.write = ega_putchar |
}; |
void ega_init(ioport_t base, uintptr_t videoram_phys) |
{ |
outb(ega_base + EGA_INDEX_REG, 0xe); |
outb(ega_base + EGA_DATA_REG, (uint8_t) ((ega_cursor >> 8) & 0xff)); |
outb(ega_base + EGA_INDEX_REG, 0xf); |
outb(ega_base + EGA_DATA_REG, (uint8_t) (ega_cursor & 0xff)); |
/* Initialize the software structure. */ |
ega_base = base; |
backbuf = (uint8_t *) malloc(SCREEN * 2, 0); |
if (!backbuf) |
panic("Unable to allocate backbuffer."); |
videoram = (uint8_t *) hw_map(videoram_phys, SCREEN * 2); |
/* Clear the screen and set the cursor position. */ |
memsetw(videoram, SCREEN, 0x0720); |
memsetw(backbuf, SCREEN, 0x0720); |
ega_move_cursor(); |
chardev_initialize("ega_out", &ega_console, &ega_ops); |
stdout = &ega_console; |
ega_parea.pbase = videoram_phys; |
ega_parea.vbase = (uintptr_t) videoram; |
ega_parea.frames = 1; |
ega_parea.cacheable = false; |
ddi_parea_register(&ega_parea); |
sysinfo_set_item_val("fb", NULL, true); |
sysinfo_set_item_val("fb.kind", NULL, 2); |
sysinfo_set_item_val("fb.width", NULL, ROW); |
sysinfo_set_item_val("fb.height", NULL, ROWS); |
sysinfo_set_item_val("fb.blinking", NULL, true); |
sysinfo_set_item_val("fb.address.physical", NULL, videoram_phys); |
} |
void ega_redraw(void) |
/trunk/kernel/generic/include/console/chardev.h |
---|
50,7 → 50,7 |
/** Resume pushing characters. */ |
void (* resume)(struct chardev *); |
/** Write character to stream. */ |
void (* write)(struct chardev *, char c); |
void (* write)(struct chardev *, char c, bool silent); |
/** Read character directly from device, assume interrupts disabled. */ |
char (* read)(struct chardev *); |
} chardev_operations_t; |
/trunk/kernel/generic/include/console/console.h |
---|
49,6 → 49,9 |
extern count_t gets(chardev_t *chardev, char *buf, size_t buflen); |
extern void putchar(char c); |
extern void grab_console(void); |
extern void release_console(void); |
extern void arch_grab_console(void); |
extern void arch_release_console(void); |
/trunk/kernel/generic/include/syscall/syscall.h |
---|
79,6 → 79,7 |
SYS_SYSINFO_VALUE, |
SYS_DEBUG_ENABLE_CONSOLE, |
SYS_DEBUG_DISABLE_CONSOLE, |
SYS_IPC_CONNECT_KBOX, |
SYSCALL_END |
} syscall_t; |
/trunk/kernel/generic/src/console/console.c |
---|
65,12 → 65,15 |
/**< Number of stored kernel log characters for uspace */ |
static size_t klog_uspace = 0; |
/**< Silent output */ |
static bool silent = false; |
/**< Kernel log spinlock */ |
SPINLOCK_INITIALIZE(klog_lock); |
/** Physical memory area used for klog buffer */ |
static parea_t klog_parea; |
/* |
* For now, we use 0 as INR. |
* However, it is therefore desirable to have architecture specific |
144,6 → 147,18 |
spinlock_unlock(&klog_lock); |
} |
void grab_console(void) |
{ |
silent = false; |
arch_grab_console(); |
} |
void release_console(void) |
{ |
silent = true; |
arch_release_console(); |
} |
/** Get character from character device. Do not echo character. |
* |
* @param chardev Character device. |
199,7 → 214,7 |
{ |
index_t index = 0; |
char ch; |
while (index < buflen) { |
ch = _getc(chardev); |
if (ch == '\b') { |
213,7 → 228,7 |
continue; |
} |
putchar(ch); |
if (ch == '\n') { /* end of string => write 0, return */ |
buf[index] = '\0'; |
return (count_t) index; |
253,7 → 268,7 |
/* Print charaters stored in kernel log */ |
index_t i; |
for (i = klog_len - klog_stored; i < klog_len; i++) |
stdout->op->write(stdout, klog[(klog_start + i) % KLOG_SIZE]); |
stdout->op->write(stdout, klog[(klog_start + i) % KLOG_SIZE], silent); |
klog_stored = 0; |
} |
265,7 → 280,7 |
klog_start = (klog_start + 1) % KLOG_SIZE; |
if (stdout->op->write) |
stdout->op->write(stdout, c); |
stdout->op->write(stdout, c, silent); |
else { |
/* The character is just in the kernel log */ |
if (klog_stored < klog_len) |
/trunk/kernel/generic/src/console/cmd.c |
---|
976,7 → 976,7 |
int cmd_continue(cmd_arg_t *argv) |
{ |
printf("The kernel will now relinquish the console.\n"); |
arch_release_console(); |
release_console(); |
if ((kconsole_notify) && (kconsole_irq.notif_cfg.notify)) |
ipc_irq_send_msg_0(&kconsole_irq); |
/trunk/kernel/generic/src/syscall/syscall.c |
---|
92,7 → 92,7 |
static unative_t sys_debug_enable_console(void) |
{ |
#ifdef CONFIG_KCONSOLE |
arch_grab_console(); |
grab_console(); |
return true; |
#else |
return false; |
99,6 → 99,13 |
#endif |
} |
/** Tell kernel to relinquish keyboard/console access */ |
static unative_t sys_debug_disable_console(void) |
{ |
release_console(); |
return true; |
} |
/** Dispatch system call */ |
unative_t syscall_handler(unative_t a1, unative_t a2, unative_t a3, |
unative_t a4, unative_t a5, unative_t a6, unative_t id) |
184,7 → 191,8 |
/* Debug calls */ |
(syshandler_t) sys_debug_enable_console, |
(syshandler_t) sys_debug_disable_console, |
(syshandler_t) sys_ipc_connect_kbox |
}; |
/trunk/kernel/arch/sparc64/src/drivers/sgcn.c |
---|
295,16 → 295,17 |
* feed character is written ('\n'), the carriage return character ('\r') is |
* written straight away. |
*/ |
static void sgcn_putchar(struct chardev * cd, const char c) |
static void sgcn_putchar(struct chardev * cd, const char c, bool silent) |
{ |
spinlock_lock(&sgcn_output_lock); |
sgcn_do_putchar(c); |
if (c == '\n') { |
sgcn_do_putchar('\r'); |
if (!silent) { |
spinlock_lock(&sgcn_output_lock); |
sgcn_do_putchar(c); |
if (c == '\n') |
sgcn_do_putchar('\r'); |
spinlock_unlock(&sgcn_output_lock); |
} |
spinlock_unlock(&sgcn_output_lock); |
} |
/** |
/trunk/kernel/arch/ia64/src/ia64.c |
---|
254,8 → 254,8 |
ns16550_grab(); |
#else |
i8042_grab(); |
#endif |
#endif |
#endif |
#endif |
} |
/** Return console to userspace |
/trunk/kernel/arch/ia64/src/ski/ski.c |
---|
56,9 → 56,6 |
static bool kbd_disabled; |
static void ski_putchar(chardev_t *d, const char ch); |
static int32_t ski_getchar(void); |
/** Display character on debug console |
* |
* Use SSC (Simulator System Call) to |
67,19 → 64,21 |
* @param d Character device. |
* @param ch Character to be printed. |
*/ |
void ski_putchar(chardev_t *d, const char ch) |
static void ski_putchar(chardev_t *d, const char ch, bool silent) |
{ |
asm volatile ( |
"mov r15 = %0\n" |
"mov r32 = %1\n" /* r32 is in0 */ |
"break 0x80000\n" /* modifies r8 */ |
: |
: "i" (SKI_PUTCHAR), "r" (ch) |
: "r15", "in0", "r8" |
); |
if (ch == '\n') |
ski_putchar(d, '\r'); |
if (!silent) { |
asm volatile ( |
"mov r15 = %0\n" |
"mov r32 = %1\n" /* r32 is in0 */ |
"break 0x80000\n" /* modifies r8 */ |
: |
: "i" (SKI_PUTCHAR), "r" (ch) |
: "r15", "in0", "r8" |
); |
if (ch == '\n') |
ski_putchar(d, '\r'); |
} |
} |
/** Ask debug console if a key was pressed. |
91,7 → 90,7 |
* |
* @return ASCII code of pressed key or 0 if no key pressed. |
*/ |
int32_t ski_getchar(void) |
static int32_t ski_getchar(void) |
{ |
uint64_t ch; |
/trunk/kernel/arch/arm32/include/machine.h |
---|
102,18 → 102,17 |
extern uintptr_t machine_get_fb_address(void); |
#ifdef MACHINE_GXEMUL_TESTARM |
#define machine_console_init(devno) gxemul_console_init(devno) |
#define machine_grab_console gxemul_grab_console |
#define machine_release_console gxemul_release_console |
#define machine_hw_map_init gxemul_hw_map_init |
#define machine_timer_irq_start gxemul_timer_irq_start |
#define machine_cpu_halt gxemul_cpu_halt |
#define machine_get_memory_size gxemul_get_memory_size |
#define machine_debug_putc(ch) gxemul_debug_putc(ch) |
#define machine_irq_exception(exc_no, istate) \ |
gxemul_irq_exception(exc_no, istate) |
#define machine_get_fb_address gxemul_get_fb_address |
#ifdef MACHINE_GXEMUL_TESTARM |
#define machine_console_init(devno) gxemul_console_init(devno) |
#define machine_grab_console gxemul_grab_console |
#define machine_release_console gxemul_release_console |
#define machine_hw_map_init gxemul_hw_map_init |
#define machine_timer_irq_start gxemul_timer_irq_start |
#define machine_cpu_halt gxemul_cpu_halt |
#define machine_get_memory_size gxemul_get_memory_size |
#define machine_debug_putc(ch) gxemul_debug_putc(ch) |
#define machine_irq_exception(exc_no, istate) gxemul_irq_exception(exc_no, istate) |
#define machine_get_fb_address gxemul_get_fb_address |
#endif |
#endif |
/trunk/kernel/arch/arm32/src/drivers/gxemul.c |
---|
133,9 → 133,10 |
* @param dev Not used. |
* @param ch Characted to be printed. |
*/ |
static void gxemul_write(chardev_t *dev, const char ch) |
static void gxemul_write(chardev_t *dev, const char ch, bool silent) |
{ |
*((char *) gxemul_hw_map.videoram) = ch; |
if (!silent) |
*((char *) gxemul_hw_map.videoram) = ch; |
} |
/** Enables gxemul keyboard (interrupt unmasked). |
/trunk/kernel/arch/ppc32/src/ppc32.c |
---|
147,8 → 147,7 |
(uintptr_t) kernel_uarg->uspace_entry); |
/* Unreachable */ |
for (;;) |
; |
while (true); |
} |
/** Acquire console back for kernel |
/trunk/kernel/arch/ia32xen/src/drivers/xconsole.c |
---|
55,28 → 55,30 |
stdout = &xen_console; |
} |
void xen_putchar(chardev_t *d, const char ch) |
void xen_putchar(chardev_t *d, const char ch, bool silent) |
{ |
if (start_info.console.domU.evtchn != 0) { |
uint32_t cons = console_page.out_cons; |
uint32_t prod = console_page.out_prod; |
memory_barrier(); |
if ((prod - cons) > sizeof(console_page.out)) |
return; |
if (ch == '\n') |
console_page.out[MASK_INDEX(prod++, console_page.out)] = '\r'; |
console_page.out[MASK_INDEX(prod++, console_page.out)] = ch; |
write_barrier(); |
console_page.out_prod = prod; |
xen_notify_remote(start_info.console.domU.evtchn); |
} else |
xen_console_io(CONSOLE_IO_WRITE, 1, &ch); |
if (!silent) { |
if (start_info.console.domU.evtchn != 0) { |
uint32_t cons = console_page.out_cons; |
uint32_t prod = console_page.out_prod; |
memory_barrier(); |
if ((prod - cons) > sizeof(console_page.out)) |
return; |
if (ch == '\n') |
console_page.out[MASK_INDEX(prod++, console_page.out)] = '\r'; |
console_page.out[MASK_INDEX(prod++, console_page.out)] = ch; |
write_barrier(); |
console_page.out_prod = prod; |
xen_notify_remote(start_info.console.domU.evtchn); |
} else |
xen_console_io(CONSOLE_IO_WRITE, 1, &ch); |
} |
} |
/** @} |
/trunk/kernel/arch/mips32/src/drivers/serial.c |
---|
26,7 → 26,7 |
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
*/ |
/** @addtogroup mips32 |
/** @addtogroup mips32 |
* @{ |
*/ |
/** @file |
46,16 → 46,18 |
static serial_t sconf[SERIAL_MAX]; |
static bool kb_enabled; |
static void serial_write(chardev_t *d, const char ch) |
static void serial_write(chardev_t *d, const char ch, bool silent) |
{ |
serial_t *sd = (serial_t *)d->data; |
if (ch == '\n') |
serial_write(d, '\r'); |
/* Wait until transmit buffer empty */ |
while (! (SERIAL_READ_LSR(sd->port) & (1<<TRANSMIT_EMPTY_BIT))) |
; |
SERIAL_WRITE(sd->port, ch); |
if (!silent) { |
serial_t *sd = (serial_t *)d->data; |
if (ch == '\n') |
serial_write(d, '\r'); |
/* Wait until transmit buffer empty */ |
while (!(SERIAL_READ_LSR(sd->port) & (1 << TRANSMIT_EMPTY_BIT))); |
SERIAL_WRITE(sd->port, ch); |
} |
} |
static void serial_enable(chardev_t *d) |
133,8 → 135,7 |
void serial_console(devno_t devno) |
{ |
serial_t *sd = &sconf[0]; |
chardev_initialize("serial_console", &console, &serial_ops); |
console.data = sd; |
kb_enabled = true; |
145,10 → 146,9 |
serial_irq.claim = serial_claim; |
serial_irq.handler = serial_irq_handler; |
irq_register(&serial_irq); |
/* I don't know why, but the serial interrupts simply |
* don't work on simics |
*/ |
don't work on simics */ |
virtual_timer_fnc = &serial_handler; |
stdin = &console; |
/trunk/kernel/arch/mips32/src/drivers/msim.c |
---|
26,7 → 26,7 |
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
*/ |
/** @addtogroup mips32 |
/** @addtogroup mips32 |
* @{ |
*/ |
/** @file |
58,9 → 58,10 |
}; |
/** Putchar that works with MSIM & gxemul */ |
void msim_write(chardev_t *dev, const char ch) |
void msim_write(chardev_t *dev, const char ch, bool silent) |
{ |
*((char *) MSIM_VIDEORAM) = ch; |
if (!silent) |
*((char *) MSIM_VIDEORAM) = ch; |
} |
/* Called from getc(). */ |
80,7 → 81,7 |
static char msim_do_read(chardev_t *dev) |
{ |
char ch; |
while (1) { |
ch = *((volatile char *) MSIM_KBD_ADDRESS); |
if (ch) { |
101,12 → 102,12 |
else { |
char ch = 0; |
ch = *((char *) MSIM_KBD_ADDRESS); |
if (ch =='\r') |
ch = '\n'; |
if (ch == 0x7f) |
ch = '\b'; |
chardev_push_character(&console, ch); |
ch = *((char *) MSIM_KBD_ADDRESS); |
if (ch =='\r') |
ch = '\n'; |
if (ch == 0x7f) |
ch = '\b'; |
chardev_push_character(&console, ch); |
} |
} |
/trunk/uspace/srv/console/console.c |
---|
500,7 → 500,6 |
async_set_client_connection(client_connection); |
/* Connect to keyboard driver */ |
kbd_phone = ipc_connect_me_to(PHONE_NS, SERVICE_KEYBOARD, 0, 0); |
while (kbd_phone < 0) { |
usleep(10000); |
518,6 → 517,9 |
fb_info.phone = ipc_connect_me_to(PHONE_NS, SERVICE_VIDEO, 0, 0); |
} |
/* Disable kernel output to the console */ |
__SYSCALL0(SYS_DEBUG_DISABLE_CONSOLE); |
/* Initialize gcons */ |
gcons_init(fb_info.phone); |
/* Synchronize, the gcons can have something in queue */ |
587,6 → 589,6 |
return 0; |
} |
/** @} |
*/ |