summaryrefslogtreecommitdiffstats
path: root/grub-core/bus/usb/ohci.c
blob: f0be533d41eb7cdb1dc88e79a2195237ea829d20 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
/* ohci.c - OHCI Support.  */
/*
 *  GRUB  --  GRand Unified Bootloader
 *  Copyright (C) 2008  Free Software Foundation, Inc.
 *
 *  GRUB is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  GRUB is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <grub/dl.h>
#include <grub/mm.h>
#include <grub/usb.h>
#include <grub/usbtrans.h>
#include <grub/misc.h>
#include <grub/pci.h>
#include <grub/cpu/pci.h>
#include <grub/cpu/io.h>
#include <grub/time.h>
#include <grub/cs5536.h>
#include <grub/loader.h>
#include <grub/disk.h>

GRUB_MOD_LICENSE ("GPLv3+");

struct grub_ohci_hcca
{
  /* Pointers to Interrupt Endpoint Descriptors.  Not used by
     GRUB.  */
  grub_uint32_t inttable[32];

  /* Current frame number.  */
  grub_uint16_t framenumber;

  grub_uint16_t pad;

  /* List of completed TDs.  */
  grub_uint32_t donehead;

  grub_uint8_t reserved[116];
} GRUB_PACKED;

/* OHCI General Transfer Descriptor */
struct grub_ohci_td
{
  /* Information used to construct the TOKEN packet.  */
  grub_uint32_t token;
  grub_uint32_t buffer; /* LittleEndian physical address */
  grub_uint32_t next_td; /* LittleEndian physical address */
  grub_uint32_t buffer_end; /* LittleEndian physical address */
  /* next values are not for OHCI HW */
  volatile struct grub_ohci_td *link_td; /* pointer to next free/chained TD
                          * pointer as uint32 */
  grub_uint32_t prev_td_phys; /* we need it to find previous TD
                               * physical address in CPU endian */
  grub_uint32_t tr_index; /* index of TD in transfer */
  grub_uint8_t pad[8 - sizeof (volatile struct grub_ohci_td *)]; /* padding to 32 bytes */
} GRUB_PACKED;

/* OHCI Endpoint Descriptor.  */
struct grub_ohci_ed
{
  grub_uint32_t target;
  grub_uint32_t td_tail;
  grub_uint32_t td_head;
  grub_uint32_t next_ed;
} GRUB_PACKED;

typedef volatile struct grub_ohci_td *grub_ohci_td_t;
typedef volatile struct grub_ohci_ed *grub_ohci_ed_t;

/* Experimental change of ED/TD allocation */
/* Little bit similar as in UHCI */
/* Implementation assumes:
 *      32-bits architecture - XXX: fix for 64-bits
 *      memory allocated by grub_memalign_dma32 must be continuous
 *      in virtual and also in physical memory */
struct grub_ohci
{
  volatile grub_uint32_t *iobase;
  volatile struct grub_ohci_hcca *hcca;
  grub_uint32_t hcca_addr;
  struct grub_pci_dma_chunk *hcca_chunk;
  grub_ohci_ed_t ed_ctrl; /* EDs for CONTROL */
  grub_uint32_t ed_ctrl_addr;
  struct grub_pci_dma_chunk *ed_ctrl_chunk;
  grub_ohci_ed_t ed_bulk; /* EDs for BULK */
  grub_uint32_t ed_bulk_addr;
  struct grub_pci_dma_chunk *ed_bulk_chunk;
  grub_ohci_td_t td; /* TDs */
  grub_uint32_t td_addr;
  struct grub_pci_dma_chunk *td_chunk;
  struct grub_ohci *next;
  grub_ohci_td_t td_free; /* Pointer to first free TD */
};

static struct grub_ohci *ohci;

typedef enum
{
  GRUB_OHCI_REG_REVISION = 0x00,
  GRUB_OHCI_REG_CONTROL,
  GRUB_OHCI_REG_CMDSTATUS,
  GRUB_OHCI_REG_INTSTATUS,
  GRUB_OHCI_REG_INTENA,
  GRUB_OHCI_REG_INTDIS,
  GRUB_OHCI_REG_HCCA,
  GRUB_OHCI_REG_PERIODIC,
  GRUB_OHCI_REG_CONTROLHEAD,
  GRUB_OHCI_REG_CONTROLCURR,
  GRUB_OHCI_REG_BULKHEAD,
  GRUB_OHCI_REG_BULKCURR,
  GRUB_OHCI_REG_DONEHEAD,
  GRUB_OHCI_REG_FRAME_INTERVAL,
  GRUB_OHCI_REG_PERIODIC_START = 16,
  GRUB_OHCI_REG_RHUBA = 18,
  GRUB_OHCI_REG_RHUBPORT = 21,
  GRUB_OHCI_REG_LEGACY_CONTROL = 0x100,
  GRUB_OHCI_REG_LEGACY_INPUT = 0x104,
  GRUB_OHCI_REG_LEGACY_OUTPUT = 0x108,
  GRUB_OHCI_REG_LEGACY_STATUS = 0x10c
} grub_ohci_reg_t;

#define GRUB_OHCI_RHUB_PORT_POWER_MASK 0x300
#define GRUB_OHCI_RHUB_PORT_ALL_POWERED 0x200

#define GRUB_OHCI_REG_FRAME_INTERVAL_FSMPS_MASK 0x8fff0000
#define GRUB_OHCI_REG_FRAME_INTERVAL_FSMPS_SHIFT 16
#define GRUB_OHCI_REG_FRAME_INTERVAL_FI_SHIFT 0

/* XXX: Is this choice of timings sane?  */
#define GRUB_OHCI_FSMPS 0x2778
#define GRUB_OHCI_PERIODIC_START 0x257f
#define GRUB_OHCI_FRAME_INTERVAL 0x2edf

#define GRUB_OHCI_SET_PORT_ENABLE (1 << 1)
#define GRUB_OHCI_CLEAR_PORT_ENABLE (1 << 0)
#define GRUB_OHCI_SET_PORT_RESET (1 << 4)
#define GRUB_OHCI_SET_PORT_RESET_STATUS_CHANGE (1 << 20)

#define GRUB_OHCI_REG_CONTROL_BULK_ENABLE (1 << 5)
#define GRUB_OHCI_REG_CONTROL_CONTROL_ENABLE (1 << 4)

#define GRUB_OHCI_RESET_CONNECT_CHANGE (1 << 16)
#define GRUB_OHCI_CTRL_EDS 256
#define GRUB_OHCI_BULK_EDS 510
#define GRUB_OHCI_TDS 640

#define GRUB_OHCI_ED_ADDR_MASK 0x7ff

static inline grub_ohci_ed_t
grub_ohci_ed_phys2virt (struct grub_ohci *o, int bulk, grub_uint32_t x)
{
  if (!x)
    return NULL;
  if (bulk)
    return (grub_ohci_ed_t) (x - o->ed_bulk_addr
			     + (grub_uint8_t *) o->ed_bulk);
  return (grub_ohci_ed_t) (x - o->ed_ctrl_addr
			   + (grub_uint8_t *) o->ed_ctrl);
}

static grub_uint32_t
grub_ohci_virt_to_phys (struct grub_ohci *o, int bulk, grub_ohci_ed_t x)
{
  if (!x)
    return 0;

  if (bulk)
    return (grub_uint8_t *) x - (grub_uint8_t *) o->ed_bulk + o->ed_bulk_addr;
  return (grub_uint8_t *) x - (grub_uint8_t *) o->ed_ctrl + o->ed_ctrl_addr;
}

static inline grub_ohci_td_t
grub_ohci_td_phys2virt (struct grub_ohci *o, grub_uint32_t x)
{
  if (!x)
    return NULL;
  return (grub_ohci_td_t) (x - o->td_addr + (grub_uint8_t *) o->td);
}

static grub_uint32_t
grub_ohci_td_virt2phys (struct grub_ohci *o,  grub_ohci_td_t x)
{
  if (!x)
    return 0;
  return (grub_uint8_t *)x - (grub_uint8_t *)o->td + o->td_addr;
}

  
static grub_uint32_t
grub_ohci_readreg32 (struct grub_ohci *o, grub_ohci_reg_t reg)
{
  return grub_le_to_cpu32 (*(o->iobase + reg));
}

static void
grub_ohci_writereg32 (struct grub_ohci *o,
		      grub_ohci_reg_t reg, grub_uint32_t val)
{
  *(o->iobase + reg) = grub_cpu_to_le32 (val);
}



/* Iterate over all PCI devices.  Determine if a device is an OHCI
   controller.  If this is the case, initialize it.  */
