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