f5847d999d2d3cf88542a95424b78a9bf38dce4c
[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.TRB_Types;
176       use type TRBs.Ring_Range;
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             Transfer_Info.Finish
207               (DMA_Physical      => TRBs.Get_Parameter
208                                       (Rings (EP) (Pointers (EP).Dequeue)),
209                Status            => Controller_Error,
210                Remaining_Length  => Max_Bulk_Size);
211             Dequeue_TRB (EP);
212          end loop;
213          Transfer_Info.Finish
214            (DMA_Physical      => TRBs.Get_Parameter (Rings (EP) (Current)),
215             Status            => Status,
216             Remaining_Length  => Remaining_Length);
217          Dequeue_TRB (EP);
218       end if;
219
220       pragma Debug (Invalid, Debug.Put_Reg64
221         ("WARNING: Invalid dequeue pointer", Pointer));
222       pragma Debug (Invalid, Debug.Put_Reg64
223         ("                   Ring physical", Physical (EP)));
224       pragma Debug (Invalid, Debug.Put ("Enqueue: "));
225       pragma Debug (Invalid, Debug.Put_Int32 (Int32 (Pointers (EP).Enqueue)));
226       pragma Debug (Invalid, Debug.Put ("; Dequeue: "));
227       pragma Debug (Invalid, Debug.Put_Int32 (Int32 (Pointers (EP).Dequeue)));
228       pragma Debug (Invalid, Debug.Put ("; Current: "));
229       pragma Debug (Invalid, Debug.Put_Int32 (Int32 (Current)));
230       pragma Debug (Invalid, Debug.New_Line);
231    end Dequeue;
232
233    ----------------------------------------------------------------------------
234
235    procedure Enqueue_Data_TRB
236      (EP                : Endpoint_Range;
237       Data_Length       : Natural;
238       Data_Buf          : Word64;
239       Toggle_CS         : Boolean)
240    is
241       Current : TRBs.Ring_Range;
242    begin
243       Current := Pointers (EP).Enqueue;
244
245       TRBs.Clear (Rings (EP) (Current), PCS (EP));
246       TRBs.Set_Parameter (Rings (EP) (Current), Data_Buf);
247       TRBs.Set_Length (Rings (EP) (Current), Data_Length);
248
249       TRBs.Set_Type (Rings (EP) (Current), TRBs.Normal);
250       TRBs.Set_IOC (Rings (EP) (Current));
251       TRBs.Set_ISP (Rings (EP) (Current));
252
253       Enqueue_TRB (EP);
254       if Toggle_CS then
255          Transfer_Rings.Toggle_CS (EP);
256       end if;
257    end Enqueue_Data_TRB;
258
259    procedure Requeue_Data_TRB
260      (EP       : Endpoint_Range;
261       Length   : Natural;
262       Buf_Addr : Word64)
263    is
264    begin
265       Pointers (EP).Enqueue := Prev (Pointers (EP).Enqueue);
266       Enqueue_Data_TRB (EP, Length, Buf_Addr, False);
267    end Requeue_Data_TRB;
268
269 end HW.DbC.Transfer_Rings;
270
271 --  vim: set ts=8 sts=3 sw=3 et: