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