diff options
Diffstat (limited to 'drivers/char/ftape/lowlevel/ftape-bsm.c')
-rw-r--r-- | drivers/char/ftape/lowlevel/ftape-bsm.c | 490 |
1 files changed, 490 insertions, 0 deletions
diff --git a/drivers/char/ftape/lowlevel/ftape-bsm.c b/drivers/char/ftape/lowlevel/ftape-bsm.c new file mode 100644 index 000000000..a79c468e3 --- /dev/null +++ b/drivers/char/ftape/lowlevel/ftape-bsm.c @@ -0,0 +1,490 @@ +/* + * Copyright (C) 1994-1996 Bas Laarhoven, + * (C) 1996-1997 Claus Heine. + + 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, 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; see the file COPYING. If not, write to + the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + + * + * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-bsm.c,v $ + * $Revision: 1.3 $ + * $Date: 1997/10/05 19:15:15 $ + * + * This file contains the bad-sector map handling code for + * the QIC-117 floppy tape driver for Linux. + * QIC-40, QIC-80, QIC-3010 and QIC-3020 maps are implemented. + */ + +#include <linux/string.h> + +#include <linux/ftape.h> +#include "../lowlevel/ftape-tracing.h" +#include "../lowlevel/ftape-bsm.h" +#include "../lowlevel/ftape-ctl.h" +#include "../lowlevel/ftape-rw.h" + +/* Global vars. + */ + +/* Local vars. + */ +static __u8 *bad_sector_map = NULL; +static SectorCount *bsm_hash_ptr = NULL; + +typedef enum { + forward, backward +} mode_type; + +#if 0 +/* fix_tape converts a normal QIC-80 tape into a 'wide' tape. + * For testing purposes only ! + */ +void fix_tape(__u8 * buffer, ft_format_type new_code) +{ + static __u8 list[BAD_SECTOR_MAP_SIZE]; + SectorMap *src_ptr = (SectorMap *) list; + __u8 *dst_ptr = bad_sector_map; + SectorMap map; + unsigned int sector = 1; + int i; + + if (format_code != fmt_var && format_code != fmt_big) { + memcpy(list, bad_sector_map, sizeof(list)); + memset(bad_sector_map, 0, sizeof(bad_sector_map)); + while ((__u8 *) src_ptr - list < sizeof(list)) { + map = *src_ptr++; + if (map == EMPTY_SEGMENT) { + *(SectorMap *) dst_ptr = 0x800000 + sector; + dst_ptr += 3; + sector += SECTORS_PER_SEGMENT; + } else { + for (i = 0; i < SECTORS_PER_SEGMENT; ++i) { + if (map & 1) { + *(SewctorMap *) dst_ptr = sector; + dst_ptr += 3; + } + map >>= 1; + ++sector; + } + } + } + } + bad_sector_map_changed = 1; + *(buffer + 4) = new_code; /* put new format code */ + if (format_code != fmt_var && new_code == fmt_big) { + PUT4(buffer, FT_6_HSEG_1, (__u32)GET2(buffer, 6)); + PUT4(buffer, FT_6_HSEG_2, (__u32)GET2(buffer, 8)); + PUT4(buffer, FT_6_FRST_SEG, (__u32)GET2(buffer, 10)); + PUT4(buffer, FT_6_LAST_SEG, (__u32)GET2(buffer, 12)); + memset(buffer+6, '\0', 8); + } + format_code = new_code; +} + +#endif + +/* given buffer that contains a header segment, find the end of + * of the bsm list + */ +__u8 * ftape_find_end_of_bsm_list(__u8 * address) +{ + __u8 *ptr = address + FT_HEADER_END; /* start of bsm list */ + __u8 *limit = address + FT_SEGMENT_SIZE; + while (ptr + 2 < limit) { + if (ptr[0] || ptr[1] || ptr[2]) { + ptr += 3; + } else { + return ptr; + } + } + return NULL; +} + +static inline void put_sector(SectorCount *ptr, unsigned int sector) +{ + ptr->bytes[0] = sector & 0xff; + sector >>= 8; + ptr->bytes[1] = sector & 0xff; + sector >>= 8; + ptr->bytes[2] = sector & 0xff; +} + +static inline unsigned int get_sector(SectorCount *ptr) +{ +#if 1 + unsigned int sector; + + sector = ptr->bytes[0]; + sector += ptr->bytes[1] << 8; + sector += ptr->bytes[2] << 16; + + return sector; +#else + /* GET4 gets the next four bytes in Intel little endian order + * and converts them to host byte order and handles unaligned + * access. + */ + return (GET4(ptr, 0) & 0x00ffffff); /* back to host byte order */ +#endif +} + +static void bsm_debug_fake(void) +{ + /* for testing of bad sector handling at end of tape + */ +#if 0 + ftape_put_bad_sector_entry(segments_per_track * tracks_per_tape - 3, + 0x000003e0; + ftape_put_bad_sector_entry(segments_per_track * tracks_per_tape - 2, + 0xff3fffff; + ftape_put_bad_sector_entry(segments_per_track * tracks_per_tape - 1, + 0xffffe000; +#endif + /* Enable to test bad sector handling + */ +#if 0 + ftape_put_bad_sector_entry(30, 0xfffffffe) + ftape_put_bad_sector_entry(32, 0x7fffffff); + ftape_put_bad_sector_entry(34, 0xfffeffff); + ftape_put_bad_sector_entry(36, 0x55555555); + ftape_put_bad_sector_entry(38, 0xffffffff); + ftape_put_bad_sector_entry(50, 0xffff0000); + ftape_put_bad_sector_entry(51, 0xffffffff); + ftape_put_bad_sector_entry(52, 0xffffffff); + ftape_put_bad_sector_entry(53, 0x0000ffff); +#endif + /* Enable when testing multiple volume tar dumps. + */ +#if 0 + { + int i; + + for (i = ft_first_data_segment; + i <= ft_last_data_segment - 7; ++i) { + ftape_put_bad_sector_entry(i, EMPTY_SEGMENT); + } + } +#endif + /* Enable when testing bit positions in *_error_map + */ +#if 0 + { + int i; + + for (i = first_data_segment; i <= last_data_segment; ++i) { + ftape_put_bad_sector_entry(i, + ftape_get_bad_sector_entry(i) + | 0x00ff00ff); + } + } +#endif +} + +static void print_bad_sector_map(void) +{ + unsigned int good_sectors; + unsigned int total_bad = 0; + int i; + TRACE_FUN(ft_t_flow); + + if (ft_format_code == fmt_big || + ft_format_code == fmt_var || + ft_format_code == fmt_1100ft) { + SectorCount *ptr = (SectorCount *)bad_sector_map; + unsigned int sector; + + while((sector = get_sector(ptr++)) != 0) { + if ((ft_format_code == fmt_big || + ft_format_code == fmt_var) && + sector & 0x800000) { + total_bad += FT_SECTORS_PER_SEGMENT - 3; + TRACE(ft_t_noise, "bad segment at sector: %6d", + sector & 0x7fffff); + } else { + ++total_bad; + TRACE(ft_t_noise, "bad sector: %6d", sector); + } + } + /* Display old ftape's end-of-file marks + */ +#if LINUX_VERSION_CODE >= KERNEL_VER(2,0,0) + while ((sector = get_unaligned(((__u16*)ptr)++)) != 0) { + TRACE(ft_t_noise, "Old ftape eof mark: %4d/%2d", + sector, get_unaligned(((__u16*)ptr)++)); + } +#else + while ((sector = *((__u16*)ptr)++) != 0) { + TRACE(ft_t_noise, "Old ftape eof mark: %4d/%2d", + sector, *((__u16*)ptr)++); + } +#endif + } else { /* fixed size format */ + for (i = ft_first_data_segment; + i < (int)(ft_segments_per_track * ft_tracks_per_tape); ++i) { + SectorMap map = ((SectorMap *) bad_sector_map)[i]; + + if (map) { + TRACE(ft_t_noise, + "bsm for segment %4d: 0x%08x", i, (unsigned int)map); + total_bad += ((map == EMPTY_SEGMENT) + ? FT_SECTORS_PER_SEGMENT - 3 + : count_ones(map)); + } + } + } + good_sectors = + ((ft_segments_per_track * ft_tracks_per_tape - ft_first_data_segment) + * (FT_SECTORS_PER_SEGMENT - 3)) - total_bad; + TRACE(ft_t_info, "%d Kb usable on this tape", good_sectors); + if (total_bad == 0) { + TRACE(ft_t_info, + "WARNING: this tape has no bad blocks registered !"); + } else { + TRACE(ft_t_info, "%d bad sectors", total_bad); + } + TRACE_EXIT; +} + + +void ftape_extract_bad_sector_map(__u8 * buffer) +{ + TRACE_FUN(ft_t_any); + + /* Fill the bad sector map with the contents of buffer. + */ + if (ft_format_code == fmt_var || ft_format_code == fmt_big) { + /* QIC-3010/3020 and wide QIC-80 tapes no longer have a failed + * sector log but use this area to extend the bad sector map. + */ + bad_sector_map = &buffer[FT_HEADER_END]; + } else { + /* non-wide QIC-80 tapes have a failed sector log area that + * mustn't be included in the bad sector map. + */ + bad_sector_map = &buffer[FT_FSL + FT_FSL_SIZE]; + } + if (ft_format_code == fmt_1100ft || + ft_format_code == fmt_var || + ft_format_code == fmt_big) { + bsm_hash_ptr = (SectorCount *)bad_sector_map; + } else { + bsm_hash_ptr = NULL; + } + bsm_debug_fake(); + if (TRACE_LEVEL >= ft_t_info) { + print_bad_sector_map(); + } + TRACE_EXIT; +} + +static inline SectorMap cvt2map(unsigned int sector) +{ + return 1 << (((sector & 0x7fffff) - 1) % FT_SECTORS_PER_SEGMENT); +} + +static inline int cvt2segment(unsigned int sector) +{ + return ((sector & 0x7fffff) - 1) / FT_SECTORS_PER_SEGMENT; +} + +static int forward_seek_entry(int segment_id, + SectorCount **ptr, + SectorMap *map) +{ + unsigned int sector; + int segment; + + do { + sector = get_sector((*ptr)++); + segment = cvt2segment(sector); + } while (sector != 0 && segment < segment_id); + (*ptr) --; /* point to first sector >= segment_id */ + /* Get all sectors in segment_id + */ + if (sector == 0 || segment != segment_id) { + *map = 0; + return 0; + } else if ((sector & 0x800000) && + (ft_format_code == fmt_var || ft_format_code == fmt_big)) { + *map = EMPTY_SEGMENT; + return FT_SECTORS_PER_SEGMENT; + } else { + int count = 1; + SectorCount *tmp_ptr = (*ptr) + 1; + + *map = cvt2map(sector); + while ((sector = get_sector(tmp_ptr++)) != 0 && + (segment = cvt2segment(sector)) == segment_id) { + *map |= cvt2map(sector); + ++count; + } + return count; + } +} + +static int backwards_seek_entry(int segment_id, + SectorCount **ptr, + SectorMap *map) +{ + unsigned int sector; + int segment; /* max unsigned int */ + + if (*ptr <= (SectorCount *)bad_sector_map) { + *map = 0; + return 0; + } + do { + sector = get_sector(--(*ptr)); + segment = cvt2segment(sector); + } while (*ptr > (SectorCount *)bad_sector_map && segment > segment_id); + if (segment > segment_id) { /* at start of list, no entry found */ + *map = 0; + return 0; + } else if (segment < segment_id) { + /* before smaller entry, adjust for overshoot */ + (*ptr) ++; + *map = 0; + return 0; + } else if ((sector & 0x800000) && + (ft_format_code == fmt_big || ft_format_code == fmt_var)) { + *map = EMPTY_SEGMENT; + return FT_SECTORS_PER_SEGMENT; + } else { /* get all sectors in segment_id */ + int count = 1; + + *map = cvt2map(sector); + while(*ptr > (SectorCount *)bad_sector_map) { + sector = get_sector(--(*ptr)); + segment = cvt2segment(sector); + if (segment != segment_id) { + break; + } + *map |= cvt2map(sector); + ++count; + } + if (segment < segment_id) { + (*ptr) ++; + } + return count; + } +} + +void ftape_put_bad_sector_entry(int segment_id, SectorMap new_map) +{ + SectorCount *ptr = (SectorCount *)bad_sector_map; + int count; + int new_count; + SectorMap map; + TRACE_FUN(ft_t_any); + + if (ft_format_code == fmt_1100ft || + ft_format_code == fmt_var || + ft_format_code == fmt_big) { + count = forward_seek_entry(segment_id, &ptr, &map); + new_count = count_ones(new_map); + /* If format code == 4 put empty segment instead of 32 + * bad sectors. + */ + if (ft_format_code == fmt_var || ft_format_code == fmt_big) { + if (new_count == FT_SECTORS_PER_SEGMENT) { + new_count = 1; + } + if (count == FT_SECTORS_PER_SEGMENT) { + count = 1; + } + } + if (count != new_count) { + /* insert (or delete if < 0) new_count - count + * entries. Move trailing part of list + * including terminating 0. + */ + SectorCount *hi_ptr = ptr; + + do { + } while (get_sector(hi_ptr++) != 0); + /* Note: ptr is of type byte *, and each bad sector + * consumes 3 bytes. + */ + memmove(ptr + new_count, ptr + count, + (size_t)(hi_ptr - (ptr + count))*sizeof(SectorCount)); + } + TRACE(ft_t_noise, "putting map 0x%08x at %p, segment %d", + (unsigned int)new_map, ptr, segment_id); + if (new_count == 1 && new_map == EMPTY_SEGMENT) { + put_sector(ptr++, (0x800001 + + segment_id * + FT_SECTORS_PER_SEGMENT)); + } else { + int i = 0; + + while (new_map) { + if (new_map & 1) { + put_sector(ptr++, + 1 + segment_id * + FT_SECTORS_PER_SEGMENT + i); + } + ++i; + new_map >>= 1; + } + } + } else { + ((SectorMap *) bad_sector_map)[segment_id] = new_map; + } + TRACE_EXIT; +} + +SectorMap ftape_get_bad_sector_entry(int segment_id) +{ + if (ft_used_header_segment == -1) { + /* When reading header segment we'll need a blank map. + */ + return 0; + } else if (bsm_hash_ptr != NULL) { + /* Invariants: + * map - mask value returned on last call. + * bsm_hash_ptr - points to first sector greater or equal to + * first sector in last_referenced segment. + * last_referenced - segment id used in the last call, + * sector and map belong to this id. + * This code is designed for sequential access and retries. + * For true random access it may have to be redesigned. + */ + static int last_reference = -1; + static SectorMap map = 0; + + if (segment_id > last_reference) { + /* Skip all sectors before segment_id + */ + forward_seek_entry(segment_id, &bsm_hash_ptr, &map); + } else if (segment_id < last_reference) { + /* Skip backwards until begin of buffer or + * first sector in segment_id + */ + backwards_seek_entry(segment_id, &bsm_hash_ptr, &map); + } /* segment_id == last_reference : keep map */ + last_reference = segment_id; + return map; + } else { + return ((SectorMap *) bad_sector_map)[segment_id]; + } +} + +/* This is simply here to prevent us from overwriting other kernel + * data. Writes will result in NULL Pointer dereference. + */ +void ftape_init_bsm(void) +{ + bad_sector_map = NULL; + bsm_hash_ptr = NULL; +} |