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