blob: 91e8c5cf7a710c0f61c2381fc0848cd460806ba5 [file] [log] [blame]
/*
* (C) Copyright 2002 ELTEC Elektronik AG
* Frank Gottschling <fgottschling@eltec.de>
*
* See file CREDITS for list of people who contributed to this
* project.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
/* i8042.c - Intel 8042 keyboard driver routines */
/* includes */
#include <common.h>
#ifdef CONFIG_USE_CPCIDVI
extern u8 gt_cpcidvi_in8(u32 offset);
extern void gt_cpcidvi_out8(u32 offset, u8 data);
#define in8(a) gt_cpcidvi_in8(a)
#define out8(a, b) gt_cpcidvi_out8(a, b)
#endif
#include <i8042.h>
/* defines */
#ifdef CONFIG_CONSOLE_CURSOR
extern void console_cursor(int state);
static int blinkCount = CONFIG_SYS_CONSOLE_BLINK_COUNT;
static int cursor_state;
#endif
/*
* Use a simple FIFO to convert some keys into escape sequences and to handle
* tstc vs getc. The FIFO length must be a power of two. Minimal function
* requires that it be large enough to contain the generated escape sequence for
* one key.
*/
#define KBC_FIFO_LENGTH (1 << 3)
static int kbc_fifo[KBC_FIFO_LENGTH];
static int kbc_fifo_read;
static int kbc_fifo_write;
/* locals */
static int kbd_mapping = KBD_US; /* default US keyboard */
static int kbd_flags = NORMAL; /* after reset */
static int kbd_state; /* unshift code */
static int kbd_key_release; /* key release in progress */
static int kbd_conv_usb(unsigned char scan_code);
static void kbd_led_set(int ps2_leds);
static int i8042_ready(void);
static int kbd_reset(void);
/*
* Translate PS/2 Keyboard Scan Code Set 1 values into the USB Scan Codes
*
* Reasons to use USB scan codes:
* o Standard
* o Simple conversion to ASCII / ANSI 3.64
* o Ability to share complex processing / state code
* o Shared international keymaps in higher level code
*
* Extended codes (pseudo PS/2 scan codes >= 0x80) are positionally
* translated from "0xE0 + scan code" using a linear table to save
* data size; we intentionally do not support odd multibyte sequences.
*/
/* Scan codes following a 0xE0 scan code... */
static unsigned char ext_0_key_usb[] = {
0x00, /* UNSUPPORTED: Print Screen (multibyte) */
0x00, /* UNSUPPORTED: Pause (multibyte) */
0x00, /* RESERVED: Keypad 5 (undefined action key) */
0x5E, /* Power */
0x5F, /* RESERVED: Sleep (maps to Keyboard Power) */
0x63, /* RESERVED: Wake (maps to Keyboard Power) */
0x35, /* Keypad Enter */
0x1C, /* Keypad / */
0x5B, /* Left GUI */
0x1D, /* Right Control */
0x38, /* Right Alt / Alt GR */
0x5C, /* Right GUI */
0x52, /* Insert */
0x47, /* Home */
0x49, /* Page Up */
0x53, /* Delete Forward */
0x4F, /* End */
0x51, /* Page Down */
0x4D, /* Right */
0x4B, /* Left */
0x50, /* Down */
0x48 /* Up */
};
#define EXT_0_KEY_USB_SIZE ARRAY_SIZE(ext_0_key_usb)
static unsigned char sc_to_usb[0x80 + EXT_0_KEY_USB_SIZE] = {
0x00, 0x29, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, /* scan 00-07 */
0x24, 0x25, 0x26, 0x27, 0x2D, 0x2E, 0x2A, 0x2B, /* scan 08-0F */
0x14, 0x1A, 0x08, 0x15, 0x17, 0x1C, 0x18, 0x0C, /* scan 10-17 */
0x12, 0x13, 0x2F, 0x30, 0x28, 0xE0, 0x04, 0x16, /* scan 18-1F */
0x07, 0x09, 0x0A, 0x0B, 0x0D, 0x0E, 0x0F, 0x33, /* scan 20-27 */
0x34, 0x35, 0xE1, 0x31, 0x1D, 0x1B, 0x06, 0x19, /* scan 28-2F */
0x05, 0x11, 0x10, 0x36, 0x37, 0x38, 0xE5, 0x55, /* scan 30-37 */
0xE2, 0x2C, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, /* scan 38-3F */
0x3F, 0x40, 0x41, 0x42, 0x43, 0x53, 0x47, 0x5F, /* scan 40-47 */
0x60, 0x61, 0x56, 0x5C, 0x5D, 0x5E, 0x57, 0x59, /* scan 48-4F */
0x5A, 0x5B, 0x62, 0x63, 0x00, 0x00, 0x00, 0x44, /* scan 50-57 */
0x45, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, /* scan 58-5F */
0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* scan 60-67 */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* scan 68-6F */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* scan 70-77 */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* scan 78-7F */
0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x58, 0x54, /* extended*/
0xE3, 0xE4, 0xE6, 0x5C, 0x49, 0x4A, 0x4B, 0x4C, /* extended*/
0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52 /* extended*/
};
/*
* USB scan code to ANSI 3.64 escape sequence table. This table is
* incomplete in that it does not include all possible extra keys, nor
* the 0xE1 report keys.
*/
static struct map364 {
unsigned char usb_scan_code;
char *string;
} usb_to_ansi364[] = {
{ 0x49, "\033[2~"}, /* Insert */
{ 0x4A, "\033[0H"}, /* Home */
{ 0x4B, "\033[5~"}, /* Page Up */
{ 0x4C, "\033[3~"}, /* Delete Forward */
{ 0x4D, "\033[0F"}, /* End */
{ 0x4E, "\033[3~"}, /* Page Down */
{ 0x4F, "\033[C"}, /* Right */
{ 0x50, "\033[D"}, /* Left */
{ 0x51, "\033[B"}, /* Down */
{ 0x52, "\033[A"}, /* Up */
{ 0x3A, "\033[OP" }, /* F1 */
{ 0x3B, "\033[OQ" }, /* F2 */
{ 0x3C, "\033[OR" }, /* F3 */
{ 0x3D, "\033[OS" }, /* F4 */
{ 0x3E, "\033[15~" }, /* F5 */
{ 0x3F, "\033[17~" }, /* F6 */
{ 0x40, "\033[18~" }, /* F7 */
{ 0x41, "\033[19~" }, /* F8 */
{ 0x42, "\033[20~" }, /* F9 */
{ 0x43, "\033[21~" }, /* F10 */
{ 0x44, "\033[23~" }, /* F11 */
{ 0x45, "\033[24~" } /* F12 */
};
#define USB_TO_ANSI364_SIZE ARRAY_SIZE(usb_to_ansi364)
/* Modifier bits */
#define LEFT_CNTR (1 << 0)
#define LEFT_SHIFT (1 << 1)
#define LEFT_ALT (1 << 2)
#define LEFT_GUI (1 << 3)
#define RIGHT_CNTR (1 << 4)
#define RIGHT_SHIFT (1 << 5)
#define RIGHT_ALT (1 << 6)
#define RIGHT_GUI (1 << 7)
/* USB locking modifier keys */
#define USB_KEY_NUM_LOCK 0x53
#define USB_KEY_CAPS_LOCK 0x39
#define USB_KEY_SCROLL_LOCK 0x47
/* Masking for lower to upper case conversion */
#define CAPITAL_MASK 0x20
/* USB LED bit order; the mask is to avoid sending unknown bits */
#define USB_KBD_NUMLOCK (1 << 0)
#define USB_KBD_CAPSLOCK (1 << 1)
#define USB_KBD_SCROLLLOCK (1 << 2)
#define USB_KBD_LEDMASK \
(USB_KBD_NUMLOCK | USB_KBD_CAPSLOCK | USB_KBD_SCROLLLOCK)
/*
* Convert from USB "Set LEDs" command bit order to PS/2 Port 0x60
* Command 0xED bit order.
*/
static unsigned char usb_kbd_led_to_ps2(unsigned char usb_led_bits)
{
return (((usb_led_bits) << 1) & 0x03) |
((usb_led_bits & USB_KBD_SCROLLLOCK) ? 0x01 : 0x00);
}
static unsigned char bits_modifiers; /* individual modifier keys */
static unsigned char bits_state; /* modifier state (includes LED bits) */
/* States for the FSA that manages input of PS/2 scan codes */
enum ps2_kbd_state_t {
KS_BASE = 0, /* Base state */
KS_EXTENDED_0, /* Extended (E0) scan code byte expected */
KS_EXTENDED_1A, /* Extended (E1) scan code first byte expected */
KS_EXTENDED_1B, /* Extended (E1) scan code second byte expected */
};
static enum ps2_kbd_state_t ps2_kbd_state = KS_BASE;
/* Keyboard maps */
static const unsigned char usb_kbd_numkey[] = {
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
'\r', 0x1b, '\b', '\t', ' ', '-', '=', '[', ']',
'\\', '#', ';', '\'', '`', ',', '.', '/'
};
static const unsigned char usb_kbd_numkey_shifted[] = {
'!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
'\r', 0x1b, '\b', '\t', ' ', '_', '+', '{', '}',
'|', '~', ':', '"', '~', '<', '>', '?'
};
static const unsigned char usb_kbd_numkey_ctrled[] = {
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
'\n', 0x1b, '\b', '\t', ' ', '-', '=', '[', ']',
'\\', '#', ';', '\'', '`', ',', '.', '/'
};
/*
* Simple FIFO for conversion of input characters to escape sequences
*/
/* Return true if there are no characters in the FIFO. */
static int kbd_fifo_empty(void)
{
return kbc_fifo_read == kbc_fifo_write;
}
/* Return number of characters of free space in the FIFO. */
static int kbd_fifo_free_space(void)
{
return KBC_FIFO_LENGTH - (kbc_fifo_write - kbc_fifo_read);
}
/*
* Insert a character into the FIFO. Calling this function when the FIFO is
* full will overwrite the oldest character in the FIFO.
*/
static void kbd_fifo_insert(int key)
{
int index = kbc_fifo_write & (KBC_FIFO_LENGTH - 1);
/* Special case for unregocnized keys */
if (key == 0x00)
return;
assert(kbd_fifo_free_space() > 0);
kbc_fifo[index] = key;
kbc_fifo_write++;
}
/*
* Remove a character from the FIFO, it is an error to call this function when
* the FIFO is empty.
*/
static int kbd_fifo_remove(void)
{
int index = kbc_fifo_read & (KBC_FIFO_LENGTH - 1);
int key = kbc_fifo[index];
assert(!kbd_fifo_empty());
kbc_fifo_read++;
return key;
}
/******************************************************************************/
static int kbd_controller_present(void)
{
return in8(I8042_STATUS_REG) != 0xff;
}
/*
* Implement a weak default function for boards that optionally
* need to skip the i8042 initialization.
*/
int __board_i8042_skip(void)
{
/* As default, don't skip */
return 0;
}
int board_i8042_skip(void) __attribute__((weak, alias("__board_i8042_skip")));
/*******************************************************************************
*
* i8042_flush - flush all buffer from EC to host.
* The delay is to give EC some time to fill next byte.
*/
void i8042_flush(void)
{
int timeout;
while (1) {
timeout = 100; /* wait for no longer than 100us */
while (timeout > 0 && (in8(I8042_STATUS_REG) & 0x01) == 0) {
udelay(1);
timeout--;
}
/* Try to pull next byte if not timeout. */
if (in8(I8042_STATUS_REG) & 0x01)
in8(I8042_DATA_REG);
else
return;
}
}
/*******************************************************************************
*
* i8042_disable - Disables the keyboard so that key stroke no longer generates
* scancode to host.
*/
int i8042_disable(void)
{
if (i8042_ready() == 0)
return -1;
/* Disable keyboard */
out8(I8042_COMMAND_REG, 0xad);
if (i8042_ready() == 0)
return -1;
return 0;
}
/*******************************************************************************
*
* i8042_kbd_init - reset keyboard and init state flags
*/
int i8042_kbd_init(void)
{
int keymap, try;
char *penv;
if (!kbd_controller_present() || board_i8042_skip())
return -1;
#ifdef CONFIG_USE_CPCIDVI
penv = getenv("console");
if (penv != NULL) {
if (strncmp(penv, "serial", 7) == 0)
return -1;
}
#endif
/* Init keyboard device (default US layout) */
keymap = KBD_US;
penv = getenv("keymap");
if (penv != NULL) {
if (strncmp(penv, "de", 3) == 0)
keymap = KBD_GER;
}
for (try = 0; try < KBD_RESET_TRIES; try++) {
if (kbd_reset() == 0) {
kbd_mapping = keymap;
kbd_flags = NORMAL;
kbd_state = 0;
kbd_led_set(0); /* Start with LEDs off */
return 0;
}
}
return -1;
}
/*******************************************************************************
*
* i8042_tstc - test if keyboard input is available
* option: cursor blinking if called in a loop
*/
int i8042_tstc(void)
{
unsigned char scan_code = 0;
int kbd_input;
/* If there's something in the fifo, we're done. */
if (!kbd_fifo_empty())
return 1;
#ifdef CONFIG_CONSOLE_CURSOR
if (--blinkCount == 0) {
cursor_state ^= 1;
console_cursor(cursor_state);
blinkCount = CONFIG_SYS_CONSOLE_BLINK_COUNT;
udelay(10);
}
#endif
if ((in8(I8042_STATUS_REG) & I8042_STR_OBF) == 0) {
return 0;
} else {
scan_code = in8(I8042_DATA_REG);
if (scan_code == 0xfa)
return 0;
kbd_input = kbd_conv_usb(scan_code);
if (kbd_input != -1) {
kbd_fifo_insert(kbd_input);
return 1;
}
}
return 0;
}
/******************************************************************************/
/* option: turn on/off cursor while waiting */
static void kbd_fetch_char(int test)
{
unsigned char scan_code;
int kbd_input = -1;
while (kbd_input == -1) {
while ((in8(I8042_STATUS_REG) & I8042_STR_OBF) == 0) {
#ifdef CONFIG_CONSOLE_CURSOR
if (--blinkCount == 0) {
cursor_state ^= 1;
console_cursor(cursor_state);
blinkCount = CONFIG_SYS_CONSOLE_BLINK_COUNT;
}
udelay(10);
#endif
}
scan_code = in8(I8042_DATA_REG);
if (scan_code != 0xfa) {
kbd_input = kbd_conv_usb(scan_code);
if (kbd_input != -1)
break;
}
}
kbd_fifo_insert(kbd_input);
}
/*******************************************************************************
*
* i8042_getc - wait till keyboard input is available
*/
int i8042_getc(void)
{
if (kbd_fifo_empty())
kbd_fetch_char(0);
return kbd_fifo_remove();
}
/******************************************************************************/
static unsigned char scan_code_convert_ps2_usb(unsigned char scan_code)
{
unsigned char cooked_scan_code = (scan_code & 0x7F);
unsigned char usb_scan_code = 0x00; /* keep compiler happy */
int offset;
/* Reset the FSA on illegal scan codes */
if (ps2_kbd_state != KS_BASE &&
(scan_code == 0xE0 || scan_code == 0xE1)) {
ps2_kbd_state = KS_BASE;
return 0x00;
}
/*
* If the high bit is set, this is a break code, otherwise it is a
* make code.
*/
if (scan_code & 0x80)
kbd_key_release = 1;
else
kbd_key_release = 0;
/* PS/2 Scan code FSA */
switch (ps2_kbd_state) {
case KS_BASE:
if (scan_code == 0xE0) {
ps2_kbd_state = KS_EXTENDED_0;
usb_scan_code = 0x00;
break;
}
/* 0xE1 is Only used by the Pause / Break key */
if (scan_code == 0xE1) {
ps2_kbd_state = KS_EXTENDED_1A;
usb_scan_code = 0x00;
break;
}
/*
* Convert the cooked scan code. For unrecognized scan
* codes, we have explicit 0x00 values in the table.
*/
usb_scan_code = sc_to_usb[cooked_scan_code];
break;
case KS_EXTENDED_0:
for (offset = 0; offset < EXT_0_KEY_USB_SIZE; offset++) {
if (cooked_scan_code != ext_0_key_usb[offset])
continue;
usb_scan_code = sc_to_usb[0x80 + offset];
break;
}
/*
* Note: Reset to base here precludes parsing Print Screen
* sequence 0xE0 0x2A 0xE0 0x37, and inverse sequence
* 0xE0 0xB7 0xE0 0xAA (Note inverse break scan code
* order if you intend to handle this case!).
*/
ps2_kbd_state = KS_BASE;
/* Unrecognized scan code following 0xE0 */
if (offset == EXT_0_KEY_USB_SIZE)
usb_scan_code = 0x00;
break;
case KS_EXTENDED_1A:
case KS_EXTENDED_1B:
/* The Pause key sends 2 bytes following the state change */
if (ps2_kbd_state == KS_EXTENDED_1A)
ps2_kbd_state = KS_EXTENDED_1B;
else
ps2_kbd_state = KS_BASE;
/*
* Note: The Pause key make sequence is 0xE1 0x1D 0x45; it
* is always immediately followed by its break sequence
* of 0xE1, 0x9D, 0xC5; there is no persistent make
* state duration for the Pause key, for which reason
* it is normally treated as a toggle by upper level
* software. Handling is not recommended.
*/
usb_scan_code = 0x00;
break;
}
/* Locking modifier keys notify on press and again on release. */
if (!kbd_key_release) {
unsigned char old_bits_state = bits_state;
switch (usb_scan_code) {
case USB_KEY_NUM_LOCK:
bits_state ^= USB_KBD_NUMLOCK;
break;
case USB_KEY_CAPS_LOCK:
bits_state ^= USB_KBD_CAPSLOCK;
break;
case USB_KEY_SCROLL_LOCK:
bits_state ^= USB_KBD_SCROLLLOCK;
break;
}
/* If we changed any bits, poke the LEDs. */
if (old_bits_state != bits_state)
kbd_led_set(usb_kbd_led_to_ps2(bits_state));
}
return usb_scan_code;
}
/*
* For a given USB scan code, cook the value into zero or more character
* codes. Because Ctrl-Space is NUL, we return an integer value which may
* be -1 in the case that the key doesn't result in a character code.
*/
static int usb_cook_scan_code(unsigned char usb_scan_code)
{
int index;
/*
* Handle in-band modifier keys.
*
* Note: These are not generated by USB keyboards with working
* USB 1.1 HID compliant firmware, but broken firmware
* exists. This conversion is therefore safe for all keyboards.
*/
if (usb_scan_code >= 0xE0 && usb_scan_code <= 0xE7) {
unsigned char bit = (1 << (usb_scan_code - 0xE0));
if (kbd_key_release)
bits_modifiers &= ~bit;
else
bits_modifiers |= bit;
return -1;
}
/*
* Key up only changes modifier state.
*
* LATER: Add shadow matrix support so u-boot clients can query the
* state of keys. This will allow the upper level code to
* implement things like continuing to hold the power button
* to enter DFU mode, or use Ctrl-Alt-T to enter target disk
* node, etc..
*/
if (kbd_key_release)
return -1;
/* Handle special sequence keys; this doesn't need performance */
for (index = 0; index < USB_TO_ANSI364_SIZE; index++) {
char *p;
if (usb_scan_code != usb_to_ansi364[index].usb_scan_code)
continue;
/* Stuff the FIFO ourselves; sign coerce static strings */
for (p = usb_to_ansi364[index].string; *p; p++)
kbd_fifo_insert((unsigned char)*p);
/* If not, this is an unsupported key */
return -1;
}
/* Handle numeric keypad keys */
if ((usb_scan_code > 0x1d) && (usb_scan_code < 0x3a)) {
int shifted;
if (bits_modifiers & (LEFT_CNTR | RIGHT_CNTR))
return usb_kbd_numkey_ctrled[usb_scan_code - 0x1e];
/* Shift inverts Num Lock state */
shifted = (bits_modifiers & (LEFT_SHIFT | RIGHT_SHIFT)) != 0;
if (bits_state & USB_KBD_NUMLOCK)
shifted = !shifted;
if (shifted)
return usb_kbd_numkey_shifted[usb_scan_code - 0x1e];
else
return usb_kbd_numkey[usb_scan_code - 0x1e];
}
/* Handle control keys */
/*
* Note: This is a compromise; it gets the right values for the
* right keys, but for unexpected keys, the control character
* sent will depend on the usb_scan_code. This approximates
* PS/2 historical behaviour.
*/
if (bits_modifiers & (LEFT_CNTR | RIGHT_CNTR))
return (usb_scan_code - 0x03) & 0x1F;
/* Handle ordinary alphanumerics */
if ((usb_scan_code > 0x03) && (usb_scan_code <= 0x1D)) {
int keycode = usb_scan_code - 0x04 + 'a';
/* Caps Lock */
if (bits_state & USB_KBD_CAPSLOCK)
keycode &= ~CAPITAL_MASK;
/* Shift inverts Caps Lock state */
if (bits_modifiers & (LEFT_SHIFT | RIGHT_SHIFT)) {
if (keycode & CAPITAL_MASK)
keycode &= ~CAPITAL_MASK;
else
keycode |= CAPITAL_MASK;
}
return keycode;
}
return -1;
}
static int kbd_conv_usb(unsigned char scan_code)
{
unsigned char usbcode;
usbcode = scan_code_convert_ps2_usb(scan_code);
return usb_cook_scan_code(usbcode);
}
/******************************************************************************/
static void kbd_led_set(int ps2_leds)
{
i8042_ready();
out8(I8042_DATA_REG, I8042_DATA_LED_WRITE); /* SET LED command */
i8042_ready();
out8(I8042_DATA_REG, (ps2_leds & I8042_LED_MASK)); /* LED bits only */
}
/*******************************************************************************
*
* i8042_ready - wait for the i8042 to finish processing commands/data
* or a timeout
*/
static int i8042_ready(void)
{
int kbdTimeout = KBD_TIMEOUT * 1000;
while ((in8(I8042_STATUS_REG) & I8042_STR_IBF) && kbdTimeout--)
udelay(1);
return kbdTimeout != -1;
}
/******************************************************************************/
static int kbd_reset(void)
{
if (i8042_ready() == 0)
return -1;
out8(I8042_DATA_REG, I8042_DATA_KBD_RESET);
if (i8042_ready() == 0)
return -1;
out8(I8042_COMMAND_REG, I8042_CMD_SET_CMD_BYTE);
if (i8042_ready() == 0)
return -1;
/*
* Command byte - bitmask:
*
* 7 0 = unused; set to 0
* 6 0 = no scan code conversion
* 1 = standard scan code conversion
* 5 0 = check parity, with scan code conversion
* 1 = ignore parity, no scan code coversion
* 4 0 = Enable keyboard
* 1 = Disable keyboard by forcing CLK low
* 3 0 = Normal keyboard inhibit function
* 1 = Override keyboard inhibit function (used for POST)
* 2 0 = System flag status bit indicates reset by power on
* 1 = System flag after successful controller self test
* 1 0 = unused; set to 0
* 0 0 = Do not interrupt on output buffer full
* 1 = Output buffer full causes interrupt on IRQ 1
*/
out8(I8042_DATA_REG, 0x45);
if (i8042_ready() == 0)
return -1;
out8(I8042_COMMAND_REG, I8042_CMD_ENABLE_KBD);
if (i8042_ready() == 0)
return -1;
return 0;
}