1 //
2 // Copyright (c) 2010-2025 Antmicro
3 //
4 // This file is licensed under the MIT License.
5 // Full license text is available in 'licenses/MIT.txt'.
6 //
7 using System;
8 using Antmicro.Renode.Logging;
9 using Antmicro.Renode.Peripherals.CPU;
10 using Antmicro.Renode.Utilities;
11 using Antmicro.Renode.Utilities.Packets;
12 
13 namespace Antmicro.Renode.Storage.VirtIO
14 {
15     // Implementation of split virtqueue
16     // Source: https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html#x1-350007
17     public class Virtqueue
18     {
Virtqueue(VirtIO parent, uint maxSize)19         public Virtqueue(VirtIO parent, uint maxSize)
20         {
21             this.parent = parent;
22             this.maxSize = maxSize;
23         }
24 
Reset()25         public void Reset()
26         {
27             DescTableAddress = 0;
28             AvailableAddress = 0;
29             UsedAddress = 0;
30             AvailableIndex = 0;
31             AvailableIndexFromDriver = 0;
32             UsedIndex = 0;
33             UsedIndexForDriver = 0;
34             IsReady = false;
35             IsReset = false;
36         }
37 
TryReadFromBuffers(int len, out byte[] data)38         public bool TryReadFromBuffers(int len, out byte[] data)
39         {
40             var readLen = 0;
41             data = new byte[len];
42             while(readLen < len)
43             {
44                 ReadDescriptorMetadata();
45                 var toRead = (int)Math.Min(Descriptor.Length, len - readLen);
46                 parent.Log(LogLevel.Debug, "Reading data from buffer at addr {0}, toRead {1}, length {2}, len {3}", Descriptor.BufferAddress, toRead, Descriptor.Length, len);
47 
48                 var readData = parent.SystemBus.ReadBytes(Descriptor.BufferAddress, toRead, context: GetCurrentContext());
49                 parent.Log(LogLevel.Debug, "Read data: {0}", Misc.PrettyPrintCollection(readData));
50 
51                 Array.Copy(readData, 0, data, (int)readLen, toRead);
52                 readLen += toRead;
53 
54                 var nextFlag = TrySetNextIndex();
55                 parent.Log(LogLevel.Debug, "Next flag: {0}", nextFlag);
56                 if(!nextFlag)
57                 {
58                     return false;
59                 }
60                 if(readLen < len)
61                 {
62                     parent.Log(LogLevel.Debug, "Reading next buffer");
63                 }
64             }
65             return true;
66         }
67 
TryWriteToBuffers(byte[] data)68         public bool TryWriteToBuffers(byte[] data)
69         {
70             var writtenLen = 0;
71             var dataLen = data.Length;
72             while(writtenLen < dataLen)
73             {
74                 ReadDescriptorMetadata();
75                 if(!CanSafelyWriteToBuffer())
76                 {
77                     return false;
78                 }
79                 var toWrite = Math.Min(data.Length, Descriptor.Length);
80                 parent.SystemBus.WriteBytes(data, Descriptor.BufferAddress, (long)toWrite, context: GetCurrentContext());
81 
82                 writtenLen += toWrite;
83                 var dataLeft = new byte[data.Length - toWrite];
84                 Array.Copy(data, toWrite, dataLeft, 0, dataLeft.Length);
85                 data = dataLeft;
86 
87                 BytesWritten += Descriptor.Length;
88                 parent.Log(LogLevel.Debug, "Wrote {0} bytes", toWrite);
89                 if(!TrySetNextIndex())
90                 {
91                     break;
92                 }
93                 if(writtenLen < dataLen)
94                 {
95                     parent.Log(LogLevel.Debug, "Continuing writing in next buffer...");
96                 }
97             }
98             return true;
99         }
100 
Handle()101         public void Handle()
102         {
103             var idx = (ushort)parent.SystemBus.ReadWord(AvailableAddress + (ulong)UsedAndAvailable.Index, context: GetCurrentContext());
104             // Processing all available requests
105             // We're using 2 variables: availableIndex, availableIndexFromDriver
106             // because we have to compare this value to index field in driver's
107             // struct for available descriptors.
108             // This field is meant to start at 0, then only increase and wrap around 65535.
109             // That's why we have one variable for comparing and the other one for accessing tables.
110             // Source: https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-5300013
111             while(AvailableIndexFromDriver < idx)
112             {
113                 BytesWritten = 0;
114                 var descriptor = ReadDescriptorFromAvail();
115                 DescriptorIndex = descriptor.Item1;
116 
117                 if(!parent.ProcessChain(this))
118                 {
119                     parent.Log(LogLevel.Error, "Error processing virtqueue requests");
120                     BytesWritten = 0;
121                     return;
122                 }
123                 WriteVirtqueueUsed(descriptor.Item1, descriptor.Item2);
124                 AvailableIndex = (AvailableIndex + 1u) % Size;
125                 AvailableIndexFromDriver++;
126             }
127         }
128 
129         // Write processed chain to used descriptors table.
130         // usedIndex and usedIndexForDriver work analogically
131         // to availableIndex and availableIndexFromDevice.
WriteVirtqueueUsed(int chainFirstIndex, bool noInterruptOnUsed)132         public void WriteVirtqueueUsed(int chainFirstIndex, bool noInterruptOnUsed)
133         {
134             var context = GetCurrentContext();
135             var ringAddress = UsedAddress + (ulong)UsedAndAvailable.Ring
136                     + UsedRingEntrySize * ((ulong)UsedIndex);
137             UsedIndex = (ushort)((UsedIndex + 1u) % Size);
138             UsedIndexForDriver++;
139             parent.SystemBus.WriteWord(UsedAddress + (ulong)UsedAndAvailable.Flags, 0, context: context);
140             parent.SystemBus.WriteDoubleWord(ringAddress + (ulong)UsedRing.Index, (uint)chainFirstIndex, context: context);
141             parent.SystemBus.WriteDoubleWord(ringAddress + (ulong)UsedRing.Length, (uint)BytesWritten, context: context);
142             parent.SystemBus.WriteWord(UsedAddress + (ulong)UsedAndAvailable.Index, (ushort)UsedIndexForDriver, context: context);
143             if(!noInterruptOnUsed)
144             {
145                 parent.InterruptUsedBuffer();
146             }
147         }
148 
TrySetNextIndex()149         public bool TrySetNextIndex()
150         {
151             if((Descriptor.Flags & (ushort)DescriptorFlags.Next) != 0)
152             {
153                 DescriptorIndex = Descriptor.Next;
154                 return true;
155             }
156             return false;
157         }
158 
ReadDescriptorMetadata()159         public void ReadDescriptorMetadata()
160         {
161             parent.Log(LogLevel.Debug, "Reading desc meta, queueSel: {0}, descIndex: {1}", parent.QueueSel, DescriptorIndex);
162             var descriptorAddress = DescTableAddress + DescriptorSize * (ulong)DescriptorIndex;
163             var scanBytes = parent.SystemBus.ReadBytes(descriptorAddress, DescriptorSizeOffset, context: GetCurrentContext());
164             Descriptor = Packet.Decode<DescriptorMetadata>(scanBytes);
165             parent.Log(LogLevel.Debug, "Processing buffer of addr: {0}, next: {1}, length: {2}, flags: {3}", Descriptor.BufferAddress, Descriptor.Next, Descriptor.Length, Descriptor.Flags);
166         }
167 
168         public ulong Size { get; set; }
169         /// Guest physical address of the descriptor table.
170         public ulong DescTableAddress { get; set; }
171         /// Guest physical address of the available ring.
172         public ulong AvailableAddress { get; set; }
173         /// Guest physical address of the used ring.
174         public ulong UsedAddress { get; set; }
175         public int DescriptorIndex { get; set; }
176         public ulong AvailableIndex { get; set; }
177         public ulong AvailableIndexFromDriver { get; set; }
178         public ulong UsedIndex { get; set; }
179         public ulong UsedIndexForDriver { get; set; }
180         public bool IsReady { get; set; }
181         public bool IsReset { get; set; }
182         public DescriptorMetadata Descriptor { get; set; }
183         public int BytesWritten { get; set; }
184 
185         public readonly uint maxSize;
186 
187         public const uint AvailableRingEntrySize = 0x2;
188         public const uint UsedRingEntrySize = 0x8;
189         public const uint DescriptorSize = 0x10;
190         public const uint QueueMaxSize = 1 << 15;
191         public const uint MaxBufferSize = 1 << 20;
192 
CanSafelyWriteToBuffer()193         private bool CanSafelyWriteToBuffer()
194         {
195             if((Descriptor.Flags & (ushort)DescriptorFlags.Write) == 0)
196             {
197                 parent.Log(LogLevel.Error, "IO Error: Trying to write to device-read buffer. Descriptor info: index: {0}, Address: {1}, Flags: {2}", DescriptorIndex, Descriptor.BufferAddress, Descriptor.Flags);
198                 return false;
199             }
200             return true;
201         }
202 
203         // Reads descriptor entry index and interrupt flag from available ring
ReadDescriptorFromAvail()204         private Tuple<int, bool> ReadDescriptorFromAvail()
205         {
206             var context = GetCurrentContext();
207             var flag = parent.SystemBus.ReadWord(AvailableAddress + (ulong)UsedAndAvailable.Flags, context: context);
208             var noInterruptOnUsed = (flag == (ushort)UsedAndAvailableFlags.NoNotify);
209             var chainFirstIndex = (int)parent.SystemBus.ReadWord(AvailableAddress +
210                 (ulong)UsedAndAvailable.Ring + AvailableRingEntrySize * (ulong)AvailableIndex, context: context);
211             DescriptorIndex = chainFirstIndex;
212             parent.Log(LogLevel.Debug, "Chain starting at index {0}", chainFirstIndex);
213             return Tuple.Create(chainFirstIndex, noInterruptOnUsed);
214         }
215 
216         // Returns null if no context was found; then only global peripherals will be accessible by DMA
GetCurrentContext()217         private ICPU GetCurrentContext()
218         {
219             if(!parent.SystemBus.TryGetCurrentCPU(out var cpu))
220             {
221                 return null;
222             }
223             return cpu;
224         }
225 
226         private readonly VirtIO parent;
227         private const int DescriptorSizeOffset = 0x12;
228 
229         // Used and Available have the same structure. The main difference is type of elements used in ring arrays.
230         // In Available it's only a 16bit number. In Used it's a structure described in UsedRing enum.
231         public enum UsedAndAvailable
232         {
233             Flags = 0x0,
234             Index = 0x2,
235             Ring = 0x4,
236         }
237 
238         public enum UsedRing
239         {
240             Index = 0x0,
241             Length = 0x4,
242         }
243 
244         [Flags]
245         public enum DescriptorFlags: ushort
246         {
247             Next = 1 << 0,
248             Write = 1 << 1,
249             Indirect = 1 << 2,
250         }
251 
252         [Flags]
253         public enum UsedAndAvailableFlags: ushort
254         {
255             NoNotify = 1 << 0,
256         }
257 
258         [LeastSignificantByteFirst]
259         public struct DescriptorMetadata
260         {
261             [PacketField, Width(64)]
262             public ulong BufferAddress;
263             [PacketField, Offset(doubleWords: 2), Width(32)]
264             public int Length;
265             [PacketField, Offset(doubleWords: 3), Width(16)]
266             public ushort Flags;
267             [PacketField, Offset(doubleWords: 3, bits: 16), Width(16)]
268             public ushort Next;
269         }
270     }
271 }
272