static int
grub_ohci_pci_iter (grub_pci_device_t dev, grub_pci_id_t pciid,
		    void *data __attribute__ ((unused)))
{
  grub_uint32_t interf;
  grub_uint32_t base;
  grub_pci_address_t addr;
  struct grub_ohci *o;
  grub_uint32_t revision;
  int j;
  
  /* Determine IO base address.  */
  grub_dprintf ("ohci", "pciid = %x\n", pciid);

  if (pciid == GRUB_CS5536_PCIID)
    {
      grub_uint64_t basereg;

      basereg = grub_cs5536_read_msr (dev, GRUB_CS5536_MSR_USB_OHCI_BASE);
      if (!(basereg & GRUB_CS5536_MSR_USB_BASE_MEMORY_ENABLE))
	{
	  /* Shouldn't happen.  */
	  grub_dprintf ("ohci", "No OHCI address is assigned\n");
	  return 0;
	}
      base = (basereg & GRUB_CS5536_MSR_USB_BASE_ADDR_MASK);
      basereg |= GRUB_CS5536_MSR_USB_BASE_BUS_MASTER;
      basereg &= ~GRUB_CS5536_MSR_USB_BASE_PME_ENABLED;
      basereg &= ~GRUB_CS5536_MSR_USB_BASE_PME_STATUS;
      grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_USB_OHCI_BASE, basereg);
    }
  else
    {
      grub_uint32_t class_code;
      grub_uint32_t class;
      grub_uint32_t subclass;

      addr = grub_pci_make_address (dev, GRUB_PCI_REG_CLASS);
      class_code = grub_pci_read (addr) >> 8;
      
      interf = class_code & 0xFF;
      subclass = (class_code >> 8) & 0xFF;
      class = class_code >> 16;

      /* If this is not an OHCI controller, just return.  */
      if (class != 0x0c || subclass != 0x03 || interf != 0x10)
	return 0;

      addr = grub_pci_make_address (dev, GRUB_PCI_REG_ADDRESS_REG0);
      base = grub_pci_read (addr);

      base &= GRUB_PCI_ADDR_MEM_MASK;
      if (!base)
	{
	  grub_dprintf ("ehci",
			"EHCI: EHCI is not mapper\n");
	  return 0;
	}

      /* Set bus master - needed for coreboot, VMware, broken BIOSes etc. */
      addr = grub_pci_make_address (dev, GRUB_PCI_REG_COMMAND);
      grub_pci_write_word(addr,
			  GRUB_PCI_COMMAND_MEM_ENABLED
			  | GRUB_PCI_COMMAND_BUS_MASTER
			  | grub_pci_read_word(addr));

      grub_dprintf ("ohci", "class=0x%02x 0x%02x interface 0x%02x\n",
		    class, subclass, interf);
    }

  /* Allocate memory for the controller and register it.  */
  o = grub_malloc (sizeof (*o));
  if (! o)
    return 1;
  grub_memset ((void*)o, 0, sizeof (*o));
  o->iobase = grub_pci_device_map_range (dev, base, 0x800);

  grub_dprintf ("ohci", "base=%p\n", o->iobase);

  /* Reserve memory for the HCCA.  */
  o->hcca_chunk = grub_memalign_dma32 (256, 256);
  if (! o->hcca_chunk)
    goto fail;
  o->hcca = grub_dma_get_virt (o->hcca_chunk);
  o->hcca_addr = grub_dma_get_phys (o->hcca_chunk);
  grub_memset ((void*)o->hcca, 0, sizeof(*o->hcca));
  grub_dprintf ("ohci", "hcca: chunk=%p, virt=%p, phys=0x%02x\n",
                o->hcca_chunk, o->hcca, o->hcca_addr);

  /* Reserve memory for ctrl EDs.  */
  o->ed_ctrl_chunk = grub_memalign_dma32 (16, sizeof(struct grub_ohci_ed)
					  * GRUB_OHCI_CTRL_EDS);
  if (! o->ed_ctrl_chunk)
    goto fail;
  o->ed_ctrl = grub_dma_get_virt (o->ed_ctrl_chunk);
  o->ed_ctrl_addr = grub_dma_get_phys (o->ed_ctrl_chunk);
  /* Preset EDs */
  grub_memset ((void *) o->ed_ctrl, 0, sizeof (struct grub_ohci_ed)
	       * GRUB_OHCI_CTRL_EDS);
  for (j=0; j < GRUB_OHCI_CTRL_EDS; j++)
    o->ed_ctrl[j].target = grub_cpu_to_le32_compile_time (1 << 14); /* skip */
    
  grub_dprintf ("ohci", "EDs-C: chunk=%p, virt=%p, phys=0x%02x\n",
                o->ed_ctrl_chunk, o->ed_ctrl, o->ed_ctrl_addr);

  /* Reserve memory for bulk EDs.  */
  o->ed_bulk_chunk = grub_memalign_dma32 (16, sizeof (struct grub_ohci_ed)
					  * GRUB_OHCI_BULK_EDS);
  if (! o->ed_bulk_chunk)
    goto fail;
  o->ed_bulk = grub_dma_get_virt (o->ed_bulk_chunk);
  o->ed_bulk_addr = grub_dma_get_phys (o->ed_bulk_chunk);
  /* Preset EDs */
  grub_memset ((void*)o->ed_bulk, 0, sizeof(struct grub_ohci_ed) * GRUB_OHCI_BULK_EDS);
  for (j=0; j < GRUB_OHCI_BULK_EDS; j++)
    o->ed_bulk[j].target = grub_cpu_to_le32_compile_time (1 << 14); /* skip */

  grub_dprintf ("ohci", "EDs-B: chunk=%p, virt=%p, phys=0x%02x\n",
                o->ed_bulk_chunk, o->ed_bulk, o->ed_bulk_addr);

  /* Reserve memory for TDs.  */
  o->td_chunk = grub_memalign_dma32 (32, sizeof(struct grub_ohci_td)*GRUB_OHCI_TDS);
  /* Why is it aligned on 32 boundary if spec. says 16 ?
   * We have structure 32 bytes long and we don't want cross
   * 4K boundary inside structure. */
  if (! o->td_chunk)
    goto fail;
  o->td_free = o->td = grub_dma_get_virt (o->td_chunk);
  o->td_addr = grub_dma_get_phys (o->td_chunk);
  /* Preset free TDs chain in TDs */
  grub_memset ((void*)o->td, 0, sizeof(struct grub_ohci_td) * GRUB_OHCI_TDS);
  for (j=0; j < (GRUB_OHCI_TDS-1); j++)
    o->td[j].link_td = &o->td[j+1];

  grub_dprintf ("ohci", "TDs: chunk=%p, virt=%p, phys=0x%02x\n",
                o->td_chunk, o->td, o->td_addr);

  /* Check if the OHCI revision is actually 1.0 as supported.  */
  revision = grub_ohci_readreg32 (o, GRUB_OHCI_REG_REVISION);
  grub_dprintf ("ohci", "OHCI revision=0x%02x\n", revision & 0xFF);
  if ((revision & 0xFF) != 0x10)
    goto fail;

  {
    grub_uint32_t control;
    /* Check SMM/BIOS ownership of OHCI (SMM = USB Legacy Support driver for BIOS) */
    control = grub_ohci_readreg32 (o, GRUB_OHCI_REG_CONTROL);
    if ((control & 0x100) != 0)
      {
	unsigned i;
	grub_dprintf("ohci", "OHCI is owned by SMM\n");
	/* Do change of ownership */
	/* Ownership change request */
	grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, (1<<3)); /* XXX: Magic.  */
	/* Waiting for SMM deactivation */
	for (i=0; i < 10; i++)
	  {
	    if ((grub_ohci_readreg32 (o, GRUB_OHCI_REG_CONTROL) & 0x100) == 0)
	      {
		grub_dprintf("ohci", "Ownership changed normally.\n");
		break;
	      }
	    grub_millisleep (100);
          }
	if (i >= 10)
	  {
	    grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL,
				  grub_ohci_readreg32 (o, GRUB_OHCI_REG_CONTROL) & ~0x100);
	    grub_dprintf("ohci", "Ownership changing timeout, change forced !\n");
	  }
      }
    else if (((control & 0x100) == 0) && 
	     ((control & 0xc0) != 0)) /* Not owned by SMM nor reset */
      {
	grub_dprintf("ohci", "OHCI is owned by BIOS\n");
	/* Do change of ownership - not implemented yet... */
	/* In fact we probably need to do nothing ...? */
      }
    else
      {
	grub_dprintf("ohci", "OHCI is not owned by SMM nor BIOS\n");
	/* We can setup OHCI. */
      }  
  }

  /* Suspend the OHCI by issuing a reset.  */
  grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, 1); /* XXX: Magic.  */
  grub_millisleep (1);
  grub_dprintf ("ohci", "OHCI reset\n");

  grub_ohci_writereg32 (o, GRUB_OHCI_REG_FRAME_INTERVAL,
			(GRUB_OHCI_FSMPS
			 << GRUB_OHCI_REG_FRAME_INTERVAL_FSMPS_SHIFT)
			| (GRUB_OHCI_FRAME_INTERVAL
			   << GRUB_OHCI_REG_FRAME_INTERVAL_FI_SHIFT));

  grub_ohci_writereg32 (o, GRUB_OHCI_REG_PERIODIC_START,
			GRUB_OHCI_PERIODIC_START);

  /* Setup the HCCA.  */
  o->hcca->donehead = 0;
  grub_ohci_writereg32 (o, GRUB_OHCI_REG_HCCA, o->hcca_addr);
  grub_dprintf ("ohci", "OHCI HCCA\n");

  /* Misc. pre-sets. */
  o->hcca->donehead = 0;
  grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, 0x7f); /* Clears everything */
  /* We don't want modify CONTROL/BULK HEAD registers.
   * So we assign to HEAD registers zero ED from related array
   * and we will not use this ED, it will be always skipped.
   * It should not produce notable performance penalty (I hope). */
  grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLHEAD, o->ed_ctrl_addr);
  grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLCURR, 0);
  grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKHEAD, o->ed_bulk_addr);
  grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKCURR, 0);

  /* Check OHCI Legacy Support */
  if ((revision & 0x100) != 0)
    {
      grub_dprintf ("ohci", "Legacy Support registers detected\n");
      grub_dprintf ("ohci", "Current state of legacy control reg.: 0x%04x\n",
		    grub_ohci_readreg32 (o, GRUB_OHCI_REG_LEGACY_CONTROL));
      grub_ohci_writereg32 (o, GRUB_OHCI_REG_LEGACY_CONTROL,
			    (grub_ohci_readreg32 (o, GRUB_OHCI_REG_LEGACY_CONTROL)) & ~1);
      grub_dprintf ("ohci", "OHCI Legacy Support disabled.\n");
    }

  /* Enable the OHCI + enable CONTROL and BULK LIST.  */
  grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL,
			(2 << 6)
			| GRUB_OHCI_REG_CONTROL_CONTROL_ENABLE
			| GRUB_OHCI_REG_CONTROL_BULK_ENABLE );
  grub_dprintf ("ohci", "OHCI enable: 0x%02x\n",
		(grub_ohci_readreg32 (o, GRUB_OHCI_REG_CONTROL) >> 6) & 3);

  /* Power on all ports */
  grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBA,
                       (grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBA)
                        & ~GRUB_OHCI_RHUB_PORT_POWER_MASK)
                       | GRUB_OHCI_RHUB_PORT_ALL_POWERED);
#if 0 /* We don't need it at all, handled via hotplugging */
  /* Now we have hot-plugging, we need to wait for stable power only */
  grub_millisleep (100);
#endif

  /* Link to ohci now that initialisation is successful.  */
  o->next = ohci;
  ohci = o;

  return 0;

 fail:
  if (o)
    {
      grub_dma_free (o->td_chunk);
      grub_dma_free (o->ed_bulk_chunk);
      grub_dma_free (o->ed_ctrl_chunk);
      grub_dma_free (o->hcca_chunk);
    }
  grub_free (o);

  return 0;
}


static void
grub_ohci_inithw (void)
{
  grub_pci_iterate (grub_ohci_pci_iter, NULL);
}



