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