1 //
2 // Copyright (c) 2010-2020 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.Collections.Generic;
9 using System.IO;
10 using System.Linq;
11 using Antmicro.Renode.Core;
12 using Antmicro.Renode.Core.Structure;
13 using Antmicro.Renode.Exceptions;
14 using Antmicro.Renode.Logging;
15 using Antmicro.Renode.Storage;
16 using Antmicro.Renode.Storage.SCSI;
17 using Antmicro.Renode.Storage.SCSI.Commands;
18 using Antmicro.Renode.Utilities;
19 using Antmicro.Renode.Utilities.Packets;
20 
21 namespace Antmicro.Renode.Peripherals.ATAPI
22 {
23     //This device implements ATAPI CDROM
24     public class CDROM : IAtapiPeripheral, IDisposable
25     {
CDROM(string imageFile, bool persistent = false, uint? size = null, uint blockSize = 2048)26         public CDROM(string imageFile, bool persistent = false, uint? size = null, uint blockSize = 2048)
27         {
28             BlockSize = blockSize;
29             dataBackend = DataStorage.Create(imageFile, size, persistent);
30 
31             var sizeMisalignment = dataBackend.Length % blockSize;
32             if(sizeMisalignment != 0)
33             {
34                 dataBackend.SetLength(dataBackend.Length + (blockSize - sizeMisalignment));
35                 this.Log(LogLevel.Warning, "Underlying data size extended by {0} bytes to align it to the block size ({1} bytes)", blockSize - sizeMisalignment, blockSize);
36             }
37         }
38 
Dispose()39         public void Dispose()
40         {
41             dataBackend.Dispose();
42         }
43 
Reset()44         public void Reset()
45         {
46             dataQueue.Clear();
47             dataBackend.Position = 0;
48         }
49 
SendIdentifyResponse()50         public void SendIdentifyResponse()
51         {
52             //First byte of Identify device responser - sets device type
53             var response = new byte[] {0x00, 0x05};
54             QueueData(response);
55         }
56 
DequeueData()57         public ushort DequeueData()
58         {
59             return dataQueue.TryDequeue(out var ret) ? ret : (ushort)0;
60         }
61 
HandleCommand(byte[] packet)62         public void HandleCommand(byte[] packet)
63         {
64             var command = SCSICommandDescriptorBlock.DecodeCommand(packet, 0);
65             this.Log(LogLevel.Debug, "Decoded command: {0}", command);
66             switch(command)
67             {
68                 case SCSICommand.TestUnitReady:
69                     break;
70                 case SCSICommand.Inquiry:
71                     // this is just an empty stub
72                     QueueData(new byte[36]);
73                     break;
74                 case SCSICommand.Read10:
75                     var cmd = Packet.DecodeDynamic<IReadWrite10Command>(packet, 0);
76                     this.Log(LogLevel.Debug, "Command args: LogicalBlockAddress: 0x{0:x}, TransferLength: {1}", (uint)cmd.LogicalBlockAddress, (ushort)cmd.TransferLength);
77                     var bytesCount = (int)(cmd.TransferLength * BlockSize);
78                     var readPosition = (long)cmd.LogicalBlockAddress * BlockSize;
79                     dataBackend.Position = readPosition;
80                     var data = dataBackend.ReadBytes(bytesCount);
81                     this.Log(LogLevel.Debug, "Reading {0} bytes from address 0x{1:x}", bytesCount, readPosition);
82                     QueueData(data);
83                     break;
84                 case SCSICommand.ModeSense6:
85                     // this is just an empty stub
86                     QueueData(new byte[192]);
87                     break;
88                 case SCSICommand.RequestSense:
89                     // this is just an empty stub
90                     QueueData(new byte[512]);
91                     break;
92                 default:
93                     this.Log(LogLevel.Error, "Unsupported SCSI command: {0}", command);
94                     break;
95             }
96         }
97 
98         public uint BlockSize { get; }
99         public bool DataReady { get { return dataQueue.Count != 0;}}
100 
QueueData(byte[] data)101         private void QueueData(byte[] data)
102         {
103             var dataLength = data.Length;
104             if(dataLength % 2 != 0)
105             {
106                 this.Log(LogLevel.Error, "Trying to send odd number of bytes. Padding with zeros");
107                 data = data.Concat(new byte[] { 0 }).ToArray();
108                 dataLength += 1;
109             }
110 
111             for(uint i = 0; i < dataLength - 1; i += 2)
112             {
113                 var val = data[i] | (data[i + 1] << 8);
114                 dataQueue.Enqueue((ushort)val);
115             }
116         }
117 
118         private readonly Queue<ushort> dataQueue = new Queue<ushort>();
119 
120         private readonly Stream dataBackend;
121     }
122 }
123 
124