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 #define GET_FRAG_ID(_map,_start,_idmask)				\
24 	({								\
25 		unsigned long _v2, _frag;				\
26 		unsigned int _tmp;					\
27 		_tmp = _start >> 5;					\
28 		_frag = le32_to_cpu(_map[_tmp]);			\
29 		_v2   = le32_to_cpu(_map[_tmp + 1]);			\
30 		_tmp = start & 31;					\
31 		_frag = (_frag >> _tmp) | (_v2 << (32 - _tmp));		\
32 		_frag & _idmask;					\
33 	})
34 
35 /*
36  * return the map bit offset of the fragment frag_id in
37  * the zone dm.
38  * Note that the loop is optimised for best asm code -
39  * look at the output of:
40  *  gcc -D__KERNEL__ -O2 -I../../include -o - -S map.c
41  */
42 static int
lookup_zone(const struct adfs_discmap * dm,const unsigned int idlen,const unsigned int frag_id,unsigned int * offset)43 lookup_zone(const struct adfs_discmap *dm, const unsigned int idlen,
44 	    const unsigned int frag_id, unsigned int *offset)
45 {
46 	const unsigned int mapsize = dm->dm_endbit;
47 	const unsigned int idmask = (1 << idlen) - 1;
48 	unsigned long *map = ((unsigned long *)dm->dm_bh->b_data) + 1;
49 	unsigned int start = dm->dm_startbit;
50 	unsigned int mapptr;
51 
52 	do {
53 		unsigned long frag;
54 
55 		frag = GET_FRAG_ID(map, start, idmask);
56 		mapptr = start + idlen;
57 
58 		/*
59 		 * find end of fragment
60 		 */
61 		{
62 			unsigned long v2;
63 
64 			while ((v2 = map[mapptr >> 5] >> (mapptr & 31)) == 0) {
65 				mapptr = (mapptr & ~31) + 32;
66 				if (mapptr >= mapsize)
67 					goto error;
68 			}
69 
70 			mapptr += 1 + ffz(~v2);
71 		}
72 
73 		if (frag == frag_id)
74 			goto found;
75 again:
76 		start = mapptr;
77 	} while (mapptr < mapsize);
78 
79 error:
80 	return -1;
81 
82 found:
83 	{
84 		int length = mapptr - start;
85 		if (*offset >= length) {
86 			*offset -= length;
87 			goto again;
88 		}
89 	}
90 	return start + *offset;
91 }
92 
93 /*
94  * Scan the free space map, for this zone, calculating the total
95  * number of map bits in each free space fragment.
96  *
97  * Note: idmask is limited to 15 bits [3.2]
98  */
99 static unsigned int
scan_free_map(struct adfs_sb_info * asb,struct adfs_discmap * dm)100 scan_free_map(struct adfs_sb_info *asb, struct adfs_discmap *dm)
101 {
102 	const unsigned int mapsize = dm->dm_endbit + 32;
103 	const unsigned int idlen  = asb->s_idlen;
104 	const unsigned int frag_idlen = idlen <= 15 ? idlen : 15;
105 	const unsigned int idmask = (1 << frag_idlen) - 1;
106 	unsigned long *map = (unsigned long *)dm->dm_bh->b_data;
107 	unsigned int start = 8, mapptr;
108 	unsigned long frag;
109 	unsigned long total = 0;
110 
111 	/*
112 	 * get fragment id
113 	 */
114 	frag = GET_FRAG_ID(map, start, idmask);
115 
116 	/*
117 	 * If the freelink is null, then no free fragments
118 	 * exist in this zone.
119 	 */
120 	if (frag == 0)
121 		return 0;
122 
123 	do {
124 		start += frag;
125 
126 		/*
127 		 * get fragment id
128 		 */
129 		frag = GET_FRAG_ID(map, start, idmask);
130 		mapptr = start + idlen;
131 
132 		/*
133 		 * find end of fragment
134 		 */
135 		{
136 			unsigned long v2;
137 
138 			while ((v2 = map[mapptr >> 5] >> (mapptr & 31)) == 0) {
139 				mapptr = (mapptr & ~31) + 32;
140 				if (mapptr >= mapsize)
141 					goto error;
142 			}
143 
144 			mapptr += 1 + ffz(~v2);
145 		}
146 
147 		total += mapptr - start;
148 	} while (frag >= idlen + 1);
149 
150 	if (frag != 0)
151 		printk(KERN_ERR "adfs: undersized free fragment\n");
152 
153 	return total;
154 error:
155 	printk(KERN_ERR "adfs: oversized free fragment\n");
156 	return 0;
157 }
158 
159 static int
scan_map(struct adfs_sb_info * asb,unsigned int zone,const unsigned int frag_id,unsigned int mapoff)160 scan_map(struct adfs_sb_info *asb, unsigned int zone,
161 	 const unsigned int frag_id, unsigned int mapoff)
162 {
163 	const unsigned int idlen = asb->s_idlen;
164 	struct adfs_discmap *dm, *dm_end;
165 	int result;
166 
167 	dm	= asb->s_map + zone;
168 	zone	= asb->s_map_size;
169 	dm_end	= asb->s_map + zone;
170 
171 	do {
172 		result = lookup_zone(dm, idlen, frag_id, &mapoff);
173 
174 		if (result != -1)
175 			goto found;
176 
177 		dm ++;
178 		if (dm == dm_end)
179 			dm = asb->s_map;
180 	} while (--zone > 0);
181 
182 	return -1;
183 found:
184 	result -= dm->dm_startbit;
185 	result += dm->dm_startblk;
186 
187 	return result;
188 }
189 
190 /*
191  * calculate the amount of free blocks in the map.
192  *
193  *              n=1
194  *  total_free = E(free_in_zone_n)
195  *              nzones
196  */
197 unsigned int
adfs_map_free(struct super_block * sb)198 adfs_map_free(struct super_block *sb)
199 {
200 	struct adfs_sb_info *asb = &sb->u.adfs_sb;
201 	struct adfs_discmap *dm;
202 	unsigned int total = 0;
203 	unsigned int zone;
204 
205 	dm   = asb->s_map;
206 	zone = asb->s_map_size;
207 
208 	do {
209 		total += scan_free_map(asb, dm++);
210 	} while (--zone > 0);
211 
212 	return signed_asl(total, asb->s_map2blk);
213 }
214 
adfs_map_lookup(struct super_block * sb,int frag_id,int offset)215 int adfs_map_lookup (struct super_block *sb, int frag_id, int offset)
216 {
217 	struct adfs_sb_info *asb = &sb->u.adfs_sb;
218 	unsigned int zone, mapoff;
219 	int result;
220 
221 	/*
222 	 * map & root fragment is special - it starts in the center of the
223 	 * disk.  The other fragments start at zone (frag / ids_per_zone)
224 	 */
225 	if (frag_id == ADFS_ROOT_FRAG)
226 		zone = asb->s_map_size >> 1;
227 	else
228 		zone = frag_id / asb->s_ids_per_zone;
229 
230 	if (zone >= asb->s_map_size)
231 		goto bad_fragment;
232 
233 	/* Convert sector offset to map offset */
234 	mapoff = signed_asl(offset, -asb->s_map2blk);
235 
236 	read_lock(&adfs_map_lock);
237 	result = scan_map(asb, zone, frag_id, mapoff);
238 	read_unlock(&adfs_map_lock);
239 
240 	if (result > 0) {
241 		unsigned int secoff;
242 
243 		/* Calculate sector offset into map block */
244 		secoff = offset - signed_asl(mapoff, asb->s_map2blk);
245 		return secoff + signed_asl(result, asb->s_map2blk);
246 	}
247 
248 	adfs_error(sb, "fragment %04X at offset %d not found in map",
249 		   frag_id, offset);
250 	return 0;
251 
252 bad_fragment:
253 	adfs_error(sb, "fragment %X is invalid (zone = %d, max = %d)",
254 		   frag_id, zone, asb->s_map_size);
255 	return 0;
256 }
257