| /* |
| * 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; |
| static SectorCount *bsm_hash_ptr; |
| |
| typedef enum { |
| forward, backward |
| } mode_type; |
| |
| #if 0 |
| static void ftape_put_bad_sector_entry(int segment_id, SectorMap new_map); |
| #endif |
| |
| #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; |
| __u16 *ptr16; |
| |
| 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 |
| */ |
| ptr16 = (__u16*)ptr; |
| while ((sector = get_unaligned(ptr16++)) != 0) { |
| TRACE(ft_t_noise, "Old ftape eof mark: %4d/%2d", |
| sector, get_unaligned(ptr16++)); |
| } |
| } 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; |
| } |
| } |
| |
| #if 0 |
| static 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; |
| } |
| #endif /* 0 */ |
| |
| 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; |
| |
| 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; |
| } |