1 /** @file
2   Internal floppy disk controller programming functions for the floppy driver.
3 
4 Copyright (c) 2006 - 2018, Intel Corporation. All rights reserved.<BR>
5 SPDX-License-Identifier: BSD-2-Clause-Patent
6 
7 **/
8 
9 #include "IsaFloppy.h"
10 
11 /**
12   Detect whether a floppy drive is present or not.
13 
14   @param[in] FdcDev  A pointer to the FDC_BLK_IO_DEV
15 
16   @retval EFI_SUCCESS    The floppy disk drive is present
17   @retval EFI_NOT_FOUND  The floppy disk drive is not present
18 **/
19 EFI_STATUS
DiscoverFddDevice(IN FDC_BLK_IO_DEV * FdcDev)20 DiscoverFddDevice (
21   IN FDC_BLK_IO_DEV  *FdcDev
22   )
23 {
24   EFI_STATUS  Status;
25 
26   FdcDev->BlkIo.Media = &FdcDev->BlkMedia;
27 
28   Status = FddIdentify (FdcDev);
29   if (EFI_ERROR (Status)) {
30     return EFI_NOT_FOUND;
31   }
32 
33   FdcDev->BlkIo.Reset               = FdcReset;
34   FdcDev->BlkIo.FlushBlocks         = FddFlushBlocks;
35   FdcDev->BlkIo.ReadBlocks          = FddReadBlocks;
36   FdcDev->BlkIo.WriteBlocks         = FddWriteBlocks;
37   FdcDev->BlkMedia.LogicalPartition = FALSE;
38   FdcDev->BlkMedia.WriteCaching     = FALSE;
39 
40   return EFI_SUCCESS;
41 }
42 
43 /**
44   Do recalibrate and check if the drive is present or not
45   and set the media parameters if the driver is present.
46 
47   @param[in] FdcDev  A pointer to the FDC_BLK_IO_DEV
48 
49   @retval EFI_SUCCESS       The floppy disk drive is present
50   @retval EFI_DEVICE_ERROR  The floppy disk drive is not present
51 **/
52 EFI_STATUS
FddIdentify(IN FDC_BLK_IO_DEV * FdcDev)53 FddIdentify (
54   IN FDC_BLK_IO_DEV  *FdcDev
55   )
56 {
57   EFI_STATUS  Status;
58 
59   //
60   // Set Floppy Disk Controller's motor on
61   //
62   Status = MotorOn (FdcDev);
63   if (EFI_ERROR (Status)) {
64     return EFI_DEVICE_ERROR;
65   }
66 
67   Status = Recalibrate (FdcDev);
68 
69   if (EFI_ERROR (Status)) {
70     MotorOff (FdcDev);
71     FdcDev->ControllerState->NeedRecalibrate = TRUE;
72     return EFI_DEVICE_ERROR;
73   }
74   //
75   // Set Media Parameter
76   //
77   FdcDev->BlkIo.Media->RemovableMedia = TRUE;
78   FdcDev->BlkIo.Media->MediaPresent   = TRUE;
79   FdcDev->BlkIo.Media->MediaId = 0;
80 
81   //
82   // Check Media
83   //
84   Status = DisketChanged (FdcDev);
85 
86   if (Status == EFI_NO_MEDIA) {
87     FdcDev->BlkIo.Media->MediaPresent = FALSE;
88   } else if ((Status != EFI_MEDIA_CHANGED) &&
89              (Status != EFI_SUCCESS)) {
90     MotorOff (FdcDev);
91     return Status;
92   }
93 
94   //
95   // Check Disk Write Protected
96   //
97   Status = SenseDrvStatus (FdcDev, 0);
98 
99   if (Status == EFI_WRITE_PROTECTED) {
100     FdcDev->BlkIo.Media->ReadOnly = TRUE;
101   } else if (Status == EFI_SUCCESS) {
102     FdcDev->BlkIo.Media->ReadOnly = FALSE;
103   } else {
104     return EFI_DEVICE_ERROR;
105   }
106 
107   MotorOff (FdcDev);
108 
109   //
110   // Set Media Default Type
111   //
112   FdcDev->BlkIo.Media->BlockSize  = DISK_1440K_BYTEPERSECTOR;
113   FdcDev->BlkIo.Media->LastBlock  = DISK_1440K_EOT * 2 * (DISK_1440K_MAXTRACKNUM + 1) - 1;
114 
115   return EFI_SUCCESS;
116 }
117 
118 /**
119   Reset the Floppy Logic Drive.
120 
121   @param  FdcDev FDC_BLK_IO_DEV * : A pointer to the FDC_BLK_IO_DEV
122 
123   @retval EFI_SUCCESS:    The Floppy Logic Drive is reset
124   @retval EFI_DEVICE_ERROR: The Floppy Logic Drive is not functioning correctly and
125                       can not be reset
126 
127 **/
128 EFI_STATUS
FddReset(IN FDC_BLK_IO_DEV * FdcDev)129 FddReset (
130   IN FDC_BLK_IO_DEV  *FdcDev
131   )
132 {
133   UINT8 Data;
134   UINT8 StatusRegister0;
135   UINT8 PresentCylinderNumber;
136   UINTN Index;
137 
138   //
139   // Report reset progress code
140   //
141   REPORT_STATUS_CODE_WITH_DEVICE_PATH (
142     EFI_PROGRESS_CODE,
143     EFI_PERIPHERAL_REMOVABLE_MEDIA | EFI_P_PC_RESET,
144     FdcDev->DevicePath
145     );
146 
147   //
148   // Reset specified Floppy Logic Drive according to FdcDev -> Disk
149   // Set Digital Output Register(DOR) to do reset work
150   //   bit0 & bit1 of DOR : Drive Select
151   //   bit2 : Reset bit
152   //   bit3 : DMA and Int bit
153   // Reset : a "0" written to bit2 resets the FDC, this reset will remain
154   //         active until
155   //         a "1" is written to this bit.
156   // Reset step 1:
157   //         use bit0 & bit1 to  select the logic drive
158   //         write "0" to bit2
159   //
160   Data = 0x0;
161   Data = (UINT8) (Data | (SELECT_DRV & FdcDev->Disk));
162   FdcWritePort (FdcDev, FDC_REGISTER_DOR, Data);
163 
164   //
165   // wait some time,at least 120us
166   //
167   MicroSecondDelay (500);
168 
169   //
170   // Reset step 2:
171   //   write "1" to bit2
172   //   write "1" to bit3 : enable DMA
173   //
174   Data |= 0x0C;
175   FdcWritePort (FdcDev, FDC_REGISTER_DOR, Data);
176 
177   //
178   // Experience value
179   //
180   MicroSecondDelay (2000);
181 
182   //
183   // wait specified floppy logic drive is not busy
184   //
185   if (EFI_ERROR (FddWaitForBSYClear (FdcDev, 1))) {
186     return EFI_DEVICE_ERROR;
187   }
188   //
189   // Set the Transfer Data Rate
190   //
191   FdcWritePort (FdcDev, FDC_REGISTER_CCR, 0x0);
192 
193   //
194   // Experience value
195   //
196   MicroSecondDelay (100);
197 
198   //
199   // Issue Sense interrupt command for each drive (total 4 drives)
200   //
201   for (Index = 0; Index < 4; Index++) {
202     if (EFI_ERROR (SenseIntStatus (FdcDev, &StatusRegister0, &PresentCylinderNumber))) {
203       return EFI_DEVICE_ERROR;
204     }
205   }
206   //
207   // issue Specify command
208   //
209   if (EFI_ERROR (Specify (FdcDev))) {
210     return EFI_DEVICE_ERROR;
211   }
212 
213   return EFI_SUCCESS;
214 }
215 
216 /**
217   Turn the floppy disk drive's motor on.
218   The drive's motor must be on before any command can be executed.
219 
220   @param[in] FdcDev  A pointer to the FDC_BLK_IO_DEV
221 
222   @retval  EFI_SUCCESS            The drive's motor was turned on successfully
223   @retval  EFI_DEVICE_ERROR       The drive is busy, so can not turn motor on
224 **/
225 EFI_STATUS
MotorOn(IN FDC_BLK_IO_DEV * FdcDev)226 MotorOn (
227   IN FDC_BLK_IO_DEV  *FdcDev
228   )
229 {
230   EFI_STATUS  Status;
231   UINT8       DorData;
232 
233   //
234   // Control of the floppy drive motors is a big pain. If motor is off, you have
235   // to turn it on first. But you can not leave the motor on all the time, since
236   // that would wear out the disk. On the other hand, if you turn the motor off
237   // after each operation, the system performance will be awful. The compromise
238   // used in this driver is to leave the motor on for 2 seconds after
239   // each operation. If a new operation is started in that interval(2s),
240   // the motor need not be turned on again. If no new operation is started,
241   // a timer goes off and the motor is turned off
242   //
243   //
244   // Cancel the timer
245   //
246   Status = gBS->SetTimer (FdcDev->Event, TimerCancel, 0);
247   ASSERT_EFI_ERROR (Status);
248 
249   //
250   // Get the motor status
251   //
252   DorData = FdcReadPort (FdcDev, FDC_REGISTER_DOR);
253 
254   if (((FdcDev->Disk == FdcDisk0) && ((DorData & 0x10) == 0x10)) ||
255       ((FdcDev->Disk == FdcDisk1) && ((DorData & 0x21) == 0x21))
256       ) {
257     return EFI_SUCCESS;
258   }
259   //
260   // The drive's motor is off, so need turn it on
261   // first look at command and drive are busy or not
262   //
263   if (EFI_ERROR (FddWaitForBSYClear (FdcDev, 1))) {
264     return EFI_DEVICE_ERROR;
265   }
266   //
267   // for drive A: 1CH, drive B: 2DH
268   //
269   DorData = 0x0C;
270   DorData = (UINT8) (DorData | (SELECT_DRV & FdcDev->Disk));
271   if (FdcDev->Disk == FdcDisk0) {
272     //
273     // drive A
274     //
275     DorData |= DRVA_MOTOR_ON;
276   } else {
277     //
278     // drive B
279     //
280     DorData |= DRVB_MOTOR_ON;
281   }
282 
283   FdcWritePort (FdcDev, FDC_REGISTER_DOR, DorData);
284 
285   //
286   // Experience value
287   //
288   MicroSecondDelay (4000);
289 
290   return EFI_SUCCESS;
291 }
292 
293 /**
294   Set a Timer and when Timer goes off, turn the motor off.
295 
296   @param[in] FdcDev  A pointer to the FDC_BLK_IO_DEV
297 
298   @retval  EFI_SUCCESS            Set the Timer successfully
299   @retval  EFI_INVALID_PARAMETER  Fail to Set the timer
300 **/
301 EFI_STATUS
MotorOff(IN FDC_BLK_IO_DEV * FdcDev)302 MotorOff (
303   IN FDC_BLK_IO_DEV  *FdcDev
304   )
305 {
306   //
307   // Set the timer : 2s
308   //
309   return gBS->SetTimer (FdcDev->Event, TimerRelative, 20000000);
310 }
311 
312 /**
313   Detect whether the disk in the drive is changed or not.
314 
315   @param[in] FdcDev  A pointer to FDC_BLK_IO_DEV
316 
317   @retval  EFI_SUCCESS        No disk media change
318   @retval  EFI_DEVICE_ERROR   Fail to do the recalibrate or seek operation
319   @retval  EFI_NO_MEDIA       No disk in the drive
320   @retval  EFI_MEDIA_CHANGED  There is a new disk in the drive
321 **/
322 EFI_STATUS
DisketChanged(IN FDC_BLK_IO_DEV * FdcDev)323 DisketChanged (
324   IN FDC_BLK_IO_DEV  *FdcDev
325   )
326 {
327   EFI_STATUS  Status;
328   UINT8       Data;
329 
330   //
331   // Check change line
332   //
333   Data = FdcReadPort (FdcDev, FDC_REGISTER_DIR);
334 
335   //
336   // Io delay
337   //
338   MicroSecondDelay (50);
339 
340   if ((Data & DIR_DCL) == 0x80) {
341     //
342     // disk change line is active
343     //
344     if (FdcDev->PresentCylinderNumber != 0) {
345       Status = Recalibrate (FdcDev);
346     } else {
347       Status = Seek (FdcDev, 0x30);
348     }
349 
350     if (EFI_ERROR (Status)) {
351       FdcDev->ControllerState->NeedRecalibrate = TRUE;
352       return EFI_DEVICE_ERROR;
353       //
354       // Fail to do the seek or recalibrate operation
355       //
356     }
357 
358     Data = FdcReadPort (FdcDev, FDC_REGISTER_DIR);
359 
360     //
361     // Io delay
362     //
363     MicroSecondDelay (50);
364 
365     if ((Data & DIR_DCL) == 0x80) {
366       return EFI_NO_MEDIA;
367     }
368 
369     return EFI_MEDIA_CHANGED;
370   }
371 
372   return EFI_SUCCESS;
373 }
374 
375 /**
376   Do the Specify command, this command sets DMA operation
377   and the initial values for each of the three internal
378   times: HUT, SRT and HLT.
379 
380   @param[in] FdcDev  Pointer to instance of FDC_BLK_IO_DEV
381 
382   @retval EFI_SUCCESS       Execute the Specify command successfully
383   @retval EFI_DEVICE_ERROR  Fail to execute the command
384 **/
385 EFI_STATUS
Specify(IN FDC_BLK_IO_DEV * FdcDev)386 Specify (
387   IN FDC_BLK_IO_DEV  *FdcDev
388   )
389 {
390   FDD_SPECIFY_CMD Command;
391   UINTN           Index;
392   UINT8           *CommandPointer;
393 
394   ZeroMem (&Command, sizeof (FDD_SPECIFY_CMD));
395   Command.CommandCode = SPECIFY_CMD;
396   //
397   // set SRT, HUT
398   //
399   Command.SrtHut = 0xdf;
400   //
401   // 0xdf;
402   //
403   // set HLT and DMA
404   //
405   Command.HltNd   = 0x02;
406 
407   CommandPointer  = (UINT8 *) (&Command);
408   for (Index = 0; Index < sizeof (FDD_SPECIFY_CMD); Index++) {
409     if (EFI_ERROR (DataOutByte (FdcDev, CommandPointer++))) {
410       return EFI_DEVICE_ERROR;
411     }
412   }
413 
414   return EFI_SUCCESS;
415 }
416 
417 /**
418   Set the head of floppy drive to track 0.
419 
420   @param  FdcDev FDC_BLK_IO_DEV *: A pointer to FDC_BLK_IO_DEV
421   @retval EFI_SUCCESS:    Execute the Recalibrate operation successfully
422   @retval EFI_DEVICE_ERROR: Fail to execute the Recalibrate operation
423 
424 **/
425 EFI_STATUS
Recalibrate(IN FDC_BLK_IO_DEV * FdcDev)426 Recalibrate (
427   IN FDC_BLK_IO_DEV  *FdcDev
428   )
429 {
430   FDD_COMMAND_PACKET2 Command;
431   UINTN               Index;
432   UINT8               StatusRegister0;
433   UINT8               PresentCylinderNumber;
434   UINT8               *CommandPointer;
435   UINT8               Count;
436 
437   Count = 2;
438 
439   while (Count > 0) {
440     ZeroMem (&Command, sizeof (FDD_COMMAND_PACKET2));
441     Command.CommandCode = RECALIBRATE_CMD;
442     //
443     // drive select
444     //
445     if (FdcDev->Disk == FdcDisk0) {
446       Command.DiskHeadSel = 0;
447       //
448       // 0
449       //
450     } else {
451       Command.DiskHeadSel = 1;
452       //
453       // 1
454       //
455     }
456 
457     CommandPointer = (UINT8 *) (&Command);
458     for (Index = 0; Index < sizeof (FDD_COMMAND_PACKET2); Index++) {
459       if (EFI_ERROR (DataOutByte (FdcDev, CommandPointer++))) {
460         return EFI_DEVICE_ERROR;
461       }
462     }
463     //
464     // Experience value
465     //
466     MicroSecondDelay (250000);
467     //
468     // need modify according to 1.44M or 2.88M
469     //
470     if (EFI_ERROR (SenseIntStatus (FdcDev, &StatusRegister0, &PresentCylinderNumber))) {
471       return EFI_DEVICE_ERROR;
472     }
473 
474     if ((StatusRegister0 & 0xf0) == 0x20 && PresentCylinderNumber == 0) {
475       FdcDev->PresentCylinderNumber             = 0;
476       FdcDev->ControllerState->NeedRecalibrate  = FALSE;
477       return EFI_SUCCESS;
478     } else {
479       Count--;
480       if (Count == 0) {
481         return EFI_DEVICE_ERROR;
482       }
483     }
484   }
485   //
486   // end while
487   //
488   return EFI_SUCCESS;
489 }
490 
491 /**
492   Set the head of floppy drive to the new cylinder.
493 
494   @param  FdcDev FDC_BLK_IO_DEV *: A pointer to FDC_BLK_IO_DEV
495   @param  Lba EFI_LBA     : The logic block address want to seek
496 
497   @retval  EFI_SUCCESS:    Execute the Seek operation successfully
498   @retval  EFI_DEVICE_ERROR: Fail to execute the Seek operation
499 
500 **/
501 EFI_STATUS
Seek(IN FDC_BLK_IO_DEV * FdcDev,IN EFI_LBA Lba)502 Seek (
503   IN FDC_BLK_IO_DEV  *FdcDev,
504   IN EFI_LBA         Lba
505   )
506 {
507   FDD_SEEK_CMD  Command;
508   UINT8         EndOfTrack;
509   UINT8         Head;
510   UINT8         Cylinder;
511   UINT8         StatusRegister0;
512   UINT8         *CommandPointer;
513   UINT8         PresentCylinderNumber;
514   UINTN         Index;
515   UINT8         DelayTime;
516 
517   if (FdcDev->ControllerState->NeedRecalibrate) {
518     if (EFI_ERROR (Recalibrate (FdcDev))) {
519       FdcDev->ControllerState->NeedRecalibrate = TRUE;
520       return EFI_DEVICE_ERROR;
521     }
522   }
523 
524   EndOfTrack = DISK_1440K_EOT;
525   //
526   // Calculate cylinder based on Lba and EOT
527   //
528   Cylinder = (UINT8) ((UINTN) Lba / EndOfTrack / 2);
529 
530   //
531   // if the destination cylinder is the present cylinder, unnecessary to do the
532   // seek operation
533   //
534   if (FdcDev->PresentCylinderNumber == Cylinder) {
535     return EFI_SUCCESS;
536   }
537   //
538   // Calculate the head : 0 or 1
539   //
540   Head = (UINT8) ((UINTN) Lba / EndOfTrack % 2);
541 
542   ZeroMem (&Command, sizeof (FDD_SEEK_CMD));
543   Command.CommandCode = SEEK_CMD;
544   if (FdcDev->Disk == FdcDisk0) {
545     Command.DiskHeadSel = 0;
546     //
547     // 0
548     //
549   } else {
550     Command.DiskHeadSel = 1;
551     //
552     // 1
553     //
554   }
555 
556   Command.DiskHeadSel = (UINT8) (Command.DiskHeadSel | (Head << 2));
557   Command.NewCylinder = Cylinder;
558 
559   CommandPointer      = (UINT8 *) (&Command);
560   for (Index = 0; Index < sizeof (FDD_SEEK_CMD); Index++) {
561     if (EFI_ERROR (DataOutByte (FdcDev, CommandPointer++))) {
562       return EFI_DEVICE_ERROR;
563     }
564   }
565   //
566   // Io delay
567   //
568   MicroSecondDelay (100);
569 
570   //
571   // Calculate waiting time
572   //
573   if (FdcDev->PresentCylinderNumber > Cylinder) {
574     DelayTime = (UINT8) (FdcDev->PresentCylinderNumber - Cylinder);
575   } else {
576     DelayTime = (UINT8) (Cylinder - FdcDev->PresentCylinderNumber);
577   }
578 
579   MicroSecondDelay ((DelayTime + 1) * 4000);
580 
581   if (EFI_ERROR (SenseIntStatus (FdcDev, &StatusRegister0, &PresentCylinderNumber))) {
582     return EFI_DEVICE_ERROR;
583   }
584 
585   if ((StatusRegister0 & 0xf0) == 0x20) {
586     FdcDev->PresentCylinderNumber = Command.NewCylinder;
587     return EFI_SUCCESS;
588   } else {
589     FdcDev->ControllerState->NeedRecalibrate = TRUE;
590     return EFI_DEVICE_ERROR;
591   }
592 }
593 
594 /**
595   Do the Sense Interrupt Status command, this command
596   resets the interrupt signal.
597 
598   @param  FdcDev FDC_BLK_IO_DEV *: A pointer to FDC_BLK_IO_DEV
599   @param  StatusRegister0 UINT8 *: Be used to save Status Register 0 read from FDC
600   @param  PresentCylinderNumber  UINT8 *: Be used to save present cylinder number
601                                     read from FDC
602 
603   @retval  EFI_SUCCESS:    Execute the Sense Interrupt Status command successfully
604   @retval  EFI_DEVICE_ERROR: Fail to execute the command
605 
606 **/
607 EFI_STATUS
SenseIntStatus(IN FDC_BLK_IO_DEV * FdcDev,IN OUT UINT8 * StatusRegister0,IN OUT UINT8 * PresentCylinderNumber)608 SenseIntStatus (
609   IN     FDC_BLK_IO_DEV  *FdcDev,
610   IN OUT UINT8           *StatusRegister0,
611   IN OUT UINT8           *PresentCylinderNumber
612   )
613 {
614   UINT8 Command;
615 
616   Command = SENSE_INT_STATUS_CMD;
617   if (EFI_ERROR (DataOutByte (FdcDev, &Command))) {
618     return EFI_DEVICE_ERROR;
619   }
620 
621   if (EFI_ERROR (DataInByte (FdcDev, StatusRegister0))) {
622     return EFI_DEVICE_ERROR;
623   }
624 
625   if (EFI_ERROR (DataInByte (FdcDev, PresentCylinderNumber))) {
626     return EFI_DEVICE_ERROR;
627   }
628 
629   return EFI_SUCCESS;
630 }
631 
632 /**
633   Do the Sense Drive Status command.
634 
635   @param  FdcDev FDC_BLK_IO_DEV *: A pointer to FDC_BLK_IO_DEV
636   @param  Lba EFI_LBA     : Logic block address
637 
638   @retval  EFI_SUCCESS:    Execute the Sense Drive Status command successfully
639   @retval  EFI_DEVICE_ERROR: Fail to execute the command
640   @retval  EFI_WRITE_PROTECTED:The disk is write protected
641 
642 **/
643 EFI_STATUS
SenseDrvStatus(IN FDC_BLK_IO_DEV * FdcDev,IN EFI_LBA Lba)644 SenseDrvStatus (
645   IN FDC_BLK_IO_DEV  *FdcDev,
646   IN EFI_LBA         Lba
647   )
648 {
649   FDD_COMMAND_PACKET2 Command;
650   UINT8               Head;
651   UINT8               EndOfTrack;
652   UINTN               Index;
653   UINT8               StatusRegister3;
654   UINT8               *CommandPointer;
655 
656   //
657   // Sense Drive Status command obtains drive status information,
658   // it has not execution phase and goes directly to the result phase from the
659   // command phase, Status Register 3 contains the drive status information
660   //
661   ZeroMem (&Command, sizeof (FDD_COMMAND_PACKET2));
662   Command.CommandCode = SENSE_DRV_STATUS_CMD;
663 
664   if (FdcDev->Disk == FdcDisk0) {
665     Command.DiskHeadSel = 0;
666   } else {
667     Command.DiskHeadSel = 1;
668   }
669 
670   EndOfTrack  = DISK_1440K_EOT;
671   Head        = (UINT8) ((UINTN) Lba / EndOfTrack % 2);
672   Command.DiskHeadSel = (UINT8) (Command.DiskHeadSel | (Head << 2));
673 
674   CommandPointer = (UINT8 *) (&Command);
675   for (Index = 0; Index < sizeof (FDD_COMMAND_PACKET2); Index++) {
676     if (EFI_ERROR (DataOutByte (FdcDev, CommandPointer++))) {
677       return EFI_DEVICE_ERROR;
678     }
679   }
680 
681   if (EFI_ERROR (DataInByte (FdcDev, &StatusRegister3))) {
682     return EFI_DEVICE_ERROR;
683   }
684   //
685   // Io delay
686   //
687   MicroSecondDelay (50);
688 
689   //
690   // Check Status Register 3 to get drive status information
691   //
692   return CheckStatus3 (StatusRegister3);
693 }
694 
695 /**
696   Update the disk media properties and if necessary reinstall Block I/O interface.
697 
698   @param  FdcDev FDC_BLK_IO_DEV *: A pointer to FDC_BLK_IO_DEV
699 
700   @retval  EFI_SUCCESS:    Do the operation successfully
701   @retval  EFI_DEVICE_ERROR: Fail to the operation
702 
703 **/
704 EFI_STATUS
DetectMedia(IN FDC_BLK_IO_DEV * FdcDev)705 DetectMedia (
706   IN FDC_BLK_IO_DEV  *FdcDev
707   )
708 {
709   EFI_STATUS  Status;
710   BOOLEAN     Reset;
711   BOOLEAN     ReadOnlyLastTime;
712   BOOLEAN     MediaPresentLastTime;
713 
714   Reset                = FALSE;
715   ReadOnlyLastTime     = FdcDev->BlkIo.Media->ReadOnly;
716   MediaPresentLastTime = FdcDev->BlkIo.Media->MediaPresent;
717 
718   //
719   // Check disk change
720   //
721   Status = DisketChanged (FdcDev);
722 
723   if (Status == EFI_MEDIA_CHANGED) {
724     FdcDev->BlkIo.Media->MediaId++;
725     FdcDev->BlkIo.Media->MediaPresent = TRUE;
726     Reset = TRUE;
727   } else if (Status == EFI_NO_MEDIA) {
728     FdcDev->BlkIo.Media->MediaPresent = FALSE;
729   } else if (Status != EFI_SUCCESS) {
730     MotorOff (FdcDev);
731     return Status;
732     //
733     // EFI_DEVICE_ERROR
734     //
735   }
736 
737   if (FdcDev->BlkIo.Media->MediaPresent) {
738     //
739     // Check disk write protected
740     //
741     Status = SenseDrvStatus (FdcDev, 0);
742     if (Status == EFI_WRITE_PROTECTED) {
743       FdcDev->BlkIo.Media->ReadOnly = TRUE;
744     } else {
745       FdcDev->BlkIo.Media->ReadOnly = FALSE;
746     }
747   }
748 
749   if (FdcDev->BlkIo.Media->MediaPresent && (ReadOnlyLastTime != FdcDev->BlkIo.Media->ReadOnly)) {
750     Reset = TRUE;
751   }
752 
753   if (MediaPresentLastTime != FdcDev->BlkIo.Media->MediaPresent) {
754     Reset = TRUE;
755   }
756 
757   if (Reset) {
758     Status = gBS->ReinstallProtocolInterface (
759                     FdcDev->Handle,
760                     &gEfiBlockIoProtocolGuid,
761                     &FdcDev->BlkIo,
762                     &FdcDev->BlkIo
763                     );
764 
765     if (EFI_ERROR (Status)) {
766       return Status;
767     }
768   }
769 
770   return EFI_SUCCESS;
771 }
772 
773 /**
774   Set the data rate and so on.
775 
776   @param  FdcDev  A pointer to FDC_BLK_IO_DEV
777 
778   @retval EFI_SUCCESS success to set the data rate
779 **/
780 EFI_STATUS
Setup(IN FDC_BLK_IO_DEV * FdcDev)781 Setup (
782   IN FDC_BLK_IO_DEV  *FdcDev
783   )
784 {
785   EFI_STATUS  Status;
786 
787   //
788   // Set data rate 500kbs
789   //
790   FdcWritePort (FdcDev, FDC_REGISTER_CCR, 0x0);
791 
792   //
793   // Io delay
794   //
795   MicroSecondDelay (50);
796 
797   Status = Specify (FdcDev);
798 
799   if (EFI_ERROR (Status)) {
800     return EFI_DEVICE_ERROR;
801   }
802 
803   return EFI_SUCCESS;
804 }
805 
806 /**
807   Read or Write a number of blocks in the same cylinder.
808 
809   @param  FdcDev      A pointer to FDC_BLK_IO_DEV
810   @param  HostAddress device address
811   @param  Lba         The starting logic block address to read from on the device
812   @param  NumberOfBlocks The number of block wanted to be read or write
813   @param  Read        Operation type: read or write
814 
815   @retval EFI_SUCCESS Success operate
816 
817 **/
818 EFI_STATUS
ReadWriteDataSector(IN FDC_BLK_IO_DEV * FdcDev,IN VOID * HostAddress,IN EFI_LBA Lba,IN UINTN NumberOfBlocks,IN BOOLEAN Read)819 ReadWriteDataSector (
820   IN  FDC_BLK_IO_DEV  *FdcDev,
821   IN  VOID            *HostAddress,
822   IN  EFI_LBA         Lba,
823   IN  UINTN           NumberOfBlocks,
824   IN  BOOLEAN         Read
825   )
826 {
827   EFI_STATUS                                    Status;
828   FDD_COMMAND_PACKET1                           Command;
829   FDD_RESULT_PACKET                             Result;
830   UINTN                                         Index;
831   UINTN                                         Times;
832   UINT8                                         *CommandPointer;
833 
834   EFI_PHYSICAL_ADDRESS                          DeviceAddress;
835   EFI_ISA_IO_PROTOCOL                           *IsaIo;
836   UINTN                                         NumberofBytes;
837   VOID                                          *Mapping;
838   EFI_ISA_IO_PROTOCOL_OPERATION                 Operation;
839   EFI_STATUS                                    Status1;
840   UINT8                                         Channel;
841   EFI_ISA_ACPI_RESOURCE                         *ResourceItem;
842   UINT32                                        Attribute;
843 
844   Status = Seek (FdcDev, Lba);
845   if (EFI_ERROR (Status)) {
846     return EFI_DEVICE_ERROR;
847   }
848   //
849   // Map Dma
850   //
851   IsaIo         = FdcDev->IsaIo;
852   NumberofBytes = NumberOfBlocks * 512;
853   if (Read == READ) {
854     Operation = EfiIsaIoOperationSlaveWrite;
855   } else {
856     Operation = EfiIsaIoOperationSlaveRead;
857   }
858 
859   ResourceItem  = IsaIo->ResourceList->ResourceItem;
860   Index         = 0;
861   while (ResourceItem[Index].Type != EfiIsaAcpiResourceEndOfList) {
862     if (ResourceItem[Index].Type == EfiIsaAcpiResourceDma) {
863       break;
864     }
865 
866     Index++;
867   }
868 
869   if (ResourceItem[Index].Type == EfiIsaAcpiResourceEndOfList) {
870     return EFI_DEVICE_ERROR;
871   }
872 
873   Channel   = (UINT8) IsaIo->ResourceList->ResourceItem[Index].StartRange;
874   Attribute = IsaIo->ResourceList->ResourceItem[Index].Attribute;
875 
876   Status1 = IsaIo->Map (
877                     IsaIo,
878                     Operation,
879                     Channel,
880                     Attribute,
881                     HostAddress,
882                     &NumberofBytes,
883                     &DeviceAddress,
884                     &Mapping
885                     );
886   if (EFI_ERROR (Status1)) {
887     return Status1;
888   }
889 
890   //
891   // Allocate Read or Write command packet
892   //
893   ZeroMem (&Command, sizeof (FDD_COMMAND_PACKET1));
894   if (Read == READ) {
895     Command.CommandCode = READ_DATA_CMD | CMD_MT | CMD_MFM | CMD_SK;
896   } else {
897     Command.CommandCode = WRITE_DATA_CMD | CMD_MT | CMD_MFM;
898   }
899 
900   FillPara (FdcDev, Lba, &Command);
901 
902   //
903   // Write command bytes to FDC
904   //
905   CommandPointer = (UINT8 *) (&Command);
906   for (Index = 0; Index < sizeof (FDD_COMMAND_PACKET1); Index++) {
907     if (EFI_ERROR (DataOutByte (FdcDev, CommandPointer++))) {
908       return EFI_DEVICE_ERROR;
909     }
910   }
911   //
912   // wait for some time
913   //
914   Times = (STALL_1_SECOND / 50) + 1;
915   do {
916     if ((FdcReadPort (FdcDev, FDC_REGISTER_MSR) & 0xc0) == 0xc0) {
917       break;
918     }
919 
920     MicroSecondDelay (50);
921     Times = Times - 1;
922   } while (Times > 0);
923 
924   if (Times == 0) {
925     return EFI_TIMEOUT;
926   }
927   //
928   // Read result bytes from FDC
929   //
930   CommandPointer = (UINT8 *) (&Result);
931   for (Index = 0; Index < sizeof (FDD_RESULT_PACKET); Index++) {
932     if (EFI_ERROR (DataInByte (FdcDev, CommandPointer++))) {
933       return EFI_DEVICE_ERROR;
934     }
935   }
936   //
937   // Flush before Unmap
938   //
939   if (Read == READ) {
940     Status1 = IsaIo->Flush (IsaIo);
941     if (EFI_ERROR (Status1)) {
942       return Status1;
943     }
944   }
945   //
946   // Unmap Dma
947   //
948   Status1 = IsaIo->Unmap (IsaIo, Mapping);
949   if (EFI_ERROR (Status1)) {
950     return Status1;
951   }
952 
953   return CheckResult (&Result, FdcDev);
954 }
955 
956 /**
957   Fill in FDD command's parameter.
958 
959   @param FdcDev   Pointer to instance of FDC_BLK_IO_DEV
960   @param Lba      The starting logic block address to read from on the device
961   @param Command  FDD command
962 
963 **/
964 VOID
FillPara(IN FDC_BLK_IO_DEV * FdcDev,IN EFI_LBA Lba,IN FDD_COMMAND_PACKET1 * Command)965 FillPara (
966   IN  FDC_BLK_IO_DEV       *FdcDev,
967   IN  EFI_LBA              Lba,
968   IN  FDD_COMMAND_PACKET1  *Command
969   )
970 {
971   UINT8 EndOfTrack;
972 
973   //
974   // Get EndOfTrack from the Para table
975   //
976   EndOfTrack = DISK_1440K_EOT;
977 
978   //
979   // Fill the command parameter
980   //
981   if (FdcDev->Disk == FdcDisk0) {
982     Command->DiskHeadSel = 0;
983   } else {
984     Command->DiskHeadSel = 1;
985   }
986 
987   Command->Cylinder = (UINT8) ((UINTN) Lba / EndOfTrack / 2);
988   Command->Head     = (UINT8) ((UINTN) Lba / EndOfTrack % 2);
989   Command->Sector   = (UINT8) ((UINT8) ((UINTN) Lba % EndOfTrack) + 1);
990   Command->DiskHeadSel = (UINT8) (Command->DiskHeadSel | (Command->Head << 2));
991   Command->Number     = DISK_1440K_NUMBER;
992   Command->EndOfTrack = DISK_1440K_EOT;
993   Command->GapLength  = DISK_1440K_GPL;
994   Command->DataLength = DISK_1440K_DTL;
995 }
996 
997 /**
998   Read result byte from Data Register of FDC.
999 
1000   @param FdcDev   Pointer to instance of FDC_BLK_IO_DEV
1001   @param Pointer  Buffer to store the byte read from FDC
1002 
1003   @retval EFI_SUCCESS       Read result byte from FDC successfully
1004   @retval EFI_DEVICE_ERROR  The FDC is not ready to be read
1005 
1006 **/
1007 EFI_STATUS
DataInByte(IN FDC_BLK_IO_DEV * FdcDev,OUT UINT8 * Pointer)1008 DataInByte (
1009   IN  FDC_BLK_IO_DEV  *FdcDev,
1010   OUT UINT8           *Pointer
1011   )
1012 {
1013   UINT8 Data;
1014 
1015   //
1016   // wait for 1ms and detect the FDC is ready to be read
1017   //
1018   if (EFI_ERROR (FddDRQReady (FdcDev, DATA_IN, 1))) {
1019     return EFI_DEVICE_ERROR;
1020     //
1021     // is not ready
1022     //
1023   }
1024 
1025   Data = FdcReadPort (FdcDev, FDC_REGISTER_DTR);
1026 
1027   //
1028   // Io delay
1029   //
1030   MicroSecondDelay (50);
1031 
1032   *Pointer = Data;
1033   return EFI_SUCCESS;
1034 }
1035 
1036 /**
1037   Write command byte to Data Register of FDC.
1038 
1039   @param FdcDev  Pointer to instance of FDC_BLK_IO_DEV
1040   @param Pointer Be used to save command byte written to FDC
1041 
1042   @retval  EFI_SUCCESS:    Write command byte to FDC successfully
1043   @retval  EFI_DEVICE_ERROR: The FDC is not ready to be written
1044 
1045 **/
1046 EFI_STATUS
DataOutByte(IN FDC_BLK_IO_DEV * FdcDev,IN UINT8 * Pointer)1047 DataOutByte (
1048   IN FDC_BLK_IO_DEV  *FdcDev,
1049   IN UINT8           *Pointer
1050   )
1051 {
1052   UINT8 Data;
1053 
1054   //
1055   // wait for 1ms and detect the FDC is ready to be written
1056   //
1057   if (EFI_ERROR (FddDRQReady (FdcDev, DATA_OUT, 1))) {
1058     //
1059     // Not ready
1060     //
1061     return EFI_DEVICE_ERROR;
1062   }
1063 
1064   Data = *Pointer;
1065 
1066   FdcWritePort (FdcDev, FDC_REGISTER_DTR, Data);
1067 
1068   //
1069   // Io delay
1070   //
1071   MicroSecondDelay (50);
1072 
1073   return EFI_SUCCESS;
1074 }
1075 
1076 /**
1077   Detect the specified floppy logic drive is busy or not within a period of time.
1078 
1079   @param FdcDev           Indicate it is drive A or drive B
1080   @param Timeout          The time period for waiting
1081 
1082   @retval EFI_SUCCESS:  The drive and command are not busy
1083   @retval EFI_TIMEOUT:  The drive or command is still busy after a period time that
1084                         set by Timeout
1085 
1086 **/
1087 EFI_STATUS
FddWaitForBSYClear(IN FDC_BLK_IO_DEV * FdcDev,IN UINTN Timeout)1088 FddWaitForBSYClear (
1089   IN FDC_BLK_IO_DEV  *FdcDev,
1090   IN UINTN           Timeout
1091   )
1092 {
1093   UINTN Delay;
1094   UINT8 StatusRegister;
1095   UINT8 Mask;
1096 
1097   //
1098   // How to determine drive and command are busy or not: by the bits of
1099   // Main Status Register
1100   // bit0: Drive 0 busy (drive A)
1101   // bit1: Drive 1 busy (drive B)
1102   // bit4: Command busy
1103   //
1104   //
1105   // set mask: for drive A set bit0 & bit4; for drive B set bit1 & bit4
1106   //
1107   Mask  = (UINT8) ((FdcDev->Disk == FdcDisk0 ? MSR_DAB : MSR_DBB) | MSR_CB);
1108 
1109   Delay = ((Timeout * STALL_1_MSECOND) / 50) + 1;
1110   do {
1111     StatusRegister = FdcReadPort (FdcDev, FDC_REGISTER_MSR);
1112     if ((StatusRegister & Mask) == 0x00) {
1113       break;
1114       //
1115       // not busy
1116       //
1117     }
1118 
1119     MicroSecondDelay (50);
1120     Delay = Delay - 1;
1121   } while (Delay > 0);
1122 
1123   if (Delay == 0) {
1124     return EFI_TIMEOUT;
1125   }
1126 
1127   return EFI_SUCCESS;
1128 }
1129 
1130 /**
1131   Determine whether FDC is ready to write or read.
1132 
1133   @param  FdcDev Pointer to instance of FDC_BLK_IO_DEV
1134   @param  Dio BOOLEAN:      Indicate the FDC is waiting to write or read
1135   @param  Timeout           The time period for waiting
1136 
1137   @retval EFI_SUCCESS:  FDC is ready to write or read
1138   @retval EFI_NOT_READY:  FDC is not ready within the specified time period
1139 
1140 **/
1141 EFI_STATUS
FddDRQReady(IN FDC_BLK_IO_DEV * FdcDev,IN BOOLEAN Dio,IN UINTN Timeout)1142 FddDRQReady (
1143   IN FDC_BLK_IO_DEV  *FdcDev,
1144   IN BOOLEAN         Dio,
1145   IN UINTN           Timeout
1146   )
1147 {
1148   UINTN Delay;
1149   UINT8 StatusRegister;
1150   UINT8 DataInOut;
1151 
1152   //
1153   // Before writing to FDC or reading from FDC, the Host must examine
1154   // the bit7(RQM) and bit6(DIO) of the Main Status Register.
1155   // That is to say:
1156   //  command bytes can not be written to Data Register
1157   //  unless RQM is 1 and DIO is 0
1158   //  result bytes can not be read from Data Register
1159   //  unless RQM is 1 and DIO is 1
1160   //
1161   DataInOut = (UINT8) (Dio << 6);
1162   //
1163   // in order to compare bit6
1164   //
1165   Delay = ((Timeout * STALL_1_MSECOND) / 50) + 1;
1166   do {
1167     StatusRegister = FdcReadPort (FdcDev, FDC_REGISTER_MSR);
1168     if ((StatusRegister & MSR_RQM) == MSR_RQM && (StatusRegister & MSR_DIO) == DataInOut) {
1169       break;
1170       //
1171       // FDC is ready
1172       //
1173     }
1174 
1175     MicroSecondDelay (50);
1176     //
1177     // Stall for 50 us
1178     //
1179     Delay = Delay - 1;
1180   } while (Delay > 0);
1181 
1182   if (Delay == 0) {
1183     return EFI_NOT_READY;
1184     //
1185     // FDC is not ready within the specified time period
1186     //
1187   }
1188 
1189   return EFI_SUCCESS;
1190 }
1191 
1192 /**
1193   Set FDC control structure's attribute according to result.
1194 
1195   @param Result  Point to result structure
1196   @param FdcDev  FDC control structure
1197 
1198   @retval EFI_DEVICE_ERROR - GC_TODO: Add description for return value
1199   @retval EFI_DEVICE_ERROR - GC_TODO: Add description for return value
1200   @retval EFI_DEVICE_ERROR - GC_TODO: Add description for return value
1201   @retval EFI_SUCCESS - GC_TODO: Add description for return value
1202 
1203 **/
1204 EFI_STATUS
CheckResult(IN FDD_RESULT_PACKET * Result,IN OUT FDC_BLK_IO_DEV * FdcDev)1205 CheckResult (
1206   IN     FDD_RESULT_PACKET  *Result,
1207   IN OUT FDC_BLK_IO_DEV     *FdcDev
1208   )
1209 {
1210   //
1211   // Check Status Register0
1212   //
1213   if ((Result->Status0 & STS0_IC) != IC_NT) {
1214     if ((Result->Status0 & STS0_SE) == 0x20) {
1215       //
1216       // seek error
1217       //
1218       FdcDev->ControllerState->NeedRecalibrate = TRUE;
1219     }
1220 
1221     FdcDev->ControllerState->NeedRecalibrate = TRUE;
1222     return EFI_DEVICE_ERROR;
1223   }
1224   //
1225   // Check Status Register1
1226   //
1227   if ((Result->Status1 & (STS1_EN | STS1_DE | STS1_OR | STS1_ND | STS1_NW | STS1_MA)) != 0) {
1228     FdcDev->ControllerState->NeedRecalibrate = TRUE;
1229     return EFI_DEVICE_ERROR;
1230   }
1231   //
1232   // Check Status Register2
1233   //
1234   if ((Result->Status2 & (STS2_CM | STS2_DD | STS2_WC | STS2_BC | STS2_MD)) != 0) {
1235     FdcDev->ControllerState->NeedRecalibrate = TRUE;
1236     return EFI_DEVICE_ERROR;
1237   }
1238 
1239   return EFI_SUCCESS;
1240 }
1241 
1242 /**
1243   Check the drive status information.
1244 
1245   @param StatusRegister3  the value of Status Register 3
1246 
1247   @retval EFI_SUCCESS           The disk is not write protected
1248   @retval EFI_WRITE_PROTECTED:  The disk is write protected
1249 
1250 **/
1251 EFI_STATUS
CheckStatus3(IN UINT8 StatusRegister3)1252 CheckStatus3 (
1253   IN UINT8 StatusRegister3
1254   )
1255 {
1256   if ((StatusRegister3 & STS3_WP) != 0) {
1257     return EFI_WRITE_PROTECTED;
1258   }
1259 
1260   return EFI_SUCCESS;
1261 }
1262 
1263 /**
1264   Calculate the number of block in the same cylinder according to LBA.
1265 
1266   @param FdcDev FDC_BLK_IO_DEV *: A pointer to FDC_BLK_IO_DEV
1267   @param LBA EFI_LBA:      The starting logic block address
1268   @param NumberOfBlocks UINTN: The number of blocks
1269 
1270   @return The number of blocks in the same cylinder which the starting
1271         logic block address is LBA
1272 
1273 **/
1274 UINTN
GetTransferBlockCount(IN FDC_BLK_IO_DEV * FdcDev,IN EFI_LBA LBA,IN UINTN NumberOfBlocks)1275 GetTransferBlockCount (
1276   IN  FDC_BLK_IO_DEV  *FdcDev,
1277   IN  EFI_LBA         LBA,
1278   IN  UINTN           NumberOfBlocks
1279   )
1280 {
1281   UINT8 EndOfTrack;
1282   UINT8 Head;
1283   UINT8 SectorsInTrack;
1284 
1285   //
1286   // Calculate the number of block in the same cylinder
1287   //
1288   EndOfTrack      = DISK_1440K_EOT;
1289   Head            = (UINT8) ((UINTN) LBA / EndOfTrack % 2);
1290 
1291   SectorsInTrack  = (UINT8) (EndOfTrack * (2 - Head) - (UINT8) ((UINTN) LBA % EndOfTrack));
1292   if (SectorsInTrack < NumberOfBlocks) {
1293     return SectorsInTrack;
1294   } else {
1295     return NumberOfBlocks;
1296   }
1297 }
1298 
1299 /**
1300   When the Timer(2s) off, turn the drive's motor off.
1301 
1302   @param Event EFI_EVENT: Event(the timer) whose notification function is being
1303                      invoked
1304   @param Context VOID *:  Pointer to the notification function's context
1305 
1306 **/
1307 VOID
1308 EFIAPI
FddTimerProc(IN EFI_EVENT Event,IN VOID * Context)1309 FddTimerProc (
1310   IN EFI_EVENT  Event,
1311   IN VOID       *Context
1312   )
1313 {
1314   FDC_BLK_IO_DEV  *FdcDev;
1315   UINT8           Data;
1316 
1317   FdcDev = (FDC_BLK_IO_DEV *) Context;
1318 
1319   //
1320   // Get the motor status
1321   //
1322   Data = FdcReadPort (FdcDev, FDC_REGISTER_DOR);
1323 
1324   if (((FdcDev->Disk == FdcDisk0) && ((Data & 0x10) != 0x10)) ||
1325       ((FdcDev->Disk == FdcDisk1) && ((Data & 0x21) != 0x21))
1326       ) {
1327     return ;
1328   }
1329   //
1330   // the motor is on, so need motor off
1331   //
1332   Data = 0x0C;
1333   Data = (UINT8) (Data | (SELECT_DRV & FdcDev->Disk));
1334   FdcWritePort (FdcDev, FDC_REGISTER_DOR, Data);
1335   MicroSecondDelay (500);
1336 }
1337 
1338 /**
1339   Read an I/O port of FDC.
1340 
1341   @param[in] FdcDev  A pointer to FDC_BLK_IO_DEV.
1342   @param[in] Offset  The address offset of the I/O port.
1343 
1344   @retval  8-bit data read from the I/O port.
1345 **/
1346 UINT8
FdcReadPort(IN FDC_BLK_IO_DEV * FdcDev,IN UINT32 Offset)1347 FdcReadPort (
1348   IN FDC_BLK_IO_DEV  *FdcDev,
1349   IN UINT32          Offset
1350   )
1351 {
1352   EFI_STATUS  Status;
1353   UINT8       Data;
1354 
1355   Status = FdcDev->IsaIo->Io.Read (
1356                             FdcDev->IsaIo,
1357                             EfiIsaIoWidthUint8,
1358                             FdcDev->BaseAddress + Offset,
1359                             1,
1360                             &Data
1361                             );
1362   ASSERT_EFI_ERROR (Status);
1363 
1364   return Data;
1365 }
1366 
1367 /**
1368   Write an I/O port of FDC.
1369 
1370   @param[in] FdcDev  A pointer to FDC_BLK_IO_DEV
1371   @param[in] Offset  The address offset of the I/O port
1372   @param[in] Data    8-bit Value written to the I/O port
1373 **/
1374 VOID
FdcWritePort(IN FDC_BLK_IO_DEV * FdcDev,IN UINT32 Offset,IN UINT8 Data)1375 FdcWritePort (
1376   IN FDC_BLK_IO_DEV  *FdcDev,
1377   IN UINT32          Offset,
1378   IN UINT8           Data
1379   )
1380 {
1381   EFI_STATUS  Status;
1382 
1383   Status = FdcDev->IsaIo->Io.Write (
1384                             FdcDev->IsaIo,
1385                             EfiIsaIoWidthUint8,
1386                             FdcDev->BaseAddress + Offset,
1387                             1,
1388                             &Data
1389                             );
1390   ASSERT_EFI_ERROR (Status);
1391 }
1392 
1393