Initial commit of OpenSPARC T2 architecture model.
[OpenSPARC-T2-SAM] / sam-t2 / sam / devices / remote / remote.cc
// ========== Copyright Header Begin ==========================================
//
// OpenSPARC T2 Processor File: remote.cc
// Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved.
// DO NOT ALTER OR REMOVE COPYRIGHT NOTICES.
//
// The above named program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License version 2 as published by the Free Software Foundation.
//
// The above named 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 work; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
//
// ========== Copyright Header End ============================================
/////////////////////////////////////////////////////////////////////
//
// FIle: remote.cc
//
// Copyright (C) 2005 Sun Microsystems, Inc.
// All rights reserved.
//
//
// Frontend socket interface for the debugger
//
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/conf.h>
#include <stropts.h>
#include <signal.h>
#include <netinet/in.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h> /* for memset in FD_ZERO */
#include <errno.h>
#include <thread.h>
#include <stdlib.h>
#include <assert.h>
#include "types.h"
#include "cpu_interface.h"
#include "remote.h"
// output debug log messages
int g_debug_on = 1;
#define DEBUG(s) if(g_debug_on){s}
#define SWAP_BIT_5_0(I) ((( (I) & 0x01e ) | ( ((I) & 0x020) >> 5 )) & 0x1f)
const int FOREVER = 1;
// command delimiters
const char START_CHR = '$';
const char END_CHR = '#';
typedef enum INSTATE
{
UNATTACHED,
STOPPED,
RUNNING
} state_t;
const int MAX_BUF_SIZE = 8192;
// external routines called from this module
extern int cpu_set_breakpoint ( int cpu_id, VCPU_BpType type, uint64_t addr );
extern int cpu_remove_breakpoint ( int cpu_id, VCPU_BpType type, uint64_t addr );
extern void UI_exec_cmd ( char *cmd );
// Convert number NIB to a hex digit.
static int tohex (int nib)
{
if (nib < 10)
return '0' + nib;
else
return 'a' + nib - 10;
}
// Convert hex digit A to a number.
static int fromhex (uint8_t a)
{
int value = 0;
if (a >= '0' && a <= '9')
value = a - '0';
else if (a >= 'a' && a <= 'f')
value = a - 'a' + 10;
else if (a >= 'A' && a <= 'F')
value = a - 'A' + 10;
else
printf ("Reply contains invalid hex digit %d", a);
return value & 0xf;
}
// read next parameter from the command string;
// return param value;
// set string pointer to the next param
static uint64_t read_next_param ( uint8_t **p )
{
uint64_t value = 0;
if (p != NULL)
{
// remove all spaces
while((*p)[0] == ' ')
(*p)++;
for (int i=0; i<100; i++)
{
uint8_t c = (*p)[i];
if (c == '#' || c == '\0')
{
// last character
*p = NULL;
break;
}
else if (c == ',' || c == ':' || c == ' ')
{
// end of parameter
(*p)+=i+1;
break;
}
else
{
// convert from hex
value = (value<<4) | fromhex(c);
}
}
}
return value;
}
static uint64_t get_value
(
uint8_t **buf, // buffer to get value from
int size // size of the value
)
{
assert( size <= 8 );
int i = 0;
uint64_t value = 0;
for (int offset = size * 8 - 4; offset>=0; offset -= 4)
value = value | fromhex ( (*buf)[i++] ) << offset;
*buf += i; // update buffer pointer
return value;
}
static void put_value
(
uint8_t **buf, // buffer to put a hex string value
uint64_t val, // value
int size // number of bytes ( should less or equal 8 )
)
{
assert( size <= 8 );
int i = 0;
for (int offset = size * 8 - 4; offset>=0; offset -= 4)
{
(*buf)[i++] = uint8_t( tohex (int(val >> offset)&0xf ));
}
*buf += i; // update the buffer pointer
}
class SocketInterface
{
public:
int id; // connection id
SocketInterface *next;
int socket;
state_t state;
thread_t thread_id;
int cpu_id; // current cpu id
uint64_t symtab_base;
char * symtab_fname;
// communication buffers
uint8_t send_buffer[MAX_BUF_SIZE];
uint8_t recv_buffer[MAX_BUF_SIZE];
public:
SocketInterface ()
{
id = 0;
next = NULL;
state = UNATTACHED;
thread_id = 0;
cpu_id = 0;
}
~SocketInterface () { close(socket); }
// send an ack - command was recieved
void reply_ack ()
{
uint8_t ack[4] = {'+',0};
DEBUG( printf("send an ack : + ; "); );
write( socket, ack, 1);
}
void reply_nack ()
{
uint8_t nack[4] = {'-',0};
DEBUG( printf("send a nack : -\n"); );
write( socket, nack, 1);
}
void reply_ok ()
{
send_buffer[1] = 'O';
send_buffer[2] = 'K';
this->send(3);
}
void reply_not_implemented ()
{
this->send(0);
}
// send error number in hex
void reply_error ( uint32_t error_number )
{
send_buffer[1] = 'E';
send_buffer[2] = tohex ((error_number >> 4) & 0xf);
send_buffer[3] = tohex (error_number & 0xf);
this->send (4);
}
// send target stop reason in hex
void reply_target_stopped (uint32_t reason)
{
send_buffer[1] = 'S';
send_buffer[2] = tohex ((reason >> 4) & 0xf);
send_buffer[3] = tohex (reason & 0xf);
this->send (4);
}
// send a data from the send_buffer;
// add first, last control symbols and a checksum
int send ( int size )
{
int n = 0;
// first control char
send_buffer[n++] = START_CHR;
uint8_t check_sum = 0;
while (n<size)
{
if ( n > MAX_BUF_SIZE-3 )
return -1;
uint8_t c = send_buffer[n];
// check null terminated
if ( c == '\0')
break;
check_sum += c;
n++;
}
// insert end control char
send_buffer[n++] = END_CHR;
// insert a check sum
send_buffer[n++] = tohex ((check_sum >> 4) & 0xf);
send_buffer[n++] = tohex (check_sum & 0xf);
send_buffer[n] = 0; // null terminate
DEBUG( printf("\n intf %d send a reply :%s \n", id, send_buffer); );
uint64_t res = write( socket, send_buffer, n);
return (res != n) ? -1 : 0;
}
// read a command into recieve buffer;
// overwrite a previous command if it was there;
// return the number of bytes read,-1 if error
int recv ()
{
uint8_t c;
int n = 0;
int packet_started = 0;
while ( n < MAX_BUF_SIZE )
{
// read one character
if ( read (socket, &c, 1) == 1 )
{
// one character recieved
if ( c == START_CHR)
{
// begin a new command
n = 0;
packet_started = 1;
continue;
}
if ( c == END_CHR)
{
// last character in the command
recv_buffer[n] = 0; // null terminated
return n;
}
// accumulate the characters
if (packet_started)
{
recv_buffer[n] = c;
}
n++;
}
}
return (-1); // data is incomplete
}
// read one register value from the recieve buffer
int read_reg ()
{
uint8_t *args = recv_buffer+1;
int reg_idx = (int)read_next_param ( &args );
uint8_t *sbuf = send_buffer+1; // skip first control char
uint64_t value = 0;
int size = 8;
// read 8 byte gpr value
if (reg_idx < 32)
{
g_vcpu[cpu_id]->get_reg(VCPU_IRF_0 + reg_idx, &value);
put_value( &sbuf, value, 8);
}
else if (reg_idx < 64) // read 4 byte fpr value
{
g_vcpu[cpu_id]->get_reg(VCPU_FRF_0 + reg_idx - 32, &value);
put_value( &sbuf, value, 4);
}
else if (reg_idx < 80) // read 8 byte fpr value
{
g_vcpu[cpu_id]->get_reg(VCPU_DRF_0 + reg_idx - 64 + 16, &value);
put_value( &sbuf, value, 8);
}
else if (reg_idx < 90) // read control reg value
{
switch(reg_idx)
{
case 80: g_vcpu[cpu_id]->get_reg(VCPU_ASR_PC, &value); break;
case 81: g_vcpu[cpu_id]->get_reg(VCPU_ASR_NPC, &value); break;
case 82: g_vcpu[cpu_id]->get_reg(VCPU_PR_PSTATE, &value); break;
case 83: g_vcpu[cpu_id]->get_reg(VCPU_ASR_FSR, &value); break;
case 84: g_vcpu[cpu_id]->get_reg(VCPU_ASR_FPRS, &value); break;
case 85: g_vcpu[cpu_id]->get_reg(VCPU_ASR_Y, &value); break;
case 86: g_vcpu[cpu_id]->get_reg(VCPU_PR_CWP, &value); break;
case 87: g_vcpu[cpu_id]->get_reg(VCPU_PR_PSTATE, &value); break;
case 88: g_vcpu[cpu_id]->get_reg(VCPU_ASR_ASI, &value); break;
case 89: g_vcpu[cpu_id]->get_reg(VCPU_ASR_CCR, &value); break;
}
put_value( &sbuf, value, 8);
}
else if (reg_idx < 122) // read 8 byte fpr value
{
g_vcpu[cpu_id]->get_reg(VCPU_DRF_0 + reg_idx - 90, &value);
put_value( &sbuf, value, 8);
}
else if (reg_idx < 138) // read 16 byte fpr value
{
int regnum = (reg_idx - 122) * 2;
g_vcpu[cpu_id]->get_reg(VCPU_DRF_0 + regnum, &value);
put_value( &sbuf, value, 8);
g_vcpu[cpu_id]->get_reg(VCPU_DRF_0 + regnum + 1, &value);
put_value( &sbuf, value, 8);
}
else
{
reply_error (0);
return 1;
}
int msg_size = int(sbuf - send_buffer);
send(msg_size);
return 0;
}
int read_all_regs ()
{
uint8_t *sbuf = send_buffer+1; // skip first control char
// read all GPRs
for (int i=0; i<32; i++)
{
uint64_t gpr;
g_vcpu[cpu_id]->get_reg(VCPU_IRF_0 + i, &gpr);
put_value( &sbuf, gpr, 8);
}
// read all 32 bit fp regs
for (int i=0; i<32; i++)
{
uint64_t fpr = 0;
g_vcpu[cpu_id]->get_reg(VCPU_FRF_0 + i, &fpr);
put_value( &sbuf, fpr, 4);
}
// read all 64 bit fp regs
for (int i=32; i<64; i+=2)
{
// swap bit 5 and 0
uint64_t dfpr = 0;
g_vcpu[cpu_id]->get_reg(VCPU_DRF_0 + (i / 2), &dfpr);
put_value( &sbuf, dfpr, 8);
}
// read control registers
uint64_t reg;
g_vcpu[cpu_id]->get_reg(VCPU_ASR_PC, &reg);
put_value( &sbuf, reg, 8);
g_vcpu[cpu_id]->get_reg(VCPU_ASR_NPC, &reg);
put_value( &sbuf, reg, 8);
g_vcpu[cpu_id]->get_reg(VCPU_PR_PSTATE, &reg);
put_value( &sbuf, reg, 8);
g_vcpu[cpu_id]->get_reg(VCPU_ASR_FSR, &reg);
put_value( &sbuf, reg, 8);
g_vcpu[cpu_id]->get_reg(VCPU_ASR_FPRS, &reg);
put_value( &sbuf, reg, 8);
g_vcpu[cpu_id]->get_reg(VCPU_ASR_Y, &reg);
put_value( &sbuf, reg, 8);
int msg_size = int(sbuf - send_buffer);
send(msg_size);
return 0;
}
int write_all_regs ()
{
uint64_t reg;
uint8_t *buf = recv_buffer;
// write all GPRs
for (int i=0; i<32; i++)
{
reg = get_value( &buf, 8);
g_vcpu[cpu_id]->set_reg(VCPU_IRF_0 + i, reg);
}
// write all 32 bit fp regs
for (int i=0; i<32; i++)
{
reg = get_value( &buf, 4);
g_vcpu[cpu_id]->set_reg(VCPU_FRF_0 + i, reg);
}
// write all 64 bit fp regs
for (int i=32; i<64; i+=2)
{
reg = get_value( &buf, 8);
g_vcpu[cpu_id]->set_reg(VCPU_DRF_0 + (i / 2), reg);
}
// write control registers
reg = get_value( &buf, 8);
g_vcpu[cpu_id]->set_reg(VCPU_ASR_PC, reg);
reg = get_value( &buf, 8);
g_vcpu[cpu_id]->set_reg(VCPU_ASR_NPC, reg);
reg = get_value( &buf, 8);
g_vcpu[cpu_id]->set_reg(VCPU_PR_PSTATE, reg);
reg = get_value( &buf, 8);
g_vcpu[cpu_id]->set_reg(VCPU_ASR_FSR, reg);
reg = get_value( &buf, 8);
g_vcpu[cpu_id]->set_reg(VCPU_ASR_FPRS, reg);
reg = get_value( &buf, 8);
g_vcpu[cpu_id]->set_reg(VCPU_ASR_Y, reg);
return 0;
}
int read_mem ()
{
uint8_t *args = recv_buffer+1;
uint64_t addr = read_next_param ( &args );
int size = (int)read_next_param ( &args );
if (size <= 0)
{
reply_error (0);
return 1;
}
if (size > MAX_BUF_SIZE-3)
{
size = MAX_BUF_SIZE-3;
}
uint8_t *sbuf = send_buffer+1;
// read from the double word aligned address
uint64_t addr8 = addr & ~uint64_t(7);
uint64_t value = 0;
if ( g_vcpu[cpu_id]->read_mem(addr8, &value, 8) != 0 )
{
reply_error (1);
return 1;
}
DEBUG( printf(" read dword %llx from address %llx \n", value, addr8); );
// alignement for the very first byte
int start_byte_idx = int(addr & 0x7);
int end_byte_idx = start_byte_idx + size-1;
if (end_byte_idx>7)
end_byte_idx = 7;
int chunck_size = end_byte_idx - start_byte_idx + 1;
if (end_byte_idx<7)
value >>= ((7-end_byte_idx)*8);
if (chunck_size<8)
value &= ((uint64_t(1)<<(chunck_size*8))-1);
put_value( &sbuf, value, chunck_size);
int msg_size = int(sbuf - send_buffer);
send( msg_size );
return 0;
}
int write_mem()
{
uint8_t *args = recv_buffer+1;
uint64_t addr = read_next_param ( &args );
int size = (int)read_next_param ( &args );
// allow only double word aligned addresses
if ((size <= 0) || ( addr & 0x7 ) )
{
reply_error (0);
return 1;
}
for (int i=0; i<size/8; i++)
{
// double word aligned address
addr = ( addr + i*8 ) & ~uint64_t(7);
uint64_t value = get_value ( &args ,size );
if (g_vcpu[cpu_id]->write_mem(addr, value, 8) != 0)
{
reply_error (1);
return 1;
}
}
reply_ok ();
return 0;
}
int set_break ()
{
uint8_t *args = recv_buffer+1;
int type = (int)read_next_param ( &args );
uint64_t addr = read_next_param ( &args );
int size = (int)read_next_param ( &args );
switch (type)
{
case 0: // sw breakpoint
case 1: // hw breakpoint
if (cpu_set_breakpoint(cpu_id, VCPU_BP_INSTR_ADDR, addr) != 0)
{
reply_error(1);
return 1;
}
break;
case 2: // write watchpoint
if (cpu_set_breakpoint(cpu_id, VCPU_BP_DATA_WRITE_ADDR, addr) != 0)
{
reply_error(1);
return 1;
}
break;
case 3: // read watchpoint
if (cpu_set_breakpoint(cpu_id, VCPU_BP_DATA_READ_ADDR, addr) != 0)
{
reply_error(1);
return 1;
}
break;
default:
reply_error (0);
return 1;
}
reply_ok ();
return 0;
}
int remove_break ()
{
uint8_t *args = recv_buffer+1;
int type = (int)read_next_param ( &args );
uint64_t addr = read_next_param ( &args );
int size = (int)read_next_param ( &args );
switch (type)
{
case 0: // sw breakpoint
case 1: // hw breakpoint
if (cpu_remove_breakpoint(cpu_id, VCPU_BP_INSTR_ADDR, addr) != 0)
{
//reply_error(1);
return 1;
}
break;
case 2: // write watchpoint
if (cpu_remove_breakpoint(cpu_id, VCPU_BP_DATA_WRITE_ADDR, addr) != 0)
{
//reply_error(1);
return 1;
}
break;
case 3: // read watchpoint
if (cpu_remove_breakpoint(cpu_id, VCPU_BP_DATA_READ_ADDR, addr) != 0)
{
//reply_error(1);
return 1;
}
break;
default:
reply_error (0);
return 1;
}
reply_ok ();
return 0;
}
};
// head of linked list of interface objects
SocketInterface * gpIntf;
// This routine is called when target is stoped.
// Inform the debugger about the reason of stopping.
int target_stopped ( int reason )
{
SocketInterface *in = gpIntf;
// for all attached remote interfaces
while ( in )
{
if(in->state == RUNNING)
{
in->reply_target_stopped (reason);
in->state = STOPPED;
}
in = in->next;
}
return 0;
}
// This new thread is invoked once contact with a debugger is
// been established. This thread exits once that debugger
// connection vanishes.
// Any one debugger can stop the simulation, all are required
// to be active to continue simulation.
extern "C"
void * in_worker_thread (void * argp)
{
SocketInterface *in = (SocketInterface *)argp;
// run the command interface until we get the
// command to termnate this session
while (FOREVER)
{
// get the command packet
int csize = in->recv();
if ( csize <= 0)
{
printf ("failed in communication");
in->reply_nack(); // send a nack
continue;
}
// send an ack
in->reply_ack();
DEBUG( printf(" remote interface %d got a command %s: ", in->id, in->recv_buffer); );
char command = in->recv_buffer[0];
switch ( command )
{
case '?': // why stopped
DEBUG( printf("get the the reason why stoped \n"); );
in->reply_target_stopped (0);
break;
case 'p': // read register
DEBUG( printf("read register :%s \n", in->recv_buffer+1); );
in->read_reg ();
//in->reply_not_implemented(); // not implemented
break;
case 'g': // read all registers
{
DEBUG( printf("read all register \n" ); );
in->read_all_regs ();
break;
}
case 'G': // write registers
{
DEBUG( printf("write registers: %s\n", in->recv_buffer+1); );
in->write_all_regs();
in->reply_ok();
break;
}
case 'm': // read memory
{
DEBUG( printf("read memory : %s \n", in->recv_buffer+1); );
in->read_mem();
break;
}
case 'M': // write memory
{
DEBUG( printf("write memory :%s \n", in->recv_buffer+1); );
in->write_mem();
break;
}
case 'c': // continue from address
DEBUG( printf("continue %s \n", in->recv_buffer+1); );
in->state = RUNNING;
UI_exec_cmd("run\n");
break;
case 's': // step from address
DEBUG( printf("step :%s \n", in->recv_buffer+1); );
in->state = RUNNING;
UI_exec_cmd("stepi 1\n");
break;
case 'z': // remove a breakpoint
DEBUG( printf("remove a breakpoint :%s \n", in->recv_buffer+1); );
in->remove_break();
break;
case 'Z': // set a breakpoint
DEBUG( printf("set a breakpoint :%s \n", in->recv_buffer+1); );
in->set_break();
break;
case 'k': // killed
goto quit;
case 'D': // detach the session
goto quit;
default:
DEBUG( printf("\n unimplemented command %s \n", in->recv_buffer); );
in->reply_not_implemented(); // not implemented
}
}
quit:
// remove interface structure from the list
SocketInterface *prev=NULL;
for ( SocketInterface *p = gpIntf;
p != NULL;
prev = p, p = p->next )
{
if (p == in)
{
if (prev)
prev->next = in->next;
else
gpIntf = in->next;
close (in->socket);
delete in;
break;
}
}
thr_exit(NULL);
return NULL;
}
// main program thread
// to wait for user interface connections.
extern "C"
void *wait_connection (void *param)
{
static fd_set comm_channels;
int tcp_attach_socket, res;
struct sockaddr_in tcp_server;
struct sockaddr_in from;
int fromlen;
int skt;
int fh, length, on=1;
int max_channel;
sigset_t sigs;
struct hostent * hp;
char * froms;
int retry_cnt=0;
sigfillset(&sigs);
thr_sigsetmask(SIG_BLOCK, &sigs, NULL);
skt = socket(AF_INET, SOCK_STREAM, 0);
if (skt < 0)
{
printf ("ERROR: cannot opening stream socket\n");
return NULL;
}
// enable the reuse of this socket if this process dies
if (setsockopt(skt,SOL_SOCKET,SO_REUSEADDR,(char*)&on,int(sizeof(on)))<0)
{
printf ("ERROR: turning on REUSEADDR \n");
return NULL;
}
RemoteConfig *config = (RemoteConfig *)param;
g_debug_on = config->debug_on;
retry: // bind the connection
tcp_server.sin_family = AF_INET;
tcp_server.sin_addr.s_addr = INADDR_ANY;
tcp_server.sin_port = config->port;
retry_cnt++;
printf("- trying port:%d", config->port);
if (bind(skt, (struct sockaddr *)&tcp_server, int(sizeof(tcp_server))) < 0)
{
switch (errno)
{
case EAGAIN:
goto retry;
case EADDRINUSE:
printf("\nPort %d already in use ", config->port);
default:
printf("ERROR: binding tcp stream socket");
return NULL;
}
}
length = int(sizeof(tcp_server));
if (getsockname(skt, (struct sockaddr *) &tcp_server, &length)==-1)
{
printf("ERROR:getting socket name");
return NULL;
}
// start waiting for the connection
printf("\nWaiting for connection on port # %d\n",ntohs(tcp_server.sin_port));
listen(skt, config->max_connections);
tcp_attach_socket = skt;
max_channel = tcp_attach_socket;
FD_ZERO(&comm_channels);
FD_SET(tcp_attach_socket, &comm_channels);
// Wait for a control connection
int next_connection = 0;
while(FOREVER)
{
if (next_connection > config->max_connections - 1)
{
sleep (2);
continue;
}
res = select(max_channel+1, &comm_channels, (fd_set*)0, (fd_set*)0, (struct timeval*)0 /* stall */);
if (res<0)
{
printf("ERROR: select");
break;
}
// accept the connection
fromlen = int(sizeof(from));
fh = accept(tcp_attach_socket,(struct sockaddr *)&from, (int*)&fromlen);
hp = gethostbyaddr((char *)&from.sin_addr, 4, AF_INET);
if (hp == (struct hostent *)0)
{
froms = inet_ntoa(from.sin_addr);
fprintf(stderr,"cant resolve hostname for %s\n", froms);;
}
else
{
froms = hp->h_name;
}
printf("got remote connection from %s : port %d\n", froms, from.sin_port);
// Create the thread to handle this debugger
// connection. It cleans up after itself
// if anything goes wrong
SocketInterface *in = new SocketInterface ();
if (gpIntf == NULL)
{
gpIntf = in;
}
else
{
in->next = gpIntf;
gpIntf = in;
}
in->id = next_connection;
next_connection++;
in->socket = fh;
DEBUG( printf("connection %d on socket_id=%d\n", in->id, in->socket); );
thr_create(NULL, NULL, in_worker_thread, (void*)in, THR_BOUND, &in->thread_id);
}; // while forever
return NULL;
}
int start_connection ( RemoteConfig *config )
{
thread_t thread_id;
// start thread waiting for a remote connection
thr_create(NULL, NULL, wait_connection, (void *)config, THR_BOUND, &thread_id);
printf ( "remote debug server started...\n");
return 0;
}
int destroy_connection ()
{
SocketInterface *in = gpIntf;
// for all attached remote interfaces
while ( in )
{
in->reply_target_stopped (SIGNAL_HUP);
SocketInterface *p = in;
in = in->next;
delete in;
}
return 0;
}
////////////////////////////////////////////////////////
//
// Get exported interface from the Remote shared library.
//
// Assign function pointers in the interface structure.
//
//
// return: 0 - success; 1 - fail;
int get_ex_remote_interface
(
RemoteExInterface *intf // pointer to the interface structure
)
{
intf->create = start_connection;
intf->destroy = destroy_connection;
intf->update_status = target_stopped;
return 0;
}