1 | #include <core.h> |
---|
2 | #include <com32.h> |
---|
3 | #include <fs.h> |
---|
4 | #include <ilog2.h> |
---|
5 | |
---|
6 | #define RETRY_COUNT 6 |
---|
7 | |
---|
8 | static inline sector_t chs_max(const struct disk *disk) |
---|
9 | { |
---|
10 | return (sector_t)disk->secpercyl << 10; |
---|
11 | } |
---|
12 | |
---|
13 | struct edd_rdwr_packet { |
---|
14 | uint16_t size; |
---|
15 | uint16_t blocks; |
---|
16 | far_ptr_t buf; |
---|
17 | uint64_t lba; |
---|
18 | }; |
---|
19 | |
---|
20 | struct edd_disk_params { |
---|
21 | uint16_t len; |
---|
22 | uint16_t flags; |
---|
23 | uint32_t phys_c; |
---|
24 | uint32_t phys_h; |
---|
25 | uint32_t phys_s; |
---|
26 | uint64_t sectors; |
---|
27 | uint16_t sector_size; |
---|
28 | far_ptr_t dpte; |
---|
29 | uint16_t devpath_key; |
---|
30 | uint8_t devpath_len; |
---|
31 | uint8_t _pad1[3]; |
---|
32 | char bus_type[4]; |
---|
33 | char if_type[8]; |
---|
34 | uint8_t if_path[8]; |
---|
35 | uint8_t dev_path[16]; |
---|
36 | uint8_t _pad2; |
---|
37 | uint8_t devpath_csum; /* Depends on devpath_len! */ |
---|
38 | } __attribute__((packed)); |
---|
39 | |
---|
40 | static inline bool is_power_of_2(uint32_t x) |
---|
41 | { |
---|
42 | return !(x & (x-1)); |
---|
43 | } |
---|
44 | |
---|
45 | static int chs_rdwr_sectors(struct disk *disk, void *buf, |
---|
46 | sector_t lba, size_t count, bool is_write) |
---|
47 | { |
---|
48 | char *ptr = buf; |
---|
49 | char *tptr; |
---|
50 | size_t chunk, freeseg; |
---|
51 | int sector_shift = disk->sector_shift; |
---|
52 | uint32_t xlba = lba + disk->part_start; /* Truncated LBA (CHS is << 2 TB) */ |
---|
53 | uint32_t t; |
---|
54 | uint32_t c, h, s; |
---|
55 | com32sys_t ireg, oreg; |
---|
56 | size_t done = 0; |
---|
57 | size_t bytes; |
---|
58 | int retry; |
---|
59 | uint32_t maxtransfer = disk->maxtransfer; |
---|
60 | |
---|
61 | if (lba + disk->part_start >= chs_max(disk)) |
---|
62 | return 0; /* Impossible CHS request */ |
---|
63 | |
---|
64 | memset(&ireg, 0, sizeof ireg); |
---|
65 | |
---|
66 | ireg.eax.b[1] = 0x02 + is_write; |
---|
67 | ireg.edx.b[0] = disk->disk_number; |
---|
68 | |
---|
69 | while (count) { |
---|
70 | chunk = count; |
---|
71 | if (chunk > maxtransfer) |
---|
72 | chunk = maxtransfer; |
---|
73 | |
---|
74 | freeseg = (0x10000 - ((size_t)ptr & 0xffff)) >> sector_shift; |
---|
75 | |
---|
76 | if ((size_t)buf <= 0xf0000 && freeseg) { |
---|
77 | /* Can do a direct load */ |
---|
78 | tptr = ptr; |
---|
79 | } else { |
---|
80 | /* Either accessing high memory or we're crossing a 64K line */ |
---|
81 | tptr = core_xfer_buf; |
---|
82 | freeseg = (0x10000 - ((size_t)tptr & 0xffff)) >> sector_shift; |
---|
83 | } |
---|
84 | if (chunk > freeseg) |
---|
85 | chunk = freeseg; |
---|
86 | |
---|
87 | s = xlba % disk->s; |
---|
88 | t = xlba / disk->s; |
---|
89 | h = t % disk->h; |
---|
90 | c = t / disk->h; |
---|
91 | |
---|
92 | if (chunk > (disk->s - s)) |
---|
93 | chunk = disk->s - s; |
---|
94 | |
---|
95 | bytes = chunk << sector_shift; |
---|
96 | |
---|
97 | if (tptr != ptr && is_write) |
---|
98 | memcpy(tptr, ptr, bytes); |
---|
99 | |
---|
100 | ireg.eax.b[0] = chunk; |
---|
101 | ireg.ecx.b[1] = c; |
---|
102 | ireg.ecx.b[0] = ((c & 0x300) >> 2) | (s+1); |
---|
103 | ireg.edx.b[1] = h; |
---|
104 | ireg.ebx.w[0] = OFFS(tptr); |
---|
105 | ireg.es = SEG(tptr); |
---|
106 | |
---|
107 | retry = RETRY_COUNT; |
---|
108 | |
---|
109 | for (;;) { |
---|
110 | if (c < 1024) { |
---|
111 | dprintf("CHS[%02x]: %u @ %llu (%u/%u/%u) %04x:%04x %s %p\n", |
---|
112 | ireg.edx.b[0], chunk, xlba, c, h, s+1, |
---|
113 | ireg.es, ireg.ebx.w[0], |
---|
114 | (ireg.eax.b[1] & 1) ? "<-" : "->", |
---|
115 | ptr); |
---|
116 | |
---|
117 | __intcall(0x13, &ireg, &oreg); |
---|
118 | if (!(oreg.eflags.l & EFLAGS_CF)) |
---|
119 | break; |
---|
120 | |
---|
121 | dprintf("CHS: error AX = %04x\n", oreg.eax.w[0]); |
---|
122 | |
---|
123 | if (retry--) |
---|
124 | continue; |
---|
125 | |
---|
126 | /* |
---|
127 | * For any starting value, this will always end with |
---|
128 | * ..., 1, 0 |
---|
129 | */ |
---|
130 | chunk >>= 1; |
---|
131 | if (chunk) { |
---|
132 | maxtransfer = chunk; |
---|
133 | retry = RETRY_COUNT; |
---|
134 | ireg.eax.b[0] = chunk; |
---|
135 | continue; |
---|
136 | } |
---|
137 | } |
---|
138 | |
---|
139 | printf("CHS: Error %04x %s sector %llu (%u/%u/%u)\n", |
---|
140 | oreg.eax.w[0], |
---|
141 | is_write ? "writing" : "reading", |
---|
142 | lba, c, h, s+1); |
---|
143 | return done; /* Failure */ |
---|
144 | } |
---|
145 | |
---|
146 | bytes = chunk << sector_shift; |
---|
147 | |
---|
148 | if (tptr != ptr && !is_write) |
---|
149 | memcpy(ptr, tptr, bytes); |
---|
150 | |
---|
151 | /* If we dropped maxtransfer, it eventually worked, so remember it */ |
---|
152 | disk->maxtransfer = maxtransfer; |
---|
153 | |
---|
154 | ptr += bytes; |
---|
155 | xlba += chunk; |
---|
156 | count -= chunk; |
---|
157 | done += chunk; |
---|
158 | } |
---|
159 | |
---|
160 | return done; |
---|
161 | } |
---|
162 | |
---|
163 | static int edd_rdwr_sectors(struct disk *disk, void *buf, |
---|
164 | sector_t lba, size_t count, bool is_write) |
---|
165 | { |
---|
166 | static __lowmem struct edd_rdwr_packet pkt; |
---|
167 | char *ptr = buf; |
---|
168 | char *tptr; |
---|
169 | size_t chunk, freeseg; |
---|
170 | int sector_shift = disk->sector_shift; |
---|
171 | com32sys_t ireg, oreg, reset; |
---|
172 | size_t done = 0; |
---|
173 | size_t bytes; |
---|
174 | int retry; |
---|
175 | uint32_t maxtransfer = disk->maxtransfer; |
---|
176 | |
---|
177 | memset(&ireg, 0, sizeof ireg); |
---|
178 | |
---|
179 | ireg.eax.b[1] = 0x42 + is_write; |
---|
180 | ireg.edx.b[0] = disk->disk_number; |
---|
181 | ireg.ds = SEG(&pkt); |
---|
182 | ireg.esi.w[0] = OFFS(&pkt); |
---|
183 | |
---|
184 | memset(&reset, 0, sizeof reset); |
---|
185 | |
---|
186 | lba += disk->part_start; |
---|
187 | while (count) { |
---|
188 | chunk = count; |
---|
189 | if (chunk > maxtransfer) |
---|
190 | chunk = maxtransfer; |
---|
191 | |
---|
192 | freeseg = (0x10000 - ((size_t)ptr & 0xffff)) >> sector_shift; |
---|
193 | |
---|
194 | if ((size_t)ptr <= 0xf0000 && freeseg) { |
---|
195 | /* Can do a direct load */ |
---|
196 | tptr = ptr; |
---|
197 | } else { |
---|
198 | /* Either accessing high memory or we're crossing a 64K line */ |
---|
199 | tptr = core_xfer_buf; |
---|
200 | freeseg = (0x10000 - ((size_t)tptr & 0xffff)) >> sector_shift; |
---|
201 | } |
---|
202 | if (chunk > freeseg) |
---|
203 | chunk = freeseg; |
---|
204 | |
---|
205 | bytes = chunk << sector_shift; |
---|
206 | |
---|
207 | if (tptr != ptr && is_write) |
---|
208 | memcpy(tptr, ptr, bytes); |
---|
209 | |
---|
210 | retry = RETRY_COUNT; |
---|
211 | |
---|
212 | for (;;) { |
---|
213 | pkt.size = sizeof pkt; |
---|
214 | pkt.blocks = chunk; |
---|
215 | pkt.buf = FAR_PTR(tptr); |
---|
216 | pkt.lba = lba; |
---|
217 | |
---|
218 | dprintf("EDD[%02x]: %u @ %llu %04x:%04x %s %p\n", |
---|
219 | ireg.edx.b[0], pkt.blocks, pkt.lba, |
---|
220 | pkt.buf.seg, pkt.buf.offs, |
---|
221 | (ireg.eax.b[1] & 1) ? "<-" : "->", |
---|
222 | ptr); |
---|
223 | |
---|
224 | __intcall(0x13, &ireg, &oreg); |
---|
225 | if (!(oreg.eflags.l & EFLAGS_CF)) |
---|
226 | break; |
---|
227 | |
---|
228 | dprintf("EDD: error AX = %04x\n", oreg.eax.w[0]); |
---|
229 | |
---|
230 | if (retry--) |
---|
231 | continue; |
---|
232 | |
---|
233 | /* |
---|
234 | * Some systems seem to get "stuck" in an error state when |
---|
235 | * using EBIOS. Doesn't happen when using CBIOS, which is |
---|
236 | * good, since some other systems get timeout failures |
---|
237 | * waiting for the floppy disk to spin up. |
---|
238 | */ |
---|
239 | __intcall(0x13, &reset, NULL); |
---|
240 | |
---|
241 | /* For any starting value, this will always end with ..., 1, 0 */ |
---|
242 | chunk >>= 1; |
---|
243 | if (chunk) { |
---|
244 | maxtransfer = chunk; |
---|
245 | retry = RETRY_COUNT; |
---|
246 | continue; |
---|
247 | } |
---|
248 | |
---|
249 | /* |
---|
250 | * Total failure. There are systems which identify as |
---|
251 | * EDD-capable but aren't; the known such systems return |
---|
252 | * error code AH=1 (invalid function), but let's not |
---|
253 | * assume that for now. |
---|
254 | * |
---|
255 | * Try to fall back to CHS. If the LBA is absurd, the |
---|
256 | * chs_max() test in chs_rdwr_sectors() will catch it. |
---|
257 | */ |
---|
258 | done = chs_rdwr_sectors(disk, buf, lba - disk->part_start, |
---|
259 | count, is_write); |
---|
260 | if (done == (count << sector_shift)) { |
---|
261 | /* Successful, assume this is a CHS disk */ |
---|
262 | disk->rdwr_sectors = chs_rdwr_sectors; |
---|
263 | return done; |
---|
264 | } |
---|
265 | printf("EDD: Error %04x %s sector %llu\n", |
---|
266 | oreg.eax.w[0], |
---|
267 | is_write ? "writing" : "reading", |
---|
268 | lba); |
---|
269 | return done; /* Failure */ |
---|
270 | } |
---|
271 | |
---|
272 | bytes = chunk << sector_shift; |
---|
273 | |
---|
274 | if (tptr != ptr && !is_write) |
---|
275 | memcpy(ptr, tptr, bytes); |
---|
276 | |
---|
277 | /* If we dropped maxtransfer, it eventually worked, so remember it */ |
---|
278 | disk->maxtransfer = maxtransfer; |
---|
279 | |
---|
280 | ptr += bytes; |
---|
281 | lba += chunk; |
---|
282 | count -= chunk; |
---|
283 | done += chunk; |
---|
284 | } |
---|
285 | return done; |
---|
286 | } |
---|
287 | |
---|
288 | struct disk *bios_disk_init(void *private) |
---|
289 | { |
---|
290 | static struct disk disk; |
---|
291 | struct bios_disk_private *priv = (struct bios_disk_private *)private; |
---|
292 | com32sys_t *regs = priv->regs; |
---|
293 | static __lowmem struct edd_disk_params edd_params; |
---|
294 | com32sys_t ireg, oreg; |
---|
295 | uint8_t devno = regs->edx.b[0]; |
---|
296 | bool cdrom = regs->edx.b[1]; |
---|
297 | sector_t part_start = regs->ecx.l | ((sector_t)regs->ebx.l << 32); |
---|
298 | uint16_t bsHeads = regs->esi.w[0]; |
---|
299 | uint16_t bsSecPerTrack = regs->edi.w[0]; |
---|
300 | uint32_t MaxTransfer = regs->ebp.l; |
---|
301 | bool ebios; |
---|
302 | int sector_size; |
---|
303 | unsigned int hard_max_transfer; |
---|
304 | |
---|
305 | memset(&ireg, 0, sizeof ireg); |
---|
306 | ireg.edx.b[0] = devno; |
---|
307 | |
---|
308 | if (cdrom) { |
---|
309 | /* |
---|
310 | * The query functions don't work right on some CD-ROM stacks. |
---|
311 | * Known affected systems: ThinkPad T22, T23. |
---|
312 | */ |
---|
313 | sector_size = 2048; |
---|
314 | ebios = true; |
---|
315 | hard_max_transfer = 32; |
---|
316 | } else { |
---|
317 | sector_size = 512; |
---|
318 | ebios = false; |
---|
319 | hard_max_transfer = 63; |
---|
320 | |
---|
321 | /* CBIOS parameters */ |
---|
322 | disk.h = bsHeads; |
---|
323 | disk.s = bsSecPerTrack; |
---|
324 | |
---|
325 | if ((int8_t)devno < 0) { |
---|
326 | /* Get hard disk geometry from BIOS */ |
---|
327 | |
---|
328 | ireg.eax.b[1] = 0x08; |
---|
329 | __intcall(0x13, &ireg, &oreg); |
---|
330 | |
---|
331 | if (!(oreg.eflags.l & EFLAGS_CF)) { |
---|
332 | disk.h = oreg.edx.b[1] + 1; |
---|
333 | disk.s = oreg.ecx.b[0] & 63; |
---|
334 | } |
---|
335 | } |
---|
336 | |
---|
337 | memset(&ireg, 0, sizeof ireg); |
---|
338 | /* Get EBIOS support */ |
---|
339 | ireg.eax.b[1] = 0x41; |
---|
340 | ireg.ebx.w[0] = 0x55aa; |
---|
341 | ireg.edx.b[0] = devno; |
---|
342 | ireg.eflags.b[0] = 0x3; /* CF set */ |
---|
343 | |
---|
344 | __intcall(0x13, &ireg, &oreg); |
---|
345 | |
---|
346 | if (!(oreg.eflags.l & EFLAGS_CF) && |
---|
347 | oreg.ebx.w[0] == 0xaa55 && (oreg.ecx.b[0] & 1)) { |
---|
348 | ebios = true; |
---|
349 | hard_max_transfer = 127; |
---|
350 | |
---|
351 | /* Query EBIOS parameters */ |
---|
352 | /* The memset() is needed once this function can be called |
---|
353 | more than once */ |
---|
354 | /* memset(&edd_params, 0, sizeof edd_params); */ |
---|
355 | edd_params.len = sizeof edd_params; |
---|
356 | |
---|
357 | memset(&ireg, 0, sizeof ireg); |
---|
358 | ireg.eax.b[1] = 0x48; |
---|
359 | ireg.edx.b[0] = devno; |
---|
360 | ireg.ds = SEG(&edd_params); |
---|
361 | ireg.esi.w[0] = OFFS(&edd_params); |
---|
362 | __intcall(0x13, &ireg, &oreg); |
---|
363 | |
---|
364 | if (!(oreg.eflags.l & EFLAGS_CF) && oreg.eax.b[1] == 0) { |
---|
365 | if (edd_params.len < sizeof edd_params) |
---|
366 | memset((char *)&edd_params + edd_params.len, 0, |
---|
367 | sizeof edd_params - edd_params.len); |
---|
368 | |
---|
369 | if (edd_params.sector_size >= 512 && |
---|
370 | is_power_of_2(edd_params.sector_size)) |
---|
371 | sector_size = edd_params.sector_size; |
---|
372 | } |
---|
373 | } |
---|
374 | |
---|
375 | } |
---|
376 | |
---|
377 | disk.disk_number = devno; |
---|
378 | disk.sector_size = sector_size; |
---|
379 | disk.sector_shift = ilog2(sector_size); |
---|
380 | disk.part_start = part_start; |
---|
381 | disk.secpercyl = disk.h * disk.s; |
---|
382 | disk.rdwr_sectors = ebios ? edd_rdwr_sectors : chs_rdwr_sectors; |
---|
383 | |
---|
384 | if (!MaxTransfer || MaxTransfer > hard_max_transfer) |
---|
385 | MaxTransfer = hard_max_transfer; |
---|
386 | |
---|
387 | disk.maxtransfer = MaxTransfer; |
---|
388 | |
---|
389 | dprintf("disk %02x cdrom %d type %d sector %u/%u offset %llu limit %u\n", |
---|
390 | devno, cdrom, ebios, sector_size, disk.sector_shift, |
---|
391 | part_start, disk.maxtransfer); |
---|
392 | |
---|
393 | disk.private = private; |
---|
394 | return &disk; |
---|
395 | } |
---|
396 | |
---|
397 | void pm_fs_init(com32sys_t *regs) |
---|
398 | { |
---|
399 | static struct bios_disk_private priv; |
---|
400 | |
---|
401 | priv.regs = regs; |
---|
402 | fs_init((const struct fs_ops **)regs->eax.l, (void *)&priv); |
---|
403 | } |
---|