File: /usr/src/linux/fs/adfs/map.c
1 /*
2 * linux/fs/adfs/map.c
3 *
4 * Copyright (C) 1997-1999 Russell King
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
9 */
10 #include <linux/version.h>
11 #include <linux/errno.h>
12 #include <linux/fs.h>
13 #include <linux/adfs_fs.h>
14 #include <linux/spinlock.h>
15
16 #include "adfs.h"
17
18 /*
19 * For the future...
20 */
21 static rwlock_t adfs_map_lock;
22
23 /*
24 * return the map bit offset of the fragment frag_id in
25 * the zone dm.
26 * Note that the loop is optimised for best asm code -
27 * look at the output of:
28 * gcc -D__KERNEL__ -O2 -I../../include -o - -S map.c
29 */
30 static int
31 lookup_zone(const struct adfs_discmap *dm, const unsigned int idlen,
32 const unsigned int frag_id, unsigned int *offset)
33 {
34 const unsigned int mapsize = dm->dm_endbit;
35 const unsigned int idmask = (1 << idlen) - 1;
36 unsigned long *map = ((unsigned long *)dm->dm_bh->b_data) + 1;
37 unsigned int start = dm->dm_startbit;
38 unsigned int mapptr;
39
40 do {
41 unsigned long frag;
42
43 /*
44 * get fragment id
45 */
46 {
47 unsigned long v2;
48 unsigned int tmp;
49
50 tmp = start >> 5;
51
52 frag = le32_to_cpu(map[tmp]);
53 v2 = le32_to_cpu(map[tmp + 1]);
54
55 tmp = start & 31;
56
57 frag = (frag >> tmp) | (v2 << (32 - tmp));
58
59 frag &= idmask;
60 }
61
62 mapptr = start + idlen;
63
64 /*
65 * find end of fragment
66 */
67 {
68 unsigned long v2;
69
70 while ((v2 = map[mapptr >> 5] >> (mapptr & 31)) == 0) {
71 mapptr = (mapptr & ~31) + 32;
72 if (mapptr >= mapsize)
73 goto error;
74 }
75
76 mapptr += 1 + ffz(~v2);
77 }
78
79 if (frag == frag_id)
80 goto found;
81 again:
82 start = mapptr;
83 } while (mapptr < mapsize);
84
85 error:
86 return -1;
87
88 found:
89 {
90 int length = mapptr - start;
91 if (*offset >= length) {
92 *offset -= length;
93 goto again;
94 }
95 }
96 return start + *offset;
97 }
98
99 /*
100 * Scan the free space map, for this zone, calculating the total
101 * number of map bits in each free space fragment.
102 *
103 * Note: idmask is limited to 15 bits [3.2]
104 */
105 static unsigned int
106 scan_free_map(struct adfs_sb_info *asb, struct adfs_discmap *dm)
107 {
108 const unsigned int mapsize = dm->dm_endbit + 32;
109 const unsigned int idlen = asb->s_idlen;
110 const unsigned int frag_idlen = idlen <= 15 ? idlen : 15;
111 const unsigned int idmask = (1 << frag_idlen) - 1;
112 unsigned long *map = (unsigned long *)dm->dm_bh->b_data;
113 unsigned int start = 8, mapptr;
114 unsigned long frag;
115 unsigned long total = 0;
116
117 /*
118 * get fragment id
119 */
120 {
121 unsigned long v2;
122 unsigned int tmp;
123
124 tmp = start >> 5;
125
126 frag = le32_to_cpu(map[tmp]);
127 v2 = le32_to_cpu(map[tmp + 1]);
128
129 tmp = start & 31;
130
131 frag = (frag >> tmp) | (v2 << (32 - tmp));
132
133 frag &= idmask;
134 }
135
136 /*
137 * If the freelink is null, then no free fragments
138 * exist in this zone.
139 */
140 if (frag == 0)
141 return 0;
142
143 do {
144 start += frag;
145
146 /*
147 * get fragment id
148 */
149 {
150 unsigned long v2;
151 unsigned int tmp;
152
153 tmp = start >> 5;
154
155 frag = le32_to_cpu(map[tmp]);
156 v2 = le32_to_cpu(map[tmp + 1]);
157
158 tmp = start & 31;
159
160 frag = (frag >> tmp) | (v2 << (32 - tmp));
161
162 frag &= idmask;
163 }
164
165 mapptr = start + idlen;
166
167 /*
168 * find end of fragment
169 */
170 {
171 unsigned long v2;
172
173 while ((v2 = map[mapptr >> 5] >> (mapptr & 31)) == 0) {
174 mapptr = (mapptr & ~31) + 32;
175 if (mapptr >= mapsize)
176 goto error;
177 }
178
179 mapptr += 1 + ffz(~v2);
180 }
181
182 total += mapptr - start;
183 } while (frag >= idlen + 1);
184
185 if (frag != 0)
186 printk(KERN_ERR "adfs: undersized free fragment\n");
187
188 return total;
189 error:
190 printk(KERN_ERR "adfs: oversized free fragment\n");
191 return 0;
192 }
193
194 static int
195 scan_map(struct adfs_sb_info *asb, unsigned int zone,
196 const unsigned int frag_id, unsigned int mapoff)
197 {
198 const unsigned int idlen = asb->s_idlen;
199 struct adfs_discmap *dm, *dm_end;
200 int result;
201
202 dm = asb->s_map + zone;
203 zone = asb->s_map_size;
204 dm_end = asb->s_map + zone;
205
206 do {
207 result = lookup_zone(dm, idlen, frag_id, &mapoff);
208
209 if (result != -1)
210 goto found;
211
212 dm ++;
213 if (dm == dm_end)
214 dm = asb->s_map;
215 } while (--zone > 0);
216
217 return -1;
218 found:
219 result -= dm->dm_startbit;
220 result += dm->dm_startblk;
221
222 return result;
223 }
224
225 /*
226 * calculate the amount of free blocks in the map.
227 *
228 * n=1
229 * total_free = E(free_in_zone_n)
230 * nzones
231 */
232 unsigned int
233 adfs_map_free(struct super_block *sb)
234 {
235 struct adfs_sb_info *asb = &sb->u.adfs_sb;
236 struct adfs_discmap *dm;
237 unsigned int total = 0;
238 unsigned int zone;
239
240 dm = asb->s_map;
241 zone = asb->s_map_size;
242
243 do {
244 total += scan_free_map(asb, dm++);
245 } while (--zone > 0);
246
247 return signed_asl(total, asb->s_map2blk);
248 }
249
250 int adfs_map_lookup (struct super_block *sb, int frag_id, int offset)
251 {
252 struct adfs_sb_info *asb = &sb->u.adfs_sb;
253 unsigned int zone, mapoff;
254 int result;
255
256 /*
257 * map & root fragment is special - it starts in the center of the
258 * disk. The other fragments start at zone (frag / ids_per_zone)
259 */
260 if (frag_id == ADFS_ROOT_FRAG)
261 zone = asb->s_map_size >> 1;
262 else
263 zone = frag_id / asb->s_ids_per_zone;
264
265 if (zone >= asb->s_map_size)
266 goto bad_fragment;
267
268 /* Convert sector offset to map offset */
269 mapoff = signed_asl(offset, -asb->s_map2blk);
270
271 read_lock(&adfs_map_lock);
272 result = scan_map(asb, zone, frag_id, mapoff);
273 read_unlock(&adfs_map_lock);
274
275 if (result > 0) {
276 unsigned int secoff;
277
278 /* Calculate sector offset into map block */
279 secoff = offset - signed_asl(mapoff, asb->s_map2blk);
280 return secoff + signed_asl(result, asb->s_map2blk);
281 }
282
283 adfs_error(sb, "fragment %04X at offset %d not found in map",
284 frag_id, offset);
285 return 0;
286
287 bad_fragment:
288 adfs_error(sb, "fragment %X is invalid (zone = %d, max = %d)",
289 frag_id, zone, asb->s_map_size);
290 return 0;
291 }
292