mirror of
				https://github.com/davidgiven/fluxengine.git
				synced 2025-10-24 11:11:02 -07:00 
			
		
		
		
	
		
			
				
	
	
		
			439 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			439 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "lib/core/globals.h"
 | |
| #include "protocol.h"
 | |
| #include "lib/data/fluxmap.h"
 | |
| #include "lib/core/bytes.h"
 | |
| #include "lib/usb/usb.pb.h"
 | |
| #include "greaseweazle.h"
 | |
| #include "serial.h"
 | |
| #include "usb.h"
 | |
| #include <unistd.h>
 | |
| 
 | |
| static const char* gw_error(int e)
 | |
| {
 | |
|     switch (e)
 | |
|     {
 | |
|         case ACK_OKAY:
 | |
|             return "OK";
 | |
|         case ACK_BAD_COMMAND:
 | |
|             return "Bad command";
 | |
|         case ACK_NO_INDEX:
 | |
|             return "No index";
 | |
|         case ACK_NO_TRK0:
 | |
|             return "No track 0";
 | |
|         case ACK_FLUX_OVERFLOW:
 | |
|             return "Overflow";
 | |
|         case ACK_FLUX_UNDERFLOW:
 | |
|             return "Underflow";
 | |
|         case ACK_WRPROT:
 | |
|             return "Write protected";
 | |
|         case ACK_NO_UNIT:
 | |
|             return "No unit";
 | |
|         case ACK_NO_BUS:
 | |
|             return "No bus";
 | |
|         case ACK_BAD_UNIT:
 | |
|             return "Invalid unit";
 | |
|         case ACK_BAD_PIN:
 | |
|             return "Invalid pin";
 | |
|         case ACK_BAD_CYLINDER:
 | |
|             return "Invalid track";
 | |
|         default:
 | |
|             return "Unknown error";
 | |
|     }
 | |
| }
 | |
| 
 | |
| static uint32_t ss_rand_next(uint32_t x)
 | |
| {
 | |
|     return (x & 1) ? (x >> 1) ^ 0x80000062 : x >> 1;
 | |
| }
 | |
| 
 | |
| class GreaseweazleUsb : public USB
 | |
| {
 | |
| private:
 | |
|     uint32_t read_28()
 | |
|     {
 | |
|         uint8_t buffer[4];
 | |
|         _serial->read(buffer, sizeof(buffer));
 | |
| 
 | |
|         return ((buffer[0] & 0xfe) >> 1) | ((buffer[1] & 0xfe) << 6) |
 | |
|                ((buffer[2] & 0xfe) << 13) | ((buffer[3] & 0xfe) << 20);
 | |
|     }
 | |
| 
 | |
|     void do_command(const Bytes& command)
 | |
|     {
 | |
|         _serial->write(command);
 | |
| 
 | |
|         uint8_t buffer[2];
 | |
|         _serial->read(buffer, sizeof(buffer));
 | |
| 
 | |
|         if (buffer[0] != command[0])
 | |
|             error(
 | |
|                 "command returned garbage (0x{:x} != 0x{:x} with status "
 | |
|                 "0x{:x})",
 | |
|                 buffer[0],
 | |
|                 command[0],
 | |
|                 buffer[1]);
 | |
|         if (buffer[1])
 | |
|             error("Greaseweazle error: {}", gw_error(buffer[1]));
 | |
|     }
 | |
| 
 | |
| public:
 | |
|     GreaseweazleUsb(const std::string& port, const GreaseweazleProto& config):
 | |
|         _serial(SerialPort::openSerialPort(port)),
 | |
|         _config(config)
 | |
|     {
 | |
|         int version = getVersion();
 | |
|         if (version >= 29)
 | |
|             _version = V29;
 | |
|         else if (version >= 24)
 | |
|             _version = V24;
 | |
|         else if (version == 22)
 | |
|             _version = V22;
 | |
|         else
 | |
|         {
 | |
|             error(
 | |
|                 "only Greaseweazle firmware versions 22 and 24 or above are "
 | |
|                 "currently "
 | |
|                 "supported, but you have version {}. Please file a bug.",
 | |
|                 version);
 | |
|         }
 | |
| 
 | |
|         /* Twiddle the baud rate, which indicates to the Greaseweazle that the
 | |
|          * data stream has been reset. */
 | |
| 
 | |
|         _serial->setBaudRate(10000);
 | |
|         usleep(100000);
 | |
|         _serial->setBaudRate(9600);
 | |
| 
 | |
|         /* Configure the hardware. */
 | |
| 
 | |
|         do_command({CMD_SET_BUS_TYPE, 3, (uint8_t)config.bus_type()});
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     int getVersion()
 | |
|     {
 | |
|         do_command({CMD_GET_INFO, 3, GETINFO_FIRMWARE});
 | |
| 
 | |
|         Bytes response = _serial->readBytes(32);
 | |
|         ByteReader br(response);
 | |
| 
 | |
|         br.seek(4);
 | |
|         nanoseconds_t freq = br.read_le32();
 | |
|         _clock = 1000000000 / freq;
 | |
| 
 | |
|         br.seek(0);
 | |
|         return br.read_be16();
 | |
|     }
 | |
| 
 | |
| public:
 | |
|     void seek(int track) override
 | |
|     {
 | |
|         do_command({CMD_SEEK, 3, (uint8_t)track});
 | |
|     }
 | |
| 
 | |
|     nanoseconds_t getRotationalPeriod(int hardSectorCount) override
 | |
|     {
 | |
|         if (hardSectorCount != 0)
 | |
|             error("hard sectors are currently unsupported on the Greaseweazle");
 | |
| 
 | |
|         /* The Greaseweazle doesn't have a command to fetch the period directly,
 | |
|          * so we have to do a flux read. */
 | |
| 
 | |
|         switch (_version)
 | |
|         {
 | |
|             case V22:
 | |
|                 do_command({CMD_READ_FLUX, 2});
 | |
|                 break;
 | |
| 
 | |
|             case V24:
 | |
|             case V29:
 | |
|             {
 | |
|                 Bytes cmd(8);
 | |
|                 cmd.writer()
 | |
|                     .write_8(CMD_READ_FLUX)
 | |
|                     .write_8(cmd.size())
 | |
|                     .write_le32(0)  // ticks default value (guessed)
 | |
|                     .write_le16(2); // revolutions
 | |
|                 do_command(cmd);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         uint32_t ticks_gw = 0;
 | |
|         uint32_t firstindex = ~0;
 | |
|         uint32_t secondindex = ~0;
 | |
|         for (;;)
 | |
|         {
 | |
|             uint8_t b = _serial->readByte();
 | |
|             if (!b)
 | |
|                 break;
 | |
| 
 | |
|             if (b == 255)
 | |
|             {
 | |
|                 switch (_serial->readByte())
 | |
|                 {
 | |
|                     case FLUXOP_INDEX:
 | |
|                     {
 | |
|                         uint32_t index = read_28() + ticks_gw;
 | |
|                         if (firstindex == ~0)
 | |
|                             firstindex = index;
 | |
|                         else if (secondindex == ~0)
 | |
|                             secondindex = index;
 | |
|                         break;
 | |
|                     }
 | |
| 
 | |
|                     case FLUXOP_SPACE:
 | |
|                         ticks_gw += read_28();
 | |
|                         break;
 | |
| 
 | |
|                     default:
 | |
|                         error("bad opcode in Greaseweazle stream");
 | |
|                 }
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 if (b < 250)
 | |
|                     ticks_gw += b;
 | |
|                 else
 | |
|                 {
 | |
|                     int delta = 250 + (b - 250) * 255 + _serial->readByte() - 1;
 | |
|                     ticks_gw += delta;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (secondindex == ~0)
 | |
|             error(
 | |
|                 "unable to determine disk rotational period (is a disk in the "
 | |
|                 "drive?)");
 | |
|         do_command({CMD_GET_FLUX_STATUS, 2});
 | |
| 
 | |
|         _revolutions = (nanoseconds_t)(secondindex - firstindex) * _clock;
 | |
|         return _revolutions;
 | |
|     }
 | |
| 
 | |
|     void testBulkWrite() override
 | |
|     {
 | |
|         std::cout << "Writing data: " << std::flush;
 | |
|         const int LEN = 10 * 1024 * 1024;
 | |
|         Bytes cmd;
 | |
|         switch (_version)
 | |
|         {
 | |
|             case V22:
 | |
|             case V24:
 | |
|             {
 | |
|                 cmd.resize(6);
 | |
|                 ByteWriter bw(cmd);
 | |
|                 bw.write_8(CMD_SINK_BYTES);
 | |
|                 bw.write_8(cmd.size());
 | |
|                 bw.write_le32(LEN);
 | |
|                 break;
 | |
|             }
 | |
| 
 | |
|             case V29:
 | |
|             {
 | |
|                 cmd.resize(10);
 | |
|                 ByteWriter bw(cmd);
 | |
|                 bw.write_8(CMD_SINK_BYTES);
 | |
|                 bw.write_8(cmd.size());
 | |
|                 bw.write_le32(LEN);
 | |
|                 bw.write_le32(0); // seed
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|         do_command(cmd);
 | |
| 
 | |
|         Bytes junk(LEN);
 | |
|         uint32_t seed = 0;
 | |
|         for (int i = 0; i < LEN; i++)
 | |
|         {
 | |
|             junk[i] = seed;
 | |
|             seed = ss_rand_next(seed);
 | |
|         }
 | |
|         double start_time = getCurrentTime();
 | |
|         _serial->write(junk);
 | |
|         _serial->readBytes(1);
 | |
|         double elapsed_time = getCurrentTime() - start_time;
 | |
| 
 | |
|         std::cout << fmt::format(
 | |
|             "transferred {} bytes from PC -> device in {} ms ({} kb/s)\n",
 | |
|             LEN,
 | |
|             int(elapsed_time * 1000.0),
 | |
|             int((LEN / 1024.0) / elapsed_time));
 | |
|     }
 | |
| 
 | |
|     void testBulkRead() override
 | |
|     {
 | |
|         std::cout << "Reading data: " << std::flush;
 | |
|         const int LEN = 10 * 1024 * 1024;
 | |
|         Bytes cmd;
 | |
|         switch (_version)
 | |
|         {
 | |
|             case V22:
 | |
|             case V24:
 | |
|             {
 | |
|                 cmd.resize(6);
 | |
|                 ByteWriter bw(cmd);
 | |
|                 bw.write_8(CMD_SOURCE_BYTES);
 | |
|                 bw.write_8(cmd.size());
 | |
|                 bw.write_le32(LEN);
 | |
|                 break;
 | |
|             }
 | |
| 
 | |
|             case V29:
 | |
|             {
 | |
|                 cmd.resize(10);
 | |
|                 ByteWriter bw(cmd);
 | |
|                 bw.write_8(CMD_SOURCE_BYTES);
 | |
|                 bw.write_8(cmd.size());
 | |
|                 bw.write_le32(LEN);
 | |
|                 bw.write_le32(0); // seed
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|         do_command(cmd);
 | |
| 
 | |
|         double start_time = getCurrentTime();
 | |
|         _serial->readBytes(LEN);
 | |
|         double elapsed_time = getCurrentTime() - start_time;
 | |
| 
 | |
|         std::cout << fmt::format(
 | |
|             "transferred {} bytes from device -> PC in {} ms ({} kb/s)\n",
 | |
|             LEN,
 | |
|             int(elapsed_time * 1000.0),
 | |
|             int((LEN / 1024.0) / elapsed_time));
 | |
|     }
 | |
| 
 | |
|     Bytes read(int side,
 | |
|         bool synced,
 | |
|         nanoseconds_t readTime,
 | |
|         nanoseconds_t hardSectorThreshold) override
 | |
|     {
 | |
|         if (hardSectorThreshold != 0)
 | |
|             error("hard sectors are currently unsupported on the Greaseweazle");
 | |
| 
 | |
|         do_command({CMD_HEAD, 3, (uint8_t)side});
 | |
| 
 | |
|         switch (_version)
 | |
|         {
 | |
|             case V22:
 | |
|             {
 | |
|                 int revolutions = (readTime + _revolutions - 1) / _revolutions;
 | |
|                 Bytes cmd(4);
 | |
|                 cmd.writer()
 | |
|                     .write_8(CMD_READ_FLUX)
 | |
|                     .write_8(cmd.size())
 | |
|                     .write_le32(revolutions + (synced ? 1 : 0));
 | |
|                 do_command(cmd);
 | |
|                 break;
 | |
|             }
 | |
| 
 | |
|             case V24:
 | |
|             case V29:
 | |
|             {
 | |
|                 Bytes cmd(8);
 | |
|                 cmd.writer()
 | |
|                     .write_8(CMD_READ_FLUX)
 | |
|                     .write_8(cmd.size())
 | |
|                     .write_le32(
 | |
|                         (readTime + (synced ? _revolutions : 0)) / _clock)
 | |
|                     .write_le16(0);
 | |
|                 do_command(cmd);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         Bytes buffer;
 | |
|         ByteWriter bw(buffer);
 | |
|         for (;;)
 | |
|         {
 | |
|             uint8_t b = _serial->readByte();
 | |
|             if (!b)
 | |
|                 break;
 | |
|             bw.write_8(b);
 | |
|         }
 | |
| 
 | |
|         do_command({CMD_GET_FLUX_STATUS, 2});
 | |
| 
 | |
|         Bytes fldata = greaseweazleToFluxEngine(buffer, _clock);
 | |
|         if (synced)
 | |
|             fldata = stripPartialRotation(fldata);
 | |
|         return fldata;
 | |
|     }
 | |
| 
 | |
|     void write(int side,
 | |
|         const Bytes& fldata,
 | |
|         nanoseconds_t hardSectorThreshold) override
 | |
|     {
 | |
|         if (hardSectorThreshold != 0)
 | |
|             error("hard sectors are currently unsupported on the Greaseweazle");
 | |
| 
 | |
|         do_command({CMD_HEAD, 3, (uint8_t)side});
 | |
|         switch (_version)
 | |
|         {
 | |
|             case V22:
 | |
|                 do_command({CMD_WRITE_FLUX, 3, 1});
 | |
|                 break;
 | |
| 
 | |
|             case V24:
 | |
|             case V29:
 | |
|                 do_command({CMD_WRITE_FLUX, 4, 1, 1});
 | |
|                 break;
 | |
|         }
 | |
|         _serial->write(fluxEngineToGreaseweazle(fldata, _clock));
 | |
|         _serial->readByte(); /* synchronise */
 | |
| 
 | |
|         do_command({CMD_GET_FLUX_STATUS, 2});
 | |
|     }
 | |
| 
 | |
|     void erase(int side, nanoseconds_t hardSectorThreshold) override
 | |
|     {
 | |
|         if (hardSectorThreshold != 0)
 | |
|             error("hard sectors are currently unsupported on the Greaseweazle");
 | |
| 
 | |
|         do_command({CMD_HEAD, 3, (uint8_t)side});
 | |
| 
 | |
|         Bytes cmd(6);
 | |
|         ByteWriter bw(cmd);
 | |
|         bw.write_8(CMD_ERASE_FLUX);
 | |
|         bw.write_8(cmd.size());
 | |
|         bw.write_le32(200e6 / _clock);
 | |
|         do_command(cmd);
 | |
|         _serial->readByte(); /* synchronise */
 | |
| 
 | |
|         do_command({CMD_GET_FLUX_STATUS, 2});
 | |
|     }
 | |
| 
 | |
|     void setDrive(int drive, bool high_density, int index_mode) override
 | |
|     {
 | |
|         do_command({CMD_SELECT, 3, (uint8_t)drive});
 | |
|         do_command({CMD_MOTOR, 4, (uint8_t)drive, 1});
 | |
|         do_command({CMD_SET_PIN, 4, 2, (uint8_t)(high_density ? 1 : 0)});
 | |
|     }
 | |
| 
 | |
|     void measureVoltages(struct voltages_frame* voltages) override
 | |
|     {
 | |
|         error("unsupported operation on the Greaseweazle");
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     enum
 | |
|     {
 | |
|         V22,
 | |
|         V24,
 | |
|         V29
 | |
|     };
 | |
| 
 | |
|     std::unique_ptr<SerialPort> _serial;
 | |
|     const GreaseweazleProto& _config;
 | |
|     int _version;
 | |
|     nanoseconds_t _clock;
 | |
|     nanoseconds_t _revolutions;
 | |
| };
 | |
| 
 | |
| USB* createGreaseweazleUsb(
 | |
|     const std::string& port, const GreaseweazleProto& config)
 | |
| {
 | |
|     return new GreaseweazleUsb(port, config);
 | |
| }
 | |
| 
 | |
| // vim: sw=4 ts=4 et
 |