static int
grub_ohci_iterate (grub_usb_controller_iterate_hook_t hook, void *hook_data)
{
  struct grub_ohci *o;
  struct grub_usb_controller dev;

  for (o = ohci; o; o = o->next)
    {
      dev.data = o;
      if (hook (&dev, hook_data))
	return 1;
    }

  return 0;
}

static grub_ohci_ed_t
grub_ohci_find_ed (struct grub_ohci *o, int bulk, grub_uint32_t target)
{
  grub_ohci_ed_t ed, ed_next;
  grub_uint32_t target_addr = target & GRUB_OHCI_ED_ADDR_MASK;
  int count;
  int i;

  /* Use proper values and structures. */
  if (bulk)
    {    
      count = GRUB_OHCI_BULK_EDS;
      ed = o->ed_bulk;
      ed_next = grub_ohci_ed_phys2virt(o, bulk,
                  grub_le_to_cpu32 (ed->next_ed) );
    }
  else
    {
      count = GRUB_OHCI_CTRL_EDS;
      ed = o->ed_ctrl;
      ed_next = grub_ohci_ed_phys2virt(o, bulk,
                  grub_le_to_cpu32 (ed->next_ed) );
    }

   /* First try to find existing ED with proper target address */
  for (i = 0; ; )
    {
      if (i && /* We ignore zero ED */
           ((ed->target & GRUB_OHCI_ED_ADDR_MASK) == target_addr))
        return ed; /* Found proper existing ED */
      i++;
      if (ed_next && (i < count))
        {
          ed = ed_next;
          ed_next = grub_ohci_ed_phys2virt(o, bulk,
                      grub_le_to_cpu32 (ed->next_ed) );
          continue;
        }
      break;
    }
  /* ED with target_addr does not exist, we have to add it */
  /* Have we any free ED in array ? */
  if (i >= count) /* No. */
    return NULL;
  /* Currently we simply take next ED in array, no allocation
   * function is used. It should be no problem until hot-plugging
   * will be implemented, i.e. until we will need to de-allocate EDs
   * of unplugged devices. */
  /* We can link new ED to previous ED safely as the new ED should
   * still have set skip bit. */
  ed->next_ed = grub_cpu_to_le32 ( grub_ohci_virt_to_phys (o,
                                     bulk, &ed[1]));
  return &ed[1];
}

static grub_ohci_td_t
grub_ohci_alloc_td (struct grub_ohci *o)
{
  grub_ohci_td_t ret;

  /* Check if there is a Transfer Descriptor available.  */
  if (! o->td_free)
    return NULL;

  ret = o->td_free; /* Take current free TD */
  o->td_free = (grub_ohci_td_t)ret->link_td; /* Advance to next free TD in chain */
  ret->link_td = 0; /* Reset link_td in allocated TD */
  return ret;
}

static void
grub_ohci_free_td (struct grub_ohci *o, grub_ohci_td_t td)
{
  grub_memset ( (void*)td, 0, sizeof(struct grub_ohci_td) ); 
  td->link_td = o->td_free; /* Cahin new free TD & rest */
  o->td_free = td; /* Change address of first free TD */
}

static void
grub_ohci_free_tds (struct grub_ohci *o, grub_ohci_td_t td)
{
  if (!td)
    return;
    
  /* Unchain first TD from previous TD if it is chained */
  if (td->prev_td_phys)
    {
      grub_ohci_td_t td_prev_virt = grub_ohci_td_phys2virt(o,
                                      td->prev_td_phys);

      if (td == (grub_ohci_td_t) td_prev_virt->link_td)
        td_prev_virt->link_td = 0;
    }
  
  /* Free all TDs from td  (chained by link_td) */
  while (td)
    {
      grub_ohci_td_t tdprev;
      
      /* Unlink the queue.  */
      tdprev = td;
      td = (grub_ohci_td_t) td->link_td;

      /* Free the TD.  */
      grub_ohci_free_td (o, tdprev);
    }
}

static void
grub_ohci_transaction (grub_ohci_td_t td,
		       grub_transfer_type_t type, unsigned int toggle,
		       grub_size_t size, grub_uint32_t data)
{
  grub_uint32_t token;
  grub_uint32_t buffer;
  grub_uint32_t buffer_end;

  grub_dprintf ("ohci", "OHCI transaction td=%p type=%d, toggle=%d, size=%lu\n",
		td, type, toggle, (unsigned long) size);

  switch (type)
    {
    case GRUB_USB_TRANSFER_TYPE_SETUP:
      token = 0 << 19;
      break;
    case GRUB_USB_TRANSFER_TYPE_IN:
      token = 2 << 19;
      break;
    case GRUB_USB_TRANSFER_TYPE_OUT:
      token = 1 << 19;
      break;
    default:
      token = 0;
      break;
    }

  /* Set the token */
  token |= ( 7 << 21); /* Never generate interrupt */
  token |= toggle << 24;
  token |= 1 << 25;

  /* Set "Not accessed" error code */
  token |= 15 << 28;

  buffer = data;
  buffer_end = buffer + size - 1;

  /* Set correct buffer values in TD if zero transfer occurs */
  if (size)
    {
      buffer = (grub_uint32_t) data;
      buffer_end = buffer + size - 1;
      td->buffer = grub_cpu_to_le32 (buffer);
      td->buffer_end = grub_cpu_to_le32 (buffer_end);
    }
  else 
    {
      td->buffer = 0;
      td->buffer_end = 0;
    }

  /* Set the rest of TD */
  td->token = grub_cpu_to_le32 (token);
  td->next_td = 0;
}

struct grub_ohci_transfer_controller_data
{
  grub_uint32_t tderr_phys;
  grub_uint32_t td_last_phys;
  grub_ohci_ed_t ed_virt;
  grub_ohci_td_t td_current_virt;
  grub_ohci_td_t td_head_virt;
};

