Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 1994-1996 Bas Laarhoven, |
| 3 | * (C) 1996-1997 Claus Heine. |
| 4 | |
| 5 | This program is free software; you can redistribute it and/or modify |
| 6 | it under the terms of the GNU General Public License as published by |
| 7 | the Free Software Foundation; either version 2, or (at your option) |
| 8 | any later version. |
| 9 | |
| 10 | This program is distributed in the hope that it will be useful, |
| 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | GNU General Public License for more details. |
| 14 | |
| 15 | You should have received a copy of the GNU General Public License |
| 16 | along with this program; see the file COPYING. If not, write to |
| 17 | the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. |
| 18 | |
| 19 | * |
| 20 | * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-bsm.c,v $ |
| 21 | * $Revision: 1.3 $ |
| 22 | * $Date: 1997/10/05 19:15:15 $ |
| 23 | * |
| 24 | * This file contains the bad-sector map handling code for |
| 25 | * the QIC-117 floppy tape driver for Linux. |
| 26 | * QIC-40, QIC-80, QIC-3010 and QIC-3020 maps are implemented. |
| 27 | */ |
| 28 | |
| 29 | #include <linux/string.h> |
| 30 | |
| 31 | #include <linux/ftape.h> |
| 32 | #include "../lowlevel/ftape-tracing.h" |
| 33 | #include "../lowlevel/ftape-bsm.h" |
| 34 | #include "../lowlevel/ftape-ctl.h" |
| 35 | #include "../lowlevel/ftape-rw.h" |
| 36 | |
| 37 | /* Global vars. |
| 38 | */ |
| 39 | |
| 40 | /* Local vars. |
| 41 | */ |
| 42 | static __u8 *bad_sector_map; |
| 43 | static SectorCount *bsm_hash_ptr; |
| 44 | |
| 45 | typedef enum { |
| 46 | forward, backward |
| 47 | } mode_type; |
| 48 | |
| 49 | #if 0 |
| 50 | static void ftape_put_bad_sector_entry(int segment_id, SectorMap new_map); |
| 51 | #endif |
| 52 | |
| 53 | #if 0 |
| 54 | /* fix_tape converts a normal QIC-80 tape into a 'wide' tape. |
| 55 | * For testing purposes only ! |
| 56 | */ |
| 57 | void fix_tape(__u8 * buffer, ft_format_type new_code) |
| 58 | { |
| 59 | static __u8 list[BAD_SECTOR_MAP_SIZE]; |
| 60 | SectorMap *src_ptr = (SectorMap *) list; |
| 61 | __u8 *dst_ptr = bad_sector_map; |
| 62 | SectorMap map; |
| 63 | unsigned int sector = 1; |
| 64 | int i; |
| 65 | |
| 66 | if (format_code != fmt_var && format_code != fmt_big) { |
| 67 | memcpy(list, bad_sector_map, sizeof(list)); |
| 68 | memset(bad_sector_map, 0, sizeof(bad_sector_map)); |
| 69 | while ((__u8 *) src_ptr - list < sizeof(list)) { |
| 70 | map = *src_ptr++; |
| 71 | if (map == EMPTY_SEGMENT) { |
| 72 | *(SectorMap *) dst_ptr = 0x800000 + sector; |
| 73 | dst_ptr += 3; |
| 74 | sector += SECTORS_PER_SEGMENT; |
| 75 | } else { |
| 76 | for (i = 0; i < SECTORS_PER_SEGMENT; ++i) { |
| 77 | if (map & 1) { |
| 78 | *(SewctorMap *) dst_ptr = sector; |
| 79 | dst_ptr += 3; |
| 80 | } |
| 81 | map >>= 1; |
| 82 | ++sector; |
| 83 | } |
| 84 | } |
| 85 | } |
| 86 | } |
| 87 | bad_sector_map_changed = 1; |
| 88 | *(buffer + 4) = new_code; /* put new format code */ |
| 89 | if (format_code != fmt_var && new_code == fmt_big) { |
| 90 | PUT4(buffer, FT_6_HSEG_1, (__u32)GET2(buffer, 6)); |
| 91 | PUT4(buffer, FT_6_HSEG_2, (__u32)GET2(buffer, 8)); |
| 92 | PUT4(buffer, FT_6_FRST_SEG, (__u32)GET2(buffer, 10)); |
| 93 | PUT4(buffer, FT_6_LAST_SEG, (__u32)GET2(buffer, 12)); |
| 94 | memset(buffer+6, '\0', 8); |
| 95 | } |
| 96 | format_code = new_code; |
| 97 | } |
| 98 | |
| 99 | #endif |
| 100 | |
| 101 | /* given buffer that contains a header segment, find the end of |
| 102 | * of the bsm list |
| 103 | */ |
| 104 | __u8 * ftape_find_end_of_bsm_list(__u8 * address) |
| 105 | { |
| 106 | __u8 *ptr = address + FT_HEADER_END; /* start of bsm list */ |
| 107 | __u8 *limit = address + FT_SEGMENT_SIZE; |
| 108 | while (ptr + 2 < limit) { |
| 109 | if (ptr[0] || ptr[1] || ptr[2]) { |
| 110 | ptr += 3; |
| 111 | } else { |
| 112 | return ptr; |
| 113 | } |
| 114 | } |
| 115 | return NULL; |
| 116 | } |
| 117 | |
| 118 | static inline void put_sector(SectorCount *ptr, unsigned int sector) |
| 119 | { |
| 120 | ptr->bytes[0] = sector & 0xff; |
| 121 | sector >>= 8; |
| 122 | ptr->bytes[1] = sector & 0xff; |
| 123 | sector >>= 8; |
| 124 | ptr->bytes[2] = sector & 0xff; |
| 125 | } |
| 126 | |
| 127 | static inline unsigned int get_sector(SectorCount *ptr) |
| 128 | { |
| 129 | #if 1 |
| 130 | unsigned int sector; |
| 131 | |
| 132 | sector = ptr->bytes[0]; |
| 133 | sector += ptr->bytes[1] << 8; |
| 134 | sector += ptr->bytes[2] << 16; |
| 135 | |
| 136 | return sector; |
| 137 | #else |
| 138 | /* GET4 gets the next four bytes in Intel little endian order |
| 139 | * and converts them to host byte order and handles unaligned |
| 140 | * access. |
| 141 | */ |
| 142 | return (GET4(ptr, 0) & 0x00ffffff); /* back to host byte order */ |
| 143 | #endif |
| 144 | } |
| 145 | |
| 146 | static void bsm_debug_fake(void) |
| 147 | { |
| 148 | /* for testing of bad sector handling at end of tape |
| 149 | */ |
| 150 | #if 0 |
| 151 | ftape_put_bad_sector_entry(segments_per_track * tracks_per_tape - 3, |
| 152 | 0x000003e0; |
| 153 | ftape_put_bad_sector_entry(segments_per_track * tracks_per_tape - 2, |
| 154 | 0xff3fffff; |
| 155 | ftape_put_bad_sector_entry(segments_per_track * tracks_per_tape - 1, |
| 156 | 0xffffe000; |
| 157 | #endif |
| 158 | /* Enable to test bad sector handling |
| 159 | */ |
| 160 | #if 0 |
| 161 | ftape_put_bad_sector_entry(30, 0xfffffffe) |
| 162 | ftape_put_bad_sector_entry(32, 0x7fffffff); |
| 163 | ftape_put_bad_sector_entry(34, 0xfffeffff); |
| 164 | ftape_put_bad_sector_entry(36, 0x55555555); |
| 165 | ftape_put_bad_sector_entry(38, 0xffffffff); |
| 166 | ftape_put_bad_sector_entry(50, 0xffff0000); |
| 167 | ftape_put_bad_sector_entry(51, 0xffffffff); |
| 168 | ftape_put_bad_sector_entry(52, 0xffffffff); |
| 169 | ftape_put_bad_sector_entry(53, 0x0000ffff); |
| 170 | #endif |
| 171 | /* Enable when testing multiple volume tar dumps. |
| 172 | */ |
| 173 | #if 0 |
| 174 | { |
| 175 | int i; |
| 176 | |
| 177 | for (i = ft_first_data_segment; |
| 178 | i <= ft_last_data_segment - 7; ++i) { |
| 179 | ftape_put_bad_sector_entry(i, EMPTY_SEGMENT); |
| 180 | } |
| 181 | } |
| 182 | #endif |
| 183 | /* Enable when testing bit positions in *_error_map |
| 184 | */ |
| 185 | #if 0 |
| 186 | { |
| 187 | int i; |
| 188 | |
| 189 | for (i = first_data_segment; i <= last_data_segment; ++i) { |
| 190 | ftape_put_bad_sector_entry(i, |
| 191 | ftape_get_bad_sector_entry(i) |
| 192 | | 0x00ff00ff); |
| 193 | } |
| 194 | } |
| 195 | #endif |
| 196 | } |
| 197 | |
| 198 | static void print_bad_sector_map(void) |
| 199 | { |
| 200 | unsigned int good_sectors; |
| 201 | unsigned int total_bad = 0; |
| 202 | int i; |
| 203 | TRACE_FUN(ft_t_flow); |
| 204 | |
| 205 | if (ft_format_code == fmt_big || |
| 206 | ft_format_code == fmt_var || |
| 207 | ft_format_code == fmt_1100ft) { |
| 208 | SectorCount *ptr = (SectorCount *)bad_sector_map; |
| 209 | unsigned int sector; |
| 210 | __u16 *ptr16; |
| 211 | |
| 212 | while((sector = get_sector(ptr++)) != 0) { |
| 213 | if ((ft_format_code == fmt_big || |
| 214 | ft_format_code == fmt_var) && |
| 215 | sector & 0x800000) { |
| 216 | total_bad += FT_SECTORS_PER_SEGMENT - 3; |
| 217 | TRACE(ft_t_noise, "bad segment at sector: %6d", |
| 218 | sector & 0x7fffff); |
| 219 | } else { |
| 220 | ++total_bad; |
| 221 | TRACE(ft_t_noise, "bad sector: %6d", sector); |
| 222 | } |
| 223 | } |
| 224 | /* Display old ftape's end-of-file marks |
| 225 | */ |
| 226 | ptr16 = (__u16*)ptr; |
| 227 | while ((sector = get_unaligned(ptr16++)) != 0) { |
| 228 | TRACE(ft_t_noise, "Old ftape eof mark: %4d/%2d", |
| 229 | sector, get_unaligned(ptr16++)); |
| 230 | } |
| 231 | } else { /* fixed size format */ |
| 232 | for (i = ft_first_data_segment; |
| 233 | i < (int)(ft_segments_per_track * ft_tracks_per_tape); ++i) { |
| 234 | SectorMap map = ((SectorMap *) bad_sector_map)[i]; |
| 235 | |
| 236 | if (map) { |
| 237 | TRACE(ft_t_noise, |
| 238 | "bsm for segment %4d: 0x%08x", i, (unsigned int)map); |
| 239 | total_bad += ((map == EMPTY_SEGMENT) |
| 240 | ? FT_SECTORS_PER_SEGMENT - 3 |
| 241 | : count_ones(map)); |
| 242 | } |
| 243 | } |
| 244 | } |
| 245 | good_sectors = |
| 246 | ((ft_segments_per_track * ft_tracks_per_tape - ft_first_data_segment) |
| 247 | * (FT_SECTORS_PER_SEGMENT - 3)) - total_bad; |
| 248 | TRACE(ft_t_info, "%d Kb usable on this tape", good_sectors); |
| 249 | if (total_bad == 0) { |
| 250 | TRACE(ft_t_info, |
| 251 | "WARNING: this tape has no bad blocks registered !"); |
| 252 | } else { |
| 253 | TRACE(ft_t_info, "%d bad sectors", total_bad); |
| 254 | } |
| 255 | TRACE_EXIT; |
| 256 | } |
| 257 | |
| 258 | |
| 259 | void ftape_extract_bad_sector_map(__u8 * buffer) |
| 260 | { |
| 261 | TRACE_FUN(ft_t_any); |
| 262 | |
| 263 | /* Fill the bad sector map with the contents of buffer. |
| 264 | */ |
| 265 | if (ft_format_code == fmt_var || ft_format_code == fmt_big) { |
| 266 | /* QIC-3010/3020 and wide QIC-80 tapes no longer have a failed |
| 267 | * sector log but use this area to extend the bad sector map. |
| 268 | */ |
| 269 | bad_sector_map = &buffer[FT_HEADER_END]; |
| 270 | } else { |
| 271 | /* non-wide QIC-80 tapes have a failed sector log area that |
| 272 | * mustn't be included in the bad sector map. |
| 273 | */ |
| 274 | bad_sector_map = &buffer[FT_FSL + FT_FSL_SIZE]; |
| 275 | } |
| 276 | if (ft_format_code == fmt_1100ft || |
| 277 | ft_format_code == fmt_var || |
| 278 | ft_format_code == fmt_big) { |
| 279 | bsm_hash_ptr = (SectorCount *)bad_sector_map; |
| 280 | } else { |
| 281 | bsm_hash_ptr = NULL; |
| 282 | } |
| 283 | bsm_debug_fake(); |
| 284 | if (TRACE_LEVEL >= ft_t_info) { |
| 285 | print_bad_sector_map(); |
| 286 | } |
| 287 | TRACE_EXIT; |
| 288 | } |
| 289 | |
| 290 | static inline SectorMap cvt2map(unsigned int sector) |
| 291 | { |
| 292 | return 1 << (((sector & 0x7fffff) - 1) % FT_SECTORS_PER_SEGMENT); |
| 293 | } |
| 294 | |
| 295 | static inline int cvt2segment(unsigned int sector) |
| 296 | { |
| 297 | return ((sector & 0x7fffff) - 1) / FT_SECTORS_PER_SEGMENT; |
| 298 | } |
| 299 | |
| 300 | static int forward_seek_entry(int segment_id, |
| 301 | SectorCount **ptr, |
| 302 | SectorMap *map) |
| 303 | { |
| 304 | unsigned int sector; |
| 305 | int segment; |
| 306 | |
| 307 | do { |
| 308 | sector = get_sector((*ptr)++); |
| 309 | segment = cvt2segment(sector); |
| 310 | } while (sector != 0 && segment < segment_id); |
| 311 | (*ptr) --; /* point to first sector >= segment_id */ |
| 312 | /* Get all sectors in segment_id |
| 313 | */ |
| 314 | if (sector == 0 || segment != segment_id) { |
| 315 | *map = 0; |
| 316 | return 0; |
| 317 | } else if ((sector & 0x800000) && |
| 318 | (ft_format_code == fmt_var || ft_format_code == fmt_big)) { |
| 319 | *map = EMPTY_SEGMENT; |
| 320 | return FT_SECTORS_PER_SEGMENT; |
| 321 | } else { |
| 322 | int count = 1; |
| 323 | SectorCount *tmp_ptr = (*ptr) + 1; |
| 324 | |
| 325 | *map = cvt2map(sector); |
| 326 | while ((sector = get_sector(tmp_ptr++)) != 0 && |
| 327 | (segment = cvt2segment(sector)) == segment_id) { |
| 328 | *map |= cvt2map(sector); |
| 329 | ++count; |
| 330 | } |
| 331 | return count; |
| 332 | } |
| 333 | } |
| 334 | |
| 335 | static int backwards_seek_entry(int segment_id, |
| 336 | SectorCount **ptr, |
| 337 | SectorMap *map) |
| 338 | { |
| 339 | unsigned int sector; |
| 340 | int segment; /* max unsigned int */ |
| 341 | |
| 342 | if (*ptr <= (SectorCount *)bad_sector_map) { |
| 343 | *map = 0; |
| 344 | return 0; |
| 345 | } |
| 346 | do { |
| 347 | sector = get_sector(--(*ptr)); |
| 348 | segment = cvt2segment(sector); |
| 349 | } while (*ptr > (SectorCount *)bad_sector_map && segment > segment_id); |
| 350 | if (segment > segment_id) { /* at start of list, no entry found */ |
| 351 | *map = 0; |
| 352 | return 0; |
| 353 | } else if (segment < segment_id) { |
| 354 | /* before smaller entry, adjust for overshoot */ |
| 355 | (*ptr) ++; |
| 356 | *map = 0; |
| 357 | return 0; |
| 358 | } else if ((sector & 0x800000) && |
| 359 | (ft_format_code == fmt_big || ft_format_code == fmt_var)) { |
| 360 | *map = EMPTY_SEGMENT; |
| 361 | return FT_SECTORS_PER_SEGMENT; |
| 362 | } else { /* get all sectors in segment_id */ |
| 363 | int count = 1; |
| 364 | |
| 365 | *map = cvt2map(sector); |
| 366 | while(*ptr > (SectorCount *)bad_sector_map) { |
| 367 | sector = get_sector(--(*ptr)); |
| 368 | segment = cvt2segment(sector); |
| 369 | if (segment != segment_id) { |
| 370 | break; |
| 371 | } |
| 372 | *map |= cvt2map(sector); |
| 373 | ++count; |
| 374 | } |
| 375 | if (segment < segment_id) { |
| 376 | (*ptr) ++; |
| 377 | } |
| 378 | return count; |
| 379 | } |
| 380 | } |
| 381 | |
| 382 | #if 0 |
| 383 | static void ftape_put_bad_sector_entry(int segment_id, SectorMap new_map) |
| 384 | { |
| 385 | SectorCount *ptr = (SectorCount *)bad_sector_map; |
| 386 | int count; |
| 387 | int new_count; |
| 388 | SectorMap map; |
| 389 | TRACE_FUN(ft_t_any); |
| 390 | |
| 391 | if (ft_format_code == fmt_1100ft || |
| 392 | ft_format_code == fmt_var || |
| 393 | ft_format_code == fmt_big) { |
| 394 | count = forward_seek_entry(segment_id, &ptr, &map); |
| 395 | new_count = count_ones(new_map); |
| 396 | /* If format code == 4 put empty segment instead of 32 |
| 397 | * bad sectors. |
| 398 | */ |
| 399 | if (ft_format_code == fmt_var || ft_format_code == fmt_big) { |
| 400 | if (new_count == FT_SECTORS_PER_SEGMENT) { |
| 401 | new_count = 1; |
| 402 | } |
| 403 | if (count == FT_SECTORS_PER_SEGMENT) { |
| 404 | count = 1; |
| 405 | } |
| 406 | } |
| 407 | if (count != new_count) { |
| 408 | /* insert (or delete if < 0) new_count - count |
| 409 | * entries. Move trailing part of list |
| 410 | * including terminating 0. |
| 411 | */ |
| 412 | SectorCount *hi_ptr = ptr; |
| 413 | |
| 414 | do { |
| 415 | } while (get_sector(hi_ptr++) != 0); |
| 416 | /* Note: ptr is of type byte *, and each bad sector |
| 417 | * consumes 3 bytes. |
| 418 | */ |
| 419 | memmove(ptr + new_count, ptr + count, |
| 420 | (size_t)(hi_ptr - (ptr + count))*sizeof(SectorCount)); |
| 421 | } |
| 422 | TRACE(ft_t_noise, "putting map 0x%08x at %p, segment %d", |
| 423 | (unsigned int)new_map, ptr, segment_id); |
| 424 | if (new_count == 1 && new_map == EMPTY_SEGMENT) { |
| 425 | put_sector(ptr++, (0x800001 + |
| 426 | segment_id * |
| 427 | FT_SECTORS_PER_SEGMENT)); |
| 428 | } else { |
| 429 | int i = 0; |
| 430 | |
| 431 | while (new_map) { |
| 432 | if (new_map & 1) { |
| 433 | put_sector(ptr++, |
| 434 | 1 + segment_id * |
| 435 | FT_SECTORS_PER_SEGMENT + i); |
| 436 | } |
| 437 | ++i; |
| 438 | new_map >>= 1; |
| 439 | } |
| 440 | } |
| 441 | } else { |
| 442 | ((SectorMap *) bad_sector_map)[segment_id] = new_map; |
| 443 | } |
| 444 | TRACE_EXIT; |
| 445 | } |
| 446 | #endif /* 0 */ |
| 447 | |
| 448 | SectorMap ftape_get_bad_sector_entry(int segment_id) |
| 449 | { |
| 450 | if (ft_used_header_segment == -1) { |
| 451 | /* When reading header segment we'll need a blank map. |
| 452 | */ |
| 453 | return 0; |
| 454 | } else if (bsm_hash_ptr != NULL) { |
| 455 | /* Invariants: |
| 456 | * map - mask value returned on last call. |
| 457 | * bsm_hash_ptr - points to first sector greater or equal to |
| 458 | * first sector in last_referenced segment. |
| 459 | * last_referenced - segment id used in the last call, |
| 460 | * sector and map belong to this id. |
| 461 | * This code is designed for sequential access and retries. |
| 462 | * For true random access it may have to be redesigned. |
| 463 | */ |
| 464 | static int last_reference = -1; |
| 465 | static SectorMap map; |
| 466 | |
| 467 | if (segment_id > last_reference) { |
| 468 | /* Skip all sectors before segment_id |
| 469 | */ |
| 470 | forward_seek_entry(segment_id, &bsm_hash_ptr, &map); |
| 471 | } else if (segment_id < last_reference) { |
| 472 | /* Skip backwards until begin of buffer or |
| 473 | * first sector in segment_id |
| 474 | */ |
| 475 | backwards_seek_entry(segment_id, &bsm_hash_ptr, &map); |
| 476 | } /* segment_id == last_reference : keep map */ |
| 477 | last_reference = segment_id; |
| 478 | return map; |
| 479 | } else { |
| 480 | return ((SectorMap *) bad_sector_map)[segment_id]; |
| 481 | } |
| 482 | } |
| 483 | |
| 484 | /* This is simply here to prevent us from overwriting other kernel |
| 485 | * data. Writes will result in NULL Pointer dereference. |
| 486 | */ |
| 487 | void ftape_init_bsm(void) |
| 488 | { |
| 489 | bad_sector_map = NULL; |
| 490 | bsm_hash_ptr = NULL; |
| 491 | } |