Welcome to The Fiwix Project
A UNIX-like kernel for the i386 architecture
1 /* 2 * fiwix/drivers/block/ide_hd.c 3 * 4 * Copyright 2018-2021, Jordi Sanfeliu. All rights reserved. 5 * Distributed under the terms of the Fiwix License. 6 */ 7 8 #include <fiwix/asm.h> 9 #include <fiwix/buffer.h> 10 #include <fiwix/ide.h> 11 #include <fiwix/ide_hd.h> 12 #include <fiwix/ioctl.h> 13 #include <fiwix/devices.h> 14 #include <fiwix/sleep.h> 15 #include <fiwix/timer.h> 16 #include <fiwix/sched.h> 17 #include <fiwix/cpu.h> 18 #include <fiwix/fs.h> 19 #include <fiwix/part.h> 20 #include <fiwix/process.h> 21 #include <fiwix/mm.h> 22 #include <fiwix/errno.h> 23 #include <fiwix/stdio.h> 24 #include <fiwix/string.h> 25 26 static struct fs_operations ide_hd_driver_fsop = { 27 0, 28 0, 29 30 ide_hd_open, 31 ide_hd_close, 32 NULL, /* read */ 33 NULL, /* write */ 34 ide_hd_ioctl, 35 NULL, /* lseek */ 36 NULL, /* readdir */ 37 NULL, /* mmap */ 38 NULL, /* select */ 39 40 NULL, /* readlink */ 41 NULL, /* followlink */ 42 NULL, /* bmap */ 43 NULL, /* lockup */ 44 NULL, /* rmdir */ 45 NULL, /* link */ 46 NULL, /* unlink */ 47 NULL, /* symlink */ 48 NULL, /* mkdir */ 49 NULL, /* mknod */ 50 NULL, /* truncate */ 51 NULL, /* create */ 52 NULL, /* rename */ 53 54 ide_hd_read, 55 ide_hd_write, 56 57 NULL, /* read_inode */ 58 NULL, /* write_inode */ 59 NULL, /* ialloc */ 60 NULL, /* ifree */ 61 NULL, /* statfs */ 62 NULL, /* read_superblock */ 63 NULL, /* remount_fs */ 64 NULL, /* write_superblock */ 65 NULL /* release_superblock */ 66 }; 67 68 static void assign_minors(__dev_t rdev, struct ide *ide, struct partition *part) 69 { 70 int n; 71 int drive, minor; 72 struct device *d; 73 74 minor = 0; 75 drive = get_ide_drive(rdev); 76 77 if(!(d = get_device(BLK_DEV, rdev))) { 78 printk("WARNING: %s(): unable to assign minors to device %d,%d.\n", __FUNCTION__, MAJOR(rdev), MINOR(rdev)); 79 return; 80 } 81 82 for(n = 0; n < NR_PARTITIONS; n++) { 83 if(drive == IDE_MASTER) { 84 minor = (1 << ide->drive[drive].minor_shift) + n; 85 } 86 if(drive == IDE_SLAVE) { 87 minor = (1 << ide->drive[drive].minor_shift) + n + 1; 88 } 89 CLEAR_MINOR(d->minors, minor); 90 if(part[n].type) { 91 SET_MINOR(d->minors, minor); 92 ((unsigned int *)d->device_data)[minor] = part[n].nr_sects / 2; 93 } 94 } 95 } 96 97 static __off_t block2sector(__blk_t block, int blksize, struct partition *part, int minor) 98 { 99 __off_t sector; 100 101 sector = block * (blksize / IDE_HD_SECTSIZE); 102 if(minor) { 103 sector += part[minor - 1].startsect; 104 } 105 return sector; 106 } 107 108 static void sector2chs(__off_t offset, int *cyl, int *head, int *sector, struct ide_drv_ident *ident) 109 { 110 *cyl = offset / (ident->logic_spt * ident->logic_heads); 111 *head = (offset / ident->logic_spt) % ident->logic_heads; 112 *sector = (offset % ident->logic_spt) + 1; 113 } 114 115 int ide_hd_open(struct inode *i, struct fd *fd_table) 116 { 117 return 0; 118 } 119 120 int ide_hd_close(struct inode *i, struct fd *fd_table) 121 { 122 sync_buffers(i->rdev); 123 return 0; 124 } 125 126 int ide_hd_read(__dev_t dev, __blk_t block, char *buffer, int blksize) 127 { 128 int minor; 129 int drive; 130 int sectors_to_read, cmd; 131 int n, status, r, retries; 132 int cyl, head, sector; 133 __off_t offset; 134 struct ide *ide; 135 struct ide_drv_ident *ident; 136 struct partition *part; 137 struct callout_req creq; 138 139 if(!(ide = get_ide_controller(dev))) { 140 return -EINVAL; 141 } 142 143 minor = MINOR(dev); 144 if((drive = get_ide_drive(dev))) { 145 minor &= ~(1 << IDE_SLAVE_MSF); 146 } 147 148 SET_IDE_RDY_RETR(retries); 149 150 blksize = blksize ? blksize : BLKSIZE_1K; 151 sectors_to_read = MIN(blksize, PAGE_SIZE) / IDE_HD_SECTSIZE; 152 153 ident = &ide->drive[drive].ident; 154 part = ide->drive[drive].part_table; 155 offset = block2sector(block, blksize, part, minor); 156 157 CLI(); 158 lock_resource(&ide->resource); 159 160 n = 0; 161 162 while(n < sectors_to_read) { 163 if(ide->drive[drive].flags & DEVICE_HAS_RW_MULTIPLE) { 164 outport_b(ide->base + IDE_SECCNT, sectors_to_read); 165 cmd = ATA_READ_MULTIPLE_PIO; 166 } else { 167 outport_b(ide->base + IDE_SECCNT, 1); 168 cmd = ATA_READ_PIO; 169 } 170 171 if(ide->drive[drive].flags & DEVICE_REQUIRES_LBA) { 172 outport_b(ide->base + IDE_SECNUM, offset & 0xFF); 173 outport_b(ide->base + IDE_LCYL, (offset >> 8) & 0xFF); 174 outport_b(ide->base + IDE_HCYL, (offset >> 16) & 0xFF); 175 if(ide_drvsel(ide, drive, IDE_LBA_MODE, (offset >> 24) & 0x0F)) { 176 printk("WARNING: %s(): %s: drive not ready.\n", __FUNCTION__, ide->drive[drive].dev_name); 177 unlock_resource(&ide->resource); 178 return -EIO; 179 } 180 } else { 181 sector2chs(offset, &cyl, &head, §or, ident); 182 outport_b(ide->base + IDE_SECNUM, sector); 183 outport_b(ide->base + IDE_LCYL, cyl); 184 outport_b(ide->base + IDE_HCYL, (cyl >> 8)); 185 if(ide_drvsel(ide, drive, IDE_CHS_MODE, head)) { 186 printk("WARNING: %s(): %s: drive not ready.\n", __FUNCTION__, ide->drive[drive].dev_name); 187 unlock_resource(&ide->resource); 188 return -EIO; 189 } 190 } 191 outport_b(ide->base + IDE_COMMAND, cmd); 192 if(ide->channel == IDE_PRIMARY) { 193 ide0_wait_interrupt = ide->base; 194 creq.fn = ide0_timer; 195 creq.arg = 0; 196 add_callout(&creq, WAIT_FOR_IDE); 197 if(ide0_wait_interrupt) { 198 sleep(&irq_ide0, PROC_UNINTERRUPTIBLE); 199 } 200 if(ide0_timeout) { 201 printk("WARNING: %s(): %s: timeout on hard disk dev %d,%d during read.\n", __FUNCTION__, ide->drive[drive].dev_name, MAJOR(dev), MINOR(dev)); 202 } else { 203 del_callout(&creq); 204 } 205 } 206 if(ide->channel == IDE_SECONDARY) { 207 ide1_wait_interrupt = ide->base; 208 creq.fn = ide1_timer; 209 creq.arg = 0; 210 add_callout(&creq, WAIT_FOR_IDE); 211 if(ide1_wait_interrupt) { 212 sleep(&irq_ide1, PROC_UNINTERRUPTIBLE); 213 } 214 if(ide1_timeout) { 215 printk("WARNING: %s(): %s: timeout on hard disk dev %d,%d during read.\n", __FUNCTION__, ide->drive[drive].dev_name, MAJOR(dev), MINOR(dev)); 216 } else { 217 del_callout(&creq); 218 } 219 } 220 for(r = 0; r < retries; r++) { 221 status = inport_b(ide->base + IDE_STATUS); 222 if(!(status & IDE_STAT_BSY) && (status & IDE_STAT_DRQ)) { 223 break; 224 } 225 ide_delay(); 226 } 227 if(status & IDE_STAT_ERR) { 228 printk("WARNING: %s(): %s: error on hard disk dev %d,%d during read.\n", __FUNCTION__, ide->drive[drive].dev_name, MAJOR(dev), MINOR(dev)); 229 printk("\tstatus=0x%x ", status); 230 ide_error(ide, status); 231 printk("\tblock %d, sector %d.\n", block, offset); 232 inport_b(ide->base + IDE_STATUS); /* clear any pending interrupt */ 233 unlock_resource(&ide->resource); 234 return -EIO; 235 } 236 237 if(cmd == ATA_READ_MULTIPLE_PIO) { 238 inport_sw(ide->base + IDE_DATA, (void *)buffer, (IDE_HD_SECTSIZE * sectors_to_read) / sizeof(short int)); 239 break; 240 } 241 inport_sw(ide->base + IDE_DATA, (void *)buffer, IDE_HD_SECTSIZE / sizeof(short int)); 242 inport_b(ide->ctrl + IDE_ALT_STATUS); /* ignore results */ 243 inport_b(ide->base + IDE_STATUS); /* clear any pending interrupt */ 244 n++; 245 offset++; 246 buffer += IDE_HD_SECTSIZE; 247 } 248 inport_b(ide->ctrl + IDE_ALT_STATUS); /* ignore results */ 249 inport_b(ide->base + IDE_STATUS); /* clear any pending interrupt */ 250 unlock_resource(&ide->resource); 251 return sectors_to_read * IDE_HD_SECTSIZE; 252 } 253 254 int ide_hd_write(__dev_t dev, __blk_t block, char *buffer, int blksize) 255 { 256 int minor; 257 int drive; 258 int sectors_to_write, cmd; 259 int n, status, r, retries; 260 int cyl, head, sector; 261 __off_t offset; 262 struct ide *ide; 263 struct ide_drv_ident *ident; 264 struct partition *part; 265 struct callout_req creq; 266 267 if(!(ide = get_ide_controller(dev))) { 268 return -EINVAL; 269 } 270 271 minor = MINOR(dev); 272 if((drive = get_ide_drive(dev))) { 273 minor &= ~(1 << IDE_SLAVE_MSF); 274 } 275 276 SET_IDE_RDY_RETR(retries); 277 278 blksize = blksize ? blksize : BLKSIZE_1K; 279 sectors_to_write = MIN(blksize, PAGE_SIZE) / IDE_HD_SECTSIZE; 280 281 ident = &ide->drive[drive].ident; 282 part = ide->drive[drive].part_table; 283 offset = block2sector(block, blksize, part, minor); 284 285 CLI(); 286 lock_resource(&ide->resource); 287 288 n = 0; 289 290 while(n < sectors_to_write) { 291 if(ide->drive[drive].flags & DEVICE_HAS_RW_MULTIPLE) { 292 outport_b(ide->base + IDE_SECCNT, sectors_to_write); 293 cmd = ATA_WRITE_MULTIPLE_PIO; 294 } else { 295 outport_b(ide->base + IDE_SECCNT, 1); 296 cmd = ATA_WRITE_PIO; 297 } 298 299 if(ide->drive[drive].flags & DEVICE_REQUIRES_LBA) { 300 outport_b(ide->base + IDE_SECNUM, offset & 0xFF); 301 outport_b(ide->base + IDE_LCYL, (offset >> 8) & 0xFF); 302 outport_b(ide->base + IDE_HCYL, (offset >> 16) & 0xFF); 303 if(ide_drvsel(ide, drive, IDE_LBA_MODE, (offset >> 24) & 0x0F)) { 304 printk("WARNING: %s(): %s: drive not ready.\n", __FUNCTION__, ide->drive[drive].dev_name); 305 unlock_resource(&ide->resource); 306 return -EIO; 307 } 308 } else { 309 sector2chs(offset, &cyl, &head, §or, ident); 310 outport_b(ide->base + IDE_SECNUM, sector); 311 outport_b(ide->base + IDE_LCYL, cyl); 312 outport_b(ide->base + IDE_HCYL, (cyl >> 8)); 313 if(ide_drvsel(ide, drive, IDE_CHS_MODE, head)) { 314 printk("WARNING: %s(): %s: drive not ready.\n", __FUNCTION__, ide->drive[drive].dev_name); 315 unlock_resource(&ide->resource); 316 return -EIO; 317 } 318 } 319 outport_b(ide->base + IDE_COMMAND, cmd); 320 for(r = 0; r < retries; r++) { 321 status = inport_b(ide->base + IDE_STATUS); 322 if(!(status & IDE_STAT_BSY) && (status & IDE_STAT_DRQ)) { 323 break; 324 } 325 ide_delay(); 326 } 327 if(status & IDE_STAT_ERR) { 328 printk("WARNING: %s(): %s: error on hard disk dev %d,%d during write.\n", __FUNCTION__, ide->drive[drive].dev_name, MAJOR(dev), MINOR(dev)); 329 printk("\tstatus=0x%x ", status); 330 ide_error(ide, status); 331 printk("\tblock %d, sector %d.\n", block, offset); 332 inport_b(ide->base + IDE_STATUS); /* clear any pending interrupt */ 333 unlock_resource(&ide->resource); 334 return -EIO; 335 } 336 337 if(cmd == ATA_WRITE_MULTIPLE_PIO) { 338 outport_sw(ide->base + IDE_DATA, (void *)buffer, (IDE_HD_SECTSIZE * sectors_to_write) / sizeof(short int)); 339 } else { 340 outport_sw(ide->base + IDE_DATA, (void *)buffer, IDE_HD_SECTSIZE / sizeof(short int)); 341 } 342 if(ide->channel == IDE_PRIMARY) { 343 ide0_wait_interrupt = ide->base; 344 creq.fn = ide0_timer; 345 creq.arg = 0; 346 add_callout(&creq, WAIT_FOR_IDE); 347 if(ide0_wait_interrupt) { 348 sleep(&irq_ide0, PROC_UNINTERRUPTIBLE); 349 } 350 if(ide0_timeout) { 351 printk("WARNING: %s(): %s: timeout on hard disk dev %d,%d during write.\n", __FUNCTION__, ide->drive[drive].dev_name, MAJOR(dev), MINOR(dev)); 352 } else { 353 del_callout(&creq); 354 } 355 } 356 if(ide->channel == IDE_SECONDARY) { 357 ide1_wait_interrupt = ide->base; 358 creq.fn = ide1_timer; 359 creq.arg = 0; 360 add_callout(&creq, WAIT_FOR_IDE); 361 if(ide1_wait_interrupt) { 362 sleep(&irq_ide1, PROC_UNINTERRUPTIBLE); 363 } 364 if(ide1_timeout) { 365 printk("WARNING: %s(): %s: timeout on hard disk dev %d,%d during write.\n", __FUNCTION__, ide->drive[drive].dev_name, MAJOR(dev), MINOR(dev)); 366 } else { 367 del_callout(&creq); 368 } 369 } 370 371 inport_b(ide->ctrl + IDE_ALT_STATUS); /* ignore results */ 372 inport_b(ide->base + IDE_STATUS); /* clear any pending interrupt */ 373 if(cmd == ATA_WRITE_MULTIPLE_PIO) { 374 break; 375 } 376 n++; 377 offset++; 378 buffer += IDE_HD_SECTSIZE; 379 } 380 inport_b(ide->ctrl + IDE_ALT_STATUS); /* ignore results */ 381 inport_b(ide->base + IDE_STATUS); /* clear any pending interrupt */ 382 unlock_resource(&ide->resource); 383 return sectors_to_write * IDE_HD_SECTSIZE; 384 } 385 386 int ide_hd_ioctl(struct inode *i, int cmd, unsigned long int arg) 387 { 388 int minor; 389 int drive; 390 struct ide *ide; 391 struct ide_drv_ident *ident; 392 struct partition *part; 393 struct hd_geometry *geom; 394 int errno; 395 396 if(!(ide = get_ide_controller(i->rdev))) { 397 return -EINVAL; 398 } 399 400 minor = MINOR(i->rdev); 401 drive = get_ide_drive(i->rdev); 402 if(drive) { 403 minor &= ~(1 << IDE_SLAVE_MSF); 404 } 405 406 ident = &ide->drive[drive].ident; 407 part = ide->drive[drive].part_table; 408 409 switch(cmd) { 410 case HDIO_GETGEO: 411 if((errno = check_user_area(VERIFY_WRITE, (void *)arg, sizeof(struct hd_geometry)))) { 412 return errno; 413 } 414 geom = (struct hd_geometry *)arg; 415 geom->cylinders = ident->logic_cyls; 416 geom->heads = (char)ident->logic_heads; 417 geom->sectors = (char)ident->logic_spt; 418 geom->start = 0; 419 if(minor) { 420 geom->start = part[minor - 1].startsect; 421 } 422 break; 423 case BLKGETSIZE: 424 if((errno = check_user_area(VERIFY_WRITE, (void *)arg, sizeof(unsigned int)))) { 425 return errno; 426 } 427 if(!minor) { 428 *(int *)arg = (unsigned int)ide->drive[drive].nr_sects; 429 } else { 430 *(int *)arg = (unsigned int)ide->drive[drive].part_table[minor - 1].nr_sects; 431 } 432 break; 433 case BLKFLSBUF: 434 sync_buffers(i->rdev); 435 invalidate_buffers(i->rdev); 436 break; 437 case BLKRRPART: 438 read_msdos_partition(i->rdev, part); 439 assign_minors(i->rdev, ide, part); 440 break; 441 default: 442 return -EINVAL; 443 break; 444 } 445 446 return 0; 447 } 448 449 int ide_hd_init(struct ide *ide, int drive) 450 { 451 int n; 452 __dev_t rdev; 453 struct device *d; 454 struct partition *part; 455 456 rdev = 0; 457 ide->drive[drive].fsop = &ide_hd_driver_fsop; 458 part = ide->drive[drive].part_table; 459 460 if(ide->channel == IDE_PRIMARY) { 461 if(drive == IDE_MASTER) { 462 rdev = MKDEV(IDE0_MAJOR, drive); 463 ide->drive[drive].minor_shift = IDE_MASTER_MSF; 464 if(!(d = get_device(BLK_DEV, rdev))) { 465 return -EINVAL; 466 } 467 ((unsigned int *)d->device_data)[0] = ide->drive[drive].nr_sects / 2; 468 } else { 469 rdev = MKDEV(IDE0_MAJOR, 1 << IDE_SLAVE_MSF); 470 ide->drive[drive].minor_shift = IDE_SLAVE_MSF; 471 if(!(d = get_device(BLK_DEV, rdev))) { 472 return -EINVAL; 473 } 474 ((unsigned int *)d->device_data)[1 << IDE_SLAVE_MSF] = ide->drive[drive].nr_sects / 2; 475 } 476 } else if(ide->channel == IDE_SECONDARY) { 477 if(drive == IDE_MASTER) { 478 rdev = MKDEV(IDE1_MAJOR, drive); 479 ide->drive[drive].minor_shift = IDE_MASTER_MSF; 480 if(!(d = get_device(BLK_DEV, rdev))) { 481 return -EINVAL; 482 } 483 ((unsigned int *)d->device_data)[0] = ide->drive[drive].nr_sects / 2; 484 } else { 485 rdev = MKDEV(IDE1_MAJOR, 1 << IDE_SLAVE_MSF); 486 ide->drive[drive].minor_shift = IDE_SLAVE_MSF; 487 if(!(d = get_device(BLK_DEV, rdev))) { 488 return -EINVAL; 489 } 490 ((unsigned int *)d->device_data)[1 << IDE_SLAVE_MSF] = ide->drive[drive].nr_sects / 2; 491 } 492 } else { 493 printk("WARNING: %s(): invalid drive number %d.\n", __FUNCTION__, drive); 494 return 1; 495 } 496 497 read_msdos_partition(rdev, part); 498 assign_minors(rdev, ide, part); 499 printk(" partition summary: "); 500 for(n = 0; n < NR_PARTITIONS; n++) { 501 /* status values other than 0x00 and 0x80 are invalid */ 502 if(part[n].status && part[n].status != 0x80) { 503 continue; 504 } 505 if(part[n].type) { 506 printk("%s%d ", ide->drive[drive].dev_name, n + 1); 507 } 508 } 509 printk("\n"); 510 511 outport_b(ide->ctrl + IDE_DEV_CTRL, IDE_DEVCTR_NIEN); 512 if(ide->drive[drive].flags & DEVICE_HAS_RW_MULTIPLE) { 513 /* read/write in 4KB blocks (8 sectors) by default */ 514 outport_b(ide->base + IDE_SECCNT, PAGE_SIZE / IDE_HD_SECTSIZE); 515 outport_b(ide->base + IDE_COMMAND, ATA_SET_MULTIPLE_MODE); 516 ide_wait400ns(ide); 517 while(inport_b(ide->base + IDE_STATUS) & IDE_STAT_BSY); 518 } 519 outport_b(ide->ctrl + IDE_DEV_CTRL, IDE_DEVCTR_DRQ); 520 521 return 0; 522 }