Implement WRITE BUFFER and WRITE WITH VERIFY commands
[SCSI2SD.git] / software / SCSI2SD / src / cdrom.c
1 //      Copyright (C) 2014 Michael McMaster <michael@codesrc.com>
2 //
3 //      This file is part of SCSI2SD.
4 //
5 //      SCSI2SD is free software: you can redistribute it and/or modify
6 //      it under the terms of the GNU General Public License as published by
7 //      the Free Software Foundation, either version 3 of the License, or
8 //      (at your option) any later version.
9 //
10 //      SCSI2SD is distributed in the hope that it will be useful,
11 //      but WITHOUT ANY WARRANTY; without even the implied warranty of
12 //      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 //      GNU General Public License for more details.
14 //
15 //      You should have received a copy of the GNU General Public License
16 //      along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
17 #pragma GCC push_options
18 #pragma GCC optimize("-flto")
19
20 #include "device.h"
21 #include "scsi.h"
22 #include "config.h"
23 #include "cdrom.h"
24
25 static const uint8_t SimpleTOC[] =
26 {
27         0x00, // toc length, MSB
28         0x12, // toc length, LSB
29         0x01, // First track number
30         0x01, // Last track number,
31         // TRACK 1 Descriptor
32         0x00, // reserved
33         0x14, // Q sub-channel encodes current position, Digital track
34         0x01, // Track 1,
35         0x00, // Reserved
36         0x00,0x00,0x00,0x00, // Track start sector (LBA)
37         0x00, // reserved
38         0x14, // Q sub-channel encodes current position, Digital track
39         0xAA, // Leadout Track
40         0x00, // Reserved
41         0x00,0x00,0x00,0x00, // Track start sector (LBA)
42 };
43
44 static const uint8_t SessionTOC[] =
45 {
46         0x00, // toc length, MSB
47         0x0A, // toc length, LSB
48         0x01, // First session number
49         0x01, // Last session number,
50         // TRACK 1 Descriptor
51         0x00, // reserved
52         0x14, // Q sub-channel encodes current position, Digital track
53         0x01, // First track number in last complete session
54         0x00, // Reserved
55         0x00,0x00,0x00,0x00 // LBA of first track in last session
56 };
57
58
59 static const uint8_t FullTOC[] =
60 {
61         0x00, // toc length, MSB
62         0x44, // toc length, LSB
63         0x01, // First session number
64         0x01, // Last session number,
65         // A0 Descriptor
66         0x01, // session number
67         0x14, // ADR/Control
68         0x00, // TNO
69         0xA0, // POINT
70         0x00, // Min
71         0x00, // Sec
72         0x00, // Frame
73         0x00, // Zero
74         0x01, // First Track number.
75         0x00, // Disc type 00 = Mode 1
76         0x00,  // PFRAME
77         // A1
78         0x01, // session number
79         0x14, // ADR/Control
80         0x00, // TNO
81         0xA1, // POINT
82         0x00, // Min
83         0x00, // Sec
84         0x00, // Frame
85         0x00, // Zero
86         0x01, // Last Track number
87         0x00, // PSEC
88         0x00,  // PFRAME
89         // A2
90         0x01, // session number
91         0x14, // ADR/Control
92         0x00, // TNO
93         0xA2, // POINT
94         0x00, // Min
95         0x00, // Sec
96         0x00, // Frame
97         0x00, // Zero
98         0x79, // LEADOUT position BCD
99         0x59, // leadout PSEC BCD
100         0x74, // leadout PFRAME BCD
101         // TRACK 1 Descriptor
102         0x01, // session number
103         0x14, // ADR/Control
104         0x00, // TNO
105         0x01, // Point
106         0x00, // Min
107         0x00, // Sec
108         0x00, // Frame
109         0x00, // Zero
110         0x00, // PMIN
111         0x00, // PSEC
112         0x00,  // PFRAME
113         // b0
114         0x01, // session number
115         0x54, // ADR/Control
116         0x00, // TNO
117         0xB1, // POINT
118         0x79, // Min BCD
119         0x59, // Sec BCD
120         0x74, // Frame BCD
121         0x00, // Zero
122         0x79, // PMIN BCD
123         0x59, // PSEC BCD
124         0x74,  // PFRAME BCD
125         // c0
126         0x01, // session number
127         0x54, // ADR/Control
128         0x00, // TNO
129         0xC0, // POINT
130         0x00, // Min
131         0x00, // Sec
132         0x00, // Frame
133         0x00, // Zero
134         0x00, // PMIN
135         0x00, // PSEC
136         0x00  // PFRAME
137 };
138
139 static void LBA2MSF(uint32_t LBA, uint8_t* MSF)
140 {
141         MSF[0] = 0; // reserved.
142         MSF[3] = LBA % 75; // M
143         uint32_t rem = LBA / 75;
144
145         MSF[2] = rem % 60; // S
146         MSF[1] = rem / 60;
147
148 }
149
150 static void doReadTOC(int MSF, uint8_t track, uint16_t allocationLength)
151 {
152         // We only support track 1.
153         // track 0 means "return all tracks"
154         if (track > 1)
155         {
156                 scsiDev.status = CHECK_CONDITION;
157                 scsiDev.target->sense.code = ILLEGAL_REQUEST;
158                 scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
159                 scsiDev.phase = STATUS;
160         }
161         else
162         {
163                 uint32_t len = sizeof(SimpleTOC);
164                 memcpy(scsiDev.data, SimpleTOC, len);
165
166                 uint32_t capacity = getScsiCapacity(
167                         scsiDev.target->cfg->sdSectorStart,
168                         scsiDev.target->liveCfg.bytesPerSector,
169                         scsiDev.target->cfg->scsiSectors);
170
171                 // Replace start of leadout track
172                 if (MSF)
173                 {
174                         LBA2MSF(capacity, scsiDev.data + 0x0E);
175                 }
176                 else
177                 {
178                         scsiDev.data[0x0E] = capacity >> 24;
179                         scsiDev.data[0x0F] = capacity >> 16;
180                         scsiDev.data[0x10] = capacity >> 8;
181                         scsiDev.data[0x11] = capacity;
182                 }
183
184                 if (len > allocationLength)
185                 {
186                         len = allocationLength;
187                 }
188                 scsiDev.dataLen = len;
189                 scsiDev.phase = DATA_IN;
190         }
191 }
192
193 static void doReadSessionInfo(uint8_t session, uint16_t allocationLength)
194 {
195         uint32_t len = sizeof(SessionTOC);
196         memcpy(scsiDev.data, SessionTOC, len);
197
198         if (len > allocationLength)
199         {
200                 len = allocationLength;
201         }
202         scsiDev.dataLen = len;
203         scsiDev.phase = DATA_IN;
204 }
205
206 static inline uint8_t
207 fromBCD(uint8_t val)
208 {
209         return ((val >> 4) * 10) + (val & 0xF);
210 }
211
212 static void doReadFullTOC(int convertBCD, uint8_t session, uint16_t allocationLength)
213 {
214         // We only support session 1.
215         if (session > 1)
216         {
217                 scsiDev.status = CHECK_CONDITION;
218                 scsiDev.target->sense.code = ILLEGAL_REQUEST;
219                 scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
220                 scsiDev.phase = STATUS;
221         }
222         else
223         {
224                 uint32_t len = sizeof(FullTOC);
225                 memcpy(scsiDev.data, FullTOC, len);
226
227                 if (convertBCD)
228                 {
229                         int descriptor = 4;
230                         while (descriptor < len)
231                         {
232                                 int i;
233                                 for (i = 0; i < 7; ++i)
234                                 {
235                                         scsiDev.data[descriptor + i] =
236                                                 fromBCD(scsiDev.data[descriptor + 4 + i]);
237                                 }
238                                 descriptor += 11;
239                         }
240
241                 }
242
243                 if (len > allocationLength)
244                 {
245                         len = allocationLength;
246                 }
247                 scsiDev.dataLen = len;
248                 scsiDev.phase = DATA_IN;
249         }
250 }
251
252 static uint8_t SimpleHeader[] =
253 {
254         0x01, // 2048byte user data, L-EC in 288 byte aux field.
255         0x00, // reserved
256         0x00, // reserved
257         0x00, // reserved
258         0x00,0x00,0x00,0x00 // Track start sector (LBA or MSF)
259 };
260
261 void doReadHeader(int MSF, uint32_t lba, uint16_t allocationLength)
262 {
263         uint32_t len = sizeof(SimpleHeader);
264         memcpy(scsiDev.data, SimpleHeader, len);
265         if (len > allocationLength)
266         {
267                 len = allocationLength;
268         }
269         scsiDev.dataLen = len;
270         scsiDev.phase = DATA_IN;
271 }
272
273
274 // Handle direct-access scsi device commands
275 int scsiCDRomCommand()
276 {
277         int commandHandled = 1;
278
279         uint8 command = scsiDev.cdb[0];
280         if (scsiDev.target->cfg->deviceType == CONFIG_OPTICAL)
281         {
282                 if (command == 0x43)
283                 {
284                         // CD-ROM Read TOC
285                         int MSF = scsiDev.cdb[1] & 0x02 ? 1 : 0;
286                         uint8_t track = scsiDev.cdb[6];
287                         uint16_t allocationLength =
288                                 (((uint32_t) scsiDev.cdb[7]) << 8) +
289                                 scsiDev.cdb[8];
290
291                         // Reject MMC commands for now, otherwise the TOC data format
292                         // won't be understood.
293                         // The "format" field is reserved for SCSI-2
294                         uint8_t format = scsiDev.cdb[2] & 0x0F;
295                         switch (format)
296                         {
297                                 case 0: doReadTOC(MSF, track, allocationLength); break; // SCSI-2
298                                 case 1: doReadSessionInfo(MSF, allocationLength); break; // MMC2
299                                 case 2: doReadFullTOC(0, track, allocationLength); break; // MMC2
300                                 case 3: doReadFullTOC(1, track, allocationLength); break; // MMC2
301                                 default:
302                                 {
303                                         scsiDev.status = CHECK_CONDITION;
304                                         scsiDev.target->sense.code = ILLEGAL_REQUEST;
305                                         scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
306                                         scsiDev.phase = STATUS;
307                                 }
308                         }
309                 }
310                 else if (command == 0x44)
311                 {
312                         // CD-ROM Read Header
313                         int MSF = scsiDev.cdb[1] & 0x02 ? 1 : 0;
314                         uint32_t lba = 0; // IGNORED for now
315                         uint16_t allocationLength =
316                                 (((uint32_t) scsiDev.cdb[7]) << 8) +
317                                 scsiDev.cdb[8];
318                         doReadHeader(MSF, lba, allocationLength);
319                 }
320                 else
321                 {
322                         commandHandled = 0;
323                 }
324         }
325         else
326         {
327                 commandHandled = 0;
328         }
329
330         return commandHandled;
331 }
332
333 #pragma GCC pop_options