1 //
2 // Copyright (c) 2010-2024 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 System.IO;
9 using System.Runtime.InteropServices;
10 using Antmicro.Renode.Peripherals.Bus;
11 using Antmicro.Renode.Core.Structure.Registers;
12 using Antmicro.Renode.Core;
13 using Antmicro.Renode.Logging;
14 using Antmicro.Renode.Storage;
15 using Antmicro.Renode.Storage.VirtIO;
16 using Antmicro.Renode.Exceptions;
17 using Antmicro.Renode.Utilities;
18 using Antmicro.Renode.Utilities.Packets;
19 
20 namespace Antmicro.Renode.Peripherals.Storage
21 {
22     // VirtIO class implementing VirtIO block devices.
23     [AllowedTranslations(AllowedTranslation.ByteToDoubleWord)]
24     public class VirtIOBlockDevice : VirtIOMMIO, IDisposable
25     {
VirtIOBlockDevice(IMachine machine)26         public VirtIOBlockDevice(IMachine machine) : base(machine)
27         {
28             storage = DataStorage.Create(size: 0);
29             lastQueueIdx = 0;
30             Virtqueues = new Virtqueue[lastQueueIdx + 1];
31             for (int i = 0; i <= lastQueueIdx; i++)
32             {
33                 Virtqueues[i] = new Virtqueue(this, Virtqueue.QueueMaxSize);
34             }
35             BitHelper.SetBit(ref deviceFeatureBits, (byte)FeatureBits.BlockFlagFlush, true);
36             BitHelper.SetBit(ref deviceFeatureBits, (byte)FeatureBits.BlockFlagConfigWCE, true);
37             DefineRegisters();
38         }
39 
Dispose()40         public void Dispose()
41         {
42             storage?.Dispose();
43         }
44 
LoadImage(WriteFilePath file, bool persistent = false)45         public void LoadImage(WriteFilePath file, bool persistent = false)
46         {
47             storage?.Dispose();
48             storage = DataStorage.Create(file, persistent: persistent);
49             capacity = (long)Math.Ceiling((decimal)storage.Length / SectorSize);
50             configHasChanged.Value = true;
51             UpdateInterrupts();
52         }
53 
WriteStatus(Virtqueue vqueue)54         public void WriteStatus(Virtqueue vqueue)
55         {
56             vqueue.ReadDescriptorMetadata();
57             SystemBus.WriteByte(vqueue.Descriptor.BufferAddress, status);
58         }
59 
Flush()60         public void Flush()
61         {
62             storage.Flush();
63         }
64 
MarkAsUnsupported()65         public void MarkAsUnsupported()
66         {
67             status = (byte)VirtIOBlockRequestStatus.Unsupported;
68             this.Log(LogLevel.Warning, "Block operation unsupported.");
69         }
70 
ProcessChain(Virtqueue vqueue)71         public override bool ProcessChain(Virtqueue vqueue)
72         {
73             vqueue.ReadDescriptorMetadata();
74             vqueue.TryReadFromBuffers(Marshal.SizeOf(typeof(Header)), out var hdrBuff);
75             if(!Packet.TryDecode<Header>(hdrBuff, out var hdr))
76             {
77                 this.Log(LogLevel.Error, "Error decoding block request header");
78                 return false;
79             }
80             SeekToSector(hdr.sector);
81             vqueue.ReadDescriptorMetadata();
82             var length = vqueue.Descriptor.Length;
83 
84             switch(hdr.type)
85             {
86                 case BlockOperations.Out:
87                     if(!vqueue.TryReadFromBuffers(length, out var res))
88                     {
89                         return false;
90                     }
91                     storage.Write(res, 0, length);
92                     break;
93 
94                 case BlockOperations.In:
95                     byte[] driverBytes = new byte[length];
96                     storage.Read(driverBytes, 0, length);
97                     if(!vqueue.TryWriteToBuffers(driverBytes))
98                     {
99                         return false;
100                     }
101                     break;
102 
103                 case BlockOperations.Flush:
104                     if(IsFeatureEnabled((byte)FeatureBits.BlockFlagFlush))
105                     {
106                         Flush();
107                     }
108                     else
109                     {
110                         MarkAsUnsupported();
111                     }
112                     break;
113 
114                 default:
115                     this.Log(LogLevel.Error, "Unsupported block operation ({0})", hdr.type);
116                     break;
117             }
118 
119             WriteStatus(vqueue);
120             return true;
121         }
122 
123         protected override uint DeviceID => 0x2;
124 
DefineRegisters()125         private void DefineRegisters()
126         {
127             DefineMMIORegisters();
128             Registers.CapacityHigh.Define(this)
129                 .WithValueField(0, 32, FieldMode.Read, name: "capacity_high", valueProviderCallback: _ => (uint)(capacity >> 32));
130 
131             Registers.CapacityLow.Define(this)
132                 .WithValueField(0, 32, FieldMode.Read, name: "capacity_low", valueProviderCallback: _ => (uint)capacity);
133 
134             // With this register driver can choose whether it will use write-back or write-through caching mode.
135             // It should be 0 by default.
136             Registers.Writeback.Define(this)
137                 .WithValueField(0, 8, FieldMode.Read, name: "writeback", valueProviderCallback: _ => 0);
138         }
139 
SeekToSector(long sector)140         private void SeekToSector(long sector)
141         {
142             var positionToSeek = SectorSize * sector;
143             if(positionToSeek >= this.storage.Length)
144             {
145                 throw new RecoverableException("Driver tried to seek beyond the loaded image end.");
146             }
147             storage.Seek(positionToSeek, SeekOrigin.Begin);
148         }
149 
150         private long capacity;
151         private Stream storage;
152         private byte status;
153 
154         private const int SectorSize = 0x200;
155 
156         private enum FeatureBits : byte
157         {
158             // Block device specific flags
159             BlockFlagSizeMax = 1,
160             BlockFlagSegmentsMaxNum = 2,
161             BlockFlagGeometry = 4,
162             BlockFlagReadOnly = 5,
163             BlockFlagBlockSize = 6,
164             BlockFlagFlush = 9,
165             BlockFlagTopology = 10,
166             BlockFlagConfigWCE = 11,
167             BlockFlagDiscard = 13,
168             BlockFlagWriteZeroes = 14,
169         }
170 
171         private enum BlockRequestHeader
172         {
173             Type = 0x0,
174             SectorLow = 0x8,
175             SectorHigh = 0xc,
176         }
177 
178         private enum BlockOperations : int
179         {
180             In = 0,
181             Out = 1,
182             Flush = 4,
183             Discard = 11,
184             WriteZeroes = 13,
185         }
186 
187         private enum VirtIOBlockRequestStatus : byte
188         {
189             Success = 0,
190             IoError = 1,
191             Unsupported = 2,
192         }
193 
194         private enum Registers : long
195         {
196             // Configuration space for block device
197             // https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.pdf#subsection.5.2.4
198             CapacityLow = 0x100,
199             CapacityHigh = 0x104,
200             SizeMax = 0x108,
201             SegMax = 0x10c,
202             Geometry = 0x110,
203             BlockSize = 0x114,
204             TopologyHigh = 0x118,
205             TopologyLow = 0x11c,
206             Writeback = 0x120,
207             MaxDiscardSectors = 0x124,
208             MaxDiscardSeg = 0x128,
209             DiscardSectorAlignment = 0x12c,
210             MaxWriteZeroesSectors = 0x130,
211             MaxWriteZeroesSeg = 0x134,
212             WriteZeroesMayUnmap = 0x138,
213         }
214 
215         [LeastSignificantByteFirst]
216         private struct Header
217         {
218             #pragma warning disable 0649
219             [PacketField, Width(32)]
220             public BlockOperations type;
221             [PacketField, Offset(doubleWords: 2), Width(64)]
222             public long sector;
223             #pragma warning restore 0649
224             // we don't use other fields from the documentation
225         }
226     }
227 }
228