[SeaBIOS] [PATCH] Fixes to the floppy driver, so it works on real (not emulated) hardware

Nikolay Nikolov posted 1 patch 6 years, 1 month ago
Patches applied successfully (tree, apply log)
git fetch https://github.com/patchew-project/seabios tags/patchew/20180130034147.6058-1-nickysn@users.sourceforge.net
src/hw/floppy.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++----------
1 file changed, 69 insertions(+), 14 deletions(-)
[SeaBIOS] [PATCH] Fixes to the floppy driver, so it works on real (not emulated) hardware
Posted by Nikolay Nikolov 6 years, 1 month ago
Several fixes to the floppy driver, so it works on a real floppy controller
with a real floppy. Tested with Coreboot on a motherboard with an ITE IT8712
Super I/O chip (and its built-in floppy controller) and a 1.44MB floppy.

Only one floppy is supported for now and only 1.44MB, but more formats will be
added in the future.

Signed-off-by: Nikolay Nikolov <nickysn@users.sourceforge.net>
---
 src/hw/floppy.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++----------
 1 file changed, 69 insertions(+), 14 deletions(-)

diff --git a/src/hw/floppy.c b/src/hw/floppy.c
index f2577c5..674909d 100644
--- a/src/hw/floppy.c
+++ b/src/hw/floppy.c
@@ -34,6 +34,11 @@
 #define FLOPPY_GAPLEN 0x1B
 #define FLOPPY_FORMAT_GAPLEN 0x6c
 #define FLOPPY_PIO_TIMEOUT 1000
+#define FLOPPY_IRQ_TIMEOUT 5000
+#define FLOPPY_STARTUP_TIME 8
+#define FLOPPY_MOTOR_HOLD 255 // Magic value, means motor must not be turned off
+#define FLOPPY_SPECIFY1 0xAF  // step rate 12ms, head unload 240ms
+#define FLOPPY_SPECIFY2 0x02  // head load time 4ms, DMA used
 
 // New diskette parameter table adding 3 parameters from IBM
 // Since no provisions are made for multiple drive types, most
@@ -191,7 +196,8 @@ static void
 floppy_disable_controller(void)
 {
     dprintf(2, "Floppy_disable_controller\n");
-    floppy_dor_write(0x00);
+    // Clear the reset bit (enter reset state) and clear 'enable IRQ and DMA'
+    floppy_dor_write(GET_LOW(FloppyDOR) & ~0x0c);
 }
 
 static int
