1 #if !PLATFORM_WINDOWS
2 //
3 // Copyright (c) 2010-2024 Antmicro
4 //
5 // This file is licensed under the MIT License.
6 // Full license text is available in 'licenses/MIT.txt'.
7 //
8 using System.Text;
9 using System;
10 using System.IO;
11 using Mono.Unix;
12 using System.Net.Sockets;
13 using Antmicro.Renode.Sockets;
14 using Antmicro.Renode.Peripherals.Bus;
15 using Antmicro.Renode.Core.Structure.Registers;
16 using Antmicro.Renode.Utilities;
17 using Antmicro.Renode.Core;
18 using Antmicro.Renode.Exceptions;
19 using Antmicro.Renode.Logging;
20 using Antmicro.Renode.Storage.VirtIO;
21 
22 namespace Antmicro.Renode.Peripherals.Storage
23 {
24     [AllowedTranslations(AllowedTranslation.ByteToDoubleWord)]
25     public class VirtIOFSDevice : VirtIOMMIO, IDisposable
26     {
VirtIOFSDevice(IMachine machine)27         public VirtIOFSDevice(IMachine machine) : base(machine)
28         {
29             BitHelper.SetBit(ref deviceFeatureBits, (byte)FeatureBits.FSFlagNotification, true);
30         }
31 
Dispose()32         public void Dispose()
33         {
34             fsSocket?.Close();
35         }
36 
Create(string fsSocketPath, string tag = R, uint numRequestQueues = 1, uint queueSize = 128)37         public void Create(string fsSocketPath, string tag = "dummyfs",
38                                 uint numRequestQueues = 1, uint queueSize = 128)
39         {
40             if(String.IsNullOrEmpty(fsSocketPath))
41             {
42                 throw new RecoverableException("Missing filesystem socket path");
43             }
44             if(String.IsNullOrEmpty(tag))
45             {
46                 throw new RecoverableException("Missing tag property");
47             }
48             if(numRequestQueues == 0)
49             {
50                 throw new RecoverableException("num-request-queues property must be larger than 0");
51             }
52             if(!Misc.IsPowerOfTwo(queueSize))
53             {
54                 throw new RecoverableException("queue-size property must be a power of 2");
55             }
56             if(queueSize > Virtqueue.QueueMaxSize)
57             {
58                 throw new RecoverableException(String.Format("queue-size property must be {0} or smaller", Virtqueue.QueueMaxSize));
59             }
60 
61             this.Log(LogLevel.Debug, "Looking for UDS socket in path: {0}", Path.GetFullPath(fsSocketPath));
62             fsSocket = SocketsManager.Instance.AcquireSocket(this, AddressFamily.Unix, SocketType.Stream, ProtocolType.IP, new UnixEndPoint(fsSocketPath), asClient: true);
63 
64             StoreTag(tag, this.tag);
65 
66             this.queueSize = queueSize;
67             this.numRequestQueues = numRequestQueues;
68             notifyBufSize = 2 * 255 + 2 * 48; //size of fuse_notify_fsnotify_out
69             // https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.pdf#subsection.5.11.2
70             lastQueueIdx = numRequestQueues + 1;
71             Virtqueues = new Virtqueue[lastQueueIdx + 1];
72             for (int i = 0; i <= lastQueueIdx; i++)
73             {
74                 Virtqueues[i] = new Virtqueue(this, queueSize);
75             }
76             DefineRegisters();
77             UpdateInterrupts();
78         }
79 
ProcessChain(Virtqueue vqueue)80         public override bool ProcessChain(Virtqueue vqueue)
81         {
82             if(fsSocket == null)
83             {
84                 return false;
85             }
86             // Read request from buffers //
87             if(!vqueue.TryReadFromBuffers(FuseInHdrLen, out var fuseInHdr))
88             {
89                 this.Log(LogLevel.Error, "Error reading FUSE request header");
90                 return false;
91             }
92 
93             this.Log(LogLevel.Debug, "FUSE request header: {0}", Misc.PrettyPrintCollection(fuseInHdr));
94             fsSocket.Send(fuseInHdr);
95 
96             var fuseInLen = BitConverter.ToInt32(fuseInHdr, 0);
97             if(!vqueue.TryReadFromBuffers(fuseInLen - FuseInHdrLen, out var fuseInData))
98             {
99                 this.Log(LogLevel.Error, "Error reading FUSE request data");
100                 return false;
101             }
102             this.Log(LogLevel.Debug, "FUSE request data: {0}", Misc.PrettyPrintCollection(fuseInData));
103             fsSocket.Send(fuseInData);
104 
105             // Read response from UDS //
106             var socketHdr = new byte[FuseOutHdrLen];
107             fsSocket.Receive(socketHdr);
108             this.Log(LogLevel.Debug, "FUSE response header: {0}", Misc.PrettyPrintCollection(socketHdr));
109             if(!vqueue.TryWriteToBuffers(socketHdr))
110             {
111                 this.Log(LogLevel.Error, "Error writing FUSE response header to buffer");
112                 return false;
113             }
114 
115             var fuseOutLen = BitConverter.ToInt32(socketHdr, 0);
116             if(fuseOutLen > FuseOutHdrLen)
117             {
118                 byte[] socketData = new byte[fuseOutLen-FuseOutHdrLen];
119                 fsSocket.Receive(socketData);
120                 this.Log(LogLevel.Debug, "FUSE response data: {0}", Misc.PrettyPrintCollection(socketData));
121                 vqueue.TryWriteToBuffers(socketData);
122             }
123             return true;
124         }
125 
126         protected override uint DeviceID => 26;
127 
StoreTag(string tag, byte[] container)128         private void StoreTag(string tag, byte[] container)
129         {
130             tag = tag.Substring(0, Math.Min((int)MaxTagLen, tag.Length));
131             byte[] toCopy = Encoding.ASCII.GetBytes(tag);
132             Array.Copy(toCopy, container, toCopy.Length);
133         }
134 
DefineRegisters()135         private void DefineRegisters()
136         {
137             DefineMMIORegisters();
138             Registers.Tag.DefineMany(this, 9, (reg, idx) =>
139             {
140                 reg.WithValueFields(0, 8, 4, FieldMode.Read, name: "tag", valueProviderCallback: (i, _) => tag[idx * 4 + i]);
141             });
142             Registers.NumRequestQueues.Define(this)
143                 .WithValueField(0, 32, FieldMode.Read, name: "num_virtqueues", valueProviderCallback: _ => numRequestQueues);
144             Registers.NotifyBufSize.Define(this)
145                 .WithValueField(0, 32, FieldMode.Read, name: "notify_buf_size", valueProviderCallback: _ => notifyBufSize);
146         }
147 
148         private byte[] tag = new byte[MaxTagLen];
149         private uint queueSize;
150         private Socket fsSocket;
151         private uint numRequestQueues;
152         private uint notifyBufSize;
153 
154         private const int FuseInHdrLen = 40;
155         private const int FuseOutHdrLen = 16;
156         private const uint MaxTagLen = 36;
157 
158         private enum FeatureBits : byte
159         {
160             // File System device specific flags
161             // https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.pdf#subsection.5.11.3
162             FSFlagNotification = 0,
163         }
164 
165         private enum Registers : long
166         {
167             // Configuration space for file system device
168             // https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.pdf#subsubsection.5.11.4.1
169             Tag = 0x100,
170             NumRequestQueues = 0x124,
171             NotifyBufSize = 0x128,
172         }
173     }
174 }
175 #endif
176