1 /* This file is part of UKNCBTL.
2 UKNCBTL is free software: you can redistribute it and/or modify it under the terms
3 of the GNU Lesser General Public License as published by the Free Software Foundation,
4 either version 3 of the License, or (at your option) any later version.
5 UKNCBTL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
6 without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7 See the GNU Lesser General Public License for more details.
8 You should have received a copy of the GNU Lesser General Public License along with
9 UKNCBTL. If not, see <http://www.gnu.org/licenses/>. */
10
11 /// \file Hard.cpp
12 /// \brief Hard disk drive emulation.
13 /// \details See defines in header file Emubase.h
14
15 #include "stdafx.h"
16 #include <sys/stat.h>
17 #include "Emubase.h"
18
19
20 //////////////////////////////////////////////////////////////////////
21 // Constants
22
23 #define TIME_PER_SECTOR (IDE_DISK_SECTOR_SIZE / 2)
24
25 #define IDE_PORT_DATA 0x1f0
26 #define IDE_PORT_ERROR 0x1f1
27 #define IDE_PORT_SECTOR_COUNT 0x1f2
28 #define IDE_PORT_SECTOR_NUMBER 0x1f3
29 #define IDE_PORT_CYLINDER_LSB 0x1f4
30 #define IDE_PORT_CYLINDER_MSB 0x1f5
31 #define IDE_PORT_HEAD_NUMBER 0x1f6
32 #define IDE_PORT_STATUS_COMMAND 0x1f7
33
34 #define IDE_STATUS_ERROR 0x01
35 #define IDE_STATUS_HIT_INDEX 0x02
36 #define IDE_STATUS_BUFFER_READY 0x08
37 #define IDE_STATUS_SEEK_COMPLETE 0x10
38 #define IDE_STATUS_DRIVE_READY 0x40
39 #define IDE_STATUS_BUSY 0x80
40
41 #define IDE_COMMAND_READ_MULTIPLE 0x20
42 #define IDE_COMMAND_READ_MULTIPLE1 0x21
43 #define IDE_COMMAND_SET_CONFIG 0x91
44 #define IDE_COMMAND_WRITE_MULTIPLE 0x30
45 #define IDE_COMMAND_WRITE_MULTIPLE1 0x31
46 #define IDE_COMMAND_IDENTIFY 0xec
47
48 #define IDE_ERROR_NONE 0x00
49 #define IDE_ERROR_DEFAULT 0x01
50 #define IDE_ERROR_UNKNOWN_COMMAND 0x04
51 #define IDE_ERROR_BAD_LOCATION 0x10
52 #define IDE_ERROR_BAD_SECTOR 0x80
53
54 enum TimeoutEvent
55 {
56 TIMEEVT_NONE = 0,
57 TIMEEVT_RESET_DONE = 1,
58 TIMEEVT_READ_SECTOR_DONE = 2,
59 TIMEEVT_WRITE_SECTOR_DONE = 3,
60 };
61
62 //////////////////////////////////////////////////////////////////////
63
64 // Inverts 512 bytes in the buffer
InvertBuffer(void * buffer)65 static void InvertBuffer(void* buffer)
66 {
67 uint32_t* p = (uint32_t*) buffer;
68 for (int i = 0; i < 128; i++)
69 {
70 *p = ~(*p);
71 p++;
72 }
73 }
74
75 //////////////////////////////////////////////////////////////////////
76
77
CHardDrive()78 CHardDrive::CHardDrive()
79 {
80 m_fpFile = nullptr;
81
82 m_status = IDE_STATUS_BUSY;
83 m_error = IDE_ERROR_NONE;
84 m_command = 0;
85 m_timeoutcount = m_timeoutevent = 0;
86 m_sectorcount = 0;
87
88 m_numsectors = m_numheads = m_numcylinders = 0;
89 m_curcylinder = m_curhead = m_curheadreg = m_cursector = m_bufferoffset = 0;
90 memset(m_buffer, 0, IDE_DISK_SECTOR_SIZE);
91
92 m_okInverted = false;
93 m_okReadOnly = false;
94 }
95
~CHardDrive()96 CHardDrive::~CHardDrive()
97 {
98 DetachImage();
99 }
100
Reset()101 void CHardDrive::Reset()
102 {
103 //#if !defined(PRODUCT)
104 // DebugPrint(_T("HDD Reset\r\n"));
105 //#endif
106
107 m_status = IDE_STATUS_BUSY;
108 m_error = IDE_ERROR_NONE;
109 m_command = 0;
110 m_timeoutcount = 2;
111 m_timeoutevent = TIMEEVT_RESET_DONE;
112 }
113
AttachImage(LPCTSTR sFileName)114 bool CHardDrive::AttachImage(LPCTSTR sFileName)
115 {
116 ASSERT(sFileName != nullptr);
117
118 // Open file
119 m_okReadOnly = false;
120 m_fpFile = ::_tfopen(sFileName, _T("r+b"));
121 if (m_fpFile == nullptr)
122 {
123 m_okReadOnly = true;
124 m_fpFile = ::_tfopen(sFileName, _T("rb"));
125 }
126 if (m_fpFile == nullptr)
127 return false;
128
129 // Check file size
130 ::fseek(m_fpFile, 0, SEEK_END);
131 uint32_t dwFileSize = ::ftell(m_fpFile);
132 ::fseek(m_fpFile, 0, SEEK_SET);
133 if (dwFileSize % 512 != 0)
134 return false;
135
136 // Read first sector
137 size_t dwBytesRead = ::fread(m_buffer, 1, 512, m_fpFile);
138 if (dwBytesRead != 512)
139 return false;
140
141 // Autodetect inverted image
142 uint8_t test = 0xff;
143 for (int i = 0x1f0; i <= 0x1fb; i++)
144 test &= m_buffer[i];
145 m_okInverted = (test == 0xff);
146 // Invert the buffer if needed
147 if (m_okInverted)
148 InvertBuffer(m_buffer);
149
150 // Calculate geometry
151 m_numsectors = *(m_buffer + 0);
152 m_numheads = *(m_buffer + 1);
153 if (m_numsectors == 0 || m_numheads == 0)
154 return false; // Geometry params are not defined
155 m_numcylinders = dwFileSize / 512 / m_numsectors / m_numheads;
156 if (m_numcylinders == 0 || m_numcylinders > 1024)
157 return false;
158
159 m_curcylinder = m_curhead = m_curheadreg = m_cursector = m_bufferoffset = 0;
160
161 m_status = IDE_STATUS_BUSY;
162 m_error = IDE_ERROR_NONE;
163
164 return true;
165 }
166
DetachImage()167 void CHardDrive::DetachImage()
168 {
169 if (m_fpFile == nullptr) return;
170
171 //FlushChanges();
172
173 ::fclose(m_fpFile);
174 m_fpFile = nullptr;
175 }
176
ReadPort(uint16_t port)177 uint16_t CHardDrive::ReadPort(uint16_t port)
178 {
179 ASSERT(port >= 0x1F0 && port <= 0x1F7);
180
181 uint16_t data = 0;
182 switch (port)
183 {
184 case IDE_PORT_DATA:
185 if (m_status & IDE_STATUS_BUFFER_READY)
186 {
187 data = *((uint16_t*)(m_buffer + m_bufferoffset));
188 if (!m_okInverted)
189 data = ~data; // Image stored non-inverted, but QBUS inverts the bits
190 m_bufferoffset += 2;
191
192 if (m_bufferoffset >= IDE_DISK_SECTOR_SIZE)
193 {
194 //#if !defined(PRODUCT)
195 // DebugPrint(_T("HDD Read sector complete\r\n"));
196 //#endif
197 ContinueRead();
198 }
199 }
200 break;
201 case IDE_PORT_ERROR:
202 data = 0xff00 | m_error;
203 break;
204 case IDE_PORT_SECTOR_COUNT:
205 data = 0xff00 | (uint16_t)m_sectorcount;
206 break;
207 case IDE_PORT_SECTOR_NUMBER:
208 data = 0xff00 | (uint16_t)m_cursector;
209 break;
210 case IDE_PORT_CYLINDER_LSB:
211 data = 0xff00 | (uint16_t)(m_curcylinder & 0xff);
212 break;
213 case IDE_PORT_CYLINDER_MSB:
214 data = 0xff00 | (uint16_t)((m_curcylinder >> 8) & 0xff);
215 break;
216 case IDE_PORT_HEAD_NUMBER:
217 data = 0xff00 | (uint16_t)m_curheadreg;
218 break;
219 case IDE_PORT_STATUS_COMMAND:
220 data = 0xff00 | m_status;
221 break;
222 }
223
224 //DebugPrintFormat(_T("HDD Read %x 0x%04x\r\n"), port, data);
225 return data;
226 }
227
WritePort(uint16_t port,uint16_t data)228 void CHardDrive::WritePort(uint16_t port, uint16_t data)
229 {
230 ASSERT(port >= 0x1F0 && port <= 0x1F7);
231
232 //#if !defined(PRODUCT)
233 // DebugPrintFormat(_T("HDD Write %x <-- 0x%04x\r\n"), port, data);
234 //#endif
235
236 switch (port)
237 {
238 case IDE_PORT_DATA:
239 if (m_status & IDE_STATUS_BUFFER_READY)
240 {
241 if (!m_okInverted)
242 data = ~data; // Image stored non-inverted, but QBUS inverts the bits
243 *((uint16_t*)(m_buffer + m_bufferoffset)) = data;
244 m_bufferoffset += 2;
245
246 if (m_bufferoffset >= IDE_DISK_SECTOR_SIZE)
247 {
248 m_status &= ~IDE_STATUS_BUFFER_READY;
249
250 ContinueWrite();
251 }
252 }
253 break;
254 case IDE_PORT_ERROR:
255 // Writing precompensation value -- ignore
256 break;
257 case IDE_PORT_SECTOR_COUNT:
258 data &= 0x0ff;
259 m_sectorcount = (data == 0) ? 256 : data;
260 break;
261 case IDE_PORT_SECTOR_NUMBER:
262 data &= 0x0ff;
263 m_cursector = data;
264 break;
265 case IDE_PORT_CYLINDER_LSB:
266 data &= 0x0ff;
267 m_curcylinder = (m_curcylinder & 0xff00) | (data & 0xff);
268 break;
269 case IDE_PORT_CYLINDER_MSB:
270 data &= 0x0ff;
271 m_curcylinder = (m_curcylinder & 0x00ff) | ((data & 0xff) << 8);
272 break;
273 case IDE_PORT_HEAD_NUMBER:
274 data &= 0x0ff;
275 m_curhead = data & 0x0f;
276 m_curheadreg = data;
277 break;
278 case IDE_PORT_STATUS_COMMAND:
279 data &= 0x0ff;
280 HandleCommand((uint8_t)data);
281 break;
282 }
283 }
284
285 // Called from CMotherboard::SystemFrame() every tick
Periodic()286 void CHardDrive::Periodic()
287 {
288 if (m_timeoutcount > 0)
289 {
290 m_timeoutcount--;
291 if (m_timeoutcount == 0)
292 {
293 int evt = m_timeoutevent;
294 m_timeoutevent = TIMEEVT_NONE;
295 switch (evt)
296 {
297 case TIMEEVT_RESET_DONE:
298 m_status &= ~IDE_STATUS_BUSY;
299 m_status |= IDE_STATUS_DRIVE_READY | IDE_STATUS_SEEK_COMPLETE;
300 break;
301 case TIMEEVT_READ_SECTOR_DONE:
302 ReadSectorDone();
303 break;
304 case TIMEEVT_WRITE_SECTOR_DONE:
305 WriteSectorDone();
306 break;
307 }
308 }
309 }
310 }
311
HandleCommand(uint8_t command)312 void CHardDrive::HandleCommand(uint8_t command)
313 {
314 m_command = command;
315 switch (command)
316 {
317 case IDE_COMMAND_READ_MULTIPLE:
318 case IDE_COMMAND_READ_MULTIPLE1:
319 //#if !defined(PRODUCT)
320 // DebugPrintFormat(_T("HDD COMMAND %02x (READ MULT): C=%d, H=%d, SN=%d, SC=%d\r\n"),
321 // command, m_curcylinder, m_curhead, m_cursector, m_sectorcount);
322 //#endif
323 m_status |= IDE_STATUS_BUSY;
324
325 m_timeoutcount = TIME_PER_SECTOR * 3; // Timeout while seek for track
326 m_timeoutevent = TIMEEVT_READ_SECTOR_DONE;
327 break;
328
329 case IDE_COMMAND_SET_CONFIG:
330 //#if !defined(PRODUCT)
331 // DebugPrintFormat(_T("HDD COMMAND %02x (SET CONFIG): H=%d, SC=%d\r\n"),
332 // command, m_curhead, m_sectorcount);
333 //#endif
334 m_numsectors = m_sectorcount;
335 m_numheads = m_curhead + 1;
336 break;
337
338 case IDE_COMMAND_WRITE_MULTIPLE:
339 case IDE_COMMAND_WRITE_MULTIPLE1:
340 //#if !defined(PRODUCT)
341 // DebugPrintFormat(_T("HDD COMMAND %02x (WRITE MULT): C=%d, H=%d, SN=%d, SC=%d\r\n"),
342 // command, m_curcylinder, m_curhead, m_cursector, m_sectorcount);
343 //#endif
344 m_bufferoffset = 0;
345 m_status |= IDE_STATUS_BUFFER_READY;
346 break;
347
348 case IDE_COMMAND_IDENTIFY:
349 //#if !defined(PRODUCT)
350 // DebugPrintFormat(_T("HDD COMMAND %02x (IDENTIFY)\r\n"), command);
351 //#endif
352 IdentifyDrive(); // Prepare the buffer
353 m_bufferoffset = 0;
354 m_sectorcount = 1;
355 m_status |= IDE_STATUS_BUFFER_READY | IDE_STATUS_SEEK_COMPLETE | IDE_STATUS_DRIVE_READY;
356 m_status &= ~IDE_STATUS_BUSY;
357 m_status &= ~IDE_STATUS_ERROR;
358 break;
359
360 default:
361 //#if !defined(PRODUCT)
362 // DebugPrintFormat(_T("HDD COMMAND %02x (UNKNOWN): C=%d, H=%d, SN=%d, SC=%d\r\n"),
363 // command, m_curcylinder, m_curhead, m_cursector, m_sectorcount);
364 // //DebugBreak(); // Implement this IDE command!
365 //#endif
366 break;
367 }
368 }
369
370 // Copy the string to the destination, swapping bytes in every word
371 // For use in CHardDrive::IdentifyDrive() method.
swap_strncpy(uint8_t * dst,const char * src,int words)372 static void swap_strncpy(uint8_t* dst, const char* src, int words)
373 {
374 int i;
375 for (i = 0; src[i] != 0; i++)
376 dst[i ^ 1] = src[i];
377 for ( ; i < words * 2; i++)
378 dst[i ^ 1] = ' ';
379 }
380
IdentifyDrive()381 void CHardDrive::IdentifyDrive()
382 {
383 uint32_t totalsectors = (uint32_t)m_numcylinders * (uint32_t)m_numheads * (uint32_t)m_numsectors;
384
385 memset(m_buffer, 0, IDE_DISK_SECTOR_SIZE);
386
387 uint16_t* pwBuffer = (uint16_t*)m_buffer;
388 pwBuffer[0] = 0x045a; // Configuration: fixed disk
389 pwBuffer[1] = (uint16_t)m_numcylinders;
390 pwBuffer[3] = (uint16_t)m_numheads;
391 pwBuffer[6] = (uint16_t)m_numsectors;
392 swap_strncpy((uint8_t*)(pwBuffer + 10), "0000000000", 10); // Serial number
393 swap_strncpy((uint8_t*)(pwBuffer + 23), "1.0", 4); // Firmware version
394 swap_strncpy((uint8_t*)(pwBuffer + 27), "UKNCBTL Hard Disk", 18); // Model
395 pwBuffer[47] = 0x8001; // Read/write multiple support
396 pwBuffer[49] = 0x2f00; // Capabilities: bit9 = LBA
397 pwBuffer[53] = 1; // Words 54-58 are valid
398 pwBuffer[54] = (uint16_t)m_numcylinders;
399 pwBuffer[55] = (uint16_t)m_numheads;
400 pwBuffer[56] = (uint16_t)m_numsectors;
401 *(uint32_t*)(pwBuffer + 57) = (uint32_t)m_numheads * (uint32_t)m_numsectors;
402 *(uint32_t*)(pwBuffer + 60) = totalsectors;
403 *(uint32_t*)(pwBuffer + 100) = totalsectors;
404
405 InvertBuffer(m_buffer);
406 }
407
CalculateOffset() const408 uint32_t CHardDrive::CalculateOffset() const
409 {
410 int sector = (m_curcylinder * m_numheads + m_curhead) * m_numsectors + (m_cursector - 1);
411 return sector * IDE_DISK_SECTOR_SIZE;
412 }
413
ReadNextSector()414 void CHardDrive::ReadNextSector()
415 {
416 m_status |= IDE_STATUS_BUSY;
417
418 m_timeoutcount = TIME_PER_SECTOR * 2; // Timeout while seek for next sector
419 m_timeoutevent = TIMEEVT_READ_SECTOR_DONE;
420 }
421
ReadSectorDone()422 void CHardDrive::ReadSectorDone()
423 {
424 m_status &= ~IDE_STATUS_BUSY;
425 m_status &= ~IDE_STATUS_ERROR;
426 m_status |= IDE_STATUS_BUFFER_READY;
427 m_status |= IDE_STATUS_SEEK_COMPLETE;
428
429 // Read sector from HDD image to the buffer
430 uint32_t fileOffset = CalculateOffset();
431 ::fseek(m_fpFile, fileOffset, SEEK_SET);
432 size_t dwBytesRead = ::fread(m_buffer, 1, IDE_DISK_SECTOR_SIZE, m_fpFile);
433 if (dwBytesRead != IDE_DISK_SECTOR_SIZE)
434 {
435 m_status |= IDE_STATUS_ERROR;
436 m_error = IDE_ERROR_BAD_SECTOR;
437 return;
438 }
439
440 if (m_sectorcount > 0)
441 m_sectorcount--;
442
443 if (m_sectorcount > 0)
444 {
445 NextSector();
446 }
447
448 m_error = IDE_ERROR_NONE;
449 m_bufferoffset = 0;
450 }
451
WriteSectorDone()452 void CHardDrive::WriteSectorDone()
453 {
454 m_status &= ~IDE_STATUS_BUSY;
455 m_status &= ~IDE_STATUS_ERROR;
456 m_status |= IDE_STATUS_BUFFER_READY;
457 m_status |= IDE_STATUS_SEEK_COMPLETE;
458
459 // Write buffer to the HDD image
460 uint32_t fileOffset = CalculateOffset();
461 //#if !defined(PRODUCT)
462 // DebugPrintFormat(_T("WriteSector %lx\r\n"), fileOffset); //DEBUG
463 //#endif
464 if (m_okReadOnly)
465 {
466 m_status |= IDE_STATUS_ERROR;
467 m_error = IDE_ERROR_BAD_SECTOR;
468 return;
469 }
470
471 ::fseek(m_fpFile, fileOffset, SEEK_SET);
472 size_t dwBytesWritten = ::fwrite(m_buffer, 1, IDE_DISK_SECTOR_SIZE, m_fpFile);
473 if (dwBytesWritten != IDE_DISK_SECTOR_SIZE)
474 {
475 m_status |= IDE_STATUS_ERROR;
476 m_error = IDE_ERROR_BAD_SECTOR;
477 return;
478 }
479
480 if (m_sectorcount > 0)
481 m_sectorcount--;
482
483 if (m_sectorcount > 0)
484 {
485 NextSector();
486 }
487
488 m_error = IDE_ERROR_NONE;
489 m_bufferoffset = 0;
490 }
491
NextSector()492 void CHardDrive::NextSector()
493 {
494 // Advance to the next sector, CHS-based
495 m_cursector++;
496 if (m_cursector > m_numsectors) // Sectors are 1-based
497 {
498 m_cursector = 1;
499 m_curhead++;
500 if (m_curhead >= m_numheads)
501 {
502 m_curhead = 0;
503 m_curcylinder++;
504 }
505 }
506 }
507
ContinueRead()508 void CHardDrive::ContinueRead()
509 {
510 m_bufferoffset = 0;
511
512 m_status &= ~IDE_STATUS_BUFFER_READY;
513 m_status &= ~IDE_STATUS_BUSY;
514
515 if (m_sectorcount > 0)
516 ReadNextSector();
517 }
518
ContinueWrite()519 void CHardDrive::ContinueWrite()
520 {
521 m_bufferoffset = 0;
522
523 m_status &= ~IDE_STATUS_BUFFER_READY;
524 m_status |= IDE_STATUS_BUSY;
525
526 m_timeoutcount = TIME_PER_SECTOR;
527 m_timeoutevent = TIMEEVT_WRITE_SECTOR_DONE;
528 }
529
530
531 //////////////////////////////////////////////////////////////////////
532