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