static grub_usb_err_t
grub_ohci_setup_transfer (grub_usb_controller_t dev,
			  grub_usb_transfer_t transfer)
{
  struct grub_ohci *o = (struct grub_ohci *) dev->data;
  int bulk = 0;
  grub_ohci_td_t td_next_virt;
  grub_uint32_t target;
  grub_uint32_t td_head_phys;
  grub_uint32_t td_tail_phys;
  int i;
  struct grub_ohci_transfer_controller_data *cdata;

  cdata = grub_zalloc (sizeof (*cdata));
  if (!cdata)
    return GRUB_USB_ERR_INTERNAL;

  /* Pre-set target for ED - we need it to find proper ED */
  /* Set the device address.  */
  target = transfer->devaddr;
  /* Set the endpoint. It should be masked, we need 4 bits only. */
  target |= (transfer->endpoint & 15) << 7;
  /* Set the device speed.  */
  target |= (transfer->dev->speed == GRUB_USB_SPEED_LOW) << 13;
  /* Set the maximum packet size.  */
  target |= transfer->max << 16;

  /* Determine if transfer type is bulk - we need to select proper ED */
  switch (transfer->type)
    {
      case GRUB_USB_TRANSACTION_TYPE_BULK:
        bulk = 1;
	break;

      case GRUB_USB_TRANSACTION_TYPE_CONTROL:
        break;

      default:
	grub_free (cdata);
        return GRUB_USB_ERR_INTERNAL;
    }

  /* Find proper ED or add new ED */
  cdata->ed_virt = grub_ohci_find_ed (o, bulk, target);
  if (!cdata->ed_virt)
    {
      grub_dprintf ("ohci","Fatal: No free ED !\n");
      grub_free (cdata);
      return GRUB_USB_ERR_INTERNAL;
    }
  
  /* Take pointer to first TD from ED */
  td_head_phys = grub_le_to_cpu32 (cdata->ed_virt->td_head) & ~0xf;
  td_tail_phys = grub_le_to_cpu32 (cdata->ed_virt->td_tail) & ~0xf;

  /* Sanity check - td_head should be equal to td_tail */
  if (td_head_phys != td_tail_phys) /* Should never happen ! */
    {
      grub_dprintf ("ohci", "Fatal: HEAD is not equal to TAIL !\n");
      grub_dprintf ("ohci", "HEAD = 0x%02x, TAIL = 0x%02x\n",
                    td_head_phys, td_tail_phys);
      /* XXX: Fix: What to do ? */
      grub_free (cdata);
      return GRUB_USB_ERR_INTERNAL;
    }
  
  /* Now we should handle first TD. If ED is newly allocated,
   * we must allocate the first TD. */
  if (!td_head_phys)
    {
      cdata->td_head_virt = grub_ohci_alloc_td (o);
      if (!cdata->td_head_virt)
	{
	  grub_free (cdata);
	  return GRUB_USB_ERR_INTERNAL; /* We don't need de-allocate ED */
	}
      /* We can set td_head only when ED is not active, i.e.
       * when it is newly allocated. */
      cdata->ed_virt->td_head
	= grub_cpu_to_le32 (grub_ohci_td_virt2phys (o, cdata->td_head_virt));
      cdata->ed_virt->td_tail = cdata->ed_virt->td_head;
    }
  else
    cdata->td_head_virt = grub_ohci_td_phys2virt ( o, td_head_phys );
    
  /* Set TDs */
  cdata->td_last_phys = td_head_phys; /* initial value to make compiler happy... */
  for (i = 0, cdata->td_current_virt = cdata->td_head_virt;
       i < transfer->transcnt; i++)
    {
      grub_usb_transaction_t tr = &transfer->transactions[i];

      grub_ohci_transaction (cdata->td_current_virt, tr->pid, tr->toggle,
			     tr->size, tr->data);

      /* Set index of TD in transfer */
      cdata->td_current_virt->tr_index = (grub_uint32_t) i;
      
      /* Remember last used (processed) TD phys. addr. */
      cdata->td_last_phys = grub_ohci_td_virt2phys (o, cdata->td_current_virt);
      
      /* Allocate next TD */
      td_next_virt = grub_ohci_alloc_td (o);
      if (!td_next_virt) /* No free TD, cancel transfer and free TDs except head TD */
        {
          if (i) /* if i==0 we have nothing to free... */
            grub_ohci_free_tds (o, grub_ohci_td_phys2virt(o,
							  grub_le_to_cpu32 (cdata->td_head_virt->next_td)));
          /* Reset head TD */
          grub_memset ( (void*)cdata->td_head_virt, 0,
                        sizeof(struct grub_ohci_td) );
          grub_dprintf ("ohci", "Fatal: No free TD !");
	  grub_free (cdata);
          return GRUB_USB_ERR_INTERNAL;
        }

      /* Chain TDs */

      cdata->td_current_virt->link_td = td_next_virt;
      cdata->td_current_virt->next_td = grub_cpu_to_le32 (
                                   grub_ohci_td_virt2phys (o,
                                     td_next_virt) );
      td_next_virt->prev_td_phys = grub_ohci_td_virt2phys (o,
                                cdata->td_current_virt);
      cdata->td_current_virt = td_next_virt;
    }

  grub_dprintf ("ohci", "Tail TD (not processed) = %p\n",
                cdata->td_current_virt);
  
  /* Setup the Endpoint Descriptor for transfer.  */
  /* First set necessary fields in TARGET but keep (or set) skip bit */
  /* Note: It could be simpler if speed, format and max. packet
   * size never change after first allocation of ED.
   * But unfortunately max. packet size may change during initial
   * setup sequence and we must handle it. */
  cdata->ed_virt->target = grub_cpu_to_le32 (target | (1 << 14));
  /* Set td_tail */
  cdata->ed_virt->td_tail
    = grub_cpu_to_le32 (grub_ohci_td_virt2phys (o, cdata->td_current_virt));
  /* Now reset skip bit */
  cdata->ed_virt->target = grub_cpu_to_le32 (target);
  /* ed_virt->td_head = grub_cpu_to_le32 (td_head); Must not be changed, it is maintained by OHCI */
  /* ed_virt->next_ed = grub_cpu_to_le32 (0); Handled by grub_ohci_find_ed, do not change ! */

  grub_dprintf ("ohci", "program OHCI\n");

  /* Program the OHCI to actually transfer.  */
  switch (transfer->type)
    {
    case GRUB_USB_TRANSACTION_TYPE_BULK:
      {
	grub_dprintf ("ohci", "BULK list filled\n");
	/* Set BulkListFilled.  */
	grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, 1 << 2);
	/* Read back of register should ensure it is really written */
	grub_ohci_readreg32 (o, GRUB_OHCI_REG_CMDSTATUS);
	break;
      }

    case GRUB_USB_TRANSACTION_TYPE_CONTROL:
      {
	grub_dprintf ("ohci", "CONTROL list filled\n");
	/* Set ControlListFilled.  */
	grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, 1 << 1);
	/* Read back of register should ensure it is really written */
	grub_ohci_readreg32 (o, GRUB_OHCI_REG_CMDSTATUS);
	break;
      }
    }

  transfer->controller_data = cdata;

  return GRUB_USB_ERR_NONE;
}

