diff options
Diffstat (limited to 'src/devices/machine/mc6843.c')
-rw-r--r-- | src/devices/machine/mc6843.c | 827 |
1 files changed, 827 insertions, 0 deletions
diff --git a/src/devices/machine/mc6843.c b/src/devices/machine/mc6843.c new file mode 100644 index 00000000000..f30dafde916 --- /dev/null +++ b/src/devices/machine/mc6843.c @@ -0,0 +1,827 @@ +// license:BSD-3-Clause +// copyright-holders:Antoine Mine +/********************************************************************** + + Copyright (C) Antoine Mine' 2007 + + Motorola 6843 Floppy Disk Controller emulation. + +**********************************************************************/ + +/* + Main MC 6843 features are: + - single density floppies + - IBM 3740 compatible + - DMA-able + - high-level commands (including multi-sector read/write) + + CLONES: HD 46503S seems to be a clone of MC 6843 + + BUGS + The driver was designed with Thomson computer emulation in mind + (CD 90-015 5"1/4 floppy controller) and works in this context. + It might work in other contexts but has currently shortcomings: + - DMA is not emulated + - Free-Format Read is not emulated + - Free-Format Write only supports track formatting, in a specific + format (FWF=1, Thomson-like sector formats) + - very rough timing: basically, there is a fixed delay between + a command request (CMR write) and its response (first byte + available, seek complete, etc.); there is no delay between + read / write + */ + + +#include "emu.h" +#include "mc6843.h" + + +/******************* parameters ******************/ + +#define VERBOSE 0 + +/* macro-command numbers */ +#define CMD_STZ 0x2 /* seek track zero */ +#define CMD_SEK 0x3 /* seek */ +#define CMD_SSR 0x4 /* single sector read */ +#define CMD_SSW 0x5 /* single sector write */ +#define CMD_RCR 0x6 /* read CRC */ +#define CMD_SWD 0x7 /* single sector write with delete data mark */ +#define CMD_MSW 0xd /* multiple sector write */ +#define CMD_MSR 0xc /* multiple sector read */ +#define CMD_FFW 0xb /* free format write */ +#define CMD_FFR 0xa /* free format read */ + +/* coarse delays */ +#define DELAY_SEEK attotime::from_usec( 100 ) /* track seek time */ +#define DELAY_ADDR attotime::from_usec( 100 ) /* search-address time */ + + + +static const char *const mc6843_cmd[16] = +{ + "---", "---", "STZ", "SEK", "SSR", "SSW", "RCR", "SWD", + "---", "---", "FFR", "FFW", "MSR", "MSW", "---", "---", +}; + + +/******************* utility function and macros ********************/ + +#define LOG(x) do { if (VERBOSE) logerror x; } while (0) + + + + +const device_type MC6843 = &device_creator<mc6843_device>; + +mc6843_device::mc6843_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : device_t(mconfig, MC6843, "MC6843 floppy controller", tag, owner, clock, "mc6843", __FILE__), + m_write_irq(*this), + m_CTAR(0), + m_CMR(0), + m_ISR(0), + m_SUR(0), + m_STRA(0), + m_STRB(0), + m_SAR(0), + m_GCR(0), + m_CCR(0), + m_LTAR(0), + m_drive(0), + m_side(0), + m_data_size(0), + m_data_idx(0), + m_data_id(0), + m_index_pulse(0), + m_timer_cont(NULL) +{ + for (int i = 0; i < 128; i++) + { + m_data[i] = 0; + } +} + +//------------------------------------------------- +// device_start - device-specific startup +//------------------------------------------------- + +void mc6843_device::device_start() +{ + m_write_irq.resolve_safe(); + + m_timer_cont = timer_alloc(TIMER_CONT); + + save_item(NAME(m_CTAR)); + save_item(NAME(m_CMR)); + save_item(NAME(m_ISR)); + save_item(NAME(m_SUR)); + save_item(NAME(m_STRA)); + save_item(NAME(m_STRB)); + save_item(NAME(m_SAR)); + save_item(NAME(m_GCR)); + save_item(NAME(m_CCR)); + save_item(NAME(m_LTAR)); + save_item(NAME(m_drive)); + save_item(NAME(m_side)); + save_item(NAME(m_data)); + save_item(NAME(m_data_size)); + save_item(NAME(m_data_idx)); + save_item(NAME(m_data_id)); +} + +//------------------------------------------------- +// device_reset - device-specific reset +//------------------------------------------------- + +void mc6843_device::device_reset() +{ + int i; + LOG (( "mc6843 reset\n" )); + + /* setup/reset floppy drive */ + for ( i = 0; i < 4; i++ ) + { + legacy_floppy_image_device * img = floppy_image( i ); + img->floppy_mon_w(CLEAR_LINE); + img->floppy_drive_set_ready_state(FLOPPY_DRIVE_READY, 0 ); + img->floppy_drive_set_rpm( 300. ); + } + + /* reset registers */ + m_CMR &= 0xf0; /* zero only command */ + m_ISR = 0; + m_STRA &= 0x5c; + m_SAR = 0; + m_STRB &= 0x20; + status_update( ); + + m_data_size = 0; + m_data_idx = 0; + m_timer_cont->adjust( attotime::never ); +} + +/************************** floppy interface ****************************/ + + + +legacy_floppy_image_device* mc6843_device::floppy_image( UINT8 drive ) +{ + legacy_floppy_image_device *img = floppy_get_device( machine(), drive ); + if (!img && owner()) { + // For slot devices, drives are typically attached to the slot rather than the machine + const char *floppy_name = NULL; + switch (drive) { + case 0: + floppy_name = FLOPPY_0; + break; + case 1: + floppy_name = FLOPPY_1; + break; + case 2: + floppy_name = FLOPPY_2; + break; + case 3: + floppy_name = FLOPPY_3; + break; + } + img = owner()->subdevice<legacy_floppy_image_device>(floppy_name); + } + return img; +} + + +legacy_floppy_image_device* mc6843_device::floppy_image( ) +{ + return floppy_image( m_drive ); +} + + +void mc6843_device::set_drive( int drive ) +{ + m_drive = drive; +} + + + +void mc6843_device::set_side( int side ) +{ + m_side = side; +} + + + +/* called after ISR or STRB has changed */ +void mc6843_device::status_update( ) +{ + int irq = 0; + + /* ISR3 */ + if ( (m_CMR & 0x40) || ! m_STRB ) + m_ISR &= ~8; + else + m_ISR |= 8; + + /* interrupts */ + if ( m_ISR & 4 ) + irq = 1; /* unmaskable */ + if ( ! (m_CMR & 0x80) ) + { + /* maskable */ + if ( m_ISR & ~4 ) + irq = 1; + } + + m_write_irq( irq ); + LOG(( "status_update: irq=%i (CMR=%02X, ISR=%02X)\n", irq, m_CMR, m_ISR )); +} + + +void mc6843_device::set_index_pulse( int index_pulse ) +{ + m_index_pulse = index_pulse; +} + + +/* called at end of command */ +void mc6843_device::cmd_end( ) +{ + int cmd = m_CMR & 0x0f; + if ( ( cmd == CMD_STZ ) || ( cmd == CMD_SEK ) ) + { + m_ISR |= 0x02; /* set Settling Time Complete */ + } + else + { + m_ISR |= 0x01; /* set Macro Command Complete */ + } + m_STRA &= ~0x80; /* clear Busy */ + m_CMR &= 0xf0; /* clear command */ + status_update( ); +} + + + +/* Seek Track Zero bottom half */ +void mc6843_device::finish_STZ( ) +{ + legacy_floppy_image_device* img = floppy_image( ); + int i; + + /* seek to track zero */ + for ( i=0; i<83; i++ ) + { + if (img->floppy_tk00_r() == CLEAR_LINE) + break; + img->floppy_drive_seek( -1 ); + } + + LOG(( "%f mc6843_finish_STZ: actual=%i\n", machine().time().as_double(), img->floppy_drive_get_current_track() )); + + /* update state */ + m_CTAR = 0; + m_GCR = 0; + m_SAR = 0; + m_STRB |= img->floppy_tk00_r() << 4; + + cmd_end( ); +} + + + +/* Seek bottom half */ +void mc6843_device::finish_SEK( ) +{ + legacy_floppy_image_device* img = floppy_image( ); + + /* seek to track */ + // TODO: not sure how CTAR bit 7 is handled here, but this is the safest approach for now + img->floppy_drive_seek( m_GCR - (m_CTAR & 0x7F) ); + + LOG(( "%f mc6843_finish_SEK: from %i to %i (actual=%i)\n", machine().time().as_double(), (m_CTAR & 0x7F), m_GCR, img->floppy_drive_get_current_track() )); + + /* update state */ + m_CTAR = m_GCR; + m_SAR = 0; + cmd_end( ); +} + + + +/* preamble to all sector read / write commands, returns 1 if found */ +int mc6843_device::address_search( chrn_id* id ) +{ + legacy_floppy_image_device* img = floppy_image( ); + int r = 0; + + while ( 1 ) + { + if ( ( ! img->floppy_drive_get_next_id( m_side, id ) ) || ( id->flags & ID_FLAG_CRC_ERROR_IN_ID_FIELD ) || ( id->N != 0 ) ) + { + /* read address error */ + LOG(( "%f mc6843_address_search: get_next_id failed\n", machine().time().as_double() )); + m_STRB |= 0x0a; /* set CRC error & Sector Address Undetected */ + cmd_end( ); + return 0; + } + + if ( id->C != m_LTAR ) + { + /* track mismatch */ + LOG(( "%f mc6843_address_search: track mismatch: logical=%i real=%i\n", machine().time().as_double(), m_LTAR, id->C )); + m_data[0] = id->C; /* make the track number available to the CPU */ + m_STRA |= 0x20; /* set Track Not Equal */ + cmd_end( ); + return 0; + } + + if ( id->R == m_SAR ) + { + /* found! */ + LOG(( "%f mc6843_address_search: sector %i found on track %i\n", machine().time().as_double(), id->R, id->C )); + if ( ! (m_CMR & 0x20) ) + { + m_ISR |= 0x04; /* if no DMA, set Status Sense */ + } + return 1; + } + + if ( img->floppy_drive_get_flag_state( FLOPPY_DRIVE_INDEX ) ) + { + r++; + if ( r >= 4 ) + { + /* time-out after 3 full revolutions */ + LOG(( "%f mc6843_address_search: no sector %i found after 3 revolutions\n", machine().time().as_double(), m_SAR )); + m_STRB |= 0x08; /* set Sector Address Undetected */ + cmd_end( ); + return 0; + } + } + } + + //return 0; /* unreachable */ +} + + + +/* preamble specific to read commands (adds extra checks) */ +int mc6843_device::address_search_read( chrn_id* id ) +{ + if ( ! address_search( id ) ) + return 0; + + if ( id->flags & ID_FLAG_CRC_ERROR_IN_DATA_FIELD ) + { + LOG(( "%f mc6843_address_search_read: data CRC error\n", machine().time().as_double() )); + m_STRB |= 0x06; /* set CRC error & Data Mark Undetected */ + cmd_end( ); + return 0; + } + + if ( id->flags & ID_FLAG_DELETED_DATA ) + { + LOG(( "%f mc6843_address_search_read: deleted data\n", machine().time().as_double() )); + m_STRA |= 0x02; /* set Delete Data Mark Detected */ + } + + return 1; +} + + + + +/* Read CRC bottom half */ +void mc6843_device::finish_RCR( ) +{ + chrn_id id; + if ( ! address_search_read( &id ) ) + return; + cmd_end( ); +} + + + +/* Single / Multiple Sector Read bottom half */ +void mc6843_device::cont_SR( ) +{ + chrn_id id; + legacy_floppy_image_device* img = floppy_image( ); + + /* sector seek */ + if ( ! address_search_read( &id ) ) + return; + + /* sector read */ + img->floppy_drive_read_sector_data( m_side, id.data_id, m_data, 128 ); + m_data_idx = 0; + m_data_size = 128; + m_STRA |= 0x01; /* set Data Transfer Request */ + status_update( ); +} + + + +/* Single / Multiple Sector Write bottom half */ +void mc6843_device::cont_SW( ) +{ + chrn_id id; + + /* sector seek */ + if ( ! address_search( &id ) ) + return; + + /* setup sector write buffer */ + m_data_idx = 0; + m_data_size = 128; + m_STRA |= 0x01; /* set Data Transfer Request */ + m_data_id = id.data_id; /* for subsequent write sector command */ + status_update( ); +} + + + +/* bottom halves, called to continue / finish a command after some delay */ +void mc6843_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) +{ + switch (id) + { + case TIMER_CONT: + { + int cmd = m_CMR & 0x0f; + + LOG(( "%f mc6843_cont: timer called for cmd=%s(%i)\n", machine().time().as_double(), mc6843_cmd[cmd], cmd )); + + m_timer_cont->adjust( attotime::never ); + + switch ( cmd ) + { + case CMD_STZ: finish_STZ( ); break; + case CMD_SEK: finish_SEK( ); break; + case CMD_SSR: cont_SR( ); break; + case CMD_SSW: cont_SW( ); break; + case CMD_RCR: finish_RCR( ); break; + case CMD_SWD: cont_SW( ); break; + case CMD_MSW: cont_SW( ); break; + case CMD_MSR: cont_SR( ); break; + } + } + break; + + default: + break; + } +} + + + +/************************** CPU interface ****************************/ + + + +READ8_MEMBER( mc6843_device::read ) +{ + UINT8 data = 0; + + switch ( offset ) { + case 0: /* Data Input Register (DIR) */ + { + int cmd = m_CMR & 0x0f; + + LOG(( "%f %s mc6843_r: data input cmd=%s(%i), pos=%i/%i, GCR=%i, ", + machine().time().as_double(), machine().describe_context(), + mc6843_cmd[cmd], cmd, m_data_idx, + m_data_size, m_GCR )); + + if ( cmd == CMD_SSR || cmd == CMD_MSR ) + { + /* sector read */ + assert( m_data_size > 0 ); + assert( m_data_idx < m_data_size ); + assert( m_data_idx < sizeof(m_data) ); + data = m_data[ m_data_idx ]; + m_data_idx++; + + if ( m_data_idx >= m_data_size ) + { + /* end of sector read */ + + m_STRA &= ~0x01; /* clear Data Transfer Request */ + + if ( cmd == CMD_MSR ) + { + /* schedule next sector in multiple sector read */ + m_GCR--; + m_SAR++; + if ( m_GCR == 0xff ) + { + cmd_end( ); + } + else if ( m_SAR > 26 ) + + { + m_STRB |= 0x08; /* set Sector Address Undetected */ + cmd_end( ); + } + else + { + m_timer_cont->adjust( DELAY_ADDR ); + } + } + else + { + cmd_end( ); + } + } + } + else if ( cmd == 0 ) + { + data = m_data[0]; + } + else + { + /* XXX TODO: other read modes */ + data = m_data[0]; + logerror( "%s mc6843 read in unsupported command mode %i\n", machine().describe_context(), cmd ); + } + + LOG(( "data=%02X\n", data )); + + break; + } + + case 1: /* Current-Track Address Register (CTAR) */ + data = m_CTAR; + LOG(( "%f %s mc6843_r: read CTAR %i (actual=%i)\n", + machine().time().as_double(), machine().describe_context(), data, + floppy_image()->floppy_drive_get_current_track())); + break; + + case 2: /* Interrupt Status Register (ISR) */ + data = m_ISR; + LOG(( "%f %s mc6843_r: read ISR %02X: cmd=%scomplete settle=%scomplete sense-rq=%i STRB=%i\n", + machine().time().as_double(), machine().describe_context(), data, + (data & 1) ? "" : "not-" , (data & 2) ? "" : "not-", + (data >> 2) & 1, (data >> 3) & 1 )); + + /* reset */ + m_ISR &= 8; /* keep STRB */ + status_update( ); + break; + + case 3: /* Status Register A (STRA) */ + { + /* update */ + legacy_floppy_image_device* img = floppy_image( ); + int flag = img->floppy_drive_get_flag_state( FLOPPY_DRIVE_READY); + m_STRA &= 0xa3; + if ( flag & FLOPPY_DRIVE_READY ) + m_STRA |= 0x04; + + m_STRA |= !img->floppy_tk00_r() << 3; + m_STRA |= !img->floppy_wpt_r() << 4; + + if ( m_index_pulse ) + m_STRA |= 0x40; + + data = m_STRA; + LOG(( "%f %s mc6843_r: read STRA %02X: data-rq=%i del-dta=%i ready=%i t0=%i wp=%i trk-dif=%i idx=%i busy=%i\n", + machine().time().as_double(), machine().describe_context(), data, + data & 1, (data >> 1) & 1, (data >> 2) & 1, (data >> 3) & 1, + (data >> 4) & 1, (data >> 5) & 1, (data >> 6) & 1, (data >> 7) & 1 )); + break; + } + + case 4: /* Status Register B (STRB) */ + data = m_STRB; + LOG(( "%f %s mc6843_r: read STRB %02X: data-err=%i CRC-err=%i dta--mrk-err=%i sect-mrk-err=%i seek-err=%i fi=%i wr-err=%i hard-err=%i\n", + machine().time().as_double(), machine().describe_context(), data, + data & 1, (data >> 1) & 1, (data >> 2) & 1, (data >> 3) & 1, + (data >> 4) & 1, (data >> 5) & 1, (data >> 6) & 1, (data >> 7) & 1 )); + + /* (partial) reset */ + m_STRB &= ~0xfb; + status_update( ); + break; + + case 7: /* Logical-Track Address Register (LTAR) */ + data = m_LTAR; + LOG(( "%f %s mc6843_r: read LTAR %i (actual=%i)\n", + machine().time().as_double(), machine().describe_context(), data, + floppy_image()->floppy_drive_get_current_track())); + break; + + default: + logerror( "%s mc6843 invalid read offset %i\n", machine().describe_context(), offset ); + } + + return data; +} + +WRITE8_MEMBER( mc6843_device::write ) +{ + switch ( offset ) { + case 0: /* Data Output Register (DOR) */ + { + int cmd = m_CMR & 0x0f; + int FWF = (m_CMR >> 4) & 1; + + LOG(( "%f %s mc6843_w: data output cmd=%s(%i), pos=%i/%i, GCR=%i, data=%02X\n", + machine().time().as_double(), machine().describe_context(), + mc6843_cmd[cmd], cmd, m_data_idx, + m_data_size, m_GCR, data )); + + if ( cmd == CMD_SSW || cmd == CMD_MSW || cmd == CMD_SWD ) + { + /* sector write */ + assert( m_data_size > 0 ); + assert( m_data_idx < m_data_size ); + assert( m_data_idx < sizeof(m_data) ); + m_data[ m_data_idx ] = data; + m_data_idx++; + if ( m_data_idx >= m_data_size ) + { + /* end of sector write */ + legacy_floppy_image_device* img = floppy_image( ); + + LOG(( "%f %s mc6843_w: write sector %i\n", machine().time().as_double(), machine().describe_context(), m_data_id )); + + img->floppy_drive_write_sector_data( + m_side, m_data_id, + m_data, m_data_size, + (cmd == CMD_SWD) ? ID_FLAG_DELETED_DATA : 0 ); + + m_STRA &= ~0x01; /* clear Data Transfer Request */ + + if ( cmd == CMD_MSW ) + { + m_GCR--; + m_SAR++; + if ( m_GCR == 0xff ) + { + cmd_end( ); + } + else if ( m_SAR > 26 ) + + { + m_STRB |= 0x08; /* set Sector Address Undetected */ + cmd_end( ); + } + else + { + m_timer_cont->adjust( DELAY_ADDR ); + } + } + else + { + cmd_end( ); + } + } + } + else if ( (cmd == CMD_FFW) && FWF ) + { + /* assume we are formatting */ + UINT8 nibble; + nibble = + (data & 0x01) | + ((data & 0x04) >> 1 )| + ((data & 0x10) >> 2 )| + ((data & 0x40) >> 3 ); + + assert( m_data_idx < sizeof(m_data) ); + + m_data[m_data_idx / 2] = + (m_data[m_data_idx / 2] << 4) | nibble; + + if ( (m_data_idx == 0) && (m_data[0] == 0xfe ) ) + { + /* address mark detected */ + m_data_idx = 2; + } + else if ( m_data_idx == 9 ) + { + /* address id field complete */ + if ( (m_data[2] == 0) && (m_data[4] == 0) ) + { + /* valid address id field */ + legacy_floppy_image_device* img = floppy_image( ); + UINT8 track = m_data[1]; + UINT8 sector = m_data[3]; + UINT8 filler = 0xe5; /* standard Thomson filler */ + LOG(( "%f %s mc6843_w: address id detected track=%i sector=%i\n", machine().time().as_double(), machine().describe_context(), track, sector)); + img->floppy_drive_format_sector( m_side, sector, track, 0, sector, 0, filler ); + } + else + { + /* abort */ + m_data_idx = 0; + } + } + else if ( m_data_idx > 0 ) + { + /* accumulate address id field */ + m_data_idx++; + } + } + else if ( cmd == 0 ) + { + /* nothing */ + } + else + { + /* XXX TODO: other write modes */ + logerror( "%s mc6843 write %02X in unsupported command mode %i (FWF=%i)\n", machine().describe_context(), data, cmd, FWF ); + } + break; + } + + case 1: /* Current-Track Address Register (CTAR) */ + m_CTAR = data; + LOG(( "%f %s mc6843_w: set CTAR to %i %02X (actual=%i) \n", + machine().time().as_double(), machine().describe_context(), m_CTAR, data, + floppy_image()->floppy_drive_get_current_track())); + break; + + case 2: /* Command Register (CMR) */ + { + int cmd = data & 15; + + LOG(( "%f %s mc6843_w: set CMR to $%02X: cmd=%s(%i) FWF=%i DMA=%i ISR3-intr=%i fun-intr=%i\n", + machine().time().as_double(), machine().describe_context(), + data, mc6843_cmd[cmd], cmd, (data >> 4) & 1, (data >> 5) & 1, + (data >> 6) & 1, (data >> 7) & 1 )); + + /* sanitize state */ + m_STRA &= ~0x81; /* clear Busy & Data Transfer Request */ + m_data_idx = 0; + m_data_size = 0; + + /* commands are initiated by updating some flags and scheduling + a bottom-half (mc6843_cont) after some delay */ + + switch (cmd) + { + case CMD_SSW: + case CMD_SSR: + case CMD_SWD: + case CMD_RCR: + case CMD_MSR: + case CMD_MSW: + m_STRA |= 0x80; /* set Busy */ + m_STRA &= ~0x22; /* clear Track Not Equal & Delete Data Mark Detected */ + m_STRB &= ~0x04; /* clear Data Mark Undetected */ + m_timer_cont->adjust( DELAY_ADDR ); + break; + case CMD_STZ: + case CMD_SEK: + m_STRA |= 0x80; /* set Busy */ + m_timer_cont->adjust( DELAY_SEEK ); + break; + case CMD_FFW: + case CMD_FFR: + m_data_idx = 0; + m_STRA |= 0x01; /* set Data Transfer Request */ + break; + } + + m_CMR = data; + status_update( ); + break; + } + + case 3: /* Set-Up Register (SUR) */ + m_SUR = data; + + /* assume CLK freq = 1MHz (IBM 3740 compatibility) */ + LOG(( "%f %s mc6843_w: set SUR to $%02X: head settling time=%fms, track-to-track seek time=%f\n", + machine().time().as_double(), machine().describe_context(), + data, 4.096 * (data & 15), 1.024 * ((data >> 4) & 15) )); + break; + + case 4: /* Sector Address Register (SAR) */ + m_SAR = data & 0x1f; + LOG(( "%f %s mc6843_w: set SAR to %i (%02X)\n", machine().time().as_double(), machine().describe_context(), m_SAR, data )); + break; + + case 5: /* General Count Register (GCR) */ + m_GCR = data & 0x7f; + LOG(( "%f %s mc6843_w: set GCR to %i (%02X)\n", machine().time().as_double(), machine().describe_context(), m_GCR, data )); + break; + + case 6: /* CRC Control Register (CCR) */ + m_CCR = data & 3; + LOG(( "%f %s mc6843_w: set CCR to %02X: CRC=%s shift=%i\n", + machine().time().as_double(), machine().describe_context(), data, + (data & 1) ? "enabled" : "disabled", (data >> 1) & 1 )); + break; + + case 7: /* Logical-Track Address Register (LTAR) */ + m_LTAR = data & 0x7f; + LOG(( "%f %s mc6843_w: set LTAR to %i %02X (actual=%i)\n", + machine().time().as_double(), machine().describe_context(), m_LTAR, data, + floppy_image()->floppy_drive_get_current_track())); + break; + + default: + logerror( "%s mc6843 invalid write offset %i (data=$%02X)\n", machine().describe_context(), offset, data ); + } +} |