From fb5cc92470319da895054f3747bf83257b889c78 Mon Sep 17 00:00:00 2001 From: Michael Zapf Date: Thu, 4 Aug 2016 01:04:00 +0200 Subject: [PATCH] Improved logging; fixed missed CTS signal, outgoing RTS, incoming DTR --- src/devices/bus/ti99_peb/ti_rs232.cpp | 138 +++++++++++++------------- src/devices/machine/tms9902.cpp | 21 ++-- 2 files changed, 83 insertions(+), 76 deletions(-) diff --git a/src/devices/bus/ti99_peb/ti_rs232.cpp b/src/devices/bus/ti99_peb/ti_rs232.cpp index ce062b9a839..5d0ae9b13bc 100644 --- a/src/devices/bus/ti99_peb/ti_rs232.cpp +++ b/src/devices/bus/ti99_peb/ti_rs232.cpp @@ -111,8 +111,13 @@ #define RECV_MODE_ESC 2 #define RECV_MODE_ESC_LINES 3 -#define VERBOSE 1 -#define LOG logerror +#define TRACE_LINES 0 +#define TRACE_SETTING 0 +#define TRACE_STATE 0 +#define TRACE_MAP 0 +#define TRACE_IN 0 +#define TRACE_OUT 0 +#define TRACE_ILA 0 #define ESC 0x1b @@ -177,7 +182,6 @@ int ti_rs232_attached_device::get_index_from_tagname() image_init_result ti_rs232_attached_device::call_load() { tms9902_device* tms9902; -// ti_rs232_pio_device* card = static_cast(owner()); int devnumber = get_index_from_tagname(); if (devnumber==0) @@ -194,7 +198,7 @@ image_init_result ti_rs232_attached_device::call_load() } else { - LOG("ti99/rs232: Could not find device tag number\n"); + logerror("Could not find device tag number\n"); return image_init_result::FAIL; } @@ -374,13 +378,13 @@ WRITE8_MEMBER(ti_rs232_pio_device::cruwrite) case 5: // Set the CTS line for RS232/1 - if (VERBOSE>5) LOG("TI-RS232/1/3: Setting CTS* via CRU to %d\n", data); + if (TRACE_LINES) logerror("(1/3) Setting CTS* via CRU to %d\n", data); output_line_state(0, CTS, (data==0)? CTS : 0); break; case 6: // Set the CTS line for RS232/2 - if (VERBOSE>5) LOG("TI-RS232/2/4: Setting CTS* via CRU to %d\n", data); + if (TRACE_LINES) logerror("(2/4) Setting CTS* via CRU to %d\n", data); output_line_state(1, CTS, (data==0)? CTS : 0); break; @@ -399,7 +403,7 @@ READ8Z_MEMBER( ti_rs232_pio_device::readz ) { if (m_senila==ASSERT_LINE) { - if (VERBOSE>3) LOG("ti99/rs232: Sensing ILA\n"); + if (TRACE_ILA) logerror("Sensing ILA\n"); *value = m_ila; // The card ROM must be unselected, or we get two values // on the data bus @@ -437,16 +441,13 @@ WRITE8_MEMBER( ti_rs232_pio_device::write ) /**************************************************************************/ - -// ========================================================== - /* The DTR line of the interface card is wired to the CTS and DSR of the UART. */ void ti_rs232_pio_device::incoming_dtr(int uartind, line_state value) { - if (VERBOSE>2) LOG("TI-RS232/%d: incoming DTR = %d\n", uartind+1, (value==ASSERT_LINE)? 1:0); + if (TRACE_LINES) logerror("(RS232/%d) Incoming DTR = %d\n", uartind+1, (value==ASSERT_LINE)? 1:0); m_uart[uartind]->rcv_cts(value); m_uart[uartind]->rcv_dsr(value); @@ -463,17 +464,21 @@ void ti_rs232_pio_device::transmit_data(int uartind, UINT8 value) serial = dynamic_cast(m_serdev[uartind]); if (!serial->exists()) { - if (VERBOSE>1) LOG("TI-RS232/%d: No serial output attached\n", uartind+1); + logerror("(RS232/%d) No serial output attached\n", uartind+1); return; } // Send a double ESC if this is not a control operation if (buf==0x1b) { - if (VERBOSE>2) LOG("TI-RS232/%d: send ESC (requires another ESC)\n", uartind+1); + if (TRACE_OUT) logerror("(RS232/%d) send ESC (requires another ESC)\n", uartind+1); serial->fwrite(&buf, 1); } - if (VERBOSE>3) LOG("TI-RS232/%d: send %c <%02x>\n", uartind+1, buf, buf); + if (TRACE_OUT) + { + char cbuf = (buf < 0x20 || buf > 0x7e)? '.' : (char)buf; + logerror("(RS232/%d) send %c <%02x>\n", uartind+1, cbuf, buf); + } serial->fwrite(&buf, 1); } @@ -522,11 +527,11 @@ UINT8 ti_rs232_pio_device::map_lines_out(int uartind, UINT8 value) // 00ab cdef = setting line RTS=a, CTS=b, DSR=c, DCD=d, DTR=e, RI=f - if (VERBOSE>3) LOG("TI-RS232/%d: out connector pins = 0x%02x; translate for DTE\n", uartind+1, value); + if (TRACE_LINES) logerror("(RS232/%d) out connector pins = 0x%02x; translate for DTE\n", uartind+1, value); if (value & BRK) { - if (VERBOSE>5) LOG("TI-RS232/%d: ... sending BRK\n", uartind+1); + if (TRACE_MAP) logerror("(RS232/%d) Sending BRK\n", uartind+1); ret |= EXCEPT | BRK; } @@ -535,17 +540,17 @@ UINT8 ti_rs232_pio_device::map_lines_out(int uartind, UINT8 value) // V1 if (value & CTS) { - if (VERBOSE>5) LOG("TI-RS232/%d: ... cannot map CTS line, ignoring\n", uartind+1); + if (TRACE_MAP) logerror("(RS232/%d) Cannot map CTS line, ignoring\n", uartind+1); } if (value & DSR) { ret |= DTR; - if (VERBOSE>5) LOG("TI-RS232/%d: ... setting DTR line\n", uartind+1); + if (TRACE_MAP) logerror("(RS232/%d) Setting DTR line\n", uartind+1); } if (value & DCD) { ret |= RTS; - if (VERBOSE>5) LOG("TI-RS232/%d: ... setting RTS line\n", uartind+1); + if (TRACE_MAP) logerror("(RS232/%d) Setting RTS line\n", uartind+1); } } else @@ -556,12 +561,12 @@ UINT8 ti_rs232_pio_device::map_lines_out(int uartind, UINT8 value) if (value & CTS) { ret |= RTS; - if (VERBOSE>5) LOG("TI-RS232/%d: ... setting RTS line\n", uartind+1); + if (TRACE_MAP) logerror("(RS232/%d) Setting RTS line\n", uartind+1); } if (value & DCD) { ret |= DTR; - if (VERBOSE>5) LOG("TI-RS232/%d: ... setting DTR line\n", uartind+1); + if (TRACE_MAP) logerror("(RS232/%d) Setting DTR line\n", uartind+1); } } else @@ -570,16 +575,16 @@ UINT8 ti_rs232_pio_device::map_lines_out(int uartind, UINT8 value) if (value & CTS) { ret |= DTR; - if (VERBOSE>5) LOG("TI-RS232/%d: ... setting DTR line\n", uartind+1); + if (TRACE_MAP) logerror("(RS232/%d) Setting DTR line\n", uartind+1); } if (value & DSR) { - if (VERBOSE>5) LOG("TI-RS232/%d: ... cannot map DSR line, ignoring\n", uartind+1); + if (TRACE_MAP) logerror("(RS232/%d) Cannot map DSR line, ignoring\n", uartind+1); } if (value & DCD) { ret |= RTS; - if (VERBOSE>5) LOG("TI-RS232/%d: ... setting RTS line\n", uartind+1); + if (TRACE_MAP) logerror("(RS232/%d) Setting RTS line\n", uartind+1); } } } @@ -594,11 +599,11 @@ UINT8 ti_rs232_pio_device::map_lines_in(int uartind, UINT8 value) // 00ab cdef = setting line RTS=a, CTS=b, DSR=c, DCD=d, DTR=e, RI=f - if (VERBOSE>3) LOG("TI-RS232/%d: in connector pins = 0x%02x; translate from DTE\n", uartind+1, value); + if (TRACE_LINES) logerror("(RS232/%d) in connector pins = 0x%02x; translate from DTE\n", uartind+1, value); if (value & BRK) { - if (VERBOSE>5) LOG("TI-RS232/%d: ... getting BRK\n", uartind+1); + if (TRACE_MAP) logerror("(RS232/%d) Getting BRK\n", uartind+1); ret |= EXCEPT | BRK; } @@ -607,16 +612,16 @@ UINT8 ti_rs232_pio_device::map_lines_in(int uartind, UINT8 value) // V1 if (value & CTS) { - if (VERBOSE>5) LOG("TI-RS232/%d: ... cannot map CTS line, ignoring\n", uartind+1); + if (TRACE_MAP) logerror("(RS232/%d) Cannot map CTS line, ignoring\n", uartind+1); } if (value & DSR) { ret |= DTR; - if (VERBOSE>5) LOG("TI-RS232/%d: ... setting DTR line\n", uartind+1); + if (TRACE_MAP) logerror("(RS232/%d) Setting DTR line\n", uartind+1); } if (value & DCD) { - if (VERBOSE>5) LOG("TI-RS232/%d: ... cannot map DCD line, ignoring\n", uartind+1); + if (TRACE_MAP) logerror("(RS232/%d) Cannot map DCD line, ignoring\n", uartind+1); } } else @@ -627,15 +632,15 @@ UINT8 ti_rs232_pio_device::map_lines_in(int uartind, UINT8 value) if (value & CTS) { ret |= DTR; - if (VERBOSE>5) LOG("TI-RS232/%d: ... setting DTR line\n", uartind+1); + if (TRACE_MAP) logerror("(RS232/%d) Setting DTR line\n", uartind+1); } if (value & DSR) { - if (VERBOSE>5) LOG("TI-RS232/%d: ... cannot map DSR line, ignoring\n", uartind+1); + if (TRACE_MAP) logerror("(RS232/%d) Cannot map DSR line, ignoring\n", uartind+1); } if (value & DCD) { - if (VERBOSE>5) LOG("TI-RS232/%d: ... cannot map DCD line, ignoring\n", uartind+1); + if (TRACE_MAP) logerror("(RS232/%d) Cannot map DCD line, ignoring\n", uartind+1); } } else @@ -643,15 +648,15 @@ UINT8 ti_rs232_pio_device::map_lines_in(int uartind, UINT8 value) if (value & CTS) { ret |= DTR; - if (VERBOSE>5) LOG("TI-RS232/%d: ... setting DTR line\n", uartind+1); + if (TRACE_MAP) logerror("(RS232/%d) Setting DTR line\n", uartind+1); } if (value & DSR) { - if (VERBOSE>5) LOG("TI-RS232/%d: ... cannot map DSR line, ignoring\n", uartind+1); + if (TRACE_MAP) logerror("(RS232/%d) Cannot map DSR line, ignoring\n", uartind+1); } if (value & DCD) { - if (VERBOSE>5) LOG("TI-RS232/%d: ... cannot map DCD line, ignoring\n", uartind+1); + if (TRACE_MAP) logerror("(RS232/%d) Cannot map DCD line, ignoring\n", uartind+1); } } } @@ -691,7 +696,7 @@ void ti_rs232_pio_device::receive_data_or_line_state(int uartind) if (!serial->exists()) { - if (VERBOSE>1) LOG("TI-RS232/%d: No serial input attached\n", uartind+1); + logerror("(RS232/%d) No serial input attached\n", uartind+1); return; } @@ -718,18 +723,20 @@ void ti_rs232_pio_device::receive_data_or_line_state(int uartind) return; } + char cbuf = (buffer < 0x20 || buffer > 0x7e)? '.' : (char)buffer; + // No config parameters here, only data or line setting switch (m_recv_mode[uartind]) { case RECV_MODE_NORMAL: if (buffer==0x1b) { - if (VERBOSE>2) LOG("TI-RS232/%d: received: %c <%02x>, switch to ESC mode\n", uartind+1, buffer, buffer); + if (TRACE_IN) logerror("(RS232/%d) Received: %c <%02x>, switch to ESC mode\n", uartind+1, cbuf, buffer); m_recv_mode[uartind] = RECV_MODE_ESC; } else { - if (VERBOSE>3) LOG("TI-RS232/%d: received: %c <%02x>, pass to UART\n", uartind+1, buffer, buffer); + if (TRACE_IN) logerror("(RS232/%d) Received: %c <%02x>, pass to UART\n", uartind+1, cbuf, buffer); m_uart[uartind]->rcv_data(buffer); m_time_hold[uartind] = 0.0; } @@ -738,17 +745,17 @@ void ti_rs232_pio_device::receive_data_or_line_state(int uartind) if (buffer==0x1b) { m_recv_mode[uartind] = RECV_MODE_NORMAL; - if (VERBOSE>2) LOG("TI-RS232/%d: leaving ESC mode, received: %c <%02x>, pass to UART\n", uartind+1, buffer, buffer); + if (TRACE_STATE) logerror("(RS232/%d) Received another ESC, passing to UART, leaving ESC mode\n", uartind+1); m_uart[uartind]->rcv_data(buffer); m_time_hold[uartind] = 0.0; } else { // the byte in buffer is the length byte - if (VERBOSE>3) LOG("TI-RS232/%d: received length byte <%02x> in ESC mode\n", uartind+1, buffer); + if (TRACE_STATE) logerror("(RS232/%d) Received length byte <%02x> in ESC mode\n", uartind+1, buffer); if (buffer != 1) { - LOG("TI-RS232/%d: expected length 1 but got %02x, leaving ESC mode.\n", uartind+1, buffer); + logerror("(RS232/%d) ** ERROR: Expected length byte 1 but got 0x%02x, leaving ESC mode.\n", uartind+1, buffer); m_recv_mode[uartind] = RECV_MODE_NORMAL; } else @@ -762,6 +769,7 @@ void ti_rs232_pio_device::receive_data_or_line_state(int uartind) if (buffer & EXCEPT) { // Exception states: BRK, FRMERR, PARERR + if (TRACE_LINES) logerror("(RS232/%d) Received BRK or ERROR <%02x>\n", uartind+1, buffer); m_uart[uartind]->rcv_break(((buffer & BRK)!=0)); if (buffer & FRMERR) m_uart[uartind]->rcv_framing_error(); @@ -770,7 +778,7 @@ void ti_rs232_pio_device::receive_data_or_line_state(int uartind) else { buffer = map_lines_in(uartind, buffer); - if (VERBOSE>2) LOG("TI-RS232/%d: received (remapped) <%02x> in ESC mode\n", uartind+1, buffer); + if (TRACE_LINES) logerror("(RS232/%d) Received (remapped) <%02x> in ESC mode\n", uartind+1, buffer); // The DTR line on the RS232 connector of the board is wired to both the // CTS and the DSR pin of the TMS9902 @@ -782,7 +790,7 @@ void ti_rs232_pio_device::receive_data_or_line_state(int uartind) break; default: - if (VERBOSE>1) LOG("TI-RS232/%d: unknown mode: %d\n", uartind+1, m_recv_mode[uartind]); + logerror("(RS232/%d) Unknown mode: %d\n", uartind+1, m_recv_mode[uartind]); } } @@ -799,7 +807,7 @@ void ti_rs232_pio_device::configure_interface(int uartind, int type, int value) if (!serial->exists()) { - if (VERBOSE>1) LOG("TI-RS232/%d: No serial output attached\n", uartind+1); + logerror("(RS232/%d) No serial output attached\n", uartind+1); return; } @@ -809,7 +817,7 @@ void ti_rs232_pio_device::configure_interface(int uartind, int type, int value) switch (type) { case RATERECV: - if (VERBOSE>2) LOG("TI-RS232/%d: send receive rate %04x\n", uartind+1, value); + if (TRACE_SETTING) logerror("(RS232/%d) Send receive rate %04x\n", uartind+1, value); // value has 12 bits // 1ccc xaaa = config adapter type a // 1111 xaaa rrrr rrrr rrrr 0000 = config receive rate on a @@ -820,29 +828,29 @@ void ti_rs232_pio_device::configure_interface(int uartind, int type, int value) bufctrl[3] = (value & 0x0f)<<4; break; case RATEXMIT: - if (VERBOSE>2) LOG("TI-RS232/%d: send transmit rate %04x\n", uartind+1, value); + if (TRACE_SETTING) logerror("(RS232/%d) Send transmit rate %04x\n", uartind+1, value); bufctrl[0] = 0x03; // length bufctrl[1] |= RATEXMIT; bufctrl[2] = (value & 0x0ff0)>>4; bufctrl[3] = (value & 0x0f)<<4; break; case STOPBITS: - if (VERBOSE>2) LOG("TI-RS232/%d: send stop bit config %02x\n", uartind+1, value&0x03); + if (TRACE_SETTING) logerror("(RS232/%d) Send stop bit config %02x\n", uartind+1, value&0x03); bufctrl[1] |= STOPBITS; bufctrl[2] = (value & 0x03); break; case DATABITS: - if (VERBOSE>2) LOG("TI-RS232/%d: send data bit config %02x\n", uartind+1, value&0x03); + if (TRACE_SETTING) logerror("(RS232/%d) Send data bit config %02x\n", uartind+1, value&0x03); bufctrl[1] |= DATABITS; bufctrl[2] = (value & 0x03); break; case PARITY: - if (VERBOSE>2) LOG("TI-RS232/%d: send parity config %02x\n", uartind+1, value&0x03); + if (TRACE_SETTING) logerror("(RS232/%d) Send parity config %02x\n", uartind+1, value&0x03); bufctrl[1] |= PARITY; bufctrl[2] = (value & 0x03); break; default: - if (VERBOSE>1) LOG("TI-RS232/%d: error - unknown config type %02x\n", uartind+1, type); + logerror("(RS232/%d) Error - unknown config type %02x\n", uartind+1, type); } serial->fwrite(bufctrl, bufctrl[0]+1); @@ -850,13 +858,13 @@ void ti_rs232_pio_device::configure_interface(int uartind, int type, int value) void ti_rs232_pio_device::set_bit(int uartind, int line, int value) { - if (VERBOSE>5) + if (TRACE_LINES) { switch (line) { - case CTS: LOG("TI-RS232/%d: set CTS(out)=%s\n", uartind+1, (value!=0)? "asserted" : "cleared"); break; - case DCD: LOG("TI-RS232/%d: set DCD(out)=%s\n", uartind+1, (value!=0)? "asserted" : "cleared"); break; - case BRK: LOG("TI-RS232/%d: set BRK(out)=%s\n", uartind+1, (value!=0)? "asserted" : "cleared"); break; + case CTS: logerror("(RS232/%d) Set CTS(out)=%s\n", uartind+1, (value!=0)? "asserted" : "cleared"); break; + case DCD: logerror("(RS232/%d) Set DCD(out)=%s\n", uartind+1, (value!=0)? "asserted" : "cleared"); break; + case BRK: logerror("(RS232/%d) Set BRK(out)=%s\n", uartind+1, (value!=0)? "asserted" : "cleared"); break; } } @@ -877,7 +885,7 @@ void ti_rs232_pio_device::output_exception(int uartind, int param, UINT8 value) if (!serial->exists()) { - if (VERBOSE>1) LOG("TI-RS232/%d: No serial output attached\n", uartind+1); + logerror("(RS232/%d) No serial output attached\n", uartind+1); return; } @@ -903,15 +911,18 @@ void ti_rs232_pio_device::output_line_state(int uartind, int mask, UINT8 value) if (!serial->exists()) { - if (VERBOSE>1) LOG("TI-RS232/%d: No serial output attached\n", uartind+1); + logerror("(RS232/%d) No serial output attached\n", uartind+1); return; } + // Send ESC to serial bridge serial->fwrite(&esc, 1); - // 01ab cdef = setting line RTS=a, CTS=b, DSR=c, DCD=d, DTR=e, RI=f + // Length 1 bufctrl[0] = 1; + // 01ab cdef = setting line RTS=a, CTS=b, DSR=c, DCD=d, DTR=e, RI=f + // The CTS line (coming from a CRU bit) is connected to the CTS pin if (mask & CTS) set_bit(uartind, CTS, value & CTS); @@ -1069,16 +1080,9 @@ void ti_rs232_pio_device::device_reset() m_time_hold[0] = m_time_hold[1] = 0.0; - // The GenMod modification changes the address bus width of the Geneve. - // All peripheral cards need to be manually modified to properly decode - // the wider address. The next lines perform this soldering job - // automagically. - /* if (device->machine().root_device().ioport("MODE")->read()==GENMOD) - { - // GenMod card modification - card->select_mask = 0x1fe000; - card->select_value = 0x174000; - }*/ + // Both DTRs are pulled up + incoming_dtr(0, ASSERT_LINE); + incoming_dtr(1, ASSERT_LINE); } static MACHINE_CONFIG_FRAGMENT( ti_rs232 ) diff --git a/src/devices/machine/tms9902.cpp b/src/devices/machine/tms9902.cpp index 7702eeadf1d..424bcfdd821 100644 --- a/src/devices/machine/tms9902.cpp +++ b/src/devices/machine/tms9902.cpp @@ -135,7 +135,7 @@ void tms9902_device::rcv_cts(line_state state) else { m_DSCH = false; - if (TRACE_LINES) logerror("no change in CTS line, no interrupt."); + if (TRACE_LINES) logerror("no change in CTS line, no interrupt.\n"); } } @@ -165,7 +165,7 @@ void tms9902_device::rcv_dsr(line_state state) else { m_DSCH = false; - if (TRACE_LINES) logerror("no change in DSR line, no interrupt."); + if (TRACE_LINES) logerror("no change in DSR line, no interrupt.\n"); } } @@ -188,7 +188,7 @@ void tms9902_device::rcv_data(UINT8 data) // Receive buffer was empty m_RBRL = true; m_ROVER = false; - if (TRACE_BUFFER) logerror("Receive buffer loaded with byte %02x\n", data); + if (TRACE_BUFFER) logerror("Receive buffer loaded with byte %02x; RIENB=%d\n", data, m_RIENB); field_interrupts(); } else @@ -387,7 +387,7 @@ void tms9902_device::transmit_line_state() // The 9902 only outputs RTS and BRK if (TRACE_SETTING) logerror("transmitting line state (only RTS) = %02x\n", (m_RTSout)? 1:0); m_last_config_value = (m_RTSout)? RTS : 0; - m_ctrl_cb((offs_t)LINES, RTS); + m_ctrl_cb((offs_t)(LINES | RTS), RTS); } void tms9902_device::set_rts(line_state state) @@ -422,7 +422,6 @@ void tms9902_device::initiate_transmit() set_rts(CLEAR_LINE); else { - if (TRACE_BUFFER) logerror("transferring XBR to XSR; XSRE=false, XBRE=true\n"); m_XSR = m_XBR; m_XSRE = false; m_XBRE = true; @@ -502,10 +501,11 @@ READ8_MEMBER( tms9902_device::cruread ) break; case 0: // Bits 7-0 + if (TRACE_CRU) logerror("Reading received byte = %02x\n", m_RBR); answer = m_RBR; break; } - if (TRACE_CRU) logerror("Reading flag bits %d - %d = %02x\n", ((offset+1)*8-1), offset*8, answer); + if (TRACE_CRU && TRACE_DETAIL) logerror("Reading flag bits %d - %d = %02x\n", ((offset+1)*8-1), offset*8, answer); return answer; } @@ -560,7 +560,7 @@ void tms9902_device::reset_uart() m_DSCH = false; m_TIMELP = false; - m_CTSin = false; +// m_CTSin = false; // not a good idea - this is the latch of an incoming line m_TMR = 0; m_STOPB = 0; @@ -585,7 +585,7 @@ WRITE8_MEMBER( tms9902_device::cruwrite ) data &= 1; /* clear extra bits */ offset &= 0x1F; - if (TRACE_CRU) logerror("Setting bit %d = %02x\n", offset, data); + if (TRACE_CRU && TRACE_DETAIL) logerror("Setting bit %d = %02x\n", offset, data); if (offset <= 10) { @@ -777,7 +777,7 @@ WRITE8_MEMBER( tms9902_device::cruwrite ) // (the only way to clear the flag!) m_RIENB = (data!=0); m_RBRL = false; - if (TRACE_CRU) logerror("set RBRL=0, set RIENB=%d\n", data); + if (TRACE_CRU) logerror("Set RBRL=0, set RIENB=%d\n", data); field_interrupts(); return; case 19: @@ -828,6 +828,9 @@ void tms9902_device::device_stop() void tms9902_device::device_reset() { + // This must be true because we may have missed a CTS* assertion + // on startup, and the whole implementation relies on pushing + m_CTSin = true; reset_uart(); }