static void
pre_finish_transfer (grub_usb_controller_t dev,
		     grub_usb_transfer_t transfer)
{
  struct grub_ohci *o = dev->data;
  struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data;
  grub_uint32_t target;
  grub_uint32_t status;
  grub_uint32_t control;
  grub_uint32_t intstatus;

  /* There are many ways how the loop above can finish:
   * - normally without any error via INTSTATUS WDH bit
   *   : tderr_phys == td_last_phys, td_head == td_tail
   * - normally with error via HALT bit in ED TD HEAD
   *   : td_head = next TD after TD with error
   *   : tderr_phys = last processed and retired TD with error,
   *     i.e. should be != 0
   *   : if bad_OHCI == TRUE, tderr_phys will be probably invalid
   * - unrecoverable error - I never seen it but it could be
   *   : err_unrec == TRUE, other values can contain anything...
   * - timeout, it can be caused by:
   *  -- bad USB device - some devices have some bugs, see Linux source
   *     and related links
   *  -- bad OHCI controller - e.g. lost interrupts or does not set
   *     proper bits in INTSTATUS when real IRQ not enabled etc.,
   *     see Linux source and related links
   *     One known bug is handled - if transfer finished
   *     successfully (i.e. HEAD==TAIL, last transfer TD is retired,
   *     HALT bit is not set) and WDH bit is not set in INTSTATUS - in
   *     this case we set o->bad_OHCI=TRUE and do alternate loop
   *     and error handling - but there is problem how to find retired
   *     TD with error code if HALT occurs and if DONEHEAD is not
   *     working - we need to find TD previous to current ED HEAD
   *  -- bad code of this driver or some unknown reasons - :-(
   *     it can be e.g. bad handling of EDs/TDs/toggle bit...
   */

  /* Remember target for debug and set skip flag in ED */
  /* It should be normaly not necessary but we need it at least
   * in case of timeout */
  target = grub_le_to_cpu32 ( cdata->ed_virt->target );
  cdata->ed_virt->target = grub_cpu_to_le32 (target | (1 << 14));
  /* Read registers for debug - they should be read now because
   * debug prints case unwanted delays, so something can happen
   * in the meantime... */
  control = grub_ohci_readreg32 (o, GRUB_OHCI_REG_CONTROL);
  status = grub_ohci_readreg32 (o, GRUB_OHCI_REG_CMDSTATUS);
  intstatus = grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS);
  /* Now print debug values - to have full info what happened */
  grub_dprintf ("ohci", "loop finished: control=0x%02x status=0x%02x\n",
		control, status);
  grub_dprintf ("ohci", "intstatus=0x%02x, td_last_phys=0x%02x\n",
		intstatus, cdata->td_last_phys);
  grub_dprintf ("ohci", "TARGET=0x%02x, HEAD=0x%02x, TAIL=0x%02x\n",
                target,
                grub_le_to_cpu32 (cdata->ed_virt->td_head),
                grub_le_to_cpu32 (cdata->ed_virt->td_tail) );

}

static void
finish_transfer (grub_usb_controller_t dev,
		 grub_usb_transfer_t transfer)
{
  struct grub_ohci *o = dev->data;
  struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data;

  /* Set empty ED - set HEAD = TAIL = last (not processed) TD */
  cdata->ed_virt->td_head = grub_cpu_to_le32 (grub_le_to_cpu32 (cdata->ed_virt->td_tail) & ~0xf); 

  /* At this point always should be:
   * ED has skip bit set and halted or empty or after next SOF,
   *    i.e. it is safe to free all TDs except last not processed
   * ED HEAD == TAIL == phys. addr. of td_current_virt */

  /* Un-chainig of last TD */
  if (cdata->td_current_virt->prev_td_phys)
    {
      grub_ohci_td_t td_prev_virt
	= grub_ohci_td_phys2virt (o, cdata->td_current_virt->prev_td_phys);
      
      if (cdata->td_current_virt == (grub_ohci_td_t) td_prev_virt->link_td)
        td_prev_virt->link_td = 0;

      cdata->td_current_virt->prev_td_phys = 0;
    }

  grub_dprintf ("ohci", "OHCI finished, freeing\n");
  grub_ohci_free_tds (o, cdata->td_head_virt);
  grub_free (cdata);
}

static grub_usb_err_t
parse_halt (grub_usb_controller_t dev,
	    grub_usb_transfer_t transfer,
	    grub_size_t *actual)
{
  struct grub_ohci *o = dev->data;
  struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data;
  grub_uint8_t errcode = 0;
  grub_usb_err_t err = GRUB_USB_ERR_NAK;
  grub_ohci_td_t tderr_virt = NULL;

  *actual = 0;

  pre_finish_transfer (dev, transfer);

  /* First we must get proper tderr_phys value */
  /* Retired TD with error should be previous TD to ED->td_head */
  cdata->tderr_phys = grub_ohci_td_phys2virt (o,
                                                grub_le_to_cpu32 (cdata->ed_virt->td_head) & ~0xf )
	              ->prev_td_phys;

  /* Prepare pointer to last processed TD and get error code */
  tderr_virt = grub_ohci_td_phys2virt (o, cdata->tderr_phys);
  /* Set index of last processed TD */
  if (tderr_virt)
    {
      errcode = grub_le_to_cpu32 (tderr_virt->token) >> 28;
      transfer->last_trans = tderr_virt->tr_index;
    }
  else
    transfer->last_trans = -1;
  
  /* Evaluation of error code */
  grub_dprintf ("ohci", "OHCI tderr_phys=0x%02x, errcode=0x%02x\n",
		cdata->tderr_phys, errcode);
  switch (errcode)
    {
    case 0:
      /* XXX: Should not happen!  */
      grub_error (GRUB_ERR_IO, "OHCI failed without reporting the reason");
      err = GRUB_USB_ERR_INTERNAL;
      break;

    case 1:
      /* XXX: CRC error.  */
      err = GRUB_USB_ERR_TIMEOUT;
      break;

    case 2:
      err = GRUB_USB_ERR_BITSTUFF;
      break;

    case 3:
      /* XXX: Data Toggle error.  */
      err = GRUB_USB_ERR_DATA;
      break;

    case 4:
      err = GRUB_USB_ERR_STALL;
      break;

    case 5:
      /* XXX: Not responding.  */
      err = GRUB_USB_ERR_TIMEOUT;
      break;

    case 6:
      /* XXX: PID Check bits failed.  */
      err = GRUB_USB_ERR_BABBLE;
      break;

    case 7:
      /* XXX: PID unexpected failed.  */
      err = GRUB_USB_ERR_BABBLE;
      break;

    case 8:
      /* XXX: Data overrun error.  */
      err = GRUB_USB_ERR_DATA;
      grub_dprintf ("ohci", "Overrun, failed TD address: %p, index: %d\n",
		    tderr_virt, tderr_virt->tr_index);
      break;

    case 9:
      /* XXX: Data underrun error.  */
      grub_dprintf ("ohci", "Underrun, failed TD address: %p, index: %d\n",
		    tderr_virt, tderr_virt->tr_index);
      if (transfer->last_trans == -1)
	break;
      *actual = transfer->transactions[transfer->last_trans].size
	- (grub_le_to_cpu32 (tderr_virt->buffer_end)
	   - grub_le_to_cpu32 (tderr_virt->buffer))
	+ transfer->transactions[transfer->last_trans].preceding;
      err = GRUB_USB_ERR_NONE;
      break;

    case 10:
      /* XXX: Reserved.  */
      err = GRUB_USB_ERR_NAK;
      break;

    case 11:
      /* XXX: Reserved.  */
      err = GRUB_USB_ERR_NAK;
      break;

    case 12:
      /* XXX: Buffer overrun.  */
      err = GRUB_USB_ERR_DATA;
      break;

    case 13:
      /* XXX: Buffer underrun.  */
      err = GRUB_USB_ERR_DATA;
      break;

    default:
      err = GRUB_USB_ERR_NAK;
      break;
    }

  finish_transfer (dev, transfer);

  return err;
}

