921b21a577af283567570fa92ec98c656556e52e
[libxhcidbg.git] / src / hw-dbc-transfer_rings.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 with System.Storage_Elements;
17
18 with HW.Debug;
19 with HW.DbC.DMA_Buffers;
20 with HW.DbC.Transfer_Info;
21 with HW.DbC.TRBs;
22
23 package body HW.DbC.Transfer_Rings
24 with
25    Refined_State =>
26      (State => (PCS, Pointers),
27       DMA   => (Rings))
28 is
29
30    type Transfer_Ring_Array is array (Endpoint_Range) of TRBs.Transfer_Ring
31    with
32       Pack;
33    Rings : Transfer_Ring_Array
34    with
35       Volatile,
36       Async_Readers,
37       Async_Writers,
38       Address => System'To_Address (DMA_Buffers.Transfer_Rings_Base);
39
40    type PCS_Array is array (Endpoint_Range) of Bit;
41    PCS : PCS_Array;
42
43    type Transfer_Pointers is record
44       Enqueue  : TRBs.Ring_Range;
45       Dequeue  : TRBs.Ring_Range;
46       Full     : Boolean;
47       Toggle   : TRBs.Ring_Range;
48       Overrun  : Boolean;
49    end record;
50    type Transfer_Pointers_Array is array (Endpoint_Range) of Transfer_Pointers;
51    Pointers : Transfer_Pointers_Array;
52
53    --  Returns the physical address of the transfer ring specified by given EP.
54    function Get_Ring_Address (EP : Endpoint_Range) return Word64;
55
56    ----------------------------------------------------------------------------
57
58    function Physical (EP : Endpoint_Range) return Word64
59    is
60    begin
61       return
62          DMA_Buffers.Transfer_Rings_Base + (Word64 (EP) - 2) * TRBs.Ring_Size;
63    end Physical;
64
65    function Full (EP : Endpoint_Range) return Boolean
66    is
67    begin
68       return Pointers (EP).Full;
69    end Full;
70
71    function Last_Started (EP : Endpoint_Range) return Boolean
72    is
73       use type TRBs.Ring_Range;
74    begin
75       return
76          not Pointers (EP).Overrun and
77          Pointers (EP).Toggle = Pointers (EP).Enqueue;
78    end Last_Started;
79
80    function Get_Ring_Address (EP : Endpoint_Range) return Word64
81    with SPARK_Mode => Off
82    is
83    begin
84       return Word64
85         (System.Storage_Elements.To_Integer (Rings (EP)'Address));
86    end Get_Ring_Address;
87
88    ----------------------------------------------------------------------------
89
90    procedure Initialize (EP : Endpoint_Range)
91    is
92    begin
93       pragma Debug (Debug.Put ("Initializing Transfer_Ring at "));
94       pragma Debug (Debug.Put_Word64 (Get_Ring_Address (EP)));
95       pragma Debug (Debug.Put (" (phys: "));
96       pragma Debug (Debug.Put_Word64 (Physical (EP)));
97       pragma Debug (Debug.Put_Line (")"));
98       TRBs.Init_Cycle_Ring
99         (Physical => Physical (EP),
100          Ring     => Rings (EP));
101
102       PCS (EP)       := 1;
103       Pointers (EP)  := Transfer_Pointers'
104         (Enqueue => TRBs.Ring_Range'First,
105          Dequeue => TRBs.Ring_Range'First,
106          Full    => False,
107          Toggle  => TRBs.Ring_Range'First,
108          Overrun => False);
109    end Initialize;
110
111    ----------------------------------------------------------------------------
112
113    procedure Toggle_CS (EP : Endpoint_Range)
114    is
115       use type TRBs.Ring_Range;
116       Toggle : TRBs.Ring_Range;
117    begin
118       Toggle := Pointers (EP).Toggle;
119       while Toggle /= Pointers (EP).Enqueue or Pointers (EP).Overrun loop
120          TRBs.Set_Cycle (Rings (EP) (Toggle), PCS (EP));
121          if Toggle = TRBs.Ring_Range'Last then
122             PCS (EP) := 1 - PCS (EP);
123          end if;
124          Toggle := Toggle + 1;
125
126          Pointers (EP).Overrun := False;
127       end loop;
128       Pointers (EP).Toggle := Toggle;
129    end Toggle_CS;
130
131    ----------------------------------------------------------------------------
132
133    function Prev (Current : TRBs.Ring_Range) return TRBs.Ring_Range
134    is
135       use type TRBs.Ring_Range;
136    begin
137       return Current - (if Current = TRBs.Ring_Range'First then 2 else 1);
138    end Prev;
139
140    function Next (Current : TRBs.Ring_Range) return TRBs.Ring_Range
141    is
142       use type TRBs.Ring_Range;
143    begin
144       return Current + (if Current + 1 = TRBs.Ring_Range'Last then 2 else 1);
145    end Next;
146
147    procedure Enqueue_TRB (EP : Endpoint_Range)
148    is
149       use type TRBs.Ring_Range;
150    begin
151       Pointers (EP).Enqueue := Next (Pointers (EP).Enqueue);
152       if Pointers (EP).Enqueue = Pointers (EP).Toggle then
153          Pointers (EP).Overrun := True;
154       end if;
155       if Pointers (EP).Enqueue = Pointers (EP).Dequeue then
156          Pointers (EP).Full := True;
157       end if;
158    end Enqueue_TRB;
159
160    procedure Dequeue_TRB (EP : Endpoint_Range)
161    is
162    begin
163       Pointers (EP).Dequeue := Next (Pointers (EP).Dequeue);
164       Pointers (EP).Full := False;
165    end Dequeue_TRB;
166
167    ----------------------------------------------------------------------------
168
169    procedure Dequeue
170      (EP                : Endpoint_Range;
171       Pointer           : Word64;
172       Status            : Error;
173       Remaining_Length  : Natural)
174    is
175       use type TRBs.Ring_Range;
176
177       Current : TRBs.Ring_Range;
178       Invalid : Boolean;
179    begin
180       if Physical (EP) <= Pointer and
181          Pointer - Physical (EP) < TRBs.Ring_Size and
182          (Pointer - Physical (EP)) mod TRBs.TRB_Size = 0
183       then
184          Current := TRBs.Ring_Range
185            ((Pointer - Physical (EP)) / TRBs.TRB_Size);
186
187          if Pointers (EP).Dequeue < Pointers (EP).Enqueue then
188             Invalid :=
189                Current >= Pointers (EP).Enqueue or
190                Current < Pointers (EP).Dequeue;
191          else
192             Invalid :=
193                Current >= Pointers (EP).Enqueue and
194                Current < Pointers (EP).Dequeue;
195          end if;
196          Invalid := Invalid or Current = TRBs.Ring_Range'Last;
197       else
198          Current := TRBs.Ring_Range'First;
199          Invalid := True;
200       end if;
201
202       if not Invalid then
203          while Pointers (EP).Dequeue /= Current loop
204             -- Finish transfers that have been skipped by the controller
205             pragma Debug (Debug.Put_Line ("WARNING: Skipped TD."));
206             declare
207                Cur_Address : Word64;
208             begin
209                TRBs.Get_Parameter
210                  (Data      => Rings (EP) (Pointers (EP).Dequeue),
211                   Parameter => Cur_Address);
212                Transfer_Info.Finish
213                  (DMA_Physical      => Cur_Address,
214                   Status            => Controller_Error,
215                   Remaining_Length  => Max_Bulk_Size);
216             end;
217             Dequeue_TRB (EP);
218          end loop;
219          declare
220             Cur_Address : Word64;
221          begin
222             TRBs.Get_Parameter
223               (Data      => Rings (EP) (Current),
224                Parameter => Cur_Address);
225             Transfer_Info.Finish
226               (DMA_Physical      => Cur_Address,
227                Status            => Status,
228                Remaining_Length  => Remaining_Length);
229          end;
230          Dequeue_TRB (EP);
231       end if;
232
233       pragma Debug (Invalid, Debug.Put_Reg64
234         ("WARNING: Invalid dequeue pointer", Pointer));
235       pragma Debug (Invalid, Debug.Put_Reg64
236         ("                   Ring physical", Physical (EP)));
237       pragma Debug (Invalid, Debug.Put ("Enqueue: "));
238       pragma Debug (Invalid, Debug.Put_Int32 (Int32 (Pointers (EP).Enqueue)));
239       pragma Debug (Invalid, Debug.Put ("; Dequeue: "));
240       pragma Debug (Invalid, Debug.Put_Int32 (Int32 (Pointers (EP).Dequeue)));
241       pragma Debug (Invalid, Debug.Put ("; Current: "));
242       pragma Debug (Invalid, Debug.Put_Int32 (Int32 (Current)));
243       pragma Debug (Invalid, Debug.New_Line);
244    end Dequeue;
245
246    ----------------------------------------------------------------------------
247
248    procedure Enqueue_Data_TRB
249      (EP                : Endpoint_Range;
250       Data_Length       : Natural;
251       Data_Buf          : Word64;
252       Toggle_CS         : Boolean)
253    is
254       Current : TRBs.Ring_Range;
255    begin
256       Current := Pointers (EP).Enqueue;
257
258       TRBs.Clear (Rings (EP) (Current), PCS (EP));
259       TRBs.Set_Parameter (Rings (EP) (Current), Data_Buf);
260       TRBs.Set_Length (Rings (EP) (Current), Data_Length);
261
262       TRBs.Set_Type (Rings (EP) (Current), TRBs.Normal);
263       TRBs.Set_IOC (Rings (EP) (Current));
264       TRBs.Set_ISP (Rings (EP) (Current));
265
266       Enqueue_TRB (EP);
267       if Toggle_CS then
268          Transfer_Rings.Toggle_CS (EP);
269       end if;
270    end Enqueue_Data_TRB;
271
272    procedure Requeue_Data_TRB
273      (EP       : Endpoint_Range;
274       Length   : Natural;
275       Buf_Addr : Word64)
276    is
277    begin
278       Pointers (EP).Enqueue := Prev (Pointers (EP).Enqueue);
279       Enqueue_Data_TRB (EP, Length, Buf_Addr, False);
280    end Requeue_Data_TRB;
281
282 end HW.DbC.Transfer_Rings;
283
284 --  vim: set ts=8 sts=3 sw=3 et: