monotron_api/
lib.rs

1//! # Monotron API
2//!
3//! This crate contains the Userspace to Kernel API for the Monotron.
4//!
5//! It is pulled in by the Kernel (github.com/thejpster/monotron) and the
6//! various user-space example applications
7//! (github.com/thejpster/monotron-apps).
8//!
9//! The API in here is modelled after both the UNIX/POSIX API and the MS-DOS
10//! API. We use function pointers rather than `SWI` calls (software
11//! interrupts), provided in a structure. This structure is designed to be
12//! extensible.
13//!
14//! A C header file version of this API can be generated with `cbindgen`.
15//!
16//! All types in this file must be `#[repr(C)]`.
17#![cfg_attr(not(test), no_std)]
18#![deny(missing_docs)]
19
20/// The set of Error codes the API can report.
21#[repr(C)]
22#[derive(Debug, Clone, Copy)]
23pub enum Error {
24    /// The given filename was not found
25    FileNotFound,
26    /// The given file handle was not valid
27    BadFileHandle,
28    /// Error reading or writing
29    IOError,
30    /// You can't do that operation on that sort of file
31    NotSupported,
32    /// An unknown error occured
33    Unknown = 0xFFFF,
34}
35
36/// Describes a handle to some resource.
37#[repr(C)]
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub struct Handle(pub u16);
40
41/// Describes a string of fixed length, which must not be free'd by the
42/// recipient. The given length must not include any null terminators that may
43/// be present. The string must be valid UTF-8 (or 7-bit ASCII, which is a
44/// valid subset of UTF-8).
45#[repr(C)]
46#[derive(Debug, Clone, Eq)]
47pub struct BorrowedString {
48    /// The start of the string
49    pub ptr: *const u8,
50    /// The length of the string in bytes
51    pub length: usize,
52}
53
54impl BorrowedString {
55    /// Create a new API-compatible borrowed string, from a static string slice.
56    pub fn new(value: &'static str) -> BorrowedString {
57        BorrowedString {
58            ptr: value.as_ptr(),
59            length: value.len(),
60        }
61    }
62}
63
64impl core::cmp::PartialEq for BorrowedString {
65    fn eq(&self, rhs: &BorrowedString) -> bool {
66        if self.length == rhs.length {
67            let left = unsafe { core::slice::from_raw_parts(self.ptr, self.length) };
68            let right = unsafe { core::slice::from_raw_parts(rhs.ptr, rhs.length) };
69            left == right
70        } else {
71            false
72        }
73    }
74}
75
76/// Describes the result of a function which may return a `Handle` if
77/// everything was Ok, or return an `Error` if something went wrong.
78///
79/// This is not a standard Rust `Result` because they are not `#[repr(C)]`.
80#[repr(C)]
81#[derive(Debug)]
82pub enum HandleResult {
83    /// Success - a handle is returned
84    Ok(Handle),
85    /// Failure - an error is returned
86    Error(Error),
87}
88
89/// Describes the result of a function which may return nothing if everything
90/// was Ok, or return an `Error` if something went wrong.
91///
92/// This is not a standard Rust `Result` because they are not `#[repr(C)]`.
93#[repr(C)]
94#[derive(Debug)]
95pub enum EmptyResult {
96    /// Success - nothing is returned
97    Ok,
98    /// Failure - an error is returned
99    Error(Error),
100}
101
102/// Describes the result of a function which may return a numeric count of
103/// bytes read/written if everything was Ok, or return an `Error` if something
104/// went wrong.
105///
106/// This is not a standard Rust `Result` because they are not `#[repr(C)]`.
107#[repr(C)]
108#[derive(Debug)]
109pub enum SizeResult {
110    /// Success - a size in bytes is returned
111    Ok(usize),
112    /// Failure - an error is returned
113    Error(Error),
114}
115
116/// Describes the sort of files you will find in the system-wide virtual
117/// filesystem. Some exist on disk, and some do not.
118#[repr(C)]
119#[derive(Debug, Copy, Clone, PartialEq, Eq)]
120pub enum FileType {
121    /// A regular file
122    File,
123    /// A directory contains other files and directories
124    Directory,
125    /// A device you can read/write a block (e.g. 512 bytes) at a time
126    BlockDevice,
127    /// A device you can read/write one or more bytes at a time
128    CharDevice,
129}
130
131/// Describes an instant in time. The system only supports local time and has
132/// no concept of time zones.
133#[repr(C)]
134#[derive(Debug, Clone, PartialEq, Eq)]
135pub struct Timestamp {
136    /// The Gregorian calendar year, minus 1970 (so 10 is 1980, and 30 is the year 2000)
137    pub year_from_1970: u8,
138    /// The month of the year, where January is 1 and December is 12
139    pub month: u8,
140    /// The day of the month where 1 is the first of the month, through to 28,
141    /// 29, 30 or 31 (as appropriate)
142    pub days: u8,
143    /// The hour in the day, from 0 to 23
144    pub hours: u8,
145    /// The minutes past the hour, from 0 to 59
146    pub minutes: u8,
147    /// The seconds past the minute, from 0 to 59. Note that some filesystems
148    /// only have 2-second precision on their timestamps.
149    pub seconds: u8,
150}
151
152/// Represents the seven days of the week
153#[repr(C)]
154#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
155pub enum DayOfWeek {
156    /// First day of the week
157    Monday,
158    /// Comes after Monday
159    Tuesday,
160    /// Middle of the week
161    Wednesday,
162    /// Between Wednesday and Friday
163    Thursday,
164    /// Almost the weekend
165    Friday,
166    /// First day of the weekend
167    Saturday,
168    /// Last day of the week
169    Sunday,
170}
171
172impl DayOfWeek {
173    /// Returns the UK English word for the day of the week
174    pub fn day_str(&self) -> &'static str {
175        match self {
176            DayOfWeek::Monday => "Monday",
177            DayOfWeek::Tuesday => "Tuesday",
178            DayOfWeek::Wednesday => "Wednesday",
179            DayOfWeek::Thursday => "Thursday",
180            DayOfWeek::Friday => "Friday",
181            DayOfWeek::Saturday => "Saturday",
182            DayOfWeek::Sunday => "Sunday",
183        }
184    }
185}
186
187impl Timestamp {
188    /// Returns the day of the week for the given timestamp.
189    pub fn day_of_week(&self) -> DayOfWeek {
190        let zellers_month = ((i32::from(self.month) + 9) % 12) + 1;
191        let k = i32::from(self.days);
192        let year = if zellers_month >= 11 {
193            i32::from(self.year_from_1970) + 1969
194        } else {
195            i32::from(self.year_from_1970) + 1970
196        };
197        let d = year % 100;
198        let c = year / 100;
199        let f = k + (((13 * zellers_month) - 1) / 5) + d + (d / 4) + (c / 4) - (2 * c);
200        let day_of_week = f % 7;
201        match day_of_week {
202            0 => DayOfWeek::Sunday,
203            1 => DayOfWeek::Monday,
204            2 => DayOfWeek::Tuesday,
205            3 => DayOfWeek::Wednesday,
206            4 => DayOfWeek::Thursday,
207            5 => DayOfWeek::Friday,
208            _ => DayOfWeek::Saturday,
209        }
210    }
211
212    /// Move this timestamp forward by a number of days and seconds.
213    pub fn increment(&mut self, days: u32, seconds: u32) {
214        let new_seconds = seconds + u32::from(self.seconds);
215        self.seconds = (new_seconds % 60) as u8;
216        let new_minutes = (new_seconds / 60) + u32::from(self.minutes);
217        self.minutes = (new_minutes % 60) as u8;
218        let new_hours = (new_minutes / 60) + u32::from(self.hours);
219        self.hours = (new_hours % 24) as u8;
220        let mut new_days = (new_hours / 24) + u32::from(self.days) + days;
221        while new_days > u32::from(self.days_in_month()) {
222            new_days -= u32::from(self.days_in_month());
223            self.month += 1;
224            if self.month > 12 {
225                self.month = 1;
226                self.year_from_1970 += 1;
227            }
228        }
229        self.days = new_days as u8;
230    }
231
232    /// Returns true if this is a leap year, false otherwise.
233    pub fn is_leap_year(&self) -> bool {
234        let year = u32::from(self.year_from_1970) + 1970;
235        (year == 2000) || (((year % 4) == 0) && ((year % 100) != 0))
236    }
237
238    /// Returns the number of days in the current month
239    pub fn days_in_month(&self) -> u8 {
240        match self.month {
241            1 => 31,
242            2 if self.is_leap_year() => 29,
243            2 => 28,
244            3 => 31,
245            4 => 30,
246            5 => 31,
247            6 => 30,
248            7 => 31,
249            8 => 31,
250            9 => 30,
251            10 => 31,
252            11 => 30,
253            12 => 31,
254            _ => panic!("Bad timestamp {:?}", self),
255        }
256    }
257
258    /// Returns the current month as a UK English string (e.g. "August").
259    pub fn month_str(&self) -> &'static str {
260        match self.month {
261            1 => "January",
262            2 => "February",
263            3 => "March",
264            4 => "April",
265            5 => "May",
266            6 => "June",
267            7 => "July",
268            8 => "August",
269            9 => "September",
270            10 => "October",
271            11 => "November",
272            12 => "December",
273            _ => "Unknown",
274        }
275    }
276}
277
278impl core::fmt::Display for Timestamp {
279    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
280        write!(
281            f,
282            "{year:04}-{month:02}-{days:02}T{hours:02}:{minutes:02}:{seconds:02}",
283            year = u16::from(self.year_from_1970) + 1970u16,
284            month = self.month,
285            days = self.days,
286            hours = self.hours,
287            minutes = self.minutes,
288            seconds = self.seconds,
289        )
290    }
291}
292
293/// Describes a file as it exists on disk.
294#[repr(C)]
295#[derive(Debug, Clone)]
296pub struct DirEntry {
297    /// The file of the file this entry represents
298    pub file_type: FileType,
299    /// The name of the file (not including its full path)
300    pub name: [u8; 11],
301    /// The sie of the file in bytes
302    pub size: u32,
303    /// When this file was last modified
304    pub mtime: Timestamp,
305    /// When this file was created
306    pub ctime: Timestamp,
307    /// The various mode bits set on this file
308    pub mode: FileMode,
309}
310
311/// A bitfield indicating if a file is:
312///
313/// * read-only
314/// * a volume label
315/// * a system file
316/// * in need of archiving
317#[repr(C)]
318#[derive(Debug, Clone, Copy)]
319pub struct FileMode(u8);
320
321/// Is the read-only bit set in this FileMode bit-field?
322#[no_mangle]
323pub extern "C" fn monotron_filemode_is_readonly(flags: FileMode) -> bool {
324    (flags.0 & FileMode::READ_ONLY) != 0
325}
326
327/// Is the volume label bit set in this FileMode bit-field?
328#[no_mangle]
329pub extern "C" fn monotron_filemode_is_volume(flags: FileMode) -> bool {
330    (flags.0 & FileMode::VOLUME) != 0
331}
332
333/// Is the system bit set in this FileMode bit-field?
334#[no_mangle]
335pub extern "C" fn monotron_filemode_is_system(flags: FileMode) -> bool {
336    (flags.0 & FileMode::SYSTEM) != 0
337}
338
339/// Is the archive bit set in this FileMode bit-field?
340#[no_mangle]
341pub extern "C" fn monotron_filemode_is_archive(flags: FileMode) -> bool {
342    (flags.0 & FileMode::ARCHIVE) != 0
343}
344
345impl FileMode {
346    const READ_ONLY: u8 = 1;
347    const VOLUME: u8 = 2;
348    const SYSTEM: u8 = 4;
349    const ARCHIVE: u8 = 8;
350}
351
352/// Represents how far to move the current read/write pointer through a file.
353/// You can specify the position as relative to the start of the file,
354/// relative to the end of the file, or relative to the current pointer
355/// position.
356#[repr(C)]
357#[derive(Debug, Clone, PartialEq, Eq)]
358pub enum Offset {
359    /// Set the pointer to this many bytes from the start of the file
360    FromStart(u32),
361    /// Set the pointer to this many bytes from the current position (+ve is forwards, -ve is backwards)
362    FromCurrent(i32),
363    /// Set the pointer to this many bytes back from the end of the file
364    FromEnd(u32),
365}
366
367/// The ways in which we can open a file.
368///
369/// TODO: Replace all these booleans with a u8 flag-set
370#[repr(C)]
371pub enum OpenMode {
372    /// Open file in read-only mode. No writes allowed. One file can be opened in read-only mode multiple times.
373    ReadOnly {
374        /// Set to true if read/write requests on this handle should be non-blocking
375        non_blocking: bool,
376    },
377    /// Open a file for writing, but not reading.
378    WriteOnly {
379        /// If true, the write pointer will default to the end of the file
380        append: bool,
381        /// If true, the file will be created if it doesn't exist. If false, the file must exist. See also the `exclusive` flag.
382        create: bool,
383        /// If true AND the create flag is true, the open will fail if the file already exists.
384        exclusive: bool,
385        /// If true, the file contents will be deleted on open, giving a zero byte file.
386        truncate: bool,
387        /// Set to true if read/write requests on this handle should be non-blocking
388        non_blocking: bool,
389    },
390    /// Open a file for reading and writing.
391    ReadWrite {
392        /// If true, the write pointer will default to the end of the file
393        append: bool,
394        /// If true, the file will be created if it doesn't exist. If false, the file must exist. See also the `exclusive` flag.
395        create: bool,
396        /// If true AND the create flag is true, the open will fail if the file already exists.
397        exclusive: bool,
398        /// If true, the file contents will be deleted on open, giving a zero byte file.
399        truncate: bool,
400        /// Set to true if read/write requests on this handle should be non-blocking
401        non_blocking: bool,
402    },
403}
404
405/// Create a new Read Only open mode object, for passing to the `open` syscall.
406#[no_mangle]
407pub extern "C" fn monotron_openmode_readonly(non_blocking: bool) -> OpenMode {
408    OpenMode::ReadOnly { non_blocking }
409}
410
411/// Create a new Write Only open mode object, for passing to the `open` syscall.
412#[no_mangle]
413pub extern "C" fn monotron_openmode_writeonly(
414    append: bool,
415    create: bool,
416    exclusive: bool,
417    truncate: bool,
418    non_blocking: bool,
419) -> OpenMode {
420    OpenMode::WriteOnly {
421        append,
422        create,
423        exclusive,
424        truncate,
425        non_blocking,
426    }
427}
428
429/// Create a new Read Write open mode object, for passing to the `open` syscall.
430#[no_mangle]
431pub extern "C" fn monotron_openmode_readwrite(
432    append: bool,
433    create: bool,
434    exclusive: bool,
435    truncate: bool,
436    non_blocking: bool,
437) -> OpenMode {
438    OpenMode::ReadWrite {
439        append,
440        create,
441        exclusive,
442        truncate,
443        non_blocking,
444    }
445}
446
447/// Standard Output
448pub static STDOUT: Handle = Handle(0);
449
450/// Standard Error
451pub static STDERR: Handle = Handle(1);
452
453/// Standard Input
454pub static STDIN: Handle = Handle(2);
455
456/// This structure contains all the function pointers the application can use
457/// to access OS functions.
458#[repr(C)]
459pub struct Api {
460    /// Old function for writing a single 8-bit character to the screen.
461    pub putchar: extern "C" fn(ch: u8) -> i32,
462
463    /// Old function for writing a null-terminated 8-bit string to the screen.
464    pub puts: extern "C" fn(string: *const u8) -> i32,
465
466    /// Old function for reading one byte from stdin, blocking.
467    pub readc: extern "C" fn() -> i32,
468
469    /// Old function for checking if readc() would block.
470    pub kbhit: extern "C" fn() -> i32,
471
472    /// Old function for moving the cursor on screen. To be replaced with ANSI
473    /// escape codes.
474    pub move_cursor: extern "C" fn(row: u8, col: u8),
475
476    /// Old function for playing a note.
477    pub play: extern "C" fn(frequency: u32, channel: u8, volume: u8, waveform: u8) -> i32,
478
479    /// Old function for changing the on-screen font.
480    pub change_font: extern "C" fn(font_id: u32, font_data: *const u8),
481
482    /// Old function for reading the Joystick status.
483    pub get_joystick: extern "C" fn() -> u8,
484
485    /// Old function for turning the cursor on/off.
486    pub set_cursor_visible: extern "C" fn(enabled: u8),
487
488    /// Old function for reading the contents of the screen.
489    pub read_char_at: extern "C" fn(row: u8, col: u8) -> u16,
490
491    /// Wait for next vertical blanking interval.
492    pub wfvbi: extern "C" fn(),
493
494    /// Open/create a device/file. Returns a file handle, or an error.
495    pub open: extern "C" fn(filename: BorrowedString, mode: OpenMode) -> HandleResult,
496
497    /// Close a previously opened handle.
498    pub close: extern "C" fn(handle: Handle) -> EmptyResult,
499
500    /// Read from a file handle into the given buffer. Returns an error, or
501    /// the number of bytes read (which may be less than `buffer_len`).
502    pub read: extern "C" fn(handle: Handle, buffer: *mut u8, buffer_len: usize) -> SizeResult,
503
504    /// Write the contents of the given buffer to a file handle. Returns an
505    /// error, or the number of bytes written (which may be less than
506    /// `buffer_len`).
507    pub write: extern "C" fn(handle: Handle, buffer: *const u8, buffer_len: usize) -> SizeResult,
508
509    /// Write to the handle and the read from the handle. Useful when doing an
510    /// I2C read of a specific address. It is an error if the complete
511    /// `out_buffer` could not be written.
512    pub write_then_read: extern "C" fn(
513        handle: Handle,
514        out_buffer: *const u8,
515        out_buffer_len: usize,
516        in_buffer: *mut u8,
517        in_buffer_len: usize,
518    ) -> SizeResult,
519
520    /// Move the read/write pointer in a file.
521    pub seek: extern "C" fn(handle: Handle, offset: Offset) -> EmptyResult,
522
523    /// Open a directory. Returns a file handle, or an error.
524    pub opendir: extern "C" fn(filename: BorrowedString) -> HandleResult,
525
526    /// Read directory entry into given buffer.
527    pub readdir: extern "C" fn(handle: Handle, dir_entry: &mut DirEntry) -> EmptyResult,
528
529    /// Get information about a file by path
530    pub stat: extern "C" fn(filename: BorrowedString, stat_entry: &mut DirEntry) -> EmptyResult,
531
532    /// Get the current time
533    pub gettime: extern "C" fn() -> Timestamp,
534
535    /// Old function for writing a UTF-8 string to the screen.
536    pub puts_utf8: extern "C" fn(string: *const u8, length: usize),
537
538    /// Maps an actual line on the screen to be drawn as if it was somewhere else on the screen.
539    ///
540    /// So if you ran this, the image would look completely normal:
541    ///
542    /// ```rust
543    /// for x in 0..576 {
544    ///     map_line(x, x);
545    /// }
546    /// ```
547    ///
548    /// But if you did this, the screen would be upside down.
549    ///
550    /// ```rust
551    /// for x in 0..576 {
552    ///     map_line(x, 576 - x);
553    /// }
554    /// ```
555    ///
556    /// And if you did this, the top 32 scanlines on the screen would repeat
557    /// all the way down.
558    ///
559    /// ```rust
560    /// for x in 0..576 {
561    ///     map_line(x, x % 32);
562    /// }
563    /// ```
564    pub map_line: extern "C" fn(actual_scanline: u16, drawn_scanline: u16),
565    /// Get the current cursor position
566    pub get_cursor: extern "C" fn(row: *mut u8, col: *mut u8),
567}
568
569#[cfg(test)]
570mod test {
571    use super::*;
572
573    #[test]
574    fn day_of_week() {
575        let samples = [
576            (
577                Timestamp {
578                    year_from_1970: 49,
579                    month: 7,
580                    day: 16,
581                    hours: 0,
582                    minutes: 0,
583                    seconds: 0,
584                },
585                DayOfWeek::Tuesday,
586            ),
587            (
588                Timestamp {
589                    year_from_1970: 49,
590                    month: 7,
591                    day: 17,
592                    hours: 0,
593                    minutes: 0,
594                    seconds: 0,
595                },
596                DayOfWeek::Wednesday,
597            ),
598            (
599                Timestamp {
600                    year_from_1970: 49,
601                    month: 7,
602                    day: 18,
603                    hours: 0,
604                    minutes: 0,
605                    seconds: 0,
606                },
607                DayOfWeek::Thursday,
608            ),
609        ];
610        for (timestamp, day) in samples.iter() {
611            assert_eq!(timestamp.day_of_week(), *day);
612        }
613    }
614}