@@ -199,8 +205,9 @@ floppy_wait_irq(void)
 {
     u8 frs = GET_BDA(floppy_recalibration_status);
     SET_BDA(floppy_recalibration_status, frs & ~FRS_IRQ);
+    u32 end = timer_calc(FLOPPY_IRQ_TIMEOUT);
     for (;;) {
-        if (!GET_BDA(floppy_motor_counter)) {
+        if (timer_check(end)) {
             warn_timeout();
             floppy_disable_controller();
             return DISK_RET_ETIMEOUT;
@@ -226,6 +233,7 @@ floppy_wait_irq(void)
 #define FC_READ        (0xe6 | (8<<8) | (7<<12) | FCF_WAITIRQ)
 #define FC_WRITE       (0xc5 | (8<<8) | (7<<12) | FCF_WAITIRQ)
 #define FC_FORMAT      (0x4d | (5<<8) | (7<<12) | FCF_WAITIRQ)
+#define FC_SPECIFY     (0x03 | (2<<8) | (0<<12))
 
 // Send the specified command and it's parameters to the floppy controller.
 static int
@@ -302,9 +310,12 @@ static int
 floppy_enable_controller(void)
 {
     dprintf(2, "Floppy_enable_controller\n");
-    SET_BDA(floppy_motor_counter, FLOPPY_MOTOR_TICKS);
-    floppy_dor_write(0x00);
-    floppy_dor_write(0x0c);
+    // Clear the reset bit (enter reset state), but set 'enable IRQ and DMA'
+    floppy_dor_write((GET_LOW(FloppyDOR) & ~0x04) | 0x08);
+    // Real hardware needs a 4 microsecond delay
+    udelay(4);
+    // Set the reset bit (normal operation) and keep 'enable IRQ and DMA' on
+    floppy_dor_write(GET_LOW(FloppyDOR) | 0x0c);
     int ret = floppy_wait_irq();
     if (ret)
         return ret;
@@ -313,6 +324,30 @@ floppy_enable_controller(void)
     return floppy_pio(FC_CHECKIRQ, param);
 }
 
+static void
+floppy_turn_on_motor(u8 floppyid)
+{
+    // prevent the motor from being turned off
+    SET_BDA(floppy_motor_counter, FLOPPY_MOTOR_HOLD);
+
+    u8 motor_mask = 0x10 << floppyid;
+    if (GET_LOW(FloppyDOR) & motor_mask) {
+        // If the motor has been started, there's no need to wait for it again
+        floppy_dor_write(motor_mask | 0x0c | floppyid);
+    } else {
+        floppy_dor_write(motor_mask | 0x0c | floppyid);
+
+        msleep(FLOPPY_STARTUP_TIME * 125);
+    }
+}
+
+static void
+floppy_turn_off_motor_delayed(void)
+{
+    // reset the disk motor timeout value of INT 08
+    SET_BDA(floppy_motor_counter, FLOPPY_MOTOR_TICKS);
+}
+
 // Activate a drive and send a command to it.
 static int
 floppy_drive_pio(u8 floppyid, int command, u8 *param)
@@ -324,11 +359,8 @@ floppy_drive_pio(u8 floppyid, int command, u8 *param)
             return ret;
     }
 
-    // reset the disk motor timeout value of INT 08
-    SET_BDA(floppy_motor_counter, FLOPPY_MOTOR_TICKS);
-
-    // Turn on motor of selected drive, DMA & int enabled, normal operation
-    floppy_dor_write((floppyid ? 0x20 : 0x10) | 0x0c | floppyid);
+    // Turn on motor of selected drive and wait for it to get up to speed
+    floppy_turn_on_motor(floppyid);
 
     // Send command.
     int ret = floppy_pio(command, param);
@@ -363,6 +395,15 @@ floppy_drive_recal(u8 floppyid)
     return DISK_RET_SUCCESS;
 }
 
+static int
+floppy_drive_specify(void)
+{
+    u8 param[2];
+    param[0] = FLOPPY_SPECIFY1;
+    param[1] = FLOPPY_SPECIFY2;
+    return floppy_pio(FC_SPECIFY, param);
+}
+
 static int
 floppy_drive_readid(u8 floppyid, u8 data_rate, u8 head)
 {
@@ -433,13 +474,23 @@ floppy_prep(struct drive_s *drive_gf, u8 cylinder)
         !(GET_BDA(floppy_media_state[floppyid]) & FMS_MEDIA_DRIVE_ESTABLISHED)) {
         // Recalibrate drive.
         int ret = floppy_drive_recal(floppyid);
-        if (ret)
+        if (ret) {
+            floppy_turn_off_motor_delayed();
             return ret;
+        }
 
         // Sense media.
         ret = floppy_media_sense(drive_gf);
-        if (ret)
+        if (ret) {
+            floppy_turn_off_motor_delayed();
+            return ret;
+        }
+
+        ret = floppy_drive_specify();
+        if (ret) {
+            floppy_turn_off_motor_delayed();
             return ret;
+        }
     }
 
     // Seek to cylinder if needed.
@@ -449,8 +500,10 @@ floppy_prep(struct drive_s *drive_gf, u8 cylinder)
         param[0] = floppyid;
         param[1] = cylinder;
         int ret = floppy_drive_pio(floppyid, FC_SEEK, param);
-        if (ret)
+        if (ret) {
+            floppy_turn_off_motor_delayed();
             return ret;
+        }
         SET_BDA(floppy_track[floppyid], cylinder);
     }
 
@@ -489,9 +542,11 @@ floppy_dma_cmd(struct disk_op_s *op, int count, int command, u8 *param)
         dprintf(1, "floppy error: %02x %02x %02x %02x %02x %02x %02x\n"
                 , param[0], param[1], param[2], param[3]
                 , param[4], param[5], param[6]);
+        floppy_turn_off_motor_delayed();
         return DISK_RET_ECONTROLLER;
     }
 
+    floppy_turn_off_motor_delayed();
     return DISK_RET_SUCCESS;
 }
 
@@ -663,7 +718,7 @@ floppy_tick(void)
 
     // time to turn off drive(s)?
     u8 fcount = GET_BDA(floppy_motor_counter);