static grub_usb_err_t
parse_success (grub_usb_controller_t dev,
	       grub_usb_transfer_t transfer,
	       grub_size_t *actual)
{
  struct grub_ohci *o = dev->data;
  struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data;
  grub_ohci_td_t tderr_virt = NULL;

  pre_finish_transfer (dev, transfer);

  /* I hope we can do it as transfer (most probably) finished OK */
  cdata->tderr_phys = cdata->td_last_phys;

  /* Prepare pointer to last processed TD */
  tderr_virt = grub_ohci_td_phys2virt (o, cdata->tderr_phys);
  
  /* Set index of last processed TD */
  if (tderr_virt)
    transfer->last_trans = tderr_virt->tr_index;
  else
    transfer->last_trans = -1;
  *actual = transfer->size + 1;

  finish_transfer (dev, transfer);

  return GRUB_USB_ERR_NONE;
}

static grub_usb_err_t
parse_unrec (grub_usb_controller_t dev,
	     grub_usb_transfer_t transfer,
	     grub_size_t *actual)
{
  struct grub_ohci *o = dev->data;

  *actual = 0;

  pre_finish_transfer (dev, transfer);

  /* Don't try to get error code and last processed TD for proper
   * toggle bit value - anything can be invalid */
  grub_dprintf("ohci", "Unrecoverable error!");

  /* Do OHCI reset in case of unrecoverable error - maybe we will need
   * do more - re-enumerate bus etc. (?) */

  /* Suspend the OHCI by issuing a reset.  */
  grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, 1); /* XXX: Magic.  */
  /* Read back of register should ensure it is really written */
  grub_ohci_readreg32 (o, GRUB_OHCI_REG_CMDSTATUS);
  grub_millisleep (1);
  grub_dprintf ("ohci", "Unrecoverable error - OHCI reset\n");

  /* Misc. resets. */
  o->hcca->donehead = 0;
  grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, 0x7f); /* Clears everything */
  grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLHEAD, o->ed_ctrl_addr);
  grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLCURR, 0);
  grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKHEAD, o->ed_bulk_addr);
  grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKCURR, 0);
  /* Read back of register should ensure it is really written */
  grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS);

  /* Enable the OHCI.  */
  grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL,
			(2 << 6)
			| GRUB_OHCI_REG_CONTROL_CONTROL_ENABLE
			| GRUB_OHCI_REG_CONTROL_BULK_ENABLE );
  finish_transfer (dev, transfer);

  return GRUB_USB_ERR_UNRECOVERABLE;
}

static grub_usb_err_t
grub_ohci_check_transfer (grub_usb_controller_t dev,
			  grub_usb_transfer_t transfer,
			  grub_size_t *actual)
{
  struct grub_ohci *o = dev->data;
  struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data;
  grub_uint32_t intstatus;

  /* Check transfer status */
  intstatus = grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS);

  if ((intstatus & 0x10) != 0)
    /* Unrecoverable error - only reset can help...! */
    return parse_unrec (dev, transfer, actual);

  /* Detected a HALT.  */
  if ((grub_le_to_cpu32 (cdata->ed_virt->td_head) & 1))
    return parse_halt (dev, transfer, actual);

  /* Finished ED detection */
  if ( (grub_le_to_cpu32 (cdata->ed_virt->td_head) & ~0xfU) ==
       (grub_le_to_cpu32 (cdata->ed_virt->td_tail) & ~0xfU) ) /* Empty ED */
    {
      /* Check the HALT bit */
      /* It looks like nonsense - it was tested previously...
       * but it can change because OHCI is working
       * simultaneously via DMA... */
      if (grub_le_to_cpu32 (cdata->ed_virt->td_head) & 1)
	return parse_halt (dev, transfer, actual);
      else
        return parse_success (dev, transfer, actual);
    }

  return GRUB_USB_ERR_WAIT;
}

static grub_usb_err_t
grub_ohci_cancel_transfer (grub_usb_controller_t dev,
			   grub_usb_transfer_t transfer)
{
  struct grub_ohci *o = dev->data;
  struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data;
  grub_ohci_td_t tderr_virt = NULL;

  pre_finish_transfer (dev, transfer);

  grub_dprintf("ohci", "Timeout !\n");

  /* We should wait for next SOF to be sure that ED is unaccessed
   * by OHCI */
  /* SF bit reset. (SF bit indicates Start Of Frame (SOF) packet) */
  grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, (1<<2));
  /* Wait for new SOF */
  while ((grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS) & 0x4) == 0);

  /* Possible retired TD with error should be previous TD to ED->td_head */
  cdata->tderr_phys
    = grub_ohci_td_phys2virt (o, grub_le_to_cpu32 (cdata->ed_virt->td_head)
                              & ~0xf)->prev_td_phys;
    
  tderr_virt = grub_ohci_td_phys2virt (o,cdata-> tderr_phys);

  grub_dprintf ("ohci", "Cancel: tderr_phys=0x%x, tderr_virt=%p\n",
                cdata->tderr_phys, tderr_virt);

  if (tderr_virt)
    transfer->last_trans = tderr_virt->tr_index;
  else
    transfer->last_trans = -1;

  finish_transfer (dev, transfer);

  return GRUB_USB_ERR_NONE;
}

static grub_usb_err_t
grub_ohci_portstatus (grub_usb_controller_t dev,
		      unsigned int port, unsigned int enable)
{
   struct grub_ohci *o = (struct grub_ohci *) dev->data;
   grub_uint64_t endtime;
   int i;

   grub_dprintf ("ohci", "begin of portstatus=0x%02x\n",
                 grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port));

   if (!enable) /* We don't need reset port */
     {
       /* Disable the port and wait for it. */
       grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
                             GRUB_OHCI_CLEAR_PORT_ENABLE);
       endtime = grub_get_time_ms () + 1000;
       while ((grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port)
               & (1 << 1)))
         if (grub_get_time_ms () > endtime)
           return GRUB_USB_ERR_TIMEOUT;

       grub_dprintf ("ohci", "end of portstatus=0x%02x\n",
         grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port));
       return GRUB_USB_ERR_NONE;
     }
     
   /* OHCI does one reset signal 10ms long but USB spec.
    * requests 50ms for root hub (no need to be continuous).
    * So, we do reset 5 times... */
   for (i = 0; i < 5; i++)
     {
       /* Reset the port - timing of reset is done by OHCI */
       grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
                             GRUB_OHCI_SET_PORT_RESET);

       /* Wait for reset completion */
       endtime = grub_get_time_ms () + 1000;
       while (! (grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port)
               & GRUB_OHCI_SET_PORT_RESET_STATUS_CHANGE))
         if (grub_get_time_ms () > endtime)
           return GRUB_USB_ERR_TIMEOUT;

       /* End the reset signaling - reset the reset status change */
       grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
			     GRUB_OHCI_SET_PORT_RESET_STATUS_CHANGE);
       grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port);
     }

   /* Enable port */
   grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
                         GRUB_OHCI_SET_PORT_ENABLE);
   grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port);
   
   /* Wait for signal enabled */
   endtime = grub_get_time_ms () + 1000;
   while (! (grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port)
           & (1 << 1)))
     if (grub_get_time_ms () > endtime)
       return GRUB_USB_ERR_TIMEOUT;

   /* Reset bit Connect Status Change */
   grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
                         GRUB_OHCI_RESET_CONNECT_CHANGE);

   /* "Reset recovery time" (USB spec.) */
   grub_millisleep (10);
   
   grub_dprintf ("ohci", "end of portstatus=0x%02x\n",
		 grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port));
 
   return GRUB_USB_ERR_NONE;
}

