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}