int copy_and_gen_ecc(char *destination, byte * source, unsigned int bad_sector_map) { TRACE_FUN(8, "copy_and_gen_ecc"); int result; struct memory_segment mseg; int bads = count_ones(bad_sector_map); if (bads > 0) { TRACEi(4, "bad sectors in map:", bads); } if (bads + 3 >= SECTORS_PER_SEGMENT) { TRACE(4, "empty segment"); mseg.blocks = 0; /* skip entire segment */ result = 0; /* nothing written */ } else { mseg.blocks = SECTORS_PER_SEGMENT - bads; mseg.data = destination; memcpy(mseg.data, source, (mseg.blocks - 3) * SECTOR_SIZE); result = ecc_set_segment_parity(&mseg); if (result < 0) { TRACE(1, "ecc_set_segment_parity failed"); } else { result = (mseg.blocks - 3) * SECTOR_SIZE; } } TRACE_EXIT; return result; }
int start_writing(int mode) { TRACE_FUN(5, "start_writing"); int result = 0; buffer_struct *buff = &buffer[head]; int segment_id = buff->segment_id; if (ftape_state == writing && buff->status == waiting) { setup_new_segment(buff, segment_id, 1); if (mode == WRITE_SINGLE) { buffer[head].next_segment = 0; /* stop tape instead of pause */ } calc_next_cluster(buff); /* prepare */ buff->status = writing; if (runner_status == idle) { TRACEi(5, "starting runner for segment", segment_id); result = ftape_start_tape(segment_id, buff->sector_offset); if (result >= 0) { runner_status = running; } } if (result >= 0) { result = setup_fdc_and_dma(buff, FDC_WRITE); /* go */ } ftape_state = writing; } TRACE_EXIT; return result; }
/* Write to tape device */ static int ftape_write(struct inode *ino, struct file *fp, const char *buff, int req_len) { TRACE_FUN(8, "ftape_write"); int result = -EIO; int old_sigmask; TRACEi(5, "called with count:", req_len); if (!busy_flag || MINOR(ino->i_rdev) != ftape_unit || ftape_failure) { TRACE(1, "failed: not busy, failure or wrong unit"); TRACE_EXIT; return -EIO; } old_sigmask = current->blocked; /* save mask */ current->blocked = _BLOCK_ALL; result = _ftape_write(buff, req_len); TRACEi(7, "return with count:", result); current->blocked = old_sigmask; /* restore mask */ TRACE_EXIT; return result; }
/* Open ftape device */ static int ftape_open(struct inode *ino, struct file *filep) { TRACE_FUN(4, "ftape_open"); int result; MOD_INC_USE_COUNT; /* lock module in memory */ TRACEi(5, "called for minor", MINOR(ino->i_rdev)); if (busy_flag) { TRACE(1, "failed: already busy"); MOD_DEC_USE_COUNT; /* unlock module in memory */ TRACE_EXIT; return -EBUSY; } if ((MINOR(ino->i_rdev) & ~FTAPE_NO_REWIND) > 3) { TRACE(1, "failed: illegal unit nr"); MOD_DEC_USE_COUNT; /* unlock module in memory */ TRACE_EXIT; return -ENXIO; } if (ftape_unit == -1 || FTAPE_UNIT != (MINOR(ino->i_rdev) & 3)) { /* Other selection than last time */ ftape_init_driver(); } ftape_unit = MINOR(ino->i_rdev); ftape_failure = 0; /* allow tape operations */ old_sigmask = current->blocked; current->blocked = _BLOCK_ALL; fdc_save_drive_specs(); /* save Drive Specification regs on i82078-1's */ result = _ftape_open(); if (result < 0) { TRACE(1, "_ftape_open failed"); current->blocked = old_sigmask; /* restore mask */ MOD_DEC_USE_COUNT; /* unlock module in memory */ TRACE_EXIT; return result; } else { busy_flag = 1; /* Mask signals that will disturb proper operation of the * program that is calling. */ current->blocked = old_sigmask | _DO_BLOCK; TRACE_EXIT; return 0; } }
/* Write as much as fits from buffer to the given segment on tape * and handle retries. * Return the number of bytes written (>= 0), or: * -EIO write failed * -EINTR interrupted by signal * -ENOSPC device full */ int _write_segment(unsigned int segment_id, byte * buffer, int flush) { TRACE_FUN(5, "_write_segment"); int retry = 0; int result; history.used |= 2; for (;;) { if (segment_id > ftape_last_segment.id && !flush) { result = -ENOSPC; /* tape full */ break; } result = write_segment(segment_id, buffer, flush); if (result < 0) { if (result == -EAGAIN) { if (++retry > 100) { TRACE(1, "write failed, >100 retries in segment"); result = -EIO; /* give up */ break; } else { TRACEx1(2, "write error, retry %d", retry); } } else { TRACEi(1, "write_segment failed, error:", -result); break; } } else { /* success */ if (result == 0) { /* empty segment */ TRACE(4, "empty segment, nothing written"); } break; } /* Allow escape from loop when signaled ! */ if (current->signal & _DONT_BLOCK) { TRACE(2, "interrupted by signal"); TRACE_EXIT; result = -EINTR; /* is this the right return value ? */ break; } } TRACE_EXIT; return result; }
/* IOCTL routine called by kernel-interface code */ int _ftape_ioctl(unsigned int command, void *arg) { TRACE_FUN(8, "ftape_ioctl"); int result = EINVAL; union { struct mtop mtop; struct mtget mtget; } krnl_arg; int arg_size = (command & IOCSIZE_MASK) >> IOCSIZE_SHIFT; /* This check will only catch arguments that are too large ! */ if ((command & IOC_INOUT) && arg_size > sizeof(krnl_arg)) { TRACEi(1, "bad argument size:", arg_size); TRACE_EXIT; return -EINVAL; } if (command & IOC_IN) { int error = verify_area(VERIFY_READ, arg, arg_size); if (error) { TRACE_EXIT; return error; } memcpy_fromfs(&krnl_arg.mtop, arg, arg_size); } TRACEx1(5, "called with ioctl command: 0x%08x", command); switch (command) { /* cpio compatibility * mtrasx and mtreset are mt extension by Hennus Bergman * mtseek and mttell are mt extension by eddy olk */ case MTIOCTOP: TRACEx1(5, "calling MTIOCTOP command: 0x%08x", krnl_arg.mtop.mt_op); switch (krnl_arg.mtop.mt_op) { case MTNOP: /* gnu mt calls MTNOP before MTIOCGET to set status */ result = 0; break; case MTRESET: result = ftape_reset_drive(); init_drive_needed = 1; if (result < 0 || ftape_offline) { break; } result = ftape_seek_to_bot(); ftape_reset_position(); break; case MTREW: case MTOFFL: if (ftape_offline) { result = -EIO; break; } ftape_flush_buffers(); ftape_update_header_segments(NULL, 1); result = ftape_seek_to_bot(); ftape_reset_position(); if (krnl_arg.mtop.mt_op == MTOFFL) { going_offline = 1; TRACE(4, "Putting tape drive offline"); } result = 0; break; case MTRETEN: if (ftape_offline) { result = -EIO; break; } result = ftape_seek_to_eot(); if (result >= 0) { result = ftape_seek_to_bot(); } ftape_reset_position(); break; case MTERASE: if (ftape_offline) { result = -EIO; break; } result = ftape_erase(); break; case MTEOM: if (ftape_offline) { result = -EIO; break; } result = ftape_seek_eom(); break; case MTFSFM: if (ftape_offline) { result = -EIO; break; } eof_mark = 1; /* position ready to extend */ case MTFSF: if (ftape_offline) { result = -EIO; break; } result = ftape_seek_eof(krnl_arg.mtop.mt_count); break; case MTBSFM: if (ftape_offline) { result = -EIO; break; } eof_mark = 1; /* position ready to extend */ case MTBSF: if (ftape_offline) { result = -EIO; break; } result = ftape_seek_eof(-krnl_arg.mtop.mt_count); break; case MTFSR: if (ftape_offline) { result = -EIO; break; } tracing = krnl_arg.mtop.mt_count; TRACEx1(2, "tracing set to %d", tracing); result = 0; break; case MTBSR: if (ftape_offline) { result = -EIO; break; } #if 0 result = ftape_fix(); #else result = 0; #endif break; case MTWEOF: if (ftape_offline) { result = -EIO; break; } result = ftape_weof(krnl_arg.mtop.mt_count, ftape_seg_pos, 1); if (result >= 0) { ftape_seg_pos += krnl_arg.mtop.mt_count - 1; } break; /* MTRASx and MTRESET are mt extension by Hennus Bergman */ case MTRAS1: case MTRAS2: case MTRAS3: case MTSEEK: case MTTELL: default: TRACEi(1, "MTIOCTOP sub-command not implemented:", krnl_arg.mtop.mt_op); result = -EIO; break; } break; case MTIOCGET: krnl_arg.mtget.mt_type = drive_type.vendor_id + 0x800000; krnl_arg.mtget.mt_resid = 0; /* not implemented */ krnl_arg.mtget.mt_dsreg = 0; /* status register */ krnl_arg.mtget.mt_gstat = /* device independent status */ ((ftape_offline) ? 0 : GMT_ONLINE(-1L)) | ((write_protected) ? GMT_WR_PROT(-1L) : 0) | ((no_tape) ? GMT_DR_OPEN(-1L) : 0); krnl_arg.mtget.mt_erreg = ftape_last_error; /* error register */ result = ftape_file_no(&krnl_arg.mtget.mt_fileno, &krnl_arg.mtget.mt_blkno); break; case MTIOCPOS: TRACE(5, "Mag tape ioctl command: MTIOCPOS"); TRACE(1, "MTIOCPOS command not implemented"); break; default: result = -EINVAL; break; } if (command & IOC_OUT) { int error = verify_area(VERIFY_WRITE, arg, arg_size); if (error) { TRACE_EXIT; return error; } memcpy_tofs(arg, &krnl_arg, arg_size); } TRACE_EXIT; return result; }
/* RELEASE routine called by kernel-interface code */ int _ftape_close(void) { TRACE_FUN(8, "_ftape_close"); int result = 0; int last_segment = 0; if (!ftape_offline) { result = ftape_flush_buffers(); last_segment = ftape_seg_pos - 1; if (!(ftape_unit & FTAPE_NO_REWIND)) { if (result >= 0) { result = ftape_update_header_segments(NULL, 1); if (result < 0) { TRACE(1, "error: update of header segments failed"); } } else { TRACE(1, "error: unable to update header segments"); } } ftape_abort_operation(); if (!(ftape_unit & FTAPE_NO_REWIND)) { if (!no_tape) { TRACE(5, "rewinding tape"); result = ftape_seek_to_bot(); } ftape_reset_position(); ftape_zap_read_buffers(); ftape_zap_write_buffers(); } } ftape_detach_drive(); fdc_uninit(); if (history.used) { TRACE(3, "== Non-fatal errors this run: =="); TRACE(3, "fdc isr statistics:"); TRACEi(3, " id_am_errors :", history.id_am_errors); TRACEi(3, " id_crc_errors :", history.id_crc_errors); TRACEi(3, " data_am_errors :", history.data_am_errors); TRACEi(3, " data_crc_errors :", history.data_crc_errors); TRACEi(3, " overrun_errors :", history.overrun_errors); TRACEi(3, " no_data_errors :", history.no_data_errors); TRACEi(3, " retries :", history.retries); if (history.used & 1) { TRACE(3, "ecc statistics:"); TRACEi(3, " crc_errors :", history.crc_errors); TRACEi(3, " crc_failures :", history.crc_failures); TRACEi(3, " ecc_failures :", history.ecc_failures); TRACEi(3, " sectors corrected:", history.corrected); } TRACEx2(3, "media defects : %d%s", history.defects, history.defects ? " !!!" : ""); TRACEi(3, "repositions :", history.rewinds); TRACEi(3, "last segment :", last_segment); } if (going_offline) { going_offline = 0; ftape_offline = 1; } TRACE_EXIT; return result; }
int ftape_get_drive_status(int *new_tape, int *no_tape, int *wp_tape) { TRACE_FUN(5, "ftape_get_drive_status"); int result; int status; *no_tape = *wp_tape = 0; /* Tape drive is activated now. * First clear error status if present. */ do { result = ftape_ready_wait(timeout.reset, &status); if (result < 0) { if (result == -ETIME) { TRACE(1, "ftape_ready_wait timeout"); } else if (result == -EINTR) { TRACE(1, "ftape_ready_wait aborted"); } else { TRACE(1, "ftape_ready_wait failed"); } result = -EIO; break; } /* Clear error condition (drive is ready !) */ if (status & QIC_STATUS_ERROR) { int error; int command; TRACE(1, "error status set"); result = ftape_report_error(&error, &command, 1); if (result < 0) { TRACEi(1, "report_error_code failed:", result); ftape_reset_drive(); /* hope it's working next time */ init_drive_needed = 1; result = -EIO; break; } else if (error != 0) { TRACEi(4, "error code :", error); TRACEi(4, "error command:", command); } } if (status & QIC_STATUS_NEW_CARTRIDGE) { int error; int command; int old_tracing = tracing; /* Undocumented feature: Must clear (not present!) error * here or we'll fail later. */ tracing = 0; ftape_report_error(&error, &command, 1); tracing = old_tracing; TRACE(3, "status: new cartridge"); *new_tape = 1; } } while (status & QIC_STATUS_ERROR); *no_tape = !(status & QIC_STATUS_CARTRIDGE_PRESENT); *wp_tape = (status & QIC_STATUS_WRITE_PROTECT); if (*no_tape) { TRACE(1, "no cartridge present"); } else { if (*wp_tape) { TRACE(2, "Write protected cartridge"); } } TRACE_EXIT; return result; }
int _ftape_write(const char *buff, int req_len) { TRACE_FUN(5, "_ftape_write"); int result = 0; int cnt; int written = 0; if (write_protected) { TRACE(1, "error: cartridge write protected"); last_write_failed = 1; result = -EROFS; } else if (ftape_offline || !formatted || no_tape) { result = -EIO; } else if (first_data_segment == -1) { /* * If we haven't read the header segment yet, do it now. * This will verify the configuration, get the eof markers * and the bad sector table. * We'll use the deblock buffer for scratch. */ result = read_header_segment(deblock_buffer); if (result >= 0 && ftape_seg_pos > ftape_last_segment.id) { result = -ENOSPC; /* full is full */ } } if (result < 0) { TRACE_EXIT; return result; } /* * This part writes data blocks to tape until the * requested amount is written. * The data will go in a buffer until it's enough * for a segment without bad sectors. Then we'll write * that segment to tape. * The bytes written will be removed from the buffer * and the process is repeated until there is less * than one segment to write left in the buffer. */ while (req_len > 0) { int space_left = sizeof(deblock_buffer) - buf_pos_wr; TRACEi(7, "remaining req_len:", req_len); TRACEi(7, " buf_pos:", buf_pos_wr); cnt = (req_len < space_left) ? req_len : space_left; if (cnt > 0) { result = verify_area(VERIFY_READ, buff, cnt); if (result) { TRACE(1, "verify_area failed"); last_write_failed = 1; TRACE_EXIT; return result; } memcpy_fromfs(deblock_buffer + buf_pos_wr, buff, cnt); buff += cnt; req_len -= cnt; buf_pos_wr += cnt; } TRACEi(7, "moved into blocking buffer:", cnt); while (buf_pos_wr >= sizeof(deblock_buffer)) { /* If this is the last buffer to be written, let flush handle it. */ if (ftape_seg_pos >= ftape_last_segment.id) { TRACEi(7, "remaining in blocking buffer:", buf_pos_wr); TRACEi(7, "just written bytes:", written + cnt); TRACE_EXIT; return written + cnt; } /* Got one full buffer, write it to disk */ result = _write_segment(ftape_seg_pos, deblock_buffer, 0); TRACEi(5, "_write_segment result =", result); if (result < 0) { if (result == -EAGAIN) { TRACE(5, "retry..."); continue; /* failed, retry same segment */ } last_write_failed = 1; TRACE_EXIT; return result; } else { clear_eof_mark_if_set(ftape_seg_pos, result); } if (result > 0 && result < buf_pos_wr) { /* Partial write: move remainder in lower part of buffer */ memmove(deblock_buffer, deblock_buffer + result, buf_pos_wr - result); } TRACEi(7, "moved out of blocking buffer:", result); buf_pos_wr -= result; /* remainder */ ++ftape_seg_pos; /* Allow us to escape from this loop with a signal ! */ if (current->signal & _DONT_BLOCK) { TRACE(2, "interrupted by signal"); last_write_failed = 1; TRACE_EXIT; return -EINTR; /* is this the right return value ? */ } } written += cnt; } TRACEi(7, "remaining in blocking buffer:", buf_pos_wr); TRACEi(7, "just written bytes:", written); last_write_failed = 0; if (!need_flush && written > 0) { need_flush = 1; } TRACE_EXIT; return written; /* bytes written */ }
int ftape_flush_buffers(void) { TRACE_FUN(5, "ftape_flush_buffers"); int result; int pad_count; int data_remaining; static int active = 0; if (active) { TRACE(5, "nested call, abort"); TRACE_EXIT; return 0; } active = 1; TRACEi(5, "entered, ftape_state =", ftape_state); if (ftape_state != writing && !need_flush) { active = 0; TRACE(5, "no need for flush"); TRACE_EXIT; return 0; } data_remaining = buf_pos_wr; buf_pos_wr = 0; /* prevent further writes if this fails */ TRACE(5, "flushing write buffers"); if (last_write_failed) { ftape_zap_write_buffers(); active = 0; TRACE_EXIT; return write_protected ? -EROFS : -EIO; } /* * If there is any data not written to tape yet, append zero's * up to the end of the sector. Then write the segment(s) to tape. */ if (data_remaining > 0) { int written; do { TRACEi(4, "remaining in buffer:", data_remaining); pad_count = sizeof(deblock_buffer) - data_remaining; TRACEi(7, "flush, padding count:", pad_count); memset(deblock_buffer + data_remaining, 0, pad_count); /* pad buffer */ result = _write_segment(ftape_seg_pos, deblock_buffer, 1); if (result < 0) { if (result != -ENOSPC) { last_write_failed = 1; } active = 0; TRACE_EXIT; return result; } written = result; clear_eof_mark_if_set(ftape_seg_pos, written); TRACEi(7, "flush, moved out buffer:", written); if (written > 0) { data_remaining -= written; if (data_remaining > 0) { /* Need another segment for remaining data, move the remainder * to the beginning of the buffer */ memmove(deblock_buffer, deblock_buffer + written, data_remaining); } } ++ftape_seg_pos; } while (data_remaining > 0); /* Data written to last segment == data_remaining + written * value is in range [1..29K]. */ TRACEx2(4, "last write: %d, netto pad-count: %d", data_remaining + written, -data_remaining); if (-1024 < data_remaining && data_remaining <= 0) { /* Last sector of segment was used for data, so put eof mark * in next segment and position at second file mark. */ if (ftape_weof(2, ftape_seg_pos, 1) >= 0) { ++ftape_seg_pos; /* position between file marks */ } } else { /* Put eof mark in previous segment after data and position * at second file mark. */ ftape_weof(2, ftape_seg_pos - 1, 1 + ((SECTOR_SIZE - 1 + result + data_remaining) / SECTOR_SIZE)); } } else { TRACE(7, "deblock_buffer empty"); if (ftape_weof(2, ftape_seg_pos, 1) >= 0) { ++ftape_seg_pos; /* position between file marks */ } start_writing(WRITE_MULTI); } TRACE(7, "waiting"); result = loop_until_writes_done(); if (result < 0) { TRACE(1, "flush buffers failed"); } ftape_state = idle; last_write_failed = 0; need_flush = 0; active = 0; TRACE_EXIT; return result; }
/* Write given segment from buffer at address onto tape. */ int write_segment(unsigned segment_id, byte * address, int flushing) { TRACE_FUN(5, "write_segment"); int result = 0; int bytes_written = 0; TRACEi(5, "segment_id =", segment_id); if (ftape_state != writing) { if (ftape_state == reading) { TRACE(5, "calling ftape_abort_operation"); result = ftape_abort_operation(); if (result < 0) { TRACE(1, "ftape_abort_operation failed"); } } ftape_zap_read_buffers(); ftape_zap_write_buffers(); ftape_state = writing; } /* if all buffers full we'll have to wait... */ wait_segment(writing); if (buffer[tail].status == error) { /* setup for a retry */ buffer[tail].status = waiting; bytes_written = -EAGAIN; /* force retry */ if (buffer[tail].hard_error_map != 0) { TRACEx1(1, "warning: %d hard error(s) in written segment", count_ones(buffer[tail].hard_error_map)); TRACEx1(4, "hard_error_map = 0x%08lx", buffer[tail].hard_error_map); /* Implement hard write error recovery here */ } } else if (buffer[tail].status == done) { history.defects += count_ones(buffer[tail].hard_error_map); } else { TRACE(1, "wait for empty segment failed"); result = -EIO; } /* If just passed last segment on tape: wait for BOT or EOT mark. */ if (result >= 0 && runner_status == logical_eot) { int status; result = ftape_ready_wait(timeout.seek, &status); if (result < 0 || (status & (QIC_STATUS_AT_BOT | QIC_STATUS_AT_EOT)) == 0) { TRACE(1, "eot/bot not reached"); } else { runner_status = end_of_tape; } } /* should runner stop ? */ if (result >= 0 && (runner_status == aborting || runner_status == buffer_underrun || runner_status == end_of_tape)) { if (runner_status != end_of_tape) { result = ftape_dumb_stop(); } if (result >= 0) { if (runner_status == aborting) { if (buffer[head].status == writing) { buffer[head].status = done; /* ????? */ } } runner_status = idle; /* aborted ? */ } } /* Don't start tape if runner idle and segment empty. */ if (result >= 0 && !(runner_status == idle && get_bad_sector_entry(segment_id) == EMPTY_SEGMENT)) { if (buffer[tail].status == done) { /* now at least one buffer is empty, fill it with our data. * skip bad sectors and generate ecc. * copy_and_gen_ecc return nr of bytes written, * range 0..29 Kb inclusive ! */ result = copy_and_gen_ecc(buffer[tail].address, address, get_bad_sector_entry(segment_id)); if (result >= 0) { bytes_written = result; buffer[tail].segment_id = segment_id; buffer[tail].status = waiting; next_buffer(&tail); } } /* Start tape only if all buffers full or flush mode. * This will give higher probability of streaming. */ if (result >= 0 && runner_status != running && ((head == tail && buffer[tail].status == waiting) || flushing)) { result = start_writing(WRITE_MULTI); } } TRACE_EXIT; return (result < 0) ? result : bytes_written; }