-    if (fcount) {
+    if (fcount && (fcount != FLOPPY_MOTOR_HOLD)) {
         fcount--;
         SET_BDA(floppy_motor_counter, fcount);
         if (fcount == 0)
-- 
2.14.3


_______________________________________________
SeaBIOS mailing list
SeaBIOS@seabios.org
https://mail.coreboot.org/mailman/listinfo/seabios
Re: [SeaBIOS] [PATCH] Fixes to the floppy driver, so it works on real (not emulated) hardware
Posted by Kevin O'Connor 6 years, 1 month ago
On Tue, Jan 30, 2018 at 05:41:47AM +0200, Nikolay Nikolov wrote:
> Several fixes to the floppy driver, so it works on a real floppy controller
> with a real floppy. Tested with Coreboot on a motherboard with an ITE IT8712
> Super I/O chip (and its built-in floppy controller) and a 1.44MB floppy.
> 
> Only one floppy is supported for now and only 1.44MB, but more formats will be
> added in the future.

Thanks.  See my comments below.

> --- a/src/hw/floppy.c
> +++ b/src/hw/floppy.c
> @@ -34,6 +34,11 @@
>  #define FLOPPY_GAPLEN 0x1B
>  #define FLOPPY_FORMAT_GAPLEN 0x6c
>  #define FLOPPY_PIO_TIMEOUT 1000
> +#define FLOPPY_IRQ_TIMEOUT 5000
> +#define FLOPPY_STARTUP_TIME 8
> +#define FLOPPY_MOTOR_HOLD 255 // Magic value, means motor must not be turned off
> +#define FLOPPY_SPECIFY1 0xAF  // step rate 12ms, head unload 240ms
> +#define FLOPPY_SPECIFY2 0x02  // head load time 4ms, DMA used

It would be good to also update diskette_param_table2 with these new
variables (as is done for the other variables).

>  
>  // New diskette parameter table adding 3 parameters from IBM
>  // Since no provisions are made for multiple drive types, most
> @@ -191,7 +196,8 @@ static void
>  floppy_disable_controller(void)
>  {
>      dprintf(2, "Floppy_disable_controller\n");
> -    floppy_dor_write(0x00);
> +    // Clear the reset bit (enter reset state) and clear 'enable IRQ and DMA'
> +    floppy_dor_write(GET_LOW(FloppyDOR) & ~0x0c);

Why do this?  What state needs to preserved in the DOR register on a
disable?

> @@ -199,8 +205,9 @@ floppy_wait_irq(void)
>  {
>      u8 frs = GET_BDA(floppy_recalibration_status);
>      SET_BDA(floppy_recalibration_status, frs & ~FRS_IRQ);
> +    u32 end = timer_calc(FLOPPY_IRQ_TIMEOUT);
>      for (;;) {
> -        if (!GET_BDA(floppy_motor_counter)) {
> +        if (timer_check(end)) {

The floppy_motor_counter is an external variable that some legacy
programs may inspect.  I'm not sure we can not update it here.  What's
the reason for changing the timeout to use timer_calc()?

>              warn_timeout();
>              floppy_disable_controller();
>              return DISK_RET_ETIMEOUT;
> @@ -226,6 +233,7 @@ floppy_wait_irq(void)
>  #define FC_READ        (0xe6 | (8<<8) | (7<<12) | FCF_WAITIRQ)
>  #define FC_WRITE       (0xc5 | (8<<8) | (7<<12) | FCF_WAITIRQ)
>  #define FC_FORMAT      (0x4d | (5<<8) | (7<<12) | FCF_WAITIRQ)
> +#define FC_SPECIFY     (0x03 | (2<<8) | (0<<12))
>  
>  // Send the specified command and it's parameters to the floppy controller.
>  static int
> @@ -302,9 +310,12 @@ static int
>  floppy_enable_controller(void)
>  {
>      dprintf(2, "Floppy_enable_controller\n");
> -    SET_BDA(floppy_motor_counter, FLOPPY_MOTOR_TICKS);
> -    floppy_dor_write(0x00);
> -    floppy_dor_write(0x0c);
> +    // Clear the reset bit (enter reset state), but set 'enable IRQ and DMA'
> +    floppy_dor_write((GET_LOW(FloppyDOR) & ~0x04) | 0x08);
> +    // Real hardware needs a 4 microsecond delay
> +    udelay(4);

Can this be a usleep()?

> +    // Set the reset bit (normal operation) and keep 'enable IRQ and DMA' on
> +    floppy_dor_write(GET_LOW(FloppyDOR) | 0x0c);
>      int ret = floppy_wait_irq();
>      if (ret)
>          return ret;
> @@ -313,6 +324,30 @@ floppy_enable_controller(void)
>      return floppy_pio(FC_CHECKIRQ, param);
>  }
>  
> +static void
> +floppy_turn_on_motor(u8 floppyid)
> +{
> +    // prevent the motor from being turned off
> +    SET_BDA(floppy_motor_counter, FLOPPY_MOTOR_HOLD);

I don't see a reference to 255 being a special value in the bios docs
for floppy_motor_counter.  As above, this is an external variable and
I'm not sure we can change it to custom values.  Did you find a
reference to it in a specification?

> +
> +    u8 motor_mask = 0x10 << floppyid;
> +    if (GET_LOW(FloppyDOR) & motor_mask) {
> +        // If the motor has been started, there's no need to wait for it again
> +        floppy_dor_write(motor_mask | 0x0c | floppyid);
> +    } else {
> +        floppy_dor_write(motor_mask | 0x0c | floppyid);
> +
> +        msleep(FLOPPY_STARTUP_TIME * 125);
> +    }
> +}
> +
> +static void
> +floppy_turn_off_motor_delayed(void)
> +{
> +    // reset the disk motor timeout value of INT 08
> +    SET_BDA(floppy_motor_counter, FLOPPY_MOTOR_TICKS);
> +}
> +
>  // Activate a drive and send a command to it.
>  static int
>  floppy_drive_pio(u8 floppyid, int command, u8 *param)
> @@ -324,11 +359,8 @@ floppy_drive_pio(u8 floppyid, int command, u8 *param)
>              return ret;
>      }
>  
> -    // reset the disk motor timeout value of INT 08
> -    SET_BDA(floppy_motor_counter, FLOPPY_MOTOR_TICKS);
> -
> -    // Turn on motor of selected drive, DMA & int enabled, normal operation
> -    floppy_dor_write((floppyid ? 0x20 : 0x10) | 0x0c | floppyid);
> +    // Turn on motor of selected drive and wait for it to get up to speed
> +    floppy_turn_on_motor(floppyid);
>  
>      // Send command.
>      int ret = floppy_pio(command, param);
> @@ -363,6 +395,15 @@ floppy_drive_recal(u8 floppyid)
>      return DISK_RET_SUCCESS;
>  }
>  
> +static int
> +floppy_drive_specify(void)
> +{
> +    u8 param[2];
> +    param[0] = FLOPPY_SPECIFY1;
> +    param[1] = FLOPPY_SPECIFY2;
> +    return floppy_pio(FC_SPECIFY, param);
> +}
> +
>  static int
>  floppy_drive_readid(u8 floppyid, u8 data_rate, u8 head)
>  {
> @@ -433,13 +474,23 @@ floppy_prep(struct drive_s *drive_gf, u8 cylinder)
>          !(GET_BDA(floppy_media_state[floppyid]) & FMS_MEDIA_DRIVE_ESTABLISHED)) {
>          // Recalibrate drive.
>          int ret = floppy_drive_recal(floppyid);
> -        if (ret)
> +        if (ret) {
> +            floppy_turn_off_motor_delayed();
>              return ret;
> +        }
>  
>          // Sense media.
>          ret = floppy_media_sense(drive_gf);
> -        if (ret)
> +        if (ret) {
> +            floppy_turn_off_motor_delayed();
> +            return ret;
> +        }
> +
> +        ret = floppy_drive_specify();
> +        if (ret) {
> +            floppy_turn_off_motor_delayed();
>              return ret;
> +        }
>      }
>  
>      // Seek to cylinder if needed.
> @@ -449,8 +500,10 @@ floppy_prep(struct drive_s *drive_gf, u8 cylinder)
>          param[0] = floppyid;
>          param[1] = cylinder;
>          int ret = floppy_drive_pio(floppyid, FC_SEEK, param);
> -        if (ret)
> +        if (ret) {
> +            floppy_turn_off_motor_delayed();
>              return ret;
> +        }
>          SET_BDA(floppy_track[floppyid], cylinder);
>      }
>  
> @@ -489,9 +542,11 @@ floppy_dma_cmd(struct disk_op_s *op, int count, int command, u8 *param)
>          dprintf(1, "floppy error: %02x %02x %02x %02x %02x %02x %02x\n"
>                  , param[0], param[1], param[2], param[3]
>                  , param[4], param[5], param[6]);
> +        floppy_turn_off_motor_delayed();
>          return DISK_RET_ECONTROLLER;
>      }
>  
> +    floppy_turn_off_motor_delayed();
>      return DISK_RET_SUCCESS;
>  }
>  
> @@ -663,7 +718,7 @@ floppy_tick(void)
>  
>      // time to turn off drive(s)?
>      u8 fcount = GET_BDA(floppy_motor_counter);
> -    if (fcount) {
> +    if (fcount && (fcount != FLOPPY_MOTOR_HOLD)) {
>          fcount--;
>          SET_BDA(floppy_motor_counter, fcount);
>          if (fcount == 0)


-Kevin

_______________________________________________
SeaBIOS mailing list
SeaBIOS@seabios.org
https://mail.coreboot.org/mailman/listinfo/seabios
Re: [SeaBIOS] [PATCH] Fixes to the floppy driver, so it works on real (not emulated) hardware
Posted by Nikolay Nikolov 6 years, 1 month ago
On Tue, 2018-01-30 at 22:23 -0500, Kevin O'Connor wrote:
> On Tue, Jan 30, 2018 at 05:41:47AM +0200, Nikolay Nikolov wrote:
> > Several fixes to the floppy driver, so it works on a real floppy
> > controller
> > with a real floppy. Tested with Coreboot on a motherboard with an
> > ITE IT8712
> > Super I/O chip (and its built-in floppy controller) and a 1.44MB
> > floppy.
> > 
> > Only one floppy is supported for now and only 1.44MB, but more
> > formats will be
> > added in the future.
> 
> Thanks.  See my comments below.
> 
> > --- a/src/hw/floppy.c
> > +++ b/src/hw/floppy.c
> > @@ -34,6 +34,11 @@
> >  #define FLOPPY_GAPLEN 0x1B
> >  #define FLOPPY_FORMAT_GAPLEN 0x6c
> >  #define FLOPPY_PIO_TIMEOUT 1000
> > +#define FLOPPY_IRQ_TIMEOUT 5000
> > +#define FLOPPY_STARTUP_TIME 8
> > +#define FLOPPY_MOTOR_HOLD 255 // Magic value, means motor must not
> > be turned off
> > +#define FLOPPY_SPECIFY1 0xAF  // step rate 12ms, head unload 240ms
> > +#define FLOPPY_SPECIFY2 0x02  // head load time 4ms, DMA used
> 
> It would be good to also update diskette_param_table2 with these new
> variables (as is done for the other variables).

Ok, will do that.

> 
> >  
> >  // New diskette parameter table adding 3 parameters from IBM
> >  // Since no provisions are made for multiple drive types, most
> > @@ -191,7 +196,8 @@ static void
> >  floppy_disable_controller(void)
> >  {
> >      dprintf(2, "Floppy_disable_controller\n");
> > -    floppy_dor_write(0x00);
> > +    // Clear the reset bit (enter reset state) and clear 'enable
> > IRQ and DMA'
> > +    floppy_dor_write(GET_LOW(FloppyDOR) & ~0x0c);
> 
> Why do this?  What state needs to preserved in the DOR register on a
> disable?

The motor state, at least. I also try to keep the currently selected
drive. Why? Because, floppy_disable_controller is called in case of
controller init, which is called in case of any error (DOS for example,
retries the read operation several times, reinitializing the disk
controller between each retry). In this case, we want to keep the
floppy motor spinning smoothly (so it keeps a constant speed), instead
of jittering it off and on quickly. As for the currently selected
drive, I keep it just in case. I'm not sure it's necessary. I keep it,
because, if e.g. DOS encounters an error reading drive B:, it will
issue a controller reset command and then retry the read from the same
drive. In that case, we won't change the currently selected drive and
this might help, in case there's some sort of time needed to change the
selected drive. For example, see "note2" in the wiki article, in the
section that describes the DIR register:

https://wiki.osdev.org/Floppy_Disk_Controller#DIR_register.2C_Disk_Chan
ge_bit

"Note2: You must turn on the drive motor bit before you access the DIR
register for a selected drive (you do not have to wait for the motor to
spin up, though). It may also be necessary to read the register five
times (discard the first 4 values) when changing the selected drive --
because "selecting" sometimes takes a little time."

So, changing the selected drive frequently might lead to such problems.
Note that I still haven't tested SeaBIOS with two floppies on a real
hardware, so I don't know if it works at all. I do have two floppies on
the motherboard I'm testing it on, but they are different formats (1st
is 3.5-inch 1.44MB, second is 5.25-inch 1.2MB), so before testing two
floppies, I need to add 1.2MB floppy support first, but I'm planning
all these things in subsequent patches. For now, I want to handle the
most likely scenario people are going to encounter - single 1.44MB
floppy.

So, if you exclude the motor state bits and the drive select bits, the
only two bits are the ones that I reset - one is the actual reset bit
(the only bit, that actually matters), second is the IRQ enable bit.
IMHO, it shouldn't really matter whether we disable the IRQ enable bit
or not - if the reset bit is cleared, the controller isn't supposed to
generate IRQs anyway. However, it doesn't hurt to disable it as well.

> 
> > @@ -199,8 +205,9 @@ floppy_wait_irq(void)
> >  {
> >      u8 frs = GET_BDA(floppy_recalibration_status);
> >      SET_BDA(floppy_recalibration_status, frs & ~FRS_IRQ);
> > +    u32 end = timer_calc(FLOPPY_IRQ_TIMEOUT);
> >      for (;;) {
> > -        if (!GET_BDA(floppy_motor_counter)) {
> > +        if (timer_check(end)) {
> 
> The floppy_motor_counter is an external variable that some legacy
> programs may inspect.  I'm not sure we can not update it
> here.  What's
> the reason for changing the timeout to use timer_calc()?

Previously, it didn't behave well on real hardware. It set the timer to
the FLOPPY_MOTOR_TICKS value in the beginning of the disk operation,
which turned off the motor way too early, before the operation can
complete. The value in FLOPPY_MOTOR_TICKS must be set _after_ the
operation, so it keeps the motor spinning for 2 seconds _after_ the
operation, so in case, there's a new disk operation that follows soon,
it keeps the motor spinning and avoids having to wait the spin-up time 
(FLOPPY_STARTUP_TIME) again.

However, since you mentioned it, I decided to do some actual tests on
computers with different BIOSes and inspect the value. I wrote a TSR,
which hooks the IRQ 0 timer interrupt and displays it in hex in the
upper left part of the screen. Then I watched what happens during and
after accessing the floppy and the results are not exactly what my
patch does. But, unfortunately the behaviour was different on the two
computers that I tested:

1) IBM PS/2 Model 76:

The value gets decremented all the time, even when there isn't any
floppy operation. So, when it reaches 0, the motors are turned off, but
the value wraps back to 0xFF and continues counting down back to 0,
where the motors are again turned off (even if they were already off),
ad infinitum.

In the beginning of an actual floppy operation, it is set to 0xFF, so
it doesn't turn off the motor any time soon. It still counts down,
however the BIOS doesn't seem to use it as a timeout.

After the operation, the value is changed to the FLOPPY_MOTOR_TICKS
constant, so it turns the motor off 2 seconds after the operation.

2) AWARD BIOS, Gigabyte GA-K8NF-9 rev 1.0 (the motherboard I'm porting
Coreboot+SeaBIOS to)

Same as the IBM PS/2 Model 76, except, when the value reaches 0, it
stays at 0.

The only difference with my patch is, my patch keeps the value at 0xFF,
during the operation, while the AWARD BIOS keeps counting down, even
though it still doesn't use it as timeout.

---

Honestly, I don't know which behaviour is more correct. I kinda like
AWARD's behaviour better, because it doesn't periodically poke into the
floppy controller's DOR register, when there's no floppy operation.

On the other hand, IBM could be more "IBM compatible" :)

I can also test Phoenix and AMI BIOS under AMD SimNow (not on real
hardware), since I don't have any computer with a floppy and any of
these BIOSes.

I also have other vintage IBMs with floppies, but I suspect they're
going to behave the same way as model 76.

> 
> >              warn_timeout();
> >              floppy_disable_controller();
> >              return DISK_RET_ETIMEOUT;
> > @@ -226,6 +233,7 @@ floppy_wait_irq(void)
> >  #define FC_READ        (0xe6 | (8<<8) | (7<<12) | FCF_WAITIRQ)
> >  #define FC_WRITE       (0xc5 | (8<<8) | (7<<12) | FCF_WAITIRQ)
> >  #define FC_FORMAT      (0x4d | (5<<8) | (7<<12) | FCF_WAITIRQ)
> > +#define FC_SPECIFY     (0x03 | (2<<8) | (0<<12))
> >  
> >  // Send the specified command and it's parameters to the floppy
> > controller.
> >  static int
> > @@ -302,9 +310,12 @@ static int
> >  floppy_enable_controller(void)
> >  {
> >      dprintf(2, "Floppy_enable_controller\n");
> > -    SET_BDA(floppy_motor_counter, FLOPPY_MOTOR_TICKS);
> > -    floppy_dor_write(0x00);
> > -    floppy_dor_write(0x0c);
> > +    // Clear the reset bit (enter reset state), but set 'enable
> > IRQ and DMA'
> > +    floppy_dor_write((GET_LOW(FloppyDOR) & ~0x04) | 0x08);
> > +    // Real hardware needs a 4 microsecond delay
> > +    udelay(4);
> 
> Can this be a usleep()?
> 
> > +    // Set the reset bit (normal operation) and keep 'enable IRQ
> > and DMA' on
> > +    floppy_dor_write(GET_LOW(FloppyDOR) | 0x0c);
> >      int ret = floppy_wait_irq();
> >      if (ret)
> >          return ret;
> > @@ -313,6 +324,30 @@ floppy_enable_controller(void)
> >      return floppy_pio(FC_CHECKIRQ, param);
> >  }
> >  
> > +static void
> > +floppy_turn_on_motor(u8 floppyid)
> > +{
> > +    // prevent the motor from being turned off
> > +    SET_BDA(floppy_motor_counter, FLOPPY_MOTOR_HOLD);
> 
> I don't see a reference to 255 being a special value in the bios docs
> for floppy_motor_counter.  As above, this is an external variable and
> I'm not sure we can change it to custom values.  Did you find a
> reference to it in a specification?
> 
> > +
> > +    u8 motor_mask = 0x10 << floppyid;
> > +    if (GET_LOW(FloppyDOR) & motor_mask) {
> > +        // If the motor has been started, there's no need to wait
> > for it again
> > +        floppy_dor_write(motor_mask | 0x0c | floppyid);
> > +    } else {
> > +        floppy_dor_write(motor_mask | 0x0c | floppyid);
> > +
> > +        msleep(FLOPPY_STARTUP_TIME * 125);
> > +    }
> > +}
> > +
> > +static void
> > +floppy_turn_off_motor_delayed(void)
> > +{
> > +    // reset the disk motor timeout value of INT 08
> > +    SET_BDA(floppy_motor_counter, FLOPPY_MOTOR_TICKS);
> > +}
> > +
> >  // Activate a drive and send a command to it.
> >  static int
> >  floppy_drive_pio(u8 floppyid, int command, u8 *param)
> > @@ -324,11 +359,8 @@ floppy_drive_pio(u8 floppyid, int command, u8
> > *param)
> >              return ret;
> >      }
> >  
> > -    // reset the disk motor timeout value of INT 08
> > -    SET_BDA(floppy_motor_counter, FLOPPY_MOTOR_TICKS);
> > -
> > -    // Turn on motor of selected drive, DMA & int enabled, normal
> > operation
> > -    floppy_dor_write((floppyid ? 0x20 : 0x10) | 0x0c | floppyid);
> > +    // Turn on motor of selected drive and wait for it to get up
> > to speed
> > +    floppy_turn_on_motor(floppyid);
> >  
> >      // Send command.
> >      int ret = floppy_pio(command, param);
> > @@ -363,6 +395,15 @@ floppy_drive_recal(u8 floppyid)
> >      return DISK_RET_SUCCESS;
> >  }
> >  
> > +static int
> > +floppy_drive_specify(void)
> > +{
> > +    u8 param[2];
> > +    param[0] = FLOPPY_SPECIFY1;
> > +    param[1] = FLOPPY_SPECIFY2;
> > +    return floppy_pio(FC_SPECIFY, param);
> > +}
> > +
> >  static int
> >  floppy_drive_readid(u8 floppyid, u8 data_rate, u8 head)
> >  {
> > @@ -433,13 +474,23 @@ floppy_prep(struct drive_s *drive_gf, u8
> > cylinder)
> >          !(GET_BDA(floppy_media_state[floppyid]) &
> > FMS_MEDIA_DRIVE_ESTABLISHED)) {
> >          // Recalibrate drive.
> >          int ret = floppy_drive_recal(floppyid);
> > -        if (ret)
> > +        if (ret) {
> > +            floppy_turn_off_motor_delayed();
> >              return ret;
> > +        }
> >  
> >          // Sense media.
> >          ret = floppy_media_sense(drive_gf);
> > -        if (ret)
> > +        if (ret) {
> > +            floppy_turn_off_motor_delayed();
> > +            return ret;
> > +        }
> > +
> > +        ret = floppy_drive_specify();
> > +        if (ret) {
> > +            floppy_turn_off_motor_delayed();
> >              return ret;
> > +        }
> >      }
> >  
> >      // Seek to cylinder if needed.
> > @@ -449,8 +500,10 @@ floppy_prep(struct drive_s *drive_gf, u8
> > cylinder)
> >          param[0] = floppyid;
> >          param[1] = cylinder;
> >          int ret = floppy_drive_pio(floppyid, FC_SEEK, param);
> > -        if (ret)
> > +        if (ret) {
> > +            floppy_turn_off_motor_delayed();
> >              return ret;
> > +        }
> >          SET_BDA(floppy_track[floppyid], cylinder);
> >      }
> >  
> > @@ -489,9 +542,11 @@ floppy_dma_cmd(struct disk_op_s *op, int
> > count, int command, u8 *param)
> >          dprintf(1, "floppy error: %02x %02x %02x %02x %02x %02x
> > %02x\n"
> >                  , param[0], param[1], param[2], param[3]
> >                  , param[4], param[5], param[6]);
> > +        floppy_turn_off_motor_delayed();
> >          return DISK_RET_ECONTROLLER;
> >      }
> >  
> > +    floppy_turn_off_motor_delayed();
> >      return DISK_RET_SUCCESS;
> >  }
> >  
> > @@ -663,7 +718,7 @@ floppy_tick(void)
> >  
> >      // time to turn off drive(s)?
> >      u8 fcount = GET_BDA(floppy_motor_counter);
> > -    if (fcount) {
> > +    if (fcount && (fcount != FLOPPY_MOTOR_HOLD)) {
> >          fcount--;
> >          SET_BDA(floppy_motor_counter, fcount);
> >          if (fcount == 0)
> 
> 
> -Kevin

Nikolay

_______________________________________________
SeaBIOS mailing list
SeaBIOS@seabios.org
https://mail.coreboot.org/mailman/listinfo/seabios
Re: [SeaBIOS] [PATCH] Fixes to the floppy driver, so it works on real (not emulated) hardware
Posted by Nikolay Nikolov 6 years, 1 month ago
On Sat, 2018-02-03 at 00:09 +0200, Nikolay Nikolov wrote:
> On Tue, 2018-01-30 at 22:23 -0500, Kevin O'Connor wrote:
> > 
> > > @@ -199,8 +205,9 @@ floppy_wait_irq(void)
> > >  {
> > >      u8 frs = GET_BDA(floppy_recalibration_status);
> > >      SET_BDA(floppy_recalibration_status, frs & ~FRS_IRQ);
> > > +    u32 end = timer_calc(FLOPPY_IRQ_TIMEOUT);
> > >      for (;;) {
> > > -        if (!GET_BDA(floppy_motor_counter)) {
> > > +        if (timer_check(end)) {
> > 
> > The floppy_motor_counter is an external variable that some legacy
> > programs may inspect.  I'm not sure we can not update it
> > here.  What's
> > the reason for changing the timeout to use timer_calc()?
> 
> Previously, it didn't behave well on real hardware. It set the timer
> to
> the FLOPPY_MOTOR_TICKS value in the beginning of the disk operation,
> which turned off the motor way too early, before the operation can
> complete. The value in FLOPPY_MOTOR_TICKS must be set _after_ the
> operation, so it keeps the motor spinning for 2 seconds _after_ the
> operation, so in case, there's a new disk operation that follows
> soon,
> it keeps the motor spinning and avoids having to wait the spin-up
> time 
> (FLOPPY_STARTUP_TIME) again.
> 
> However, since you mentioned it, I decided to do some actual tests on
> computers with different BIOSes and inspect the value. I wrote a TSR,
> which hooks the IRQ 0 timer interrupt and displays it in hex in the
> upper left part of the screen. Then I watched what happens during and
> after accessing the floppy and the results are not exactly what my
> patch does. But, unfortunately the behaviour was different on the two
> computers that I tested:
> 
> 1) IBM PS/2 Model 76:
> 
> The value gets decremented all the time, even when there isn't any
> floppy operation. So, when it reaches 0, the motors are turned off,
> but
> the value wraps back to 0xFF and continues counting down back to 0,
> where the motors are again turned off (even if they were already
> off),
> ad infinitum.
> 
> In the beginning of an actual floppy operation, it is set to 0xFF, so
> it doesn't turn off the motor any time soon. It still counts down,
> however the BIOS doesn't seem to use it as a timeout.
> 
> After the operation, the value is changed to the FLOPPY_MOTOR_TICKS
> constant, so it turns the motor off 2 seconds after the operation.
> 
> 2) AWARD BIOS, Gigabyte GA-K8NF-9 rev 1.0 (the motherboard I'm
> porting
> Coreboot+SeaBIOS to)
> 
> Same as the IBM PS/2 Model 76, except, when the value reaches 0, it
> stays at 0.
> 
> The only difference with my patch is, my patch keeps the value at
> 0xFF,
> during the operation, while the AWARD BIOS keeps counting down, even
> though it still doesn't use it as timeout.
> 
> ---
> 
> Honestly, I don't know which behaviour is more correct. I kinda like
> AWARD's behaviour better, because it doesn't periodically poke into
> the
> floppy controller's DOR register, when there's no floppy operation.
> 
> On the other hand, IBM could be more "IBM compatible" :)
> 
> I can also test Phoenix and AMI BIOS under AMD SimNow (not on real
> hardware), since I don't have any computer with a floppy and any of
> these BIOSes.
> 
> I also have other vintage IBMs with floppies, but I suspect they're
> going to behave the same way as model 76.

Ok, tested "Phoenix ServerBIOS" and "AMI BIOS" and they behave like
AWARD. Also tested IBM 5150 (the first IBM PC), it behaves like the IBM
PS/2 Model 76. I guess, that's how early IBMs used to behave back then.
I guess, nowadays we'd better follow AWARD, Phoenix and AMI BIOS?

Nikolay

_______________________________________________
SeaBIOS mailing list
SeaBIOS@seabios.org
https://mail.coreboot.org/mailman/listinfo/seabios
Re: [SeaBIOS] [PATCH] Fixes to the floppy driver, so it works on real (not emulated) hardware
Posted by Nikolay Nikolov 6 years, 1 month ago
Sorry, forgot to reply to one of the small points in my other mail.


On 01/31/2018 05:23 AM, Kevin O'Connor wrote:
>
>>   // Send the specified command and it's parameters to the floppy controller.
>>   static int
>> @@ -302,9 +310,12 @@ static int
>>   floppy_enable_controller(void)
>>   {
>>       dprintf(2, "Floppy_enable_controller\n");
>> -    SET_BDA(floppy_motor_counter, FLOPPY_MOTOR_TICKS);
>> -    floppy_dor_write(0x00);
>> -    floppy_dor_write(0x0c);
>> +    // Clear the reset bit (enter reset state), but set 'enable IRQ and DMA'
>> +    floppy_dor_write((GET_LOW(FloppyDOR) & ~0x04) | 0x08);
>> +    // Real hardware needs a 4 microsecond delay
>> +    udelay(4);
> Can this be a usleep()?
Yes, it shouldn't be a problem.

Nikolay

_______________________________________________
SeaBIOS mailing list
SeaBIOS@seabios.org
https://mail.coreboot.org/mailman/listinfo/seabios