b0fc1e5fdaa4c93fad359a20b0b38f27c16b5525
[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 => (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    Apply_Intel_Quirk : constant Boolean := True;
40    Debug_xCap : constant Boolean := False;
41
42    Connected,
43    Running : Boolean;
44    DbC_Run_Deadline : Time.T;
45    DbC_Poll_Deadline : Time.T;
46    DbC_Stat_Deadline : Time.T;
47
48    ----------------------------------------------------------------------------
49
50    ERST : Events.ERST_Entry
51    with
52       Address => System'To_Address (DMA_Buffers.Event_Ring_Segment_Table_Base);
53
54    DbC_Context : Contexts.DbC_Context
55    with
56       Address => System'To_Address (DMA_Buffers.DbC_Context_Base);
57
58    ----------------------------------------------------------------------------
59
60    subtype Desc_Str_Range is Natural range 0 .. 14;
61    type Desc_Str is array (Desc_Str_Range) of Word16 with Pack;
62    type String_Descriptor is record
63       bLength           : Byte;
64       bDescriptor_Type  : Byte;
65       wData             : Desc_Str;
66    end record with Pack;
67
68    type Desc_Strings_Type is (String0, Manufacturer, Product, Serial_Number);
69    type Desc_Strings_Array is
70       array (Desc_Strings_Type) of String_Descriptor with Pack;
71    Desc_Strings : Desc_Strings_Array
72    with
73       Address => System'To_Address (DMA_Buffers.Descriptor_Strings_Base);
74
75    procedure String_To_Desc (Dst : out String_Descriptor; Src : in String)
76    is
77       use type Byte;
78    begin
79       Dst.bLength := 2 + 2 * Byte'Min (Dst.wData'Length, Src'Length);
80       Dst.bDescriptor_Type := 16#03#;
81       for I in Desc_Str_Range loop
82          if I < Src'Last then
83             Dst.wData (I) := Character'Pos (Src (I + 1));
84          else
85             Dst.wData (I) := 16#0000#;
86          end if;
87       end loop;
88    end String_To_Desc;
89
90    ----------------------------------------------------------------------------
91
92    procedure Find_Next_xCap (Cap_Id : in Word8; Success : out Boolean)
93    is
94       use type Word8;
95       use type Word32;
96       Current_Id : Word8;
97       Temp_Offset : Word32;
98    begin
99       Success := False;
100       if xCap_Regs.Byte_Offset = 0 then
101          Cap_Regs.Read (Temp_Offset, XHCI_Extended_Caps);
102       else
103          xCap_Regs.Read (Temp_Offset, Next_xCap);
104       end if;
105       loop
106          Temp_Offset := Shift_Left (Temp_Offset, 2);
107          pragma Debug (Debug_xCap, Debug.Put_Reg32
108            ("Find_Next_xCap Offset", Temp_Offset));
109          exit when
110             Temp_Offset = 0 or else
111             xCap_Regs.Byte_Offset > MMIO_Size - Natural (Temp_Offset) - 2;
112
113          xCap_Regs.Byte_Offset := xCap_Regs.Byte_Offset + Natural (Temp_Offset);
114
115          xCap_Regs.Read (Current_Id, Capability_ID);
116          Success := Current_Id = Cap_Id;
117          pragma Debug (Debug_xCap, Debug.Put_Reg8
118            ("Find_Next_xCap Cap_Id", Current_Id));
119          exit when Success;
120
121          xCap_Regs.Read (Temp_Offset, Next_xCap);
122       end loop;
123    end Find_Next_xCap;
124
125    ----------------------------------------------------------------------------
126
127    procedure BIOS_Handover (Success : out Boolean)
128    is
129       use type Word8;
130       BIOS_Owned  : Word8;
131       Deadline    : Time.T;
132    begin
133       xCap_Regs.Byte_Offset := 0;
134       Find_Next_xCap (1, Success);
135       if Success then
136          Legacy_Support_Regs.Byte_Offset := xCap_Regs.Byte_Offset;
137          -- See if the BIOS claims ownership
138          Legacy_Support_Regs.Read (BIOS_Owned, HC_BIOS_Owned_Semaphore);
139          if BIOS_Owned = 1 then
140             pragma Debug (Debug.Put_Line ("DbC: BIOS claims ownership."));
141
142             Legacy_Support_Regs.Write (HC_OS_Owned_Semaphore, Word8'(1));
143
144             Deadline := Time.MS_From_Now (5_000);
145             loop
146                Legacy_Support_Regs.Read (BIOS_Owned, HC_BIOS_Owned_Semaphore);
147                exit when BIOS_Owned = 0;
148                declare
149                   Timeout : constant Boolean := Time.Timed_Out (Deadline);
150                begin
151                   Success := not Timeout;
152                end;
153                exit when not Success;
154                for I in 0 .. 1234 loop
155                   null; -- Busy loop to reduce pressure on HC BIOS Owned
156                         -- Semaphore. It shouldn't generate an SMI but
157                         -- might congest the xHC?
158                end loop;
159             end loop;
160
161             pragma Debug (not Success, Debug.Put_Line
162               ("ERROR: BIOS didn't hand over xHC within 5s."));
163             pragma Debug (Success, Debug.Put_Line
164               ("DbC: BIOS hand-over succeeded."));
165          end if;
166       end if;
167    end BIOS_Handover;
168
169    procedure Reset (Initial_Reset : Boolean := False);
170
171    procedure Init
172    is
173       use type Word8;
174       CNR : Word8;
175       Deadline : Time.T;
176       Success : Boolean;
177       Cap_Length : Word8;
178    begin
179       Cap_Regs.Read (Cap_Length, Capability_Registers_Length);
180       Op_Regs.Byte_Offset := Natural (Cap_Length);
181
182       Op_Regs.Read (CNR, Controller_Not_Ready);
183       Success := CNR = 0;
184
185       if not Success then
186          pragma Debug (Debug.Put_Line ("WARNING: xHCI not ready!"));
187          Deadline := Time.MS_From_Now (1_000);
188          Success := True;
189          loop
190             Op_Regs.Read (CNR, Controller_Not_Ready);
191             exit when CNR = 0;
192             declare
193                Timed_Out : constant Boolean := Time.Timed_Out (Deadline);
194             begin
195                Success := not Timed_Out;
196             end;
197             exit when not Success;
198          end loop;
199          pragma Debug (not Success, Debug.Put_Line
200            ("ERROR: xHC not ready after 1s."));
201       end if;
202
203       if Success then
204          BIOS_Handover (Success);
205       end if;
206
207       if Success then
208          xCap_Regs.Byte_Offset := 0;
209          Find_Next_xCap (10, Success);
210       end if;
211
212       pragma Debug (not Success, Debug.Put_Line
213                     ("ERROR: Couldn't find xHCI debug capability."));
214
215       if Success then
216          Regs.Byte_Offset := xCap_Regs.Byte_Offset;
217
218          ERST := Events.ERST_Entry'
219            (Segment_Base   => DMA_Buffers.Event_Ring_Base,
220             Segment_Size   => TRBs.TRBs_Per_Ring,
221             Reserved_Z     => 0);
222
223          Desc_Strings (String0).bLength            := 16#04#;
224          Desc_Strings (String0).bDescriptor_Type   := 16#03#;
225          Desc_Strings (String0).wData := (0 => 16#0409#, others => 16#0000#);
226          String_To_Desc (Desc_Strings (Manufacturer), "secunet");
227          String_To_Desc (Desc_Strings (Product), "HW.DbC");
228          String_To_Desc (Desc_Strings (Serial_Number), "1");
229
230          Reset (Initial_Reset => True);
231       end if;
232    end Init;
233
234    ----------------------------------------------------------------------------
235
236    procedure Reset (Initial_Reset : Boolean := False)
237    is
238       use type Word8;
239       use type Word16;
240       use type Word64;
241       DCE,
242       MBS : Word8;
243    begin
244       if Regs.Byte_Offset /= 0 then
245          Regs.Write (DbC_Enable, Word8'(0));
246          loop
247             Regs.Read (DCE, DbC_Enable);
248             exit when DCE = 0;
249          end loop;
250
251          Transfers.Reset (Initial_Reset);
252
253          Regs.Write (ERST_Size, Word16'(1));
254          Regs.Write (ERST_Base_Lo, Word32
255            (DMA_Buffers.Event_Ring_Segment_Table_Base mod 16#1_0000_0000#));
256          Regs.Write (ERST_Base_Hi, Word32
257            (DMA_Buffers.Event_Ring_Segment_Table_Base  /  16#1_0000_0000#));
258          Events.Reset_Ring;
259
260          Regs.Write (ER_Dequeue_Ptr_Lo, Word32
261            (DMA_Buffers.Event_Ring_Base mod 16#1_0000_0000#));
262          Regs.Write (ER_Dequeue_Ptr_Hi, Word32
263            (DMA_Buffers.Event_Ring_Base  /  16#1_0000_0000#));
264
265          Regs.Write (Context_Pointer_Lo, Word32
266            (DMA_Buffers.DbC_Context_Base mod 16#1_0000_0000#));
267          Regs.Write (Context_Pointer_Hi, Word32
268            (DMA_Buffers.DbC_Context_Base  /  16#1_0000_0000#));
269
270          Contexts.Clear_DbC_Context (DbC_Context);
271          DbC_Context.DbC_Info :=
272            (String_0_Address              => DMA_Buffers.Descriptor_Strings_Base,
273             Manufacturer_String_Address   => DMA_Buffers.Descriptor_Strings_Base
274                                              + 1 * String_Descriptor'Size / 8,
275             Product_String_Address        => DMA_Buffers.Descriptor_Strings_Base
276                                              + 2 * String_Descriptor'Size / 8,
277             Serial_Number_String_Address  => DMA_Buffers.Descriptor_Strings_Base
278                                              + 3 * String_Descriptor'Size / 8,
279             String_0_Length               => Desc_Strings (String0).bLength,
280             Manufacturer_String_Length    => Desc_Strings (Manufacturer).bLength,
281             Product_String_Length         => Desc_Strings (Product).bLength,
282             Serial_Number_String_Length   => Desc_Strings (Serial_Number).bLength,
283             Reserved_Z                    => 0,
284             others                        => 0);
285
286          Regs.Read (MBS, Debug_Max_Burst_Size);
287          DbC_Context.OUT_EP.EP_Type                := Contexts.Bulk_O;
288          DbC_Context.OUT_EP.Max_Burst_Size         := MBS;
289          DbC_Context.OUT_EP.Max_Packet_Size        := 1024;
290          DbC_Context.OUT_EP.TR_Dequeue_Pointer_Lo  := Word28
291            (Shift_Right (Transfer_Rings.Physical (2),  4) and 16#0fff_ffff#);
292          DbC_Context.OUT_EP.TR_Dequeue_Pointer_Hi  := Word32
293            (Shift_Right (Transfer_Rings.Physical (2), 32) and 16#ffff_ffff#);
294          DbC_Context.OUT_EP.Dequeue_Cycle_State    := 1;
295          DbC_Context.OUT_EP.Average_TRB_Length     := Max_Bulk_Size / 2;
296          DbC_Context.IN_EP.EP_Type                 := Contexts.Bulk_I;
297          DbC_Context.IN_EP.Max_Burst_Size          := MBS;
298          DbC_Context.IN_EP.Max_Packet_Size         := 1024;
299          DbC_Context.IN_EP.TR_Dequeue_Pointer_Lo   := Word28
300            (Shift_Right (Transfer_Rings.Physical (3),  4) and 16#0fff_ffff#);
301          DbC_Context.IN_EP.TR_Dequeue_Pointer_Hi   := Word32
302            (Shift_Right (Transfer_Rings.Physical (3), 32) and 16#ffff_ffff#);
303          DbC_Context.IN_EP.Dequeue_Cycle_State     := 1;
304          DbC_Context.IN_EP.Average_TRB_Length      := Max_Bulk_Size / 2;
305
306          Regs.Write (DbC_Protocol, Word16'(0));  -- Debug Target vendor defined.
307          Regs.Write (Vendor_ID, Word16 (16#ffff#));
308          Regs.Write (Product_ID, Word16 (16#dbc1#));
309          Regs.Write (Device_Revision, Word16 (16#0001#));
310
311          Regs.Write (DbC_Enable, Word8'(1));
312          loop
313             Regs.Read (DCE, DbC_Enable);
314             exit when DCE = 1;
315          end loop;
316
317          if Apply_Intel_Quirk then
318             Intel_Quirk.Reset_Port;
319          end if;
320
321          Running := False;
322          Connected := False;
323          DbC_Poll_Deadline := Time.Now;
324          DbC_Stat_Deadline := Time.MS_From_Now (12_345);
325       end if;
326    end Reset;
327
328    procedure Poll (Now : Boolean := False)
329    is
330       use type Word8;
331
332       Temp8 : Word8;
333       Timed_Out : Boolean;
334    begin
335       if Regs.Byte_Offset /= 0 then
336          Timed_Out := Time.Timed_Out (DbC_Poll_Deadline);
337          if Now or else Timed_Out then
338             Regs.Read (Temp8, DbC_Enable);
339             if Temp8 = 1 then
340                Regs.Read (Temp8, Current_Connect_Status);
341                if Temp8 = 1 then
342                   -- Something is connected...
343                   DbC_Poll_Deadline := Time.MS_From_Now (10);
344                   if not Connected then
345                      pragma Debug (Debug.Put_Line ("DbC connected."));
346                      DbC_Run_Deadline := Time.MS_From_Now (333);
347                      Connected := True;
348                   end if;
349                   Regs.Read (Temp8, DbC_Run);
350                   if Temp8 = 1 then
351                      -- ...configured too
352                      if not Running then
353                         pragma Debug (Debug.Put_Line ("DbC configured."));
354                         Transfers.Start;
355                         Running := True;
356                      end if;
357                   elsif Running then
358                      pragma Debug (Debug.Put_Line
359                        ("DbC still connected but deconfigured."));
360                      DbC_Run_Deadline := Time.MS_From_Now (333);
361                      Running := False;
362                   else
363                      Timed_Out := Time.Timed_Out (DbC_Run_Deadline);
364                      if Timed_Out then
365                         pragma Debug (Debug.Put_Line
366                           ("DbC connection timed out."));
367                         Reset;
368                      end if;
369                   end if;
370                else
371                   -- Nothing connected
372                   DbC_Poll_Deadline := Time.MS_From_Now (333);
373                   if Connected then
374                      pragma Debug (Debug.Put_Line ("DbC disconnected."));
375                      Connected := False;
376                      Running := False;
377                   end if;
378                end if;
379             else
380                pragma Debug (Debug.Put_Line ("DbC got disabled, huh?"));
381                Reset;
382             end if;
383             Events.Handle_Events;
384             Timed_Out := Time.Timed_Out (DbC_Stat_Deadline);
385             if Timed_Out then
386                pragma Debug (Transfer_Info.Dump_Stats);
387                DbC_Stat_Deadline := Time.MS_From_Now (12_345);
388             end if;
389          end if;
390       end if;
391    end Poll;
392
393    procedure Receive (Buf : in out Buffer; Len : in out Natural)
394    is
395    begin
396       Poll (Now => True);
397
398       Transfers.Receive (Buf, Len);
399    end Receive;
400
401    procedure Send (Buf : Buffer; Len : in out Natural; Success : out Boolean)
402    is
403    begin
404       Poll (Now => True);
405
406       Transfers.Send
407         (Buf         => Buf,
408          Len         => Len,
409          Start_Now   => Running,
410          Success     => Success);
411    end Send;
412
413    procedure Ring_Doorbell (EP : Endpoint_Range)
414    is
415       use type Word8;
416    begin
417       Regs.Write (Doorbell_Target, Word8 (EP) - 2);
418    end Ring_Doorbell;
419
420 end HW.DbC;
421
422 --  vim: set ts=8 sts=3 sw=3 et: