greaseweazle/lib.rs
1//! A Rust library for controlling a Greaseweazle from the host system.
2//!
3//! A Greaseweazle is a device that provides direct control and raw flux-level access to a floppy
4//! drive; see <https://github.com/keirf/greaseweazle/> for more information.
5//! This project is not maintained by Keir Fraser and is not affiliated with the official
6//! Greaseweazle project in any way.
7//!
8//! # Usage
9//!
10//! The [`Greaseweazle`] object represents a connection to the Greaseweazle. When you create it,
11//! it will establish a serial connection to the device. You can then call its methods to send
12//! commands.
13//!
14//! # Example
15//!
16//! ```no_run
17//! use greaseweazle::Greaseweazle;
18//!
19//! let port_infos = greaseweazle::enumerate()?;
20//! let mut gw = Greaseweazle::new(&port_infos[0].port_name).unwrap();
21//! let info = gw.get_firmware_info().unwrap();
22//! println!("{info:?}");
23//! Ok::<(), greaseweazle::CommandError>(())
24//! ```
25
26use byteorder::{LE, ReadBytesExt, WriteBytesExt as _};
27use num_enum::{FromPrimitive, IntoPrimitive};
28use serialport::{ClearBuffer, SerialPort, SerialPortInfo, SerialPortType, UsbPortInfo};
29use std::{
30 borrow::Cow,
31 error::Error,
32 fmt,
33 io::{self, BufRead, BufReader, Read, Write as _},
34 time::Duration,
35};
36pub use {config::*, flux::*};
37
38mod config;
39mod flux;
40
41/// The connection to the Greaseweazle device.
42///
43/// The Greaseweazle does not handle multiple connections, and creating more than one
44/// `Greaseweazle` object for the same device will probably result in errors.
45#[derive(Debug)]
46pub struct Greaseweazle {
47 serial: BufReader<Box<dyn SerialPort>>,
48 write_buf: Vec<u8>,
49}
50
51impl Greaseweazle {
52 /// Creates a new connection to the Greaseweazle device, using the serial port located at the
53 /// given `serial_path`.
54 ///
55 /// This does not check whether the device connected to the serial port is in fact a
56 /// Greaseweazle. You should call [`get_firmware_info`](Self::get_firmware_info) after creating
57 /// a connection, to ensure that the device accepts Greaseweazle commands and responds as
58 /// expected.
59 pub fn new<'a>(serial_path: impl Into<Cow<'a, str>>) -> Result<Self, io::Error> {
60 let mut serial = serialport::new(serial_path, BaudRate::ClearComms.into())
61 .timeout(Duration::from_secs(60))
62 .dtr_on_open(true)
63 .open()?;
64
65 serial.set_baud_rate(BaudRate::Normal.into())?;
66 serial.clear(ClearBuffer::All)?;
67 serial.write_request_to_send(true)?;
68
69 Ok(Self {
70 serial: BufReader::new(serial),
71 write_buf: Vec::with_capacity(1024),
72 })
73 }
74
75 /// Returns a reference to the internal serial port.
76 #[inline]
77 pub fn serial(&self) -> &dyn SerialPort {
78 self.serial.get_ref().as_ref()
79 }
80
81 /// Returns a mutable reference to the internal serial port. This can be used for raw
82 /// communication with the Greaseweazle.
83 ///
84 /// You can send invalid data this way, which may disrupt future commands and require creating
85 /// a new connection. Use with care.
86 #[inline]
87 pub fn serial_mut(&mut self) -> &mut dyn SerialPort {
88 self.serial.get_mut().as_mut()
89 }
90
91 /// Resets the Greaseweazle to its default state when first powered on.
92 pub fn reset(&mut self) -> Result<(), CommandError> {
93 self.send_command(Command::Reset, |_write| Ok(()))
94 }
95
96 // *** Configuration ***
97
98 /// Returns information about the hardware and firmware of the Greaseweazle device.
99 pub fn get_firmware_info(&mut self) -> Result<FirmwareInfo, CommandError> {
100 self.send_command(Command::GetInfo, |write| {
101 write.write_u8(GetInfo::Firmware.into())?;
102 Ok(())
103 })?;
104
105 let mut buf = [0; 32];
106 self.serial.read_exact(&mut buf)?;
107 FirmwareInfo::read_from(buf.as_slice()).map_err(Into::into)
108 }
109
110 /// Sets the floppy bus type for future commands.
111 pub fn set_bus_type(&mut self, bus_type: BusType) -> Result<(), CommandError> {
112 self.send_command(Command::SetBusType, |write| {
113 write.write_u8(bus_type.into())?;
114 Ok(())
115 })
116 }
117
118 /// Returns information about the specified drive, or the currently selected drive if
119 /// not specified.
120 ///
121 /// You must have called [`set_bus_type`](Self::set_bus_type) first, and `drive_num` must be
122 /// a valid drive number for the selected bus type. If `drive_num` is `None`, you must also
123 /// have called [`select_drive`](Self::select_drive) first.
124 pub fn get_drive_info(&mut self, drive_num: Option<u8>) -> Result<DriveInfo, CommandError> {
125 if let Some(drive_num) = drive_num {
126 let code = u8::from(GetInfo::Drive)
127 .checked_add(drive_num)
128 .ok_or(GreaseweazleError::InvalidDriveNumber)?;
129 self.send_command(Command::GetInfo, |write| {
130 write.write_u8(code)?;
131 Ok(())
132 })?;
133 } else {
134 self.send_command(Command::GetInfo, |write| {
135 write.write_u8(GetInfo::CurrentDrive.into())?;
136 Ok(())
137 })?;
138 }
139
140 let mut buf = [0; 32];
141 self.serial.read_exact(&mut buf)?;
142 DriveInfo::read_from(buf.as_slice()).map_err(Into::into)
143 }
144
145 /// Returns the current delay/timing settings.
146 pub fn get_delays(&mut self) -> Result<Delays, CommandError> {
147 let mut value_count = 8;
148
149 // Older firmware does not support all values, so if an error is returned,
150 // incrementally ask for fewer values.
151 while let Err(err) = self.send_command(Command::GetParams, |write| {
152 write.write_u8(Param::Delays.into())?;
153 write.write_u8(value_count * 2)?;
154 Ok(())
155 }) {
156 if value_count > 5
157 && matches!(
158 err,
159 CommandError::GreaseweazleError(GreaseweazleError::BadCommand)
160 )
161 {
162 value_count -= 1;
163 } else {
164 return Err(err);
165 }
166 }
167
168 Ok(Delays {
169 select_us: self.serial.read_u16::<LE>()?,
170 step_us: self.serial.read_u16::<LE>()?,
171 seek_settle_ms: self.serial.read_u16::<LE>()?,
172 motor_ms: self.serial.read_u16::<LE>()?,
173 watchdog_ms: self.serial.read_u16::<LE>()?,
174 pre_write_us: if value_count >= 6 {
175 Some(self.serial.read_u16::<LE>()?)
176 } else {
177 None
178 },
179 post_write_us: if value_count >= 7 {
180 Some(self.serial.read_u16::<LE>()?)
181 } else {
182 None
183 },
184 index_mask_us: if value_count >= 8 {
185 Some(self.serial.read_u16::<LE>()?)
186 } else {
187 None
188 },
189 })
190 }
191
192 /// Sets new delay/timing settings.
193 pub fn set_delays(&mut self, delays: Delays) -> Result<(), CommandError> {
194 self.send_command(Command::SetParams, |write| {
195 write.write_u8(Param::Delays.into())?;
196
197 let Delays {
198 select_us: select,
199 step_us: step,
200 seek_settle_ms: seek_settle,
201 motor_ms: motor,
202 watchdog_ms: watchdog,
203 pre_write_us: pre_write,
204 post_write_us: post_write,
205 index_mask_us: index_mask,
206 } = delays;
207
208 write.write_u16::<LE>(select)?;
209 write.write_u16::<LE>(step)?;
210 write.write_u16::<LE>(seek_settle)?;
211 write.write_u16::<LE>(motor)?;
212 write.write_u16::<LE>(watchdog)?;
213
214 let Some(pre_write) = pre_write else {
215 return Ok(());
216 };
217 write.write_u16::<LE>(pre_write)?;
218
219 let Some(post_write) = post_write else {
220 return Ok(());
221 };
222 write.write_u16::<LE>(post_write)?;
223
224 let Some(index_mask) = index_mask else {
225 return Ok(());
226 };
227 write.write_u16::<LE>(index_mask)?;
228
229 Ok(())
230 })
231 }
232
233 // *** Control ***
234
235 /// Returns whether a pin on the floppy interface is high.
236 pub fn get_pin(&mut self, pin: Pin) -> Result<PinLevel, CommandError> {
237 self.send_command(Command::GetPin, |write| {
238 write.write_u8(pin.into())?;
239 Ok(())
240 })?;
241
242 Ok(self.serial.read_u8()?.into())
243 }
244
245 /// Sets the level of a pin on the floppy interface.
246 pub fn set_pin(&mut self, pin: Pin, level: PinLevel) -> Result<(), CommandError> {
247 self.send_command(Command::SetPin, |write| {
248 write.write_u8(pin.into())?;
249 write.write_u8(level.into())?;
250 Ok(())
251 })
252 }
253
254 /// Selects the drive that future commands should be sent to. If `drive_num` is `None`,
255 /// deselects all drives.
256 ///
257 /// You must have called [`set_bus_type`](Self::set_bus_type) first, and `drive_num` must be
258 /// a valid drive number for the selected bus type.
259 pub fn select_drive(&mut self, drive_num: Option<u8>) -> Result<(), CommandError> {
260 if let Some(drive_num) = drive_num {
261 self.send_command(Command::Select, |write| {
262 write.write_u8(drive_num)?;
263 Ok(())
264 })
265 } else {
266 self.send_command(Command::Deselect, |_write| Ok(()))
267 }
268 }
269
270 /// Selects which head on the drive should be used for future read/write commands.
271 ///
272 /// Head `0` is the bottom head, head `1` is the top head. Other values will probably result in
273 /// an error.
274 pub fn select_head(&mut self, head: u8) -> Result<(), CommandError> {
275 self.send_command(Command::Head, |write| {
276 write.write_u8(head)?;
277 Ok(())
278 })
279 }
280
281 /// Sets the state of the drive motor for the drive.
282 ///
283 /// You must have called [`set_bus_type`](Self::set_bus_type) first, and `drive_num` must be
284 /// a valid drive number for the selected bus type.
285 pub fn set_drive_motor(&mut self, drive_num: u8, on: bool) -> Result<(), CommandError> {
286 self.send_command(Command::Motor, |write| {
287 write.write_u8(drive_num)?;
288 write.write_u8(on.into())?;
289 Ok(())
290 })
291 }
292
293 /// Seeks the head to the specified cylinder.
294 ///
295 /// You must have called [`set_bus_type`](Self::set_bus_type) and
296 /// [`select_drive`](Self::select_drive) first. Negative numbers for `cylinder` are supported
297 /// for "flippy" drives. For normal drives, you should use only non-negative values.
298 ///
299 /// After seeking, you can call [`get_pin`](Self::get_pin) with [`Pin::Track0`] to ensure that
300 /// the track 0 indicator has the expected state.
301 ///
302 /// # Notes on Greaseweazle record-keeping
303 ///
304 /// The Greaseweazle internally keeps a record of the cylinder that the head is currently on,
305 /// and updates it when seeking. However, this is not perfect, and can be disrupted if you seek
306 /// to an invalid cylinder (beyond the capabilities of the drive), if the Greaseweazle loses
307 /// contact with the drive, or if the drive seeks on its own without being told to
308 /// (some drives do this when first powered on).
309 ///
310 /// When first powered on, the Greaseweazle will always seek down on each drive until the
311 /// drive indicates track 0. If, during operation, the Greaseweazle has lost track of
312 /// the head position, you can call [`reset`](Self::reset) to let it recalibrate.
313 pub fn seek(&mut self, cylinder: i16) -> Result<(), CommandError> {
314 if let Ok(cylinder) = i8::try_from(cylinder) {
315 self.send_command(Command::Seek, |write| {
316 write.write_i8(cylinder)?;
317 Ok(())
318 })
319 } else {
320 self.send_command(Command::Seek, |write| {
321 write.write_i16::<LE>(cylinder)?;
322 Ok(())
323 })
324 }
325 }
326
327 /// Seeks the head to cylinder 0, then attempts to step down by one cylinder.
328 ///
329 /// For most drives, this is equivalent to simply seeking to cylinder 0 directly, as stepping
330 /// down from cylinder 0 is not possible. However, some flippy-modded drives erroneously do not
331 /// indicate track 0 when they step upwards from cylinder -1. This command exists to correct
332 /// for those.
333 pub fn no_click_step(&mut self) -> Result<(), CommandError> {
334 self.send_command(Command::NoClickStep, |_write| Ok(()))
335 }
336
337 // *** Flux ***
338
339 /// Reads flux from the currently selected drive, head and cylinder.
340 /// The drive motor must be on for this to work.
341 ///
342 /// Reading continues for the specified duration or the specified number of index pulses,
343 /// whichever limit is reached first.
344 /// Specifying `0` for either of these values means no limit is set.
345 pub fn read_flux(
346 &mut self,
347 duration: Ticks,
348 indexes: u16,
349 ) -> Result<EncodedFlux, CommandError> {
350 self.send_command(Command::ReadFlux, |write| {
351 write.write_u32::<LE>(duration.into())?;
352 write.write_u16::<LE>(indexes)?;
353 Ok(())
354 })?;
355
356 let mut buf = Vec::new();
357 self.serial.read_until(0, &mut buf)?;
358 self.send_command(Command::GetFluxStatus, |_write| Ok(()))?;
359
360 Ok(EncodedFlux::from_encoded_bytes(buf))
361 }
362
363 /// Writes flux to the currently selected drive, head and cylinder.
364 /// The drive motor must be on for this to work.
365 ///
366 /// If `start_at_index` is `true`, writing starts when the next index pulse occurs,
367 /// otherwise writing starts immediately. Writing ends when all the provided flux is written.
368 /// If `stop_at_index` is `true`, writing ends earlier if an index pulse occurs first, and
369 /// the remaining flux is discarded.
370 ///
371 /// `hard_sector_period`, if not `0`, specifies that the disk is hard-sectored, and the
372 /// Greaseweazle should expect sector pulses at roughly this interval.
373 /// This helps the Greaseweazle distinguish between sector holes and index holes.
374 pub fn write_flux(
375 &mut self,
376 flux: &EncodedFlux,
377 start_at_index: bool,
378 stop_at_index: bool,
379 hard_sector_period: Ticks,
380 ) -> Result<(), CommandError> {
381 if hard_sector_period.0 == 0 {
382 self.send_command(Command::WriteFlux, |write| {
383 write.write_u8(start_at_index.into())?;
384 write.write_u8(stop_at_index.into())?;
385 Ok(())
386 })?;
387 } else {
388 self.send_command(Command::WriteFlux, |write| {
389 write.write_u8(start_at_index.into())?;
390 write.write_u8(stop_at_index.into())?;
391 write.write_u32::<LE>(hard_sector_period.into())?;
392 Ok(())
393 })?;
394 }
395
396 self.serial.get_mut().write_all(flux.as_bytes())?;
397 self.serial.read_u8()?; // wait for Greaseweazle to finish
398 self.send_command(Command::GetFluxStatus, |_write| Ok(()))?;
399
400 Ok(())
401 }
402
403 /// Erases flux on the currently selected drive, head and cylinder, for the specified duration.
404 /// The drive motor must be on for this to work.
405 pub fn erase_flux(&mut self, duration: Ticks) -> Result<(), CommandError> {
406 self.send_command(Command::EraseFlux, |write| {
407 write.write_u32::<LE>(duration.into())?;
408 Ok(())
409 })?;
410
411 self.serial.read_u8()?; // wait for Greaseweazle to finish
412 self.send_command(Command::GetFluxStatus, |_write| Ok(()))?;
413
414 Ok(())
415 }
416
417 // *** Bandwidth ***
418
419 /// Receives randomly generated bytes from the Greaseweazle.
420 ///
421 /// This is used for the [`get_bandwidth_stats`](Self::get_bandwidth_stats) command.
422 pub fn source_bytes(&mut self, buf: &mut [u8], seed: u32) -> Result<(), CommandError> {
423 self.send_command(Command::SourceBytes, |write| {
424 write.write_u32::<LE>(buf.len().try_into().expect("buffer was too long"))?;
425 write.write_u32::<LE>(seed)?;
426 Ok(())
427 })?;
428
429 self.serial.read_exact(buf).map_err(Into::into)
430 }
431
432 /// Sends randomly generated bytes to the Greaseweazle.
433 ///
434 /// This is used for the [`get_bandwidth_stats`](Self::get_bandwidth_stats) command.
435 pub fn sink_bytes(&mut self, buf: &[u8], seed: u32) -> Result<(), CommandError> {
436 self.send_command(Command::SinkBytes, |write| {
437 write.write_u32::<LE>(buf.len().try_into().expect("buffer was too long"))?;
438 write.write_u32::<LE>(seed)?;
439 Ok(())
440 })?;
441
442 self.serial.get_mut().write_all(buf)?;
443 let ack_code = self.serial.read_u8()?;
444
445 if ack_code != 0 {
446 return Err(GreaseweazleError::from(ack_code).into());
447 }
448
449 Ok(())
450 }
451
452 /// Gets bandwidth statistics after calling [`source_bytes`](Self::source_bytes) and/or
453 /// [`sink_bytes`](Self::sink_bytes).
454 pub fn get_bandwidth_stats(&mut self) -> Result<BandwidthStats, CommandError> {
455 self.send_command(Command::GetInfo, |write| {
456 write.write_u8(GetInfo::BandwidthStats.into())?;
457 Ok(())
458 })?;
459
460 let mut buf = [0; 32];
461 self.serial.read_exact(&mut buf)?;
462 BandwidthStats::read_from(buf.as_slice()).map_err(Into::into)
463 }
464
465 fn send_command(
466 &mut self,
467 command: Command,
468 write_data: impl FnOnce(&mut Vec<u8>) -> Result<(), io::Error>,
469 ) -> Result<(), CommandError> {
470 let sent_command = u8::from(command);
471
472 self.write_buf.clear();
473 self.write_buf.write_u8(sent_command)?;
474 self.write_buf.write_u8(0)?;
475 write_data(&mut self.write_buf)?;
476 self.write_buf[1] = self
477 .write_buf
478 .len()
479 .try_into()
480 .expect("command was too long");
481
482 let serial = self.serial.get_mut();
483 serial.write_all(&self.write_buf)?;
484 serial.flush()?;
485
486 #[cfg(feature = "log")]
487 log::trace!("Sent command `{command:?}` with data: {:?}", self.write_buf);
488
489 let mut response = [0; 2];
490 self.serial.read_exact(&mut response)?;
491 let mut response = response.as_slice();
492 let received_command = response.read_u8()?;
493
494 let result = match response.read_u8()? {
495 0 => {
496 #[cfg(feature = "log")]
497 log::trace!("Received command `{received_command:?}` response `Ok`");
498 Ok(())
499 }
500 code => {
501 let err = GreaseweazleError::from(code);
502 #[cfg(feature = "log")]
503 log::trace!("Received command `{received_command:?}` response `{err:?}`");
504 Err(err.into())
505 }
506 };
507
508 if received_command != sent_command {
509 return Err(CommandError::ResponseCommandMismatch {
510 sent_command,
511 received_command,
512 });
513 }
514
515 result
516 }
517}
518
519/// Error that can occur when executing a Greaseweazle command.
520#[derive(Debug)]
521#[non_exhaustive]
522pub enum CommandError {
523 /// An I/O error occurred while sending/receiving data.
524 IoError(io::Error),
525
526 /// The Greaseweazle responded with an error code.
527 GreaseweazleError(GreaseweazleError),
528
529 /// The command code returned by the Greaseweazle does not match the code of the command that
530 /// was sent.
531 ///
532 /// This may indicate that the communication with the Greaseweazle has gone out of sync,
533 /// and requires recreating the connection. It may also happen if the connected device is not
534 /// actually a Greaseweazle.
535 ResponseCommandMismatch {
536 /// The command code that was sent to the Greaseweazle.
537 sent_command: u8,
538
539 /// The command code that was received back from the Greaseweazle.
540 received_command: u8,
541 },
542}
543
544impl fmt::Display for CommandError {
545 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
546 match self {
547 Self::IoError(_) => {
548 write!(f, "an I/O error occurred while sending/receiving data")
549 }
550 Self::GreaseweazleError(_) => {
551 write!(f, "the Greaseweazle responded with an error code")
552 }
553 Self::ResponseCommandMismatch { .. } => {
554 write!(
555 f,
556 "the command code returned by the Greaseweazle does not match the code of the \
557 command that was sent."
558 )
559 }
560 }
561 }
562}
563
564impl Error for CommandError {
565 fn source(&self) -> Option<&(dyn Error + 'static)> {
566 match self {
567 Self::IoError(error) => Some(error),
568 Self::GreaseweazleError(error) => Some(error),
569 Self::ResponseCommandMismatch { .. } => None,
570 }
571 }
572}
573
574impl From<io::Error> for CommandError {
575 fn from(value: io::Error) -> Self {
576 Self::IoError(value)
577 }
578}
579
580impl From<GreaseweazleError> for CommandError {
581 fn from(value: GreaseweazleError) -> Self {
582 Self::GreaseweazleError(value)
583 }
584}
585
586/// An error code that can be returned by the Greaseweazle.
587#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
588#[non_exhaustive]
589#[repr(u8)]
590pub enum GreaseweazleError {
591 /// Bad command.
592 BadCommand = 1,
593
594 /// No index.
595 NoIndex = 2,
596
597 /// Track 0 not found.
598 NoTrack0 = 3,
599
600 /// Flux overflow.
601 FluxOverflow = 4,
602
603 /// Flux underflow.
604 FluxUnderflow = 5,
605
606 /// Disk is write protected.
607 WriteProtected = 6,
608
609 /// No drive selected
610 /// (using [`select_drive`](Greaseweazle::select_drive)).
611 NoDriveSelected = 7,
612
613 /// No bus type selected
614 /// (using [`set_bus_type`](Greaseweazle::set_bus_type)).
615 NoBusTypeSelected = 8,
616
617 /// Invalid drive number.
618 InvalidDriveNumber = 9,
619
620 /// Invalid pin.
621 InvalidPin = 10,
622
623 /// Invalid cylinder.
624 InvalidCylinder = 11,
625
626 /// Out of SRAM.
627 OutOfSram = 12,
628
629 /// Out of flash.
630 OutOfFlash = 13,
631
632 /// Unknown error.
633 #[num_enum(catch_all)]
634 Unknown(u8),
635}
636
637/// Returns a list of all Greaseweazle devices that can be found on the system.
638///
639/// # Linux note
640///
641/// On Linux, this uses standard system APIs, but may give better results by using the `udev`
642/// library. To use it, enable the `libudev` feature on this crate.
643pub fn enumerate() -> Result<Vec<SerialPortInfo>, io::Error> {
644 let mut infos = serialport::available_ports()?;
645
646 infos.retain(|info| {
647 let SerialPortType::UsbPort(info) = &info.port_type else {
648 return false;
649 };
650
651 let &UsbPortInfo {
652 vid,
653 pid,
654 ref serial_number,
655 ref manufacturer,
656 ref product,
657 } = info;
658
659 if vid == 0x1209 {
660 if pid == 0x4d69 {
661 return true;
662 }
663
664 if pid == 0x0001
665 && serial_number
666 .as_deref()
667 .is_some_and(|serial_number| serial_number.starts_with("GW"))
668 {
669 return true;
670 }
671 }
672
673 if let Some(product) = product {
674 if product == "Greaseweazle" && manufacturer.as_deref() == Some("Keir Fraser") {
675 return true;
676 }
677
678 if product.to_ascii_lowercase().contains("gw-compat") {
679 return true;
680 }
681 }
682
683 false
684 });
685
686 Ok(infos)
687}
688
689/// A time duration expressed in Greaseweazle sample ticks.
690///
691/// A sample tick is a time unit equal to the period of a sample.
692/// There are [`FirmwareInfo::sample_freq`] sample ticks in a second, and one sample tick is
693/// `1.0 / sample_freq` seconds.
694#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
695#[repr(transparent)]
696pub struct Ticks(pub u32);
697
698impl Ticks {
699 /// Converts `duration` to sample ticks, with the provided sample frequency.
700 ///
701 /// Panics if overflow occurs.
702 #[inline]
703 pub fn from_duration(duration: Duration, sample_freq: u32) -> Self {
704 Self::try_from_duration(duration, sample_freq).expect("overflow occurred")
705 }
706
707 /// Tries to convert `duration` to sample ticks, with the provided sample frequency.
708 ///
709 /// Returns `None` if the value is too big to fit in a `u32`. The conversion is lossless if
710 /// `duration` is an exact multiple of the tick duration for `sample_freq`.
711 #[inline]
712 pub fn try_from_duration(duration: Duration, sample_freq: u32) -> Option<Self> {
713 Some(Self(
714 u32::try_from(duration.saturating_mul(sample_freq).as_secs()).ok()?,
715 ))
716 }
717
718 /// Converts sample ticks to a `Duration`, with the provided sample frequency.
719 ///
720 /// Rounding errors may occur during division.
721 #[inline]
722 pub fn to_duration(self, sample_freq: u32) -> Duration {
723 Duration::from_secs(self.0.into()) / sample_freq
724 }
725}
726
727impl From<u32> for Ticks {
728 #[inline]
729 fn from(value: u32) -> Self {
730 Self(value)
731 }
732}
733
734impl From<Ticks> for u32 {
735 #[inline]
736 fn from(value: Ticks) -> Self {
737 value.0
738 }
739}
740
741impl fmt::Display for GreaseweazleError {
742 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
743 match self {
744 Self::BadCommand => write!(f, "bad command"),
745 Self::NoIndex => write!(f, "no index"),
746 Self::NoTrack0 => write!(f, "track 0 not found"),
747 Self::FluxOverflow => write!(f, "flux overflow"),
748 Self::FluxUnderflow => write!(f, "flux underflow"),
749 Self::WriteProtected => write!(f, "disk is write protected"),
750 Self::NoDriveSelected => write!(f, "no drive selected"),
751 Self::NoBusTypeSelected => write!(f, "no bus type selected"),
752 Self::InvalidDriveNumber => write!(f, "invalid drive number"),
753 Self::InvalidPin => write!(f, "invalid pin"),
754 Self::InvalidCylinder => write!(f, "invalid cylinder"),
755 Self::OutOfSram => write!(f, "out of SRAM"),
756 Self::OutOfFlash => write!(f, "out of flash"),
757 Self::Unknown(code) => write!(f, "unknown error {code}"),
758 }
759 }
760}
761
762impl Error for GreaseweazleError {}
763
764/// A pin on the floppy interface of the Greaseweazle.
765///
766/// This type contains only pins that can be used with [`get_pin`](Greaseweazle::get_pin) and
767/// [`set_pin`](Greaseweazle::set_pin). Some pins have two or more incompatible functions,
768/// depending on the drive and how it's configured, and there may be more obscure drives with yet
769/// other functions not mentioned here.
770#[derive(Debug, Clone, Copy, FromPrimitive, IntoPrimitive)]
771#[non_exhaustive]
772#[repr(u8)]
773pub enum Pin {
774 /// Can be set.
775 ///
776 /// - **Density select**:
777 /// - [`Low`] = High density.
778 /// - [`High`] = Low density.
779 /// - **Head load**: This function is used on some early floppy drives.
780 /// - [`Low`] = The head is pressed down onto the disk, ready for reading/writing.
781 /// - [`High`] = The head is released from the disk.
782 ///
783 /// [`Low`]: PinLevel::Low
784 /// [`High`]: PinLevel::High
785 DensitySelect = 2,
786
787 /// Can be set.
788 ///
789 /// - **Head load**:
790 /// - [`Low`] = The head is pressed down onto the disk, ready for reading/writing.
791 /// - [`High`] = The head is released from the disk.
792 /// - **In use**:
793 /// - [`Low`] = The drive is prepared for reading/writing.
794 /// Some drives may lock the door/eject mechanism, but on others, this signal only makes
795 /// the drive light turn on.
796 ///
797 /// [`Low`]: PinLevel::Low
798 /// [`High`]: PinLevel::High
799 #[allow(non_camel_case_types)]
800 HeadLoad_InUse = 4,
801
802 /// Can be set.
803 ///
804 /// - **Drive select 3**: This function is used on most floppy drives, but is not present on
805 /// the IBM PC floppy controller.
806 /// - [`Low`] = Selects drive 3. This probably should not be used with the Greaseweazle.
807 /// - **Ready**: This function is used on a few early floppy drives.
808 /// - [`Low`] = Disk is inserted and spinning.
809 ///
810 /// [`Low`]: PinLevel::Low
811 #[allow(non_camel_case_types)]
812 DriveSelect3 = 6,
813
814 /// Read-only, cannot be set.
815 ///
816 /// - **Index**:
817 /// - [`Low`] = The index hole is over the index sensor.
818 ///
819 /// [`Low`]: PinLevel::Low
820 Index = 8,
821
822 /// Read-only, cannot be set.
823 ///
824 /// - **Track 0**:
825 /// - [`Low`] = The head is positioned above cylinder 0.
826 ///
827 /// [`Low`]: PinLevel::Low
828 Track0 = 26,
829
830 /// Read-only, cannot be set.
831 ///
832 /// - **Write protect**:
833 /// - [`Low`] = The disk is write-protected.
834 ///
835 /// [`Low`]: PinLevel::Low
836 WriteProtect = 28,
837
838 /// Read-only, cannot be set.
839 ///
840 /// - **Disk change**: This function is used on the IBM PC.
841 /// - [`Low`] = Disk has been changed since the last access.
842 /// - **Ready**: This function is used on some non-IBM PC drives.
843 /// - [`Low`] = The disk is inserted and spinning, ready to load the head.
844 /// - **In use**: This function is used on a few early floppy drives.
845 /// - [`Low`] = The drive is prepared for reading/writing.
846 /// Some drives may lock the door/eject mechanism, but on others, this signal only makes
847 /// the drive light turn on.
848 ///
849 /// [`Low`]: PinLevel::Low
850 #[allow(non_camel_case_types)]
851 DiskChange_Ready = 34,
852
853 /// Other pin.
854 ///
855 /// This is provided for future extensions of the Greaseweazle.
856 #[num_enum(catch_all)]
857 Other(u8),
858}
859
860/// Possible levels for a pin.
861#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
862#[repr(u8)]
863pub enum PinLevel {
864 /// Logic low, or 0.
865 Low = 0,
866
867 /// Logic high, or 1.
868 #[num_enum(default)]
869 High = 1,
870}
871
872/// Bandwidth statistics.
873///
874/// Returned by the [`get_bandwidth_stats`](Greaseweazle::get_bandwidth_stats) command.
875#[derive(Debug, Clone, Copy)]
876#[non_exhaustive]
877pub struct BandwidthStats {
878 /// The minimum reported bandwidth.
879 pub min: Bandwidth,
880
881 /// The maximum reported bandwidth.
882 pub max: Bandwidth,
883}
884
885impl BandwidthStats {
886 pub(crate) fn read_from(mut read: impl Read) -> Result<Self, io::Error> {
887 Ok(Self {
888 min: Bandwidth::read_from(&mut read)?,
889 max: Bandwidth::read_from(&mut read)?,
890 })
891 }
892}
893
894/// Bandwidth value.
895#[derive(Debug, Clone, Copy)]
896#[non_exhaustive]
897pub struct Bandwidth {
898 /// Number of bytes transferred.
899 pub bytes: u32,
900
901 /// The number of microseconds it took to transfer `bytes` bytes.
902 pub per_us: u32,
903}
904
905impl Bandwidth {
906 fn read_from(mut read: impl Read) -> Result<Self, io::Error> {
907 Ok(Self {
908 bytes: read.read_u32::<LE>()?,
909 per_us: read.read_u32::<LE>()?,
910 })
911 }
912
913 /// Returns the bandwidth in bits per second.
914 #[inline]
915 pub const fn as_bits_per_sec(&self) -> f64 {
916 self.bytes as f64 / self.per_us as f64 * 8_000_000.0
917 }
918}
919
920#[derive(IntoPrimitive)]
921#[repr(u32)]
922pub(crate) enum BaudRate {
923 Normal = 9600,
924 ClearComms = 10000,
925}
926
927#[derive(Debug, Clone, Copy, IntoPrimitive)]
928#[non_exhaustive]
929#[repr(u8)]
930pub(crate) enum Command {
931 GetInfo = 0,
932 #[allow(dead_code)]
933 Update = 1,
934 Seek = 2,
935 Head = 3,
936 SetParams = 4,
937 GetParams = 5,
938 Motor = 6,
939 ReadFlux = 7,
940 WriteFlux = 8,
941 GetFluxStatus = 9,
942 #[allow(dead_code)]
943 SwitchFwMode = 11,
944 Select = 12,
945 Deselect = 13,
946 SetBusType = 14,
947 SetPin = 15,
948 Reset = 16,
949 EraseFlux = 17,
950 SourceBytes = 18,
951 SinkBytes = 19,
952 GetPin = 20,
953 #[allow(dead_code)]
954 TestMode = 21,
955 NoClickStep = 22,
956}