static grub_usb_speed_t
grub_ohci_detect_dev (grub_usb_controller_t dev, int port, int *changed)
{
   struct grub_ohci *o = (struct grub_ohci *) dev->data;
   grub_uint32_t status;

   status = grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port);

   grub_dprintf ("ohci", "detect_dev status=0x%02x\n", status);

   /* Connect Status Change bit - it detects change of connection */
   if (status & GRUB_OHCI_RESET_CONNECT_CHANGE)
     {
       *changed = 1;
       /* Reset bit Connect Status Change */
       grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
			     GRUB_OHCI_RESET_CONNECT_CHANGE);
     }
   else
     *changed = 0;

   if (! (status & 1))
     return GRUB_USB_SPEED_NONE;
   else if (status & (1 << 9))
     return GRUB_USB_SPEED_LOW;
   else
     return GRUB_USB_SPEED_FULL;
}

static int
grub_ohci_hubports (grub_usb_controller_t dev)
{
  struct grub_ohci *o = (struct grub_ohci *) dev->data;
  grub_uint32_t portinfo;

  portinfo = grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBA);

  grub_dprintf ("ohci", "root hub ports=%d\n", portinfo & 0xFF);

  return portinfo & 0xFF;
}

static grub_err_t
grub_ohci_fini_hw (int noreturn __attribute__ ((unused)))
{
  struct grub_ohci *o;

  for (o = ohci; o; o = o->next)
    {
      int i, nports = grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBA) & 0xff;
      grub_uint64_t maxtime;

      /* Set skip in all EDs */
      if (o->ed_bulk)
        for (i=0; i < GRUB_OHCI_BULK_EDS; i++)
          o->ed_bulk[i].target |= grub_cpu_to_le32_compile_time (1 << 14); /* skip */
      if (o->ed_ctrl)
        for (i=0; i < GRUB_OHCI_CTRL_EDS; i++)
          o->ed_ctrl[i].target |= grub_cpu_to_le32_compile_time (1 << 14); /* skip */

      /* We should wait for next SOF to be sure that all EDs are
       * unaccessed by OHCI. But OHCI can be non-functional, so
       * more than 1ms timeout have to be applied. */
      /* SF bit reset. (SF bit indicates Start Of Frame (SOF) packet) */
      grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, (1<<2));
      maxtime = grub_get_time_ms () + 2;
      /* Wait for new SOF or timeout */
      while ( ((grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS) & 0x4)
                 == 0) || (grub_get_time_ms () >= maxtime) );

      for (i = 0; i < nports; i++)
	grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + i,
			      GRUB_OHCI_CLEAR_PORT_ENABLE);

      grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, 1);
      grub_millisleep (1);
      grub_ohci_writereg32 (o, GRUB_OHCI_REG_HCCA, 0);
      grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLHEAD, 0);
      grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLCURR, 0);
      grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKHEAD, 0);
      grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKCURR, 0);
      grub_ohci_writereg32 (o, GRUB_OHCI_REG_DONEHEAD, 0);
      grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL, 0);
      /* Read back of register should ensure it is really written */
      grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS);

#if 0 /* Is this necessary before booting? Probably not .(?)
       * But it must be done if module is removed ! (Or not ?)
       * How to do it ? - Probably grub_ohci_restore_hw should be more
       * complicated. (?)
       * (If we do it, we need to reallocate EDs and TDs in function
       * grub_ohci_restore_hw ! */

      /* Free allocated EDs and TDs */
      grub_dma_free (o->td_chunk);
      grub_dma_free (o->ed_bulk_chunk);
      grub_dma_free (o->ed_ctrl_chunk);
      grub_dma_free (o->hcca_chunk);
#endif
    }
  grub_millisleep (10);

  return GRUB_ERR_NONE;
}

static grub_err_t
grub_ohci_restore_hw (void)
{
  struct grub_ohci *o;

  for (o = ohci; o; o = o->next)
    {
      grub_ohci_writereg32 (o, GRUB_OHCI_REG_HCCA, o->hcca_addr);
      o->hcca->donehead = 0;
      grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, 0x7f); /* Clears everything */
      grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLHEAD, o->ed_ctrl_addr);
      grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLCURR, 0);
      grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKHEAD, o->ed_bulk_addr);
      grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKCURR, 0);
      /* Read back of register should ensure it is really written */
      grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS);

      /* Enable the OHCI.  */
      grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL,
                            (2 << 6)
                            | GRUB_OHCI_REG_CONTROL_CONTROL_ENABLE
                            | GRUB_OHCI_REG_CONTROL_BULK_ENABLE );
    }

  return GRUB_ERR_NONE;
}


static struct grub_usb_controller_dev usb_controller =
{
  .name = "ohci",
  .iterate = grub_ohci_iterate,
  .setup_transfer = grub_ohci_setup_transfer,
  .check_transfer = grub_ohci_check_transfer,
  .cancel_transfer = grub_ohci_cancel_transfer,
  .hubports = grub_ohci_hubports,
  .portstatus = grub_ohci_portstatus,
  .detect_dev = grub_ohci_detect_dev,
  /* estimated max. count of TDs for one bulk transfer */
  .max_bulk_tds = GRUB_OHCI_TDS * 3 / 4
};

static struct grub_preboot *fini_hnd;

GRUB_MOD_INIT(ohci)
{
  COMPILE_TIME_ASSERT (sizeof (struct grub_ohci_td) == 32);
  COMPILE_TIME_ASSERT (sizeof (struct grub_ohci_ed) == 16);

  grub_stop_disk_firmware ();

  grub_ohci_inithw ();
  grub_usb_controller_dev_register (&usb_controller);
  fini_hnd = grub_loader_register_preboot_hook (grub_ohci_fini_hw,
						grub_ohci_restore_hw,
						GRUB_LOADER_PREBOOT_HOOK_PRIO_DISK);
}

GRUB_MOD_FINI(ohci)
{
  grub_ohci_fini_hw (0);
  grub_loader_unregister_preboot_hook (fini_hnd);
  grub_usb_controller_dev_unregister (&usb_controller);
}