5c5709a54eed55c8314d06df29c0e6a3a05f6999
[libxhcidbg.git] / src / hw-dbc.adb
1 --
2 -- Copyright (C) 2016-2017 secunet Security Networks AG
3 --
4 -- This program is free software; you can redistribute it and/or modify
5 -- it under the terms of the GNU General Public License as published by
6 -- the Free Software Foundation; either version 2 of the License, or
7 -- (at your option) any later version.
8 --
9 -- This program is distributed in the hope that it will be useful,
10 -- but WITHOUT ANY WARRANTY; without even the implied warranty of
11 -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 -- GNU General Public License for more details.
13 --
14
15 with System;
16
17 with HW.Time;
18 with HW.Debug;
19
20 with HW.DbC.Intel_Quirk;
21 with HW.DbC.DMA_Buffers;
22 with HW.DbC.Transfer_Info;
23 with HW.DbC.Transfer_Rings;
24 with HW.DbC.Transfers;
25 with HW.DbC.Contexts;
26 with HW.DbC.Events;
27 with HW.DbC.TRBs;
28
29 package body HW.DbC
30 with
31    Refined_State => (State => (Reset_Intermission_End, Connected, Running,
32                                DbC_Run_Deadline, DbC_Poll_Deadline,
33                                DbC_Stat_Deadline, Events.State,
34                                Transfer_Info.State, Transfer_Rings.State),
35                      DMA   => (ERST, DbC_Context, Desc_Strings, Events.DMA,
36                                Transfer_Rings.DMA))
37 is
38
39    Perform_Hardware_Reset : constant Boolean := True;
40    Apply_Intel_Quirk : constant Boolean := True;
41    Debug_xCap : constant Boolean := False;
42
43    Reset_Intermission_MS : constant := 736;  -- seems reliable above 722ms
44    Reset_Intermission_End : Time.T;
45
46    Connected,
47    Running : Boolean;
48    DbC_Run_Deadline : Time.T;
49    DbC_Poll_Deadline : Time.T;
50    DbC_Stat_Deadline : Time.T;
51
52    ----------------------------------------------------------------------------
53
54    ERST : Events.ERST_Entry
55    with
56       Address => System'To_Address (DMA_Buffers.Event_Ring_Segment_Table_Base);
57
58    DbC_Context : Contexts.DbC_Context
59    with
60       Address => System'To_Address (DMA_Buffers.DbC_Context_Base);
61
62    ----------------------------------------------------------------------------
63
64    subtype Desc_Str_Range is Natural range 0 .. 14;
65    type Desc_Str is array (Desc_Str_Range) of Word16 with Pack;
66    type String_Descriptor is record
67       bLength           : Byte;
68       bDescriptor_Type  : Byte;
69       wData             : Desc_Str;
70    end record with Pack;
71
72    type Desc_Strings_Type is (String0, Manufacturer, Product, Serial_Number);
73    type Desc_Strings_Array is
74       array (Desc_Strings_Type) of String_Descriptor with Pack;
75    Desc_Strings : Desc_Strings_Array
76    with
77       Address => System'To_Address (DMA_Buffers.Descriptor_Strings_Base);
78
79    procedure String_To_Desc (Dst : out String_Descriptor; Src : in String)
80    is
81       use type Byte;
82    begin
83       Dst.bLength := 2 + 2 * Byte'Min (Dst.wData'Length, Src'Length);
84       Dst.bDescriptor_Type := 16#03#;
85       for I in Desc_Str_Range loop
86          if I < Src'Last then
87             Dst.wData (I) := Character'Pos (Src (I + 1));
88          else
89             Dst.wData (I) := 16#0000#;
90          end if;
91       end loop;
92    end String_To_Desc;
93
94    ----------------------------------------------------------------------------
95
96    procedure Find_Next_xCap (Cap_Id : in Word8; Success : out Boolean)
97    is
98       use type Word8;
99       use type Word32;
100       Current_Id : Word8;
101       Temp_Offset : Word32;
102    begin
103       Success := False;
104       if xCap_Regs.Byte_Offset = 0 then
105          Cap_Regs.Read (Temp_Offset, XHCI_Extended_Caps);
106       else
107          xCap_Regs.Read (Temp_Offset, Next_xCap);
108       end if;
109       loop
110          Temp_Offset := Shift_Left (Temp_Offset, 2);
111          pragma Debug (Debug_xCap, Debug.Put_Reg32
112            ("Find_Next_xCap Offset", Temp_Offset));
113          exit when
114             Temp_Offset = 0 or else
115             xCap_Regs.Byte_Offset > MMIO_Size - Natural (Temp_Offset) - 2;
116
117          xCap_Regs.Byte_Offset := xCap_Regs.Byte_Offset + Natural (Temp_Offset);
118
119          xCap_Regs.Read (Current_Id, Capability_ID);
120          Success := Current_Id = Cap_Id;
121          pragma Debug (Debug_xCap, Debug.Put_Reg8
122            ("Find_Next_xCap Cap_Id", Current_Id));
123          exit when Success;
124
125          xCap_Regs.Read (Temp_Offset, Next_xCap);
126       end loop;
127    end Find_Next_xCap;
128
129    ----------------------------------------------------------------------------
130
131    procedure BIOS_Handover (Success : out Boolean)
132    is
133       use type Word8;
134       BIOS_Owned  : Word8;
135       Deadline    : Time.T;
136    begin
137       xCap_Regs.Byte_Offset := 0;
138       Find_Next_xCap (1, Success);
139       if Success then
140          Legacy_Support_Regs.Byte_Offset := xCap_Regs.Byte_Offset;
141          -- See if the BIOS claims ownership
142          Legacy_Support_Regs.Read (BIOS_Owned, HC_BIOS_Owned_Semaphore);
143          if BIOS_Owned = 1 then
144             pragma Debug (Debug.Put_Line ("DbC: BIOS claims ownership."));
145
146             Legacy_Support_Regs.Write (HC_OS_Owned_Semaphore, Word8'(1));
147
148             Deadline := Time.MS_From_Now (5_000);
149             loop
150                Legacy_Support_Regs.Read (BIOS_Owned, HC_BIOS_Owned_Semaphore);
151                exit when BIOS_Owned = 0;
152                declare
153                   Timeout : constant Boolean := Time.Timed_Out (Deadline);
154                begin
155                   Success := not Timeout;
156                end;
157                exit when not Success;
158                for I in 0 .. 1234 loop
159                   null; -- Busy loop to reduce pressure on HC BIOS Owned
160                         -- Semaphore. It shouldn't generate an SMI but
161                         -- might congest the xHC?
162                end loop;
163             end loop;
164
165             pragma Debug (not Success, Debug.Put_Line
166               ("ERROR: BIOS didn't hand over xHC within 5s."));
167             pragma Debug (Success, Debug.Put_Line
168               ("DbC: BIOS hand-over succeeded."));
169          end if;
170       end if;
171    end BIOS_Handover;
172
173    procedure Reset_xHC (Success : out Boolean)
174    is
175       use type Word8;
176       HCH,
177       HCR : Word8;
178       Deadline : Time.T;
179    begin
180       Op_Regs.Write (Run_Stop, Word8'(0));
181       Deadline := Time.MS_From_Now (1_000);
182       Success := True;
183       loop
184          Op_Regs.Read (HCH, HC_Halted);
185          exit when HCH = 1;
186          Success := not Time.Timed_Out (Deadline);
187          exit when not Success;
188       end loop;
189       pragma Debug (not Success, Debug.Put_Line
190         ("ERROR: xHC didn't halt within 1s."));
191
192       if Success then
193          Op_Regs.Write (Host_Controller_Reset, Word8'(1));
194          Deadline := Time.MS_From_Now (1_000);
195
196          -- Some Intel xHCI implementations are known to freak out rarely
197          -- (anything can happen up to global reset assertion) if the
198          -- Host Controller Reset bit is polled before the controller is
199          -- ready.
200          Time.M_Delay (1); -- Delay here or hell freezes over
201
202          loop
203             Op_Regs.Read (HCR, Host_Controller_Reset);
204             exit when HCR = 0;
205             Success := not Time.Timed_Out (Deadline);
206             exit when not Success;
207          end loop;
208          pragma Debug (not Success, Debug.Put_Line
209            ("ERROR: xHC didn't finish reset within 1s."));
210       end if;
211    end Reset_xHC;
212
213    procedure Reset (Initial_Reset : Boolean := False);
214
215    procedure Init
216    is
217       use type Word8;
218       CNR : Word8;
219       Deadline : Time.T;
220       Success : Boolean;
221       Cap_Length : Word8;
222    begin
223       Cap_Regs.Read (Cap_Length, Capability_Registers_Length);
224       Op_Regs.Byte_Offset := Natural (Cap_Length);
225
226       Op_Regs.Read (CNR, Controller_Not_Ready);
227       Success := CNR = 0;
228
229       if not Success then
230          pragma Debug (Debug.Put_Line ("WARNING: xHCI not ready!"));
231          Deadline := Time.MS_From_Now (1_000);
232          Success := True;
233          loop
234             Op_Regs.Read (CNR, Controller_Not_Ready);
235             exit when CNR = 0;
236             Success := not Time.Timed_Out (Deadline);
237             exit when not Success;
238          end loop;
239          pragma Debug (not Success, Debug.Put_Line
240            ("ERROR: xHC not ready after 1s."));
241       end if;
242
243       if Success then
244          BIOS_Handover (Success);
245       end if;
246
247       if Perform_Hardware_Reset and then Success then
248          Reset_xHC (Success);
249          Reset_Intermission_End := Time.MS_From_Now (Reset_Intermission_MS);
250       else
251          Reset_Intermission_End := Time.Now;
252       end if;
253
254       if Success then
255          xCap_Regs.Byte_Offset := 0;
256          Find_Next_xCap (10, Success);
257       end if;
258
259       pragma Debug (not Success, Debug.Put_Line
260                     ("ERROR: Couldn't find xHCI debug capability."));
261
262       if Success then
263          Regs.Byte_Offset := xCap_Regs.Byte_Offset;
264
265          ERST := Events.ERST_Entry'
266            (Segment_Base   => DMA_Buffers.Event_Ring_Base,
267             Segment_Size   => TRBs.TRBs_Per_Ring,
268             Reserved_Z     => 0);
269
270          Desc_Strings (String0).bLength            := 16#04#;
271          Desc_Strings (String0).bDescriptor_Type   := 16#03#;
272          Desc_Strings (String0).wData := (0 => 16#0409#, others => 16#0000#);
273          String_To_Desc (Desc_Strings (Manufacturer), "secunet");
274          String_To_Desc (Desc_Strings (Product), "HW.DbC");
275          String_To_Desc (Desc_Strings (Serial_Number), "1");
276
277          Reset (Initial_Reset => True);
278       end if;
279    end Init;
280
281    ----------------------------------------------------------------------------
282
283    procedure Reset (Initial_Reset : Boolean := False)
284    is
285       use type Word8;
286       use type Word16;
287       use type Word64;
288       DCE,
289       MBS : Word8;
290    begin
291       if Regs.Byte_Offset /= 0 then
292          Regs.Write (DbC_Enable, Word8'(0));
293          loop
294             Regs.Read (DCE, DbC_Enable);
295             exit when DCE = 0;
296          end loop;
297
298          Transfers.Reset (Initial_Reset);
299
300          Regs.Write (ERST_Size, Word16'(1));
301          Regs.Write (ERST_Base_Lo, Word32
302            (DMA_Buffers.Event_Ring_Segment_Table_Base mod 16#1_0000_0000#));
303          Regs.Write (ERST_Base_Hi, Word32
304            (DMA_Buffers.Event_Ring_Segment_Table_Base  /  16#1_0000_0000#));
305          Events.Reset_Ring;
306
307          Regs.Write (ER_Dequeue_Ptr_Lo, Word32
308            (DMA_Buffers.Event_Ring_Base mod 16#1_0000_0000#));
309          Regs.Write (ER_Dequeue_Ptr_Hi, Word32
310            (DMA_Buffers.Event_Ring_Base  /  16#1_0000_0000#));
311
312          Regs.Write (Context_Pointer_Lo, Word32
313            (DMA_Buffers.DbC_Context_Base mod 16#1_0000_0000#));
314          Regs.Write (Context_Pointer_Hi, Word32
315            (DMA_Buffers.DbC_Context_Base  /  16#1_0000_0000#));
316
317          Contexts.Clear_DbC_Context (DbC_Context);
318          DbC_Context.DbC_Info :=
319            (String_0_Address              => DMA_Buffers.Descriptor_Strings_Base,
320             Manufacturer_String_Address   => DMA_Buffers.Descriptor_Strings_Base
321                                              + 1 * String_Descriptor'Size / 8,
322             Product_String_Address        => DMA_Buffers.Descriptor_Strings_Base
323                                              + 2 * String_Descriptor'Size / 8,
324             Serial_Number_String_Address  => DMA_Buffers.Descriptor_Strings_Base
325                                              + 3 * String_Descriptor'Size / 8,
326             String_0_Length               => Desc_Strings (String0).bLength,
327             Manufacturer_String_Length    => Desc_Strings (Manufacturer).bLength,
328             Product_String_Length         => Desc_Strings (Product).bLength,
329             Serial_Number_String_Length   => Desc_Strings (Serial_Number).bLength,
330             Reserved_Z                    => 0,
331             others                        => 0);
332
333          Regs.Read (MBS, Debug_Max_Burst_Size);
334          DbC_Context.OUT_EP.EP_Type                := Contexts.Bulk_O;
335          DbC_Context.OUT_EP.Max_Burst_Size         := MBS;
336          DbC_Context.OUT_EP.Max_Packet_Size        := 1024;
337          DbC_Context.OUT_EP.TR_Dequeue_Pointer_Lo  := Word28
338            (Shift_Right (Transfer_Rings.Physical (2),  4) and 16#0fff_ffff#);
339          DbC_Context.OUT_EP.TR_Dequeue_Pointer_Hi  := Word32
340            (Shift_Right (Transfer_Rings.Physical (2), 32) and 16#ffff_ffff#);
341          DbC_Context.OUT_EP.Dequeue_Cycle_State    := 1;
342          DbC_Context.OUT_EP.Average_TRB_Length     := Max_Bulk_Size / 2;
343          DbC_Context.IN_EP.EP_Type                 := Contexts.Bulk_I;
344          DbC_Context.IN_EP.Max_Burst_Size          := MBS;
345          DbC_Context.IN_EP.Max_Packet_Size         := 1024;
346          DbC_Context.IN_EP.TR_Dequeue_Pointer_Lo   := Word28
347            (Shift_Right (Transfer_Rings.Physical (3),  4) and 16#0fff_ffff#);
348          DbC_Context.IN_EP.TR_Dequeue_Pointer_Hi   := Word32
349            (Shift_Right (Transfer_Rings.Physical (3), 32) and 16#ffff_ffff#);
350          DbC_Context.IN_EP.Dequeue_Cycle_State     := 1;
351          DbC_Context.IN_EP.Average_TRB_Length      := Max_Bulk_Size / 2;
352
353          Regs.Write (DbC_Protocol, Word16'(0));  -- Debug Target vendor defined.
354          Regs.Write (Vendor_ID, Word16 (16#ffff#));
355          Regs.Write (Product_ID, Word16 (16#dbc1#));
356          Regs.Write (Device_Revision, Word16 (16#0001#));
357
358          Time.Delay_Until (Reset_Intermission_End);
359          Regs.Write (DbC_Enable, Word8'(1));
360          loop
361             Regs.Read (DCE, DbC_Enable);
362             exit when DCE = 1;
363          end loop;
364
365          if Apply_Intel_Quirk then
366             Intel_Quirk.Reset_Port;
367          end if;
368
369          Running := False;
370          Connected := False;
371          DbC_Poll_Deadline := Time.Now;
372          DbC_Stat_Deadline := Time.MS_From_Now (12_345);
373       end if;
374    end Reset;
375
376    procedure Poll (Now : Boolean := False)
377    is
378       use type Word8;
379       use type Word64;
380       Temp8 : Word8;
381       Timed_Out : Boolean;
382    begin
383       if Regs.Byte_Offset /= 0 then
384          Timed_Out := Now or else Time.Timed_Out (DbC_Poll_Deadline);
385          if Timed_Out then
386             Regs.Read (Temp8, DbC_Enable);
387             if Temp8 = 1 then
388                Regs.Read (Temp8, Current_Connect_Status);
389                if Temp8 = 1 then
390                   -- Something is connected...
391                   DbC_Poll_Deadline := Time.MS_From_Now (10);
392                   if not Connected then
393                      pragma Debug (Debug.Put_Line ("DbC connected."));
394                      DbC_Run_Deadline := Time.MS_From_Now (333);
395                      Connected := True;
396                   end if;
397                   Regs.Read (Temp8, DbC_Run);
398                   if Temp8 = 1 then
399                      -- ...configured too
400                      if not Running then
401                         pragma Debug (Debug.Put_Line ("DbC configured."));
402                         Transfers.Start;
403                         Running := True;
404                      end if;
405                   elsif Running then
406                      pragma Debug (Debug.Put_Line
407                        ("DbC still connected but deconfigured."));
408                      DbC_Run_Deadline := Time.MS_From_Now (333);
409                      Running := False;
410                   else
411                      Timed_Out := Time.Timed_Out (DbC_Run_Deadline);
412                      if Timed_Out then
413                         pragma Debug (Debug.Put_Line
414                           ("DbC connection timed out."));
415                         Reset;
416                      end if;
417                   end if;
418                else
419                   -- Nothing connected
420                   DbC_Poll_Deadline := Time.MS_From_Now (333);
421                   if Connected then
422                      pragma Debug (Debug.Put_Line ("DbC disconnected."));
423                      Connected := False;
424                      Running := False;
425                   end if;
426                end if;
427             else
428                Reset_Intermission_End :=
429                   Time.MS_From_Now (Reset_Intermission_MS);
430                pragma Debug (Debug.Put_Line ("DbC got disabled, huh?"));
431                Reset;
432             end if;
433             Events.Handle_Events;
434             Timed_Out := Time.Timed_Out (DbC_Stat_Deadline);
435             if Timed_Out then
436                pragma Debug (Transfer_Info.Dump_Stats);
437                DbC_Stat_Deadline := Time.MS_From_Now (12_345);
438             end if;
439          end if;
440       end if;
441    end Poll;
442
443    procedure Receive (Buf : in out Buffer; Len : in out Natural)
444    is
445    begin
446       Poll (Now => True);
447
448       Transfers.Receive (Buf, Len);
449    end Receive;
450
451    procedure Send (Buf : Buffer; Len : in out Natural; Success : out Boolean)
452    is
453    begin
454       Poll (Now => True);
455
456       Transfers.Send
457         (Buf         => Buf,
458          Len         => Len,
459          Start_Now   => Running,
460          Success     => Success);
461    end Send;
462
463    procedure Ring_Doorbell (EP : Endpoint_Range)
464    is
465       use type Word8;
466    begin
467       Regs.Write (Doorbell_Target, Word8 (EP) - 2);
468    end Ring_Doorbell;
469
470 end HW.DbC;
471
472 --  vim: set ts=8 sts=3 sw=3 et: