├── .gitattributes ├── .github └── workflows │ └── lint.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── clicky-core ├── Cargo.toml └── src │ ├── block │ ├── backend │ │ ├── mem.rs │ │ ├── mod.rs │ │ ├── null.rs │ │ └── raw.rs │ └── mod.rs │ ├── devices │ ├── _newdev.rs │ ├── display │ │ ├── hd66753.rs │ │ └── mod.rs │ ├── generic │ │ ├── asanram.rs │ │ ├── ide │ │ │ ├── identify.rs │ │ │ ├── mod.rs │ │ │ └── reg.rs │ │ ├── mod.rs │ │ ├── ram.rs │ │ └── stub.rs │ ├── i2c │ │ ├── devices │ │ │ ├── mod.rs │ │ │ └── pcf5060x.rs │ │ ├── mod.rs │ │ └── prelude.rs │ ├── mod.rs │ ├── platform │ │ ├── mod.rs │ │ ├── pp.rs │ │ └── pp │ │ │ ├── cachecon.rs │ │ │ ├── cfg_timer.rs │ │ │ ├── cpucon.rs │ │ │ ├── cpuid.rs │ │ │ ├── devcon.rs │ │ │ ├── dma.rs │ │ │ ├── eide.rs │ │ │ ├── evp.rs │ │ │ ├── flash.rs │ │ │ ├── gpio.rs │ │ │ ├── i2c.rs │ │ │ ├── i2s.rs │ │ │ ├── intcon.rs │ │ │ ├── mailbox.rs │ │ │ ├── memcon.rs │ │ │ ├── opto.rs │ │ │ ├── piezo.rs │ │ │ ├── ppcon.rs │ │ │ ├── pwm.rs │ │ │ ├── serial.rs │ │ │ └── usec_timer.rs │ ├── prelude.rs │ └── util │ │ ├── arcmutex.rs │ │ ├── mem_sniffer.rs │ │ └── mod.rs │ ├── error.rs │ ├── executor │ └── mod.rs │ ├── gui │ └── mod.rs │ ├── lib.rs │ ├── memory │ ├── access.rs │ ├── armv4t_adaptor.rs │ └── mod.rs │ ├── signal │ ├── gpio.rs │ ├── irq.rs │ └── mod.rs │ └── sys │ ├── ipod4g │ ├── controls.rs │ ├── gdb.rs │ ├── hle_bootloader │ │ ├── firmware.rs │ │ ├── mod.rs │ │ └── sysinfo.rs │ └── mod.rs │ └── mod.rs ├── clicky-desktop ├── Cargo.toml ├── README.md └── src │ ├── backends │ ├── minifb.rs │ └── mod.rs │ ├── blockcfg.rs │ ├── controls │ ├── minifb │ │ ├── ipod4g.rs │ │ └── mod.rs │ └── mod.rs │ ├── gdb.rs │ └── main.rs ├── clicky-web ├── .appveyor.yml ├── .gitignore ├── .travis.yml ├── Cargo.toml ├── README.md ├── src │ └── lib.rs └── www │ ├── .gitignore │ ├── .travis.yml │ ├── README.md │ ├── bootstrap.js │ ├── clicky.worker.js │ ├── index.html │ ├── index.js │ ├── main.css │ ├── package-lock.json │ ├── package.json │ ├── resources │ └── README.md │ └── webpack.config.js ├── docs ├── ARCHITECTURE.md ├── DEVGUIDE.md ├── QUICKSTART.md └── REPO_LAYOUT.md ├── relativity ├── Cargo.toml ├── README.md └── src │ └── lib.rs ├── resources ├── .gitignore ├── NOTES.md ├── documentation │ ├── LINKS.md │ ├── Linux4NanoReport.pdf │ ├── Rockbox │ │ ├── ipod4g.h │ │ └── pp5020.h │ ├── component_spec_sheets │ │ ├── PCF50606_Philips.pdf │ │ ├── ipod_4g_display_ehd66753.pdf │ │ └── ipod_5g_video_BCM2722_brief.pdf │ ├── d0948r4c-ATA-2.pdf │ ├── iPodLinux │ │ ├── PP5002.pdf │ │ ├── PP5020.pdf │ │ ├── firmware_and_boot.pdf │ │ ├── flash_decryption.pdf │ │ ├── ipod_games.pdf │ │ └── memory_controller.pdf │ └── memory_controller.txt ├── flashutils │ ├── .gitignore │ ├── flashsplit.c │ └── ipodloader_diagmode.sh ├── ipodloader │ ├── .gdbinit │ ├── .gitignore │ ├── ChangeLog │ ├── Makefile │ ├── README │ ├── README.md │ ├── arm_elf_40.x │ ├── getopt.c │ ├── happymac.c │ ├── loader.c │ ├── make_fw.c │ ├── pics │ │ └── happymac.xpm │ ├── startup.s │ ├── tools.c │ ├── tools.h │ ├── tux.c │ └── xpm2src.pl ├── ipodloader2 │ ├── .gdbinit │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── TODO │ ├── arm_elf_40.x │ ├── ata2.c │ ├── ata2.h │ ├── bootloader.h │ ├── config.c │ ├── config.h │ ├── console.c │ ├── console.h │ ├── ext2.c │ ├── ext2.h │ ├── fat32.c │ ├── fat32.h │ ├── fb.c │ ├── fb.h │ ├── fontlarge.h │ ├── fontmedium.h │ ├── fontsmall.h │ ├── fwfs.c │ ├── fwfs.h │ ├── getLoader2Args │ │ ├── Makefile │ │ ├── getLoader2Args.c │ │ └── readme.txt │ ├── hfsplusstructs.h │ ├── interrupt-entry.s │ ├── interrupts.c │ ├── interrupts.h │ ├── ipodhw.c │ ├── ipodhw.h │ ├── keypad.c │ ├── keypad.h │ ├── loader.c │ ├── lockicon.h │ ├── loop.bin │ ├── macpartitions.cc │ ├── macpartitions.h │ ├── menu.c │ ├── menu.h │ ├── minilibc.c │ ├── minilibc.h │ ├── startup.s │ ├── unicodecmp.h │ ├── vfs.c │ └── vfs.h └── loop.bin ├── rustfmt.toml ├── screenshots ├── clicky-ipodloader2-lle.gif ├── clicky-rockbox-boot.gif ├── logo-cropped.png └── logo.png └── scripts └── rawhd ├── add_ipodloader_cfg.sh ├── copy_rockbox.sh ├── loopback_delete.sh ├── loopback_setup.sh ├── make_rawhd.sh └── patch_aupd_bit.sh /.gitattributes: -------------------------------------------------------------------------------- 1 | resources/** linguist-vendored 2 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | clippy_check: 11 | strategy: 12 | matrix: 13 | crate: 14 | - clicky-core 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v1 18 | - run: rustup component add clippy 19 | - uses: actions-rs/clippy-check@v1 20 | with: 21 | token: ${{ secrets.GITHUB_TOKEN }} 22 | args: --all-features --package ${{ matrix.crate }} 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | 4 | .gdb_history 5 | sysdump.log 6 | *.img* 7 | 8 | # I use a custom config to speed up link times 9 | .cargo/ 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["clicky-core", "clicky-desktop", "clicky-web", "relativity"] 3 | 4 | [profile.release] 5 | panic = "abort" 6 | debug = true 7 | -------------------------------------------------------------------------------- /clicky-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "clicky-core" 3 | version = "0.1.0" 4 | authors = ["Daniel Prilik "] 5 | edition = "2018" 6 | 7 | [features] 8 | wasm-bindgen = [ "relativity/wasm-bindgen", "chrono/wasmbind" ] 9 | 10 | [dependencies] 11 | # home-grown deps 12 | relativity = { path = "../relativity/" } 13 | 14 | # general utilities 15 | bit_field = "0.10" 16 | bytemuck = "1.2" 17 | byteorder = "1.3" 18 | cfg-if = "0.1" 19 | chrono = "0.4" 20 | either = "1.9.0" 21 | log = "0.4" 22 | num_enum = "0.5" 23 | static_assertions = "1.1" 24 | thiserror = "1.0" 25 | 26 | # emulation related 27 | armv4t_emu = { git = "https://github.com/daniel5151/armv4t_emu.git" } 28 | gdbstub = "0.4" 29 | 30 | # async/await 31 | async-channel = "1.4" 32 | blocking = "0.5" 33 | futures-executor = { version = "0.3", features = ["thread-pool"] } # TEMP 34 | pin-utils = "0.1" 35 | [dependencies.futures] 36 | version = "0.3" 37 | default-features = false 38 | features = ["std"] 39 | -------------------------------------------------------------------------------- /clicky-core/src/block/backend/mem.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Cursor, Read, Seek, Write}; 2 | use std::pin::Pin; 3 | use std::task::{Context, Poll}; 4 | 5 | use futures::io::{AsyncRead, AsyncSeek, AsyncWrite}; 6 | 7 | use crate::block::BlockDev; 8 | 9 | /// Memory-backed block device. No fancy features, just raw 1:1 access to an 10 | /// in-memory buffer. 11 | pub struct Mem { 12 | len: usize, 13 | data: Cursor>, 14 | } 15 | 16 | impl std::fmt::Debug for Mem { 17 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 18 | f.debug_struct("Mem") 19 | .field("len", &self.len) 20 | .field("data", &"[...]") 21 | .finish() 22 | } 23 | } 24 | 25 | impl Mem { 26 | /// Create a memory-backed block device from some existing data. 27 | pub fn new(data: Box<[u8]>) -> Mem { 28 | Mem { 29 | len: data.len(), 30 | data: Cursor::new(data), 31 | } 32 | } 33 | } 34 | 35 | impl BlockDev for Mem { 36 | fn len(&self) -> u64 { 37 | self.len as u64 38 | } 39 | } 40 | 41 | impl Read for Mem { 42 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 43 | self.data.read(buf) 44 | } 45 | } 46 | 47 | impl Write for Mem { 48 | fn write(&mut self, buf: &[u8]) -> io::Result { 49 | self.data.write(buf) 50 | } 51 | 52 | fn flush(&mut self) -> io::Result<()> { 53 | self.data.flush() 54 | } 55 | } 56 | 57 | impl Seek for Mem { 58 | fn seek(&mut self, pos: io::SeekFrom) -> io::Result { 59 | self.data.seek(pos) 60 | } 61 | } 62 | 63 | impl AsyncRead for Mem { 64 | fn poll_read( 65 | mut self: Pin<&mut Self>, 66 | _cx: &mut Context<'_>, 67 | buf: &mut [u8], 68 | ) -> Poll> { 69 | Poll::Ready(io::Read::read(&mut *self, buf)) 70 | } 71 | } 72 | 73 | impl AsyncWrite for Mem { 74 | fn poll_write( 75 | mut self: Pin<&mut Self>, 76 | _cx: &mut Context<'_>, 77 | buf: &[u8], 78 | ) -> Poll> { 79 | Poll::Ready(io::Write::write(&mut *self, buf)) 80 | } 81 | 82 | fn poll_flush(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 83 | Poll::Ready(io::Write::flush(&mut *self)) 84 | } 85 | 86 | fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 87 | Poll::Ready(Ok(())) 88 | } 89 | } 90 | 91 | impl AsyncSeek for Mem { 92 | fn poll_seek( 93 | mut self: Pin<&mut Self>, 94 | _cx: &mut Context<'_>, 95 | pos: io::SeekFrom, 96 | ) -> Poll> { 97 | Poll::Ready(io::Seek::seek(&mut *self, pos)) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /clicky-core/src/block/backend/mod.rs: -------------------------------------------------------------------------------- 1 | //! Block device backends. 2 | 3 | mod mem; 4 | mod null; 5 | mod raw; 6 | 7 | pub use mem::Mem; 8 | pub use null::Null; 9 | pub use raw::Raw; 10 | -------------------------------------------------------------------------------- /clicky-core/src/block/backend/null.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Read, Seek, Write}; 2 | use std::pin::Pin; 3 | use std::task::{Context, Poll}; 4 | 5 | use futures::io::{AsyncRead, AsyncSeek, AsyncWrite}; 6 | 7 | use crate::block::BlockDev; 8 | 9 | /// Null block device. Can be configured to report any size, where reads always 10 | /// return zero, and writes are a noop. 11 | #[derive(Debug)] 12 | pub struct Null { 13 | len: u64, 14 | offset: u64, 15 | } 16 | 17 | impl Null { 18 | pub fn new(reported_len: u64) -> Null { 19 | Null { 20 | len: reported_len, 21 | offset: 0, 22 | } 23 | } 24 | } 25 | 26 | impl BlockDev for Null { 27 | fn len(&self) -> u64 { 28 | self.len 29 | } 30 | } 31 | 32 | impl Read for Null { 33 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 34 | buf.iter_mut().for_each(|b| *b = 0); 35 | Ok(buf.len()) 36 | } 37 | } 38 | 39 | impl Write for Null { 40 | fn write(&mut self, buf: &[u8]) -> io::Result { 41 | // noop 42 | Ok(buf.len()) 43 | } 44 | 45 | fn flush(&mut self) -> io::Result<()> { 46 | // noop 47 | Ok(()) 48 | } 49 | } 50 | 51 | impl Seek for Null { 52 | fn seek(&mut self, pos: io::SeekFrom) -> io::Result { 53 | // noop 54 | self.offset = match pos { 55 | io::SeekFrom::Start(v) => v, 56 | io::SeekFrom::End(v) => match self.len as i64 - v { 57 | o if o >= 0 => o as u64, 58 | _ => { 59 | return Err(io::Error::new( 60 | io::ErrorKind::InvalidInput, 61 | "cannot seek to negative offset", 62 | )) 63 | } 64 | }, 65 | io::SeekFrom::Current(v) => match self.offset as i64 + v { 66 | o if o >= 0 => o as u64, 67 | _ => { 68 | return Err(io::Error::new( 69 | io::ErrorKind::InvalidInput, 70 | "cannot seek to negative offset", 71 | )) 72 | } 73 | }, 74 | }; 75 | 76 | Ok(self.offset) 77 | } 78 | } 79 | 80 | impl AsyncRead for Null { 81 | fn poll_read( 82 | self: Pin<&mut Self>, 83 | _cx: &mut Context<'_>, 84 | buf: &mut [u8], 85 | ) -> Poll> { 86 | Poll::Ready(io::Read::read(self.get_mut(), buf)) 87 | } 88 | } 89 | 90 | impl AsyncWrite for Null { 91 | fn poll_write( 92 | self: Pin<&mut Self>, 93 | _cx: &mut Context<'_>, 94 | buf: &[u8], 95 | ) -> Poll> { 96 | Poll::Ready(io::Write::write(self.get_mut(), buf)) 97 | } 98 | 99 | fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 100 | Poll::Ready(io::Write::flush(self.get_mut())) 101 | } 102 | 103 | fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 104 | Poll::Ready(Ok(())) 105 | } 106 | } 107 | 108 | impl AsyncSeek for Null { 109 | fn poll_seek( 110 | self: Pin<&mut Self>, 111 | _cx: &mut Context<'_>, 112 | pos: io::SeekFrom, 113 | ) -> Poll> { 114 | Poll::Ready(io::Seek::seek(self.get_mut(), pos)) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /clicky-core/src/block/backend/raw.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::{self, Read, Seek, Write}; 3 | use std::pin::Pin; 4 | use std::task::{Context, Poll}; 5 | 6 | use blocking::Unblock; 7 | use futures::io::{AsyncRead, AsyncSeek, AsyncWrite}; 8 | use futures::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt}; 9 | 10 | use crate::block::BlockDev; 11 | 12 | /// Raw, file-backed block device. No fancy features, just raw 1:1 access to 13 | /// the underlying file's contents. 14 | #[derive(Debug)] 15 | pub struct Raw { 16 | len: u64, 17 | file: Unblock, 18 | } 19 | 20 | impl Raw { 21 | pub fn new(file: File) -> io::Result { 22 | Ok(Raw { 23 | len: file.metadata()?.len(), 24 | file: Unblock::new(file), 25 | }) 26 | } 27 | } 28 | 29 | impl BlockDev for Raw { 30 | fn len(&self) -> u64 { 31 | self.len 32 | } 33 | } 34 | 35 | impl Read for Raw { 36 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 37 | futures_executor::block_on(async { AsyncReadExt::read(self, buf).await }) 38 | } 39 | } 40 | 41 | impl Write for Raw { 42 | fn write(&mut self, buf: &[u8]) -> io::Result { 43 | futures_executor::block_on(async { AsyncWriteExt::write(self, buf).await }) 44 | } 45 | 46 | fn flush(&mut self) -> io::Result<()> { 47 | futures_executor::block_on(async { AsyncWriteExt::flush(self).await }) 48 | } 49 | } 50 | 51 | impl Seek for Raw { 52 | fn seek(&mut self, pos: io::SeekFrom) -> io::Result { 53 | futures_executor::block_on(async { AsyncSeekExt::seek(self, pos).await }) 54 | } 55 | } 56 | 57 | impl AsyncRead for Raw { 58 | fn poll_read( 59 | mut self: Pin<&mut Self>, 60 | cx: &mut Context<'_>, 61 | buf: &mut [u8], 62 | ) -> Poll> { 63 | AsyncRead::poll_read(Pin::new(&mut self.file), cx, buf) 64 | } 65 | } 66 | 67 | impl AsyncWrite for Raw { 68 | fn poll_write( 69 | mut self: Pin<&mut Self>, 70 | cx: &mut Context<'_>, 71 | buf: &[u8], 72 | ) -> Poll> { 73 | AsyncWrite::poll_write(Pin::new(&mut self.file), cx, buf) 74 | } 75 | 76 | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 77 | AsyncWrite::poll_flush(Pin::new(&mut self.file), cx) 78 | } 79 | 80 | fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 81 | AsyncWrite::poll_close(Pin::new(&mut self.file), cx) 82 | } 83 | } 84 | 85 | impl AsyncSeek for Raw { 86 | fn poll_seek( 87 | mut self: Pin<&mut Self>, 88 | cx: &mut Context<'_>, 89 | pos: io::SeekFrom, 90 | ) -> Poll> { 91 | AsyncSeek::poll_seek(Pin::new(&mut self.file), cx, pos) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /clicky-core/src/block/mod.rs: -------------------------------------------------------------------------------- 1 | //! Block device interface and backend implementations. 2 | 3 | use std::fmt::Debug; 4 | 5 | use futures::io::{AsyncRead, AsyncSeek, AsyncWrite}; 6 | 7 | pub mod backend; 8 | 9 | /// Abstraction over different Block Device backends. 10 | #[allow(clippy::len_without_is_empty)] 11 | pub trait BlockDev: Send + Sync + Unpin + Debug + AsyncRead + AsyncSeek + AsyncWrite { 12 | /// Return the length (in bytes) of the underlying medium. 13 | fn len(&self) -> u64; 14 | } 15 | -------------------------------------------------------------------------------- /clicky-core/src/devices/_newdev.rs: -------------------------------------------------------------------------------- 1 | use crate::devices::prelude::*; 2 | 3 | /// TODO: add brief NewDevice description 4 | #[derive(Debug)] 5 | pub struct NewDevice {} 6 | 7 | impl NewDevice { 8 | pub fn new() -> NewDevice { 9 | NewDevice {} 10 | } 11 | } 12 | 13 | impl Device for NewDevice { 14 | fn kind(&self) -> &'static str { 15 | "NewDevice" 16 | } 17 | 18 | fn probe(&self, offset: u32) -> Probe { 19 | let reg = match offset { 20 | 0x0 => "_", 21 | _ => return Probe::Unmapped, 22 | }; 23 | 24 | Probe::Register(reg) 25 | } 26 | } 27 | 28 | impl Memory for NewDevice { 29 | fn r32(&mut self, offset: u32) -> MemResult { 30 | match offset { 31 | 0x0 => Err(StubRead(Warn, 0x00)), 32 | _ => Err(Unexpected), 33 | } 34 | } 35 | 36 | fn w32(&mut self, offset: u32, val: u32) -> MemResult<()> { 37 | match offset { 38 | 0x0 => Err(Unimplemented), 39 | _ => Err(Unexpected), 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /clicky-core/src/devices/display/mod.rs: -------------------------------------------------------------------------------- 1 | //! Display-related devices. 2 | 3 | pub mod hd66753; 4 | -------------------------------------------------------------------------------- /clicky-core/src/devices/generic/ide/reg.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case, unused)] 2 | 3 | /// status register bits 4 | pub mod STATUS { 5 | /// Busy 6 | pub const BSY: usize = 7; 7 | /// Device Ready 8 | pub const DRDY: usize = 6; 9 | /// Device Fault 10 | pub const DF: usize = 5; 11 | /// Disk Seek Complete 12 | pub const DSC: usize = 4; 13 | /// Data Request 14 | pub const DRQ: usize = 3; 15 | /// Corrected Data 16 | pub const CORR: usize = 2; 17 | /// Index (vendor specific) 18 | pub const IDX: usize = 1; 19 | /// Error 20 | pub const ERR: usize = 0; 21 | } 22 | 23 | /// Error register bits 24 | pub mod ERROR { 25 | /// Unrecoverable Data Error 26 | pub const UNC: usize = 6; 27 | /// Media Changed 28 | pub const MC: usize = 5; 29 | /// ID Not found 30 | pub const IDNF: usize = 4; 31 | /// Media Change Requested 32 | pub const MCR: usize = 3; 33 | /// Aborted Command 34 | pub const ABRT: usize = 2; 35 | /// Track 0 Not Found (during a RECALIBRATE command) 36 | pub const TKNONF: usize = 1; 37 | /// Address Mark Not Found 38 | pub const AMNF: usize = 0; 39 | } 40 | 41 | /// Device/Head register bits 42 | pub mod DEVHEAD { 43 | type Range = std::ops::RangeInclusive; 44 | /// LBA addressing 45 | pub const L: usize = 6; 46 | /// Device Index 47 | pub const DEV: usize = 4; 48 | /// Bits 24..=27 of the LBA address 49 | pub const HS: Range = 0..=3; 50 | } 51 | -------------------------------------------------------------------------------- /clicky-core/src/devices/generic/mod.rs: -------------------------------------------------------------------------------- 1 | //! Platform-agnostic devices. 2 | 3 | pub mod asanram; 4 | pub mod ide; 5 | pub mod ram; 6 | pub mod stub; 7 | 8 | pub use asanram::*; 9 | pub use ide::*; 10 | pub use ram::*; 11 | pub use stub::*; 12 | -------------------------------------------------------------------------------- /clicky-core/src/devices/generic/ram.rs: -------------------------------------------------------------------------------- 1 | use crate::devices::prelude::*; 2 | 3 | use std::vec::Vec; 4 | 5 | use byteorder::{ByteOrder, LittleEndian}; 6 | 7 | /// Basic RAM device. 8 | pub struct Ram { 9 | mem: Vec, 10 | } 11 | 12 | impl std::fmt::Debug for Ram { 13 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 14 | f.debug_struct("Ram").field("mem", &"[..]").finish() 15 | } 16 | } 17 | 18 | impl Ram { 19 | /// Allocate some RAM. `size` is the size in bytes. 20 | pub fn new(size: usize) -> Ram { 21 | Ram { 22 | mem: vec![b'-'; size], // non-zero value to make it easier to spot bugs 23 | } 24 | } 25 | 26 | pub fn bulk_write(&mut self, offset: u32, data: &[u8]) { 27 | let offset = offset as usize; 28 | self.mem[offset..offset + data.len()].copy_from_slice(data); 29 | } 30 | 31 | pub fn bulk_read(&self, offset: u32, data: &mut [u8]) { 32 | let offset = offset as usize; 33 | data.copy_from_slice(&self.mem[offset..offset + data.len()]); 34 | } 35 | } 36 | 37 | impl Device for Ram { 38 | fn kind(&self) -> &'static str { 39 | "Ram" 40 | } 41 | 42 | fn probe(&self, offset: u32) -> Probe { 43 | if (offset as usize) < self.mem.len() { 44 | Probe::Register("") 45 | } else { 46 | Probe::Unmapped 47 | } 48 | } 49 | } 50 | 51 | impl Memory for Ram { 52 | #[inline] 53 | fn r8(&mut self, offset: u32) -> MemResult { 54 | let offset = offset as usize; 55 | let val = self.mem[offset]; 56 | Ok(val) 57 | } 58 | 59 | #[inline] 60 | fn r16(&mut self, offset: u32) -> MemResult { 61 | let offset = offset as usize; 62 | let val = LittleEndian::read_u16(&self.mem[offset..]); 63 | Ok(val) 64 | } 65 | 66 | #[inline] 67 | fn r32(&mut self, offset: u32) -> MemResult { 68 | let offset = offset as usize; 69 | let val = LittleEndian::read_u32(&self.mem[offset..]); 70 | Ok(val) 71 | } 72 | 73 | #[inline] 74 | fn w8(&mut self, offset: u32, val: u8) -> MemResult<()> { 75 | let offset = offset as usize; 76 | self.mem[offset] = val; 77 | Ok(()) 78 | } 79 | 80 | #[inline] 81 | fn w16(&mut self, offset: u32, val: u16) -> MemResult<()> { 82 | let offset = offset as usize; 83 | LittleEndian::write_u16(&mut self.mem[offset..], val); 84 | Ok(()) 85 | } 86 | 87 | #[inline] 88 | fn w32(&mut self, offset: u32, val: u32) -> MemResult<()> { 89 | let offset = offset as usize; 90 | LittleEndian::write_u32(&mut self.mem[offset..], val); 91 | Ok(()) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /clicky-core/src/devices/generic/stub.rs: -------------------------------------------------------------------------------- 1 | use crate::devices::prelude::*; 2 | 3 | /// Generic stub device. Reads/Writes result in Error-level StubRead/StubWrites. 4 | /// 5 | /// THIS DEVICE SHOULD BE USED SPARINGLY AS A DEVELOPMENT AID! Please create 6 | /// _concrete_ devices when possible. 7 | #[derive(Debug)] 8 | pub struct Stub { 9 | label: &'static str, 10 | } 11 | 12 | impl Stub { 13 | pub fn new(label: &'static str) -> Stub { 14 | Stub { label } 15 | } 16 | } 17 | 18 | impl Device for Stub { 19 | fn kind(&self) -> &'static str { 20 | "Stub" 21 | } 22 | 23 | fn label(&self) -> Option<&'static str> { 24 | Some(self.label) 25 | } 26 | 27 | fn probe(&self, _offset: u32) -> Probe { 28 | Probe::Unmapped 29 | } 30 | } 31 | 32 | impl Memory for Stub { 33 | fn r8(&mut self, _offset: u32) -> MemResult { 34 | Err(StubRead(Error, 0)) 35 | } 36 | 37 | fn r16(&mut self, _offset: u32) -> MemResult { 38 | Err(StubRead(Error, 0)) 39 | } 40 | 41 | fn r32(&mut self, _offset: u32) -> MemResult { 42 | Err(StubRead(Error, 0)) 43 | } 44 | 45 | fn w8(&mut self, _offset: u32, _val: u8) -> MemResult<()> { 46 | Err(StubWrite(Error, ())) 47 | } 48 | 49 | fn w16(&mut self, _offset: u32, _val: u16) -> MemResult<()> { 50 | Err(StubWrite(Error, ())) 51 | } 52 | 53 | fn w32(&mut self, _offset: u32, _val: u32) -> MemResult<()> { 54 | Err(StubWrite(Error, ())) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /clicky-core/src/devices/i2c/devices/mod.rs: -------------------------------------------------------------------------------- 1 | mod pcf5060x; 2 | 3 | pub use pcf5060x::*; 4 | -------------------------------------------------------------------------------- /clicky-core/src/devices/i2c/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::devices::prelude::*; 2 | 3 | pub mod devices; 4 | pub mod prelude; 5 | 6 | /// Common trait implemented by all i2c devices. 7 | /// 8 | /// i2c devices implement the standard `Device` trait, albeit with a slightly 9 | /// different `probe` behavior. Instead of using the provided `offset`, they 10 | /// should instead return the name of whatever internal register was previously 11 | /// selected. 12 | pub trait I2CDevice: Device { 13 | /// Read an 8-bit value from the device. 14 | fn read(&mut self) -> MemResult; 15 | /// Write an 8-bit value from the device. 16 | fn write(&mut self, data: u8) -> MemResult<()>; 17 | /// Called at the end of a write sequence. 18 | fn write_done(&mut self) -> MemResult<()>; 19 | } 20 | 21 | impl Device for Box { 22 | fn kind(&self) -> &'static str { 23 | (**self).kind() 24 | } 25 | 26 | fn label(&self) -> Option<&'static str> { 27 | (**self).label() 28 | } 29 | 30 | fn probe(&self, offset: u32) -> Probe { 31 | (**self).probe(offset) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /clicky-core/src/devices/i2c/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use crate::devices::prelude::*; 2 | 3 | pub use crate::devices::i2c::I2CDevice; 4 | -------------------------------------------------------------------------------- /clicky-core/src/devices/mod.rs: -------------------------------------------------------------------------------- 1 | //! Peripheral devices. 2 | 3 | #![allow( 4 | clippy::unit_arg, // substantially reduces boilerplate 5 | clippy::match_bool, // can make things more clear at times 6 | clippy::new_without_default, // just adds more boilerplate 7 | )] 8 | 9 | pub mod prelude; 10 | 11 | pub mod display; 12 | pub mod generic; 13 | pub mod i2c; 14 | pub mod platform; 15 | pub mod util; 16 | 17 | /// Common trait implemented by all emulated devices. 18 | pub trait Device: Send + Sync { 19 | /// The name of the emulated device. 20 | fn kind(&self) -> &'static str; 21 | 22 | /// A descriptive label for a particular instance of the device 23 | /// (if applicable). 24 | fn label(&self) -> Option<&'static str> { 25 | None 26 | } 27 | 28 | /// Query what devices exist at a particular memory offset. 29 | fn probe(&self, offset: u32) -> Probe; 30 | } 31 | 32 | macro_rules! impl_devfwd { 33 | ($type:ty) => { 34 | impl Device for $type { 35 | fn kind(&self) -> &'static str { 36 | (**self).kind() 37 | } 38 | 39 | fn label(&self) -> Option<&'static str> { 40 | (**self).label() 41 | } 42 | 43 | fn probe(&self, offset: u32) -> Probe { 44 | (**self).probe(offset) 45 | } 46 | } 47 | }; 48 | } 49 | 50 | impl_devfwd!(Box); 51 | impl_devfwd!(&dyn Device); 52 | impl_devfwd!(&mut dyn Device); 53 | 54 | /// A link in a chain of devices corresponding to a particular memory offset. 55 | pub enum Probe { 56 | /// Branch node representing a device. 57 | Device { 58 | kind: &'static str, 59 | label: Option<&'static str>, 60 | next: Box, 61 | }, 62 | /// Leaf node representing a register. 63 | Register(&'static str), 64 | /// Unmapped memory. 65 | Unmapped, 66 | } 67 | 68 | impl Probe { 69 | // Convenience method to construct a `Probe::Device` 70 | pub fn from_device(device: &impl Device, offset: u32) -> Probe { 71 | Probe::Device { 72 | kind: device.kind(), 73 | label: device.label(), 74 | next: Box::new(device.probe(offset)), 75 | } 76 | } 77 | } 78 | 79 | impl std::fmt::Display for Probe { 80 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { 81 | match self { 82 | Probe::Device { kind, label, next } => { 83 | match label { 84 | Some(label) => write!(f, "{}:{}", kind, label)?, 85 | None => write!(f, "{}", kind)?, 86 | }; 87 | 88 | match &**next { 89 | Probe::Unmapped => {} 90 | next => write!(f, " > {}", next)?, 91 | } 92 | } 93 | Probe::Register(name) => write!(f, "{}", name)?, 94 | Probe::Unmapped => write!(f, "")?, 95 | } 96 | 97 | Ok(()) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /clicky-core/src/devices/platform/mod.rs: -------------------------------------------------------------------------------- 1 | //! Platform-specific devices. 2 | 3 | pub mod pp; 4 | -------------------------------------------------------------------------------- /clicky-core/src/devices/platform/pp.rs: -------------------------------------------------------------------------------- 1 | //! Platform support for the PortalPlayer 50xx line of SoCs. 2 | 3 | mod cachecon; 4 | mod cfg_timer; 5 | mod cpucon; 6 | mod cpuid; 7 | mod devcon; 8 | mod dma; 9 | mod eide; 10 | mod evp; 11 | mod flash; 12 | mod gpio; 13 | mod i2c; 14 | mod i2s; 15 | mod intcon; 16 | mod mailbox; 17 | mod memcon; 18 | mod opto; 19 | mod ppcon; 20 | mod serial; 21 | mod usec_timer; 22 | mod pwm; 23 | 24 | pub use cachecon::*; 25 | pub use cfg_timer::*; 26 | pub use cpucon::*; 27 | pub use cpuid::*; 28 | pub use devcon::*; 29 | pub use dma::*; 30 | pub use eide::*; 31 | pub use evp::*; 32 | pub use flash::*; 33 | pub use gpio::*; 34 | pub use i2c::*; 35 | pub use i2s::*; 36 | pub use intcon::*; 37 | pub use mailbox::*; 38 | pub use memcon::*; 39 | pub use opto::*; 40 | pub use ppcon::*; 41 | pub use serial::*; 42 | pub use usec_timer::*; 43 | pub use pwm::*; 44 | 45 | pub mod common { 46 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 47 | pub enum CpuId { 48 | Cpu, 49 | Cop, 50 | } 51 | 52 | impl std::fmt::Display for CpuId { 53 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 54 | match self { 55 | CpuId::Cpu => write!(f, "CPU"), 56 | CpuId::Cop => write!(f, "COP"), 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /clicky-core/src/devices/platform/pp/cachecon.rs: -------------------------------------------------------------------------------- 1 | use crate::devices::prelude::*; 2 | 3 | /// PP5020 Cache Controller. 4 | #[derive(Debug)] 5 | pub struct CacheCon { 6 | /// Local exception vector table enable (bit 4) 7 | pub local_evt: bool, 8 | /// Cache control enable (bit 1) 9 | cache_ctrl_enable: bool, 10 | } 11 | 12 | impl CacheCon { 13 | pub fn new() -> CacheCon { 14 | CacheCon { 15 | local_evt: false, 16 | cache_ctrl_enable: false, 17 | } 18 | } 19 | } 20 | 21 | impl Device for CacheCon { 22 | fn kind(&self) -> &'static str { 23 | "Cache Controller" 24 | } 25 | 26 | fn probe(&self, offset: u32) -> Probe { 27 | let reg = match offset { 28 | 0x00 => "Control", 29 | 0x10 => "(?)", 30 | 0x34 => "(?)", 31 | _ => return Probe::Unmapped, 32 | }; 33 | 34 | Probe::Register(reg) 35 | } 36 | } 37 | 38 | impl Memory for CacheCon { 39 | fn r32(&mut self, offset: u32) -> MemResult { 40 | match offset { 41 | 0x00 => { 42 | let val = *0u32 43 | .set_bit(4, self.local_evt) 44 | .set_bit(1, self.cache_ctrl_enable); 45 | Err(StubRead(Warn, val)) 46 | } 47 | 0x10 => Err(InvalidAccess), 48 | 0x34 => Err(InvalidAccess), 49 | _ => Err(Unexpected), 50 | } 51 | } 52 | 53 | fn w32(&mut self, offset: u32, val: u32) -> MemResult<()> { 54 | match offset { 55 | 0x00 => { 56 | self.local_evt = val.get_bit(4); 57 | self.cache_ctrl_enable = val.get_bit(1); 58 | Err(StubWrite(Error, ())) 59 | } 60 | 0x10 => Err(StubWrite(Error, ())), 61 | 0x34 => Err(StubWrite(Error, ())), 62 | _ => Err(Unexpected), 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /clicky-core/src/devices/platform/pp/cpuid.rs: -------------------------------------------------------------------------------- 1 | use crate::devices::prelude::*; 2 | 3 | use super::common::CpuId; 4 | 5 | /// Returns different value based on which CPU accesses it. 6 | #[derive(Debug)] 7 | pub struct CpuIdReg { 8 | cpuid: CpuId, 9 | } 10 | 11 | impl CpuIdReg { 12 | pub fn new() -> CpuIdReg { 13 | CpuIdReg { cpuid: CpuId::Cpu } 14 | } 15 | 16 | pub fn set_cpuid(&mut self, cpuid: CpuId) { 17 | self.cpuid = cpuid 18 | } 19 | } 20 | 21 | impl Device for CpuIdReg { 22 | fn kind(&self) -> &'static str { 23 | "CPU ID Register" 24 | } 25 | 26 | fn probe(&self, offset: u32) -> Probe { 27 | let reg = match offset { 28 | 0x0 => "CPU ID Register", 29 | _ => return Probe::Unmapped, 30 | }; 31 | 32 | Probe::Register(reg) 33 | } 34 | } 35 | 36 | impl Memory for CpuIdReg { 37 | fn r32(&mut self, offset: u32) -> MemResult { 38 | match offset { 39 | 0x0 => match self.cpuid { 40 | CpuId::Cpu => Ok(0x55555555), 41 | CpuId::Cop => Ok(0xaaaaaaaa), 42 | }, 43 | _ => Err(Unexpected), 44 | } 45 | } 46 | 47 | fn w32(&mut self, offset: u32, _val: u32) -> MemResult<()> { 48 | match offset { 49 | 0x0 => Err(InvalidAccess), 50 | _ => Err(Unexpected), 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /clicky-core/src/devices/platform/pp/devcon.rs: -------------------------------------------------------------------------------- 1 | use crate::devices::prelude::*; 2 | 3 | /// PP5020 Device Controller. 4 | #[derive(Debug)] 5 | pub struct DevCon { 6 | reset: [u32; 2], 7 | enable: [u32; 2], 8 | clock_source: u32, 9 | pll_control: u32, 10 | pll_status: u32, 11 | cache_priority: u8, 12 | mystery_i2c: u32, 13 | mystery: [u32; 1], 14 | } 15 | 16 | impl DevCon { 17 | pub fn new() -> DevCon { 18 | DevCon { 19 | reset: [0, 0], 20 | enable: [0, 0], 21 | clock_source: 0, 22 | pll_control: 0, 23 | pll_status: 0, 24 | cache_priority: 0, 25 | mystery_i2c: 0, 26 | mystery: [0; 1], 27 | } 28 | } 29 | } 30 | 31 | impl Device for DevCon { 32 | fn kind(&self) -> &'static str { 33 | "DevCon" 34 | } 35 | 36 | fn probe(&self, offset: u32) -> Probe { 37 | let reg = match offset { 38 | 0x04 => "Device Reset 1", 39 | 0x08 => "Device Reset 2", 40 | 0x0c => "Device Enable 1", 41 | 0x10 => "Device Enable 2", 42 | 0x20 => "Clock Source", 43 | 0x34 => "PLL Control", 44 | 0x3c => "PLL Status", 45 | 0x44 => "Cache Priority", 46 | 0xa4 => "(?) I2C related", 47 | 0xc4 => "(?) DMA clock related", 48 | 0xc8 => "?", 49 | _ => return Probe::Unmapped, 50 | }; 51 | 52 | Probe::Register(reg) 53 | } 54 | } 55 | 56 | impl Memory for DevCon { 57 | fn r32(&mut self, offset: u32) -> MemResult { 58 | match offset { 59 | 0x04 => Err(StubRead(Error, self.reset[0])), 60 | 0x08 => Err(StubRead(Error, self.reset[1])), 61 | 0x0c => Ok(self.enable[0]), 62 | 0x10 => Ok(self.enable[1]), 63 | 0x20 => Ok(self.clock_source), 64 | 0x34 => Ok(self.pll_control), 65 | 0x3c => Ok(self.pll_status), 66 | 0x44 => Err(StubRead(Error, self.cache_priority as u32)), 67 | 0xa4 => Err(StubRead(Error, self.mystery_i2c)), 68 | 0xc4 => Err(InvalidAccess), 69 | 0xc8 => Err(StubRead(Error, self.mystery[0])), 70 | _ => Err(Unexpected), 71 | } 72 | } 73 | 74 | fn w32(&mut self, offset: u32, val: u32) -> MemResult<()> { 75 | match offset { 76 | 0x04 => Err(StubWrite(Error, self.reset[0] = val)), 77 | 0x08 => Err(StubWrite(Error, self.reset[1] = val)), 78 | 0x0c => Err(StubWrite(Info, self.enable[0] = val)), 79 | 0x10 => Err(StubWrite(Info, self.enable[1] = val)), 80 | 0x20 => Err(StubWrite(Trace, self.clock_source = val)), 81 | 0x34 => Err(StubWrite(Trace, self.pll_control = val)), 82 | 0x3c => Err(StubWrite(Trace, self.pll_status = val)), 83 | 0x44 => Err(StubWrite(Warn, { 84 | let val = val.trunc_to_u8()?; 85 | self.cache_priority = val; 86 | })), 87 | 0xa4 => Err(StubWrite(Error, self.mystery_i2c = val)), 88 | 0xc4 => Err(StubWrite(Info, ())), 89 | 0xc8 => Err(StubWrite(Error, self.mystery[0] = val)), 90 | _ => Err(Unexpected), 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /clicky-core/src/devices/platform/pp/dma.rs: -------------------------------------------------------------------------------- 1 | use crate::devices::prelude::*; 2 | 3 | #[derive(Debug, Default)] 4 | struct Dma { 5 | label: Option<&'static str>, 6 | 7 | cmd: u32, 8 | status: u32, 9 | ram_addr: u32, 10 | flags: u32, 11 | per_addr: u32, 12 | incr: u32, 13 | } 14 | 15 | impl Device for Dma { 16 | fn kind(&self) -> &'static str { 17 | "" 18 | } 19 | 20 | fn label(&self) -> Option<&'static str> { 21 | self.label 22 | } 23 | 24 | fn probe(&self, offset: u32) -> Probe { 25 | let reg = match offset { 26 | 0x00 => "Cmd", 27 | 0x04 => "Status", 28 | 0x10 => "Ram Addr", 29 | 0x14 => "Flags", 30 | 0x18 => "Per Addr", 31 | 0x1c => "Incr", 32 | _ => return Probe::Unmapped, 33 | }; 34 | 35 | Probe::Register(reg) 36 | } 37 | } 38 | 39 | /// PP5020 DMA Engine 40 | #[derive(Debug)] 41 | pub struct DmaCon { 42 | dma: [Dma; 8], 43 | master_control: u32, 44 | master_status: u32, 45 | req_status: u32, 46 | 47 | // HACK: IDE DMA doesn't actually go through the DMA controller 48 | // that said, to keep things simple in the emulator, we route IDE DMA through the main DMA 49 | // engine... 50 | // 51 | // As per the pp5020 spec sheet: "A dedicated, high-performance ATA-66IDE controller with its 52 | // own DMA engine frees the processors from mundane management tasks." 53 | ide_dmarq: irq::Reciever, 54 | } 55 | 56 | impl DmaCon { 57 | pub fn new(ide_dmarq: irq::Reciever) -> DmaCon { 58 | let mut dma = DmaCon { 59 | dma: Default::default(), 60 | master_control: 0, 61 | master_status: 0, 62 | req_status: 0, 63 | 64 | ide_dmarq, 65 | }; 66 | 67 | dma.dma[0].label = Some("0"); 68 | dma.dma[1].label = Some("1"); 69 | dma.dma[2].label = Some("2"); 70 | dma.dma[3].label = Some("3"); 71 | dma.dma[4].label = Some("4"); 72 | dma.dma[5].label = Some("5"); 73 | dma.dma[6].label = Some("6"); 74 | dma.dma[7].label = Some("7"); 75 | 76 | dma 77 | } 78 | 79 | /// XXX: remove this once DMA is properly sorted out 80 | pub fn do_ide_dma(&self) -> bool { 81 | self.ide_dmarq.asserted() 82 | } 83 | } 84 | 85 | impl Device for DmaCon { 86 | fn kind(&self) -> &'static str { 87 | "DMA Engine" 88 | } 89 | 90 | fn probe(&self, offset: u32) -> Probe { 91 | let reg = match offset { 92 | 0x0 => "Master Control", 93 | 0x4 => "Master Status", 94 | 0x8 => "Req Status", 95 | 0x1000..=0x10ff => { 96 | let id = (offset - 0x1000) / 0x20; 97 | return Probe::from_device(&self.dma[id as usize], offset % 0x20); 98 | } 99 | _ => return Probe::Unmapped, 100 | }; 101 | 102 | Probe::Register(reg) 103 | } 104 | } 105 | 106 | impl Memory for DmaCon { 107 | fn r32(&mut self, offset: u32) -> MemResult { 108 | match offset { 109 | 0x0 => Err(StubRead(Error, self.master_control)), 110 | 0x4 => Err(StubRead(Error, self.master_status)), 111 | 0x8 => Err(StubRead(Error, self.req_status)), 112 | 0x1000..=0x10ff => { 113 | let id = (offset - 0x1000) / 0x20; 114 | let dma = &mut self.dma[id as usize]; 115 | match offset % 0x20 { 116 | 0x00 => Err(StubRead(Error, dma.cmd)), 117 | 0x04 => Err(StubRead(Error, dma.status)), 118 | 0x10 => Err(StubRead(Error, dma.ram_addr)), 119 | 0x14 => Err(StubRead(Error, dma.flags)), 120 | 0x18 => Err(StubRead(Error, dma.per_addr)), 121 | 0x1c => Err(StubRead(Error, dma.incr)), 122 | _ => Err(Unexpected), 123 | } 124 | } 125 | _ => Err(Unexpected), 126 | } 127 | } 128 | 129 | fn w32(&mut self, offset: u32, val: u32) -> MemResult<()> { 130 | match offset { 131 | 0x0 => Err(StubWrite(Error, self.master_control = val)), 132 | 0x4 => Err(StubWrite(Error, self.master_status = val)), 133 | 0x8 => Err(StubWrite(Error, self.req_status = val)), 134 | 0x1000..=0x10ff => { 135 | let id = (offset - 0x1000) / 0x20; 136 | let dma = &mut self.dma[id as usize]; 137 | match offset % 0x20 { 138 | 0x00 => Err(StubWrite(Error, dma.cmd = val)), 139 | 0x04 => Err(StubWrite(Error, dma.status = val)), 140 | 0x10 => Err(StubWrite(Error, dma.ram_addr = val)), 141 | 0x14 => Err(StubWrite(Error, dma.flags = val)), 142 | 0x18 => Err(StubWrite(Error, dma.per_addr = val)), 143 | 0x1c => Err(StubWrite(Error, dma.incr = val)), 144 | _ => Err(Unexpected), 145 | } 146 | } 147 | _ => Err(Unexpected), 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /clicky-core/src/devices/platform/pp/evp.rs: -------------------------------------------------------------------------------- 1 | use crate::devices::prelude::*; 2 | 3 | /// Returns different value based on which CPU accesses it. 4 | #[derive(Debug)] 5 | pub struct Evp { 6 | reset_vec: u32, 7 | undefined_instr_vec: u32, 8 | soft_irq_vec: u32, 9 | prefetch_abrt_vec: u32, 10 | data_abrt_vec: u32, 11 | reserved_vec: u32, 12 | normal_irq_vec: u32, 13 | high_priority_irq_vec: u32, 14 | } 15 | 16 | impl Evp { 17 | pub fn new() -> Evp { 18 | Evp { 19 | reset_vec: 0x0, 20 | undefined_instr_vec: 0x4, 21 | soft_irq_vec: 0x8, 22 | prefetch_abrt_vec: 0xC, 23 | data_abrt_vec: 0x10, 24 | reserved_vec: 0x14, 25 | normal_irq_vec: 0x18, 26 | high_priority_irq_vec: 0x1C, 27 | } 28 | } 29 | } 30 | 31 | impl Device for Evp { 32 | fn kind(&self) -> &'static str { 33 | "EVP (Exception Vector ???)" 34 | } 35 | 36 | fn probe(&self, offset: u32) -> Probe { 37 | let reg = match offset { 38 | 0x0 => "Reset Exception Handler", 39 | 0x4 => "Undefined Instruction Handler", 40 | 0x8 => "Software Interrupt Handler", 41 | 0xC => "Prefetch Abort Handler", 42 | 0x10 => "Data Abort Handler", 43 | 0x14 => "Reserved Handler", 44 | 0x18 => "Normal-priority Interrupt Handler", 45 | 0x1C => "High-priority Interrupt Handler", 46 | _ => return Probe::Unmapped, 47 | }; 48 | 49 | Probe::Register(reg) 50 | } 51 | } 52 | 53 | impl Memory for Evp { 54 | fn r32(&mut self, offset: u32) -> MemResult { 55 | match offset { 56 | 0x0 => Ok(self.reset_vec), 57 | 0x4 => Ok(self.undefined_instr_vec), 58 | 0x8 => Ok(self.soft_irq_vec), 59 | 0xC => Ok(self.prefetch_abrt_vec), 60 | 0x10 => Ok(self.data_abrt_vec), 61 | 0x14 => Ok(self.reserved_vec), 62 | 0x18 => Ok(self.normal_irq_vec), 63 | 0x1C => Ok(self.high_priority_irq_vec), 64 | _ => Err(Unexpected), 65 | } 66 | } 67 | 68 | fn w32(&mut self, offset: u32, val: u32) -> MemResult<()> { 69 | match offset { 70 | 0x0 => Ok(self.reset_vec = val), 71 | 0x4 => Ok(self.undefined_instr_vec = val), 72 | 0x8 => Ok(self.soft_irq_vec = val), 73 | 0xC => Ok(self.prefetch_abrt_vec = val), 74 | 0x10 => Ok(self.data_abrt_vec = val), 75 | 0x14 => Ok(self.reserved_vec = val), 76 | 0x18 => Ok(self.normal_irq_vec = val), 77 | 0x1C => Ok(self.high_priority_irq_vec = val), 78 | _ => Err(InvalidAccess), 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /clicky-core/src/devices/platform/pp/i2s.rs: -------------------------------------------------------------------------------- 1 | use crate::devices::prelude::*; 2 | 3 | /// PP5020 I2S controller. 4 | #[derive(Debug)] 5 | pub struct I2SCon { 6 | config: u32, 7 | clock: u32, 8 | fifo_cfg: u32, 9 | } 10 | 11 | impl I2SCon { 12 | pub fn new() -> I2SCon { 13 | I2SCon { 14 | config: 0, 15 | clock: 0, 16 | fifo_cfg: 0, 17 | } 18 | } 19 | } 20 | 21 | impl Device for I2SCon { 22 | fn kind(&self) -> &'static str { 23 | "I2S Controller" 24 | } 25 | 26 | fn probe(&self, offset: u32) -> Probe { 27 | let reg = match offset { 28 | 0x00 => "Config", 29 | 0x08 => "Clock", 30 | 0x0c => "Fifo Config", 31 | 0x40 => "Fifo Write", 32 | 0x80 => "Fifo Read", 33 | _ => return Probe::Unmapped, 34 | }; 35 | 36 | Probe::Register(reg) 37 | } 38 | } 39 | 40 | impl Memory for I2SCon { 41 | fn r32(&mut self, offset: u32) -> MemResult { 42 | match offset { 43 | 0x00 => Err(StubRead(Error, self.config)), 44 | 0x08 => Err(StubRead(Error, self.clock)), 45 | 0x0c => Err(StubRead(Error, self.fifo_cfg)), 46 | 0x40 => Err(Unimplemented), 47 | 0x80 => Err(Unimplemented), 48 | _ => Err(Unexpected), 49 | } 50 | } 51 | 52 | fn w32(&mut self, offset: u32, val: u32) -> MemResult<()> { 53 | match offset { 54 | 0x00 => Err(StubWrite(Error, self.config = val)), 55 | 0x08 => Err(StubWrite(Error, self.clock = val)), 56 | 0x0c => Err(StubWrite(Error, self.fifo_cfg = val)), 57 | 0x40 => Err(Unimplemented), 58 | 0x80 => Err(Unimplemented), 59 | _ => Err(Unexpected), 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /clicky-core/src/devices/platform/pp/mailbox.rs: -------------------------------------------------------------------------------- 1 | use crate::devices::prelude::*; 2 | 3 | use super::CpuId; 4 | 5 | /// PP5020 inter-processor Mailbox. 6 | #[derive(Debug)] 7 | pub struct Mailbox { 8 | selected_core: CpuId, 9 | cpu_irq: irq::Sender, 10 | cop_irq: irq::Sender, 11 | 12 | shared_bits: u32, 13 | } 14 | 15 | impl Mailbox { 16 | pub fn new(cpu_irq: irq::Sender, cop_irq: irq::Sender) -> Mailbox { 17 | Mailbox { 18 | selected_core: CpuId::Cpu, 19 | cpu_irq, 20 | cop_irq, 21 | 22 | shared_bits: 0, 23 | } 24 | } 25 | 26 | pub fn set_cpuid(&mut self, cpuid: CpuId) { 27 | self.selected_core = cpuid; 28 | } 29 | } 30 | 31 | impl Device for Mailbox { 32 | fn kind(&self) -> &'static str { 33 | "Mailbox" 34 | } 35 | 36 | fn probe(&self, offset: u32) -> Probe { 37 | let reg = match offset { 38 | 0x00 => "Status", 39 | 0x04 => "Set", 40 | 0x08 => "Clear", 41 | 0x0c => "?", 42 | 0x10..=0x1f => "", 43 | 0x20..=0x2f => "", 44 | _ => return Probe::Unmapped, 45 | }; 46 | 47 | Probe::Register(reg) 48 | } 49 | } 50 | 51 | impl Memory for Mailbox { 52 | fn r32(&mut self, offset: u32) -> MemResult { 53 | match offset { 54 | 0x00 => Ok({ 55 | // notice how the IRQ for the _selected_ core is asserted? 56 | match self.selected_core { 57 | CpuId::Cpu => self.cpu_irq.clear(), 58 | CpuId::Cop => self.cop_irq.clear(), 59 | } 60 | 61 | self.shared_bits 62 | }), 63 | 0x04 => Err(InvalidAccess), 64 | 0x08 => Err(InvalidAccess), 65 | 0x0c => Err(Unimplemented), 66 | 0x10..=0x1f => Err(Unimplemented), 67 | 0x20..=0x2f => Err(Unimplemented), 68 | _ => Err(Unexpected), 69 | } 70 | } 71 | 72 | fn w32(&mut self, offset: u32, val: u32) -> MemResult<()> { 73 | macro_rules! fire_irq { 74 | () => { 75 | // notice how the IRQ for the _other_ core is asserted? 76 | match self.selected_core { 77 | CpuId::Cpu => self.cop_irq.assert(), 78 | CpuId::Cop => self.cpu_irq.assert(), 79 | } 80 | }; 81 | } 82 | 83 | match offset { 84 | 0x00 => Err(InvalidAccess), 85 | 0x04 => Ok({ 86 | self.shared_bits |= val; 87 | fire_irq!() 88 | }), 89 | 0x08 => Ok({ 90 | self.shared_bits &= !val; 91 | fire_irq!() 92 | }), 93 | 0x0c => Err(Unimplemented), 94 | 0x10..=0x1f => Err(StubWrite(Error, ())), 95 | 0x20..=0x2f => Err(StubWrite(Error, ())), 96 | _ => Err(Unexpected), 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /clicky-core/src/devices/platform/pp/opto.rs: -------------------------------------------------------------------------------- 1 | use crate::devices::prelude::*; 2 | 3 | use std::sync::{Arc, Mutex}; 4 | 5 | use crate::signal::{self, gpio}; 6 | 7 | #[derive(Debug)] 8 | pub struct Controls { 9 | pub action: T, 10 | pub up: T, 11 | pub down: T, 12 | pub left: T, 13 | pub right: T, 14 | pub wheel: (T, Arc>), 15 | } 16 | 17 | impl Controls<()> { 18 | pub fn new_tx_rx( 19 | notify: signal::Trigger, 20 | ) -> (Controls, Controls) { 21 | let (action_tx, action_rx) = signal::new(notify.clone(), "Controls", "KeyAction"); 22 | let (up_tx, up_rx) = signal::new(notify.clone(), "Controls", "KeyUp"); 23 | let (down_tx, down_rx) = signal::new(notify.clone(), "Controls", "KeyDown"); 24 | let (left_tx, left_rx) = signal::new(notify.clone(), "Controls", "KeyLeft"); 25 | let (right_tx, right_rx) = signal::new(notify.clone(), "Controls", "KeyRight"); 26 | let (wheel_tx, wheel_rx) = signal::new(notify, "Controls", "Wheel"); 27 | 28 | let wheel_data = Arc::new(Mutex::new(0)); 29 | 30 | ( 31 | Controls { 32 | action: action_tx, 33 | up: up_tx, 34 | down: down_tx, 35 | left: left_tx, 36 | right: right_tx, 37 | wheel: (wheel_tx, wheel_data.clone()), 38 | }, 39 | Controls { 40 | action: action_rx, 41 | up: up_rx, 42 | down: down_rx, 43 | left: left_rx, 44 | right: right_rx, 45 | wheel: (wheel_rx, wheel_data), 46 | }, 47 | ) 48 | } 49 | } 50 | 51 | /// I2C Controller 52 | #[derive(Debug)] 53 | pub struct OptoWheel { 54 | irq: irq::Sender, 55 | controls: Option>, 56 | hold: Option, 57 | 58 | controls_status: u32, 59 | } 60 | 61 | impl OptoWheel { 62 | pub fn new(irq: irq::Sender) -> OptoWheel { 63 | OptoWheel { 64 | irq, 65 | controls: None, 66 | hold: None, 67 | 68 | controls_status: 0, 69 | } 70 | } 71 | 72 | pub fn register_controls(&mut self, controls: Controls, hold: gpio::Reciever) { 73 | self.controls = Some(controls); 74 | self.hold = Some(hold); 75 | } 76 | 77 | pub fn on_change(&mut self) { 78 | self.irq.assert() 79 | } 80 | } 81 | 82 | impl Device for OptoWheel { 83 | fn kind(&self) -> &'static str { 84 | "OptoWheel" 85 | } 86 | 87 | fn probe(&self, offset: u32) -> Probe { 88 | let reg = match offset { 89 | 0x00 => "(?) Keypad IRQ clear", 90 | 0x04 => "(?) Keypad Status", 91 | 0x20 => "?", 92 | 0x24 => "?", 93 | 0x40 => "Scroll Wheel + Keypad", 94 | _ => return Probe::Unmapped, 95 | }; 96 | 97 | Probe::Register(reg) 98 | } 99 | } 100 | 101 | impl Memory for OptoWheel { 102 | fn r32(&mut self, offset: u32) -> MemResult { 103 | match offset { 104 | 0x00 => Err(StubRead(Debug, 0)), 105 | 0x04 => Err(StubRead(Debug, self.controls_status | 0x0400_0000)), // never busy 106 | 0x20 => Err(Unimplemented), 107 | 0x24 => Err(Unimplemented), 108 | 0x40 => { 109 | let (controls, hold) = match (&self.controls, &self.hold) { 110 | (Some(controls), Some(hold)) => (controls, hold), 111 | _ => return Err(Fatal("no controls registered with i2c".into())), 112 | }; 113 | 114 | let val = *0u32 115 | .set_bits(0..=7, if hold.is_high() { 0x1a } else { 0 }) // 0x1a, or 0 if hold is engaged 116 | .set_bit(8, controls.action.asserted()) 117 | .set_bit(9, controls.right.asserted()) 118 | .set_bit(10, controls.left.asserted()) 119 | .set_bit(11, controls.down.asserted()) 120 | .set_bit(12, controls.up.asserted()) 121 | .set_bits(16..=22, *controls.wheel.1.lock().unwrap() as u32) 122 | .set_bit(30, true) // FIXME: don't always return clickwheel active? 123 | .set_bit(31, hold.is_high()); // set unless hold switch is engaged 124 | 125 | Err(StubRead(Debug, val)) 126 | } 127 | _ => Err(Unexpected), 128 | } 129 | } 130 | 131 | fn w32(&mut self, offset: u32, val: u32) -> MemResult<()> { 132 | match offset { 133 | 0x00 => Err(StubWrite(Debug, { 134 | // TODO: cross-reference this with other software (not just Rockbox) 135 | self.irq.clear() 136 | })), 137 | 0x04 => Err(StubWrite(Debug, self.controls_status = val)), 138 | 0x20 => Err(StubWrite(Debug, ())), 139 | 0x24 => Err(StubWrite(Debug, ())), 140 | 0x40 => Err(StubWrite(Debug, { 141 | // TODO: explore IRQ behavior if multiple I2C devices fire irqs 142 | self.irq.clear() 143 | })), 144 | _ => Err(Unexpected), 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /clicky-core/src/devices/platform/pp/piezo.rs: -------------------------------------------------------------------------------- 1 | use crate::devices::prelude::*; 2 | 3 | /// iPod Piezo speaker. 4 | #[derive(Debug)] 5 | pub struct Piezo { 6 | control: u32, 7 | } 8 | 9 | impl Piezo { 10 | fn on_update_piezo(&self) { 11 | // TODO: actually output some sound 12 | let _freq = self.control.get_bits(0..=15) as u16; 13 | let _form = self.control.get_bits(16..=23) as u8; 14 | let _enabled = self.control.get_bit(31); 15 | } 16 | 17 | pub fn new() -> Piezo { 18 | Piezo { control: 0 } 19 | } 20 | } 21 | 22 | impl Device for Piezo { 23 | fn kind(&self) -> &'static str { 24 | "iPod Piezo" 25 | } 26 | 27 | fn probe(&self, offset: u32) -> Probe { 28 | let reg = match offset { 29 | 0x0 => "Control", 30 | _ => return Probe::Unmapped, 31 | }; 32 | 33 | Probe::Register(reg) 34 | } 35 | } 36 | 37 | impl Memory for Piezo { 38 | fn r32(&mut self, offset: u32) -> MemResult { 39 | match offset { 40 | 0x0 => Ok(self.control), 41 | _ => Err(Unexpected), 42 | } 43 | } 44 | 45 | fn w32(&mut self, offset: u32, val: u32) -> MemResult<()> { 46 | match offset { 47 | 0x0 => { 48 | self.control = val; 49 | self.on_update_piezo(); 50 | Ok(()) 51 | } 52 | _ => Err(Unexpected), 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /clicky-core/src/devices/platform/pp/ppcon.rs: -------------------------------------------------------------------------------- 1 | use crate::devices::prelude::*; 2 | 3 | /// Poorly documented PP50XX controller 4 | #[derive(Debug)] 5 | pub struct PPCon { 6 | dev_init: [u32; 8], 7 | dev_timing: [u32; 3], 8 | bootstrap_maybe: [u32; 2], 9 | 10 | gpo_val: u32, 11 | gpo_enable: u32, 12 | gpo_input_enable: u32, 13 | } 14 | 15 | impl PPCon { 16 | pub fn new() -> PPCon { 17 | PPCon { 18 | dev_init: [0; 8], 19 | dev_timing: [0; 3], 20 | bootstrap_maybe: [0; 2], 21 | 22 | gpo_enable: 0, 23 | gpo_val: 0, 24 | gpo_input_enable: 0, 25 | } 26 | } 27 | } 28 | 29 | impl Device for PPCon { 30 | fn kind(&self) -> &'static str { 31 | "PP Controller" 32 | } 33 | 34 | fn probe(&self, offset: u32) -> Probe { 35 | let reg = match offset { 36 | 0x00 => "ID Reg 1", 37 | 0x04 => "ID Reg 2", 38 | 0x08 => "(?) Bootstrap 1", 39 | 0x0c => "(?) Bootstrap 2", 40 | 0x10 => "Dev Init 1", 41 | 0x14 => "(?) Dev Init 1.1", 42 | 0x18 => "(?) Dev Init 1.2", 43 | 0x1c => "(?) Dev Init 1.3", 44 | 0x20 => "Dev Init 2", 45 | 0x24 => "(?) Dev Init 2.1", 46 | 0x28 => "(?) Dev Init 2.2 (USB related)", 47 | 0x2c => "(?) Dev Init 2.3 (USB related)", 48 | 0x30 => "(?) Dev Timing 0", 49 | 0x34 => "Dev Timing 1", 50 | 0x3c => "(?) Dev Timing 1.1", 51 | 0x80 => "GPO32 Val", 52 | 0x84 => "GPO32 Enable", 53 | 0x88 => "GPO32 Input", 54 | 0x8c => "GPO32 Input Enable", 55 | _ => return Probe::Unmapped, 56 | }; 57 | 58 | Probe::Register(reg) 59 | } 60 | } 61 | 62 | impl Memory for PPCon { 63 | fn r32(&mut self, offset: u32) -> MemResult { 64 | match offset { 65 | 0x00 => Ok(u32::from_le_bytes(*b"PP50")), 66 | 0x04 => Ok(u32::from_le_bytes(*b"20D ")), 67 | 0x08 => Err(StubRead(Info, self.bootstrap_maybe[0])), 68 | 0x0c => Err(StubRead(Info, self.bootstrap_maybe[1])), 69 | 0x10 => Err(StubRead(Info, self.dev_init[0])), 70 | 0x14 => Err(StubRead(Info, self.dev_init[1])), 71 | 0x18 => Err(StubRead(Info, self.dev_init[2])), 72 | 0x1c => Err(StubRead(Info, self.dev_init[3])), 73 | 0x20 => Err(StubRead(Debug, self.dev_init[4])), 74 | 0x24 => Err(StubRead(Info, self.dev_init[5])), 75 | // HACK: flag needs to be set to progress through USB init in rockbox 76 | 0x28 => Err(StubRead(Info, self.dev_init[6] | 0x80)), 77 | 0x2c => Err(StubRead(Info, self.dev_init[7])), 78 | 0x30 => Err(StubRead(Info, self.dev_timing[0])), 79 | 0x34 => Err(StubRead(Debug, self.dev_timing[1])), 80 | 0x3c => Err(StubRead(Info, self.dev_timing[2])), 81 | 0x80 => Ok(self.gpo_val), 82 | 0x84 => Ok(self.gpo_enable), 83 | 0x88 => Err(StubRead(Info, 0x00)), 84 | 0x8c => Ok(self.gpo_input_enable), 85 | _ => Err(Unexpected), 86 | } 87 | } 88 | 89 | fn w32(&mut self, offset: u32, val: u32) -> MemResult<()> { 90 | match offset { 91 | 0x00 => Err(InvalidAccess), 92 | 0x04 => Err(InvalidAccess), 93 | 0x08 => Err(StubWrite(Info, self.bootstrap_maybe[0] = val)), 94 | 0x0c => Err(StubWrite(Info, self.bootstrap_maybe[1] = val)), 95 | 0x10 => Err(StubWrite(Info, self.dev_init[0] = val)), 96 | 0x14 => Err(StubWrite(Info, self.dev_init[1] = val)), 97 | 0x18 => Err(StubWrite(Info, self.dev_init[2] = val)), 98 | 0x1c => Err(StubWrite(Info, self.dev_init[3] = val)), 99 | // 0x40000000 enables/disables PPL 100 | 0x20 => Err(StubWrite(Debug, self.dev_init[4] = val)), 101 | 0x24 => Err(StubWrite(Info, self.dev_init[5] = val)), 102 | 0x28 => Err(StubWrite(Info, self.dev_init[6] = val)), 103 | 0x2c => Err(StubWrite(Info, self.dev_init[7] = val)), 104 | // HACK: flag needs to be set to progress through the Flash ROM bootloader 105 | 0x30 => Err(StubWrite(Info, self.dev_timing[0] = val | 0x8000000)), 106 | 0x34 => Err(StubWrite(Debug, self.dev_timing[1] = val)), 107 | // HACK: flag needs to be set to progress through the Flash ROM bootloader 108 | 0x3c => Err(StubWrite(Info, self.dev_timing[2] = val | 0x80000000)), 109 | 0x80 => Ok(self.gpo_val = val), 110 | 0x84 => Ok(self.gpo_enable = val), 111 | 0x88 => Err(InvalidAccess), 112 | 0x8c => Ok(self.gpo_input_enable = val), 113 | _ => Err(Unexpected), 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /clicky-core/src/devices/platform/pp/pwm.rs: -------------------------------------------------------------------------------- 1 | use crate::devices::prelude::*; 2 | 3 | #[derive(Debug, Clone, Copy)] 4 | pub struct PWMConfiguration { 5 | enabled: bool, 6 | duty: u8, 7 | scale: u16, 8 | } 9 | 10 | impl PWMConfiguration { 11 | pub fn new() -> PWMConfiguration { 12 | PWMConfiguration { 13 | enabled: false, 14 | duty: 0, 15 | scale: 0, 16 | } 17 | } 18 | 19 | fn read(&self) -> u32 { 20 | *0u32 21 | .set_bit(31, self.enabled) 22 | .set_bits(16..=23, self.duty as u32) 23 | .set_bits(0..=12, self.scale as u32) 24 | } 25 | 26 | fn write(&mut self, reg: u32) { 27 | self.enabled = reg.get_bit(31); 28 | self.duty = reg.get_bits(16..=23) as u8; 29 | self.scale = reg.get_bits(0..=12) as u16; 30 | } 31 | } 32 | 33 | // Looks faily similar to Tegra's PWM controller: https://github.com/torvalds/linux/blob/master/drivers/pwm/pwm-tegra.c 34 | // See also "Tegra 4 Technical Reference Manual", Section 39.2 PWM Registers 35 | #[derive(Debug)] 36 | pub struct PWMCon { 37 | channels: [PWMConfiguration; 4], 38 | } 39 | 40 | impl PWMCon { 41 | pub fn new() -> PWMCon { 42 | PWMCon { 43 | channels: [PWMConfiguration::new(); 4], 44 | } 45 | } 46 | } 47 | 48 | impl Device for PWMCon { 49 | fn kind(&self) -> &'static str { 50 | "PWM Controller" 51 | } 52 | 53 | fn probe(&self, offset: u32) -> Probe { 54 | let reg = match offset { 55 | 0x00 => "Channel 0 configuration (piezo)", 56 | 0x10 => "Channel 1 configuration (LCD backlight)", 57 | 0x20 => "Channel 2 configuration", 58 | 0x30 => "Channel 3 configuration", 59 | _ => return Probe::Unmapped, 60 | }; 61 | 62 | Probe::Register(reg) 63 | } 64 | } 65 | 66 | impl Memory for PWMCon { 67 | fn r32(&mut self, offset: u32) -> MemResult { 68 | match offset { 69 | 0x00 => Ok(self.channels[0].read()), 70 | 0x10 => Ok(self.channels[1].read()), 71 | 0x20 => Ok(self.channels[2].read()), 72 | 0x30 => Ok(self.channels[3].read()), 73 | _ => Err(Unexpected), 74 | } 75 | } 76 | 77 | fn w32(&mut self, offset: u32, val: u32) -> MemResult<()> { 78 | match offset { 79 | 0x00 => Ok(self.channels[0].write(val)), 80 | 0x10 => Ok(self.channels[1].write(val)), 81 | 0x20 => Ok(self.channels[2].write(val)), 82 | 0x30 => Ok(self.channels[3].write(val)), 83 | _ => Err(Unexpected), 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /clicky-core/src/devices/platform/pp/serial.rs: -------------------------------------------------------------------------------- 1 | use crate::devices::prelude::*; 2 | 3 | /// PP5020 serial controller 4 | #[derive(Debug)] 5 | pub struct Serial { 6 | label: &'static str, 7 | 8 | ier: u8, 9 | fcr: u8, 10 | lcr: u8, 11 | mcr: u8, 12 | } 13 | 14 | impl Serial { 15 | pub fn new(label: &'static str) -> Serial { 16 | Serial { 17 | label, 18 | 19 | ier: 0, 20 | fcr: 0, 21 | lcr: 0, 22 | mcr: 0, 23 | } 24 | } 25 | } 26 | 27 | impl Device for Serial { 28 | fn kind(&self) -> &'static str { 29 | "Serial" 30 | } 31 | 32 | fn label(&self) -> Option<&'static str> { 33 | Some(self.label) 34 | } 35 | 36 | fn probe(&self, offset: u32) -> Probe { 37 | let reg = match offset { 38 | 0x00 => "RBR/THR", 39 | 0x04 => "IER", 40 | 0x08 => "FCR/IIR", 41 | 0x0c => "LCR", 42 | 0x10 => "MCR", 43 | 0x14 => "LSR", 44 | 0x18 => "MSR", 45 | 0x1c => "SPR", 46 | _ => return Probe::Unmapped, 47 | }; 48 | 49 | Probe::Register(reg) 50 | } 51 | } 52 | 53 | impl Memory for Serial { 54 | fn r32(&mut self, offset: u32) -> MemResult { 55 | match offset { 56 | 0x00 => { 57 | // TODO: properly wire up uart 58 | Err(StubRead(Info, 0)) 59 | } 60 | 0x04 => Err(StubRead(Info, self.ier as u32)), 61 | 0x08 => Err(StubRead(Info, self.fcr as u32)), 62 | 0x0c => Err(StubRead(Info, self.lcr as u32)), 63 | 0x10 => Err(StubRead(Info, self.mcr as u32)), 64 | // always ready to tx and rx 65 | 0x14 => Ok(0x21), 66 | 0x18 => Err(Unimplemented), 67 | 0x1c => Err(Unimplemented), 68 | _ => Err(Unexpected), 69 | } 70 | } 71 | 72 | fn w32(&mut self, offset: u32, val: u32) -> MemResult<()> { 73 | let val = val.trunc_to_u8()?; 74 | 75 | match offset { 76 | 0x0 => Ok({ 77 | // TODO: properly wire up uart 78 | if val.is_ascii() { 79 | print!("{}", val as char); 80 | } else { 81 | print!("\\x{:02x}", val); 82 | } 83 | }), 84 | 0x04 => Err(StubWrite(Info, self.ier = val)), 85 | 0x08 => Err(StubWrite(Info, self.fcr = val)), 86 | 0x0c => Err(StubWrite(Info, self.lcr = val)), 87 | 0x10 => Err(StubWrite(Info, self.mcr = val)), 88 | 0x14 => Err(InvalidAccess), 89 | 0x18 => Err(Unimplemented), 90 | 0x1c => Err(Unimplemented), 91 | _ => Err(Unexpected), 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /clicky-core/src/devices/platform/pp/usec_timer.rs: -------------------------------------------------------------------------------- 1 | use crate::devices::prelude::*; 2 | 3 | use relativity::Instant; 4 | 5 | /// 32 bit timer which ticks every usec. 6 | #[derive(Debug)] 7 | pub struct UsecTimer { 8 | val: u32, 9 | last: Instant, 10 | } 11 | 12 | impl UsecTimer { 13 | pub fn new() -> UsecTimer { 14 | UsecTimer { 15 | val: 0, 16 | last: Instant::now(), 17 | } 18 | } 19 | } 20 | 21 | impl Device for UsecTimer { 22 | fn kind(&self) -> &'static str { 23 | "Microsecond Timer" 24 | } 25 | 26 | fn probe(&self, offset: u32) -> Probe { 27 | let reg = match offset { 28 | 0x0 => "Val", 29 | _ => return Probe::Unmapped, 30 | }; 31 | 32 | Probe::Register(reg) 33 | } 34 | } 35 | 36 | impl Memory for UsecTimer { 37 | fn r32(&mut self, offset: u32) -> MemResult { 38 | match offset { 39 | 0x0 => { 40 | let now = Instant::now(); 41 | let elapsed = now.duration_since(self.last); 42 | let elapsed_as_micros = elapsed.as_micros() as u32; 43 | // Reading the timer value in a tight loop could result in a delta time of 0 44 | if elapsed_as_micros != 0 { 45 | self.val = self.val.wrapping_add(elapsed_as_micros); 46 | self.last = now; 47 | } 48 | 49 | Ok(self.val) 50 | } 51 | _ => Err(Unexpected), 52 | } 53 | } 54 | 55 | fn w32(&mut self, offset: u32, _val: u32) -> MemResult<()> { 56 | match offset { 57 | 0x0 => Err(InvalidAccess), 58 | _ => Err(Unexpected), 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /clicky-core/src/devices/prelude.rs: -------------------------------------------------------------------------------- 1 | //! The Device Prelude. 2 | //! 3 | //! The purpose of this module is to alleviate imports of common device traits 4 | //! and types by adding a glob import to the top of device modules: 5 | 6 | pub use bit_field::BitField; 7 | pub use log::Level::*; 8 | 9 | pub use crate::devices::{Device, Probe}; 10 | pub use crate::error::{ 11 | MemException::{self, *}, 12 | MemResult, 13 | }; 14 | pub use crate::executor::*; 15 | pub use crate::memory::Memory; 16 | pub use crate::signal::{self, irq}; 17 | 18 | // XXX: the fact that this is required is indicative of the need to rework the 19 | // device memory interface. 20 | pub trait TruncateByte { 21 | fn trunc_to_u8(self) -> MemResult; 22 | } 23 | 24 | impl TruncateByte for u32 { 25 | fn trunc_to_u8(self) -> MemResult { 26 | if self > 0xff { 27 | Err(ContractViolation { 28 | msg: ">8-bit access to a 8-bit interface".into(), 29 | severity: Error, 30 | stub_val: None, 31 | }) 32 | } else { 33 | Ok(self as u8) 34 | } 35 | } 36 | } 37 | 38 | impl TruncateByte for u16 { 39 | fn trunc_to_u8(self) -> MemResult { 40 | if self > 0xff { 41 | Err(ContractViolation { 42 | msg: ">8-bit access to a 8-bit interface".into(), 43 | severity: Error, 44 | stub_val: None, 45 | }) 46 | } else { 47 | Ok(self as u8) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /clicky-core/src/devices/util/arcmutex.rs: -------------------------------------------------------------------------------- 1 | use crate::devices::prelude::*; 2 | 3 | use std::sync::Arc; 4 | use std::sync::{LockResult, Mutex, MutexGuard}; 5 | 6 | /// Wrapper around Arc> which implements the `Device` and `Memory` 7 | /// traits without having to explicitly deref + lock the underlying device. 8 | #[derive(Debug)] 9 | pub struct ArcMutexDevice { 10 | device: Arc>, 11 | } 12 | 13 | impl Clone for ArcMutexDevice { 14 | fn clone(&self) -> Self { 15 | ArcMutexDevice { 16 | device: Arc::clone(&self.device), 17 | } 18 | } 19 | } 20 | 21 | impl ArcMutexDevice { 22 | /// Wrap the provided device in an Arc> 23 | pub fn new(device: D) -> ArcMutexDevice { 24 | ArcMutexDevice { 25 | device: Arc::new(Mutex::new(device)), 26 | } 27 | } 28 | 29 | /// Lock the underlying device 30 | pub fn lock(&self) -> LockResult> { 31 | self.device.lock() 32 | } 33 | } 34 | 35 | impl Device for ArcMutexDevice { 36 | fn kind(&self) -> &'static str { 37 | self.device.lock().unwrap().kind() 38 | } 39 | 40 | fn label(&self) -> Option<&'static str> { 41 | self.device.lock().unwrap().label() 42 | } 43 | 44 | fn probe(&self, offset: u32) -> Probe { 45 | self.device.lock().unwrap().probe(offset) 46 | } 47 | } 48 | 49 | impl Memory for ArcMutexDevice { 50 | fn r32(&mut self, offset: u32) -> MemResult { 51 | self.device.lock().unwrap().r32(offset) 52 | } 53 | 54 | fn w32(&mut self, offset: u32, val: u32) -> MemResult<()> { 55 | self.device.lock().unwrap().w32(offset, val) 56 | } 57 | 58 | fn r8(&mut self, offset: u32) -> MemResult { 59 | self.device.lock().unwrap().r8(offset) 60 | } 61 | 62 | fn r16(&mut self, offset: u32) -> MemResult { 63 | self.device.lock().unwrap().r16(offset) 64 | } 65 | 66 | fn w8(&mut self, offset: u32, val: u8) -> MemResult<()> { 67 | self.device.lock().unwrap().w8(offset, val) 68 | } 69 | 70 | fn w16(&mut self, offset: u32, val: u16) -> MemResult<()> { 71 | self.device.lock().unwrap().w16(offset, val) 72 | } 73 | 74 | fn x16(&mut self, offset: u32) -> MemResult { 75 | self.device.lock().unwrap().x16(offset) 76 | } 77 | 78 | fn x32(&mut self, offset: u32) -> MemResult { 79 | self.device.lock().unwrap().x32(offset) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /clicky-core/src/devices/util/mem_sniffer.rs: -------------------------------------------------------------------------------- 1 | use crate::devices::prelude::*; 2 | 3 | use crate::memory::{MemAccess, MemAccessKind, ToMemAccess}; 4 | 5 | /// `MemSniffer` wraps a `Memory` object, forwarding requests to the underlying 6 | /// memory object, while also logging accesses with the provided callback. 7 | #[derive(Debug)] 8 | pub struct MemSniffer<'a, M, F: FnMut(MemAccess)> { 9 | mem: &'a mut M, 10 | addrs: &'a [u32], 11 | on_access: F, 12 | } 13 | 14 | impl<'a, M: Memory, F: FnMut(MemAccess)> MemSniffer<'a, M, F> { 15 | pub fn new(mem: &'a mut M, addrs: &'a [u32], on_access: F) -> MemSniffer<'a, M, F> { 16 | MemSniffer { 17 | mem, 18 | addrs, 19 | on_access, 20 | } 21 | } 22 | } 23 | 24 | macro_rules! impl_memsniff_r { 25 | ($fn:ident, $ret:ty) => { 26 | fn $fn(&mut self, addr: u32) -> MemResult<$ret> { 27 | let ret = self.mem.$fn(addr)?; 28 | if self.addrs.contains(&addr) { 29 | (self.on_access)(ret.to_memaccess(addr, MemAccessKind::Read)); 30 | } 31 | Ok(ret) 32 | } 33 | }; 34 | } 35 | 36 | macro_rules! impl_memsniff_w { 37 | ($fn:ident, $val:ty) => { 38 | fn $fn(&mut self, addr: u32, val: $val) -> MemResult<()> { 39 | self.mem.$fn(addr, val)?; 40 | if self.addrs.contains(&addr) { 41 | (self.on_access)(val.to_memaccess(addr, MemAccessKind::Write)); 42 | } 43 | Ok(()) 44 | } 45 | }; 46 | } 47 | 48 | macro_rules! impl_memsniff_x { 49 | ($fn:ident, $ret:ty) => { 50 | fn $fn(&mut self, addr: u32) -> MemResult<$ret> { 51 | let ret = self.mem.$fn(addr)?; 52 | if self.addrs.contains(&addr) { 53 | (self.on_access)(ret.to_memaccess(addr, MemAccessKind::Execute)); 54 | } 55 | Ok(ret) 56 | } 57 | }; 58 | } 59 | 60 | impl<'a, M: Device, F: FnMut(MemAccess) + Send + Sync> Device for MemSniffer<'a, M, F> { 61 | fn kind(&self) -> &'static str { 62 | self.mem.kind() 63 | } 64 | 65 | fn label(&self) -> Option<&'static str> { 66 | self.mem.label() 67 | } 68 | 69 | fn probe(&self, offset: u32) -> Probe { 70 | self.mem.probe(offset) 71 | } 72 | } 73 | 74 | impl<'a, M: Memory, F: FnMut(MemAccess)> Memory for MemSniffer<'a, M, F> { 75 | impl_memsniff_r!(r8, u8); 76 | impl_memsniff_r!(r16, u16); 77 | impl_memsniff_r!(r32, u32); 78 | impl_memsniff_w!(w8, u8); 79 | impl_memsniff_w!(w16, u16); 80 | impl_memsniff_w!(w32, u32); 81 | impl_memsniff_x!(x16, u16); 82 | impl_memsniff_x!(x32, u32); 83 | } 84 | -------------------------------------------------------------------------------- /clicky-core/src/devices/util/mod.rs: -------------------------------------------------------------------------------- 1 | //! Utilities related to devices. 2 | 3 | mod arcmutex; 4 | mod mem_sniffer; 5 | 6 | pub use arcmutex::*; 7 | pub use mem_sniffer::*; 8 | -------------------------------------------------------------------------------- /clicky-core/src/executor/mod.rs: -------------------------------------------------------------------------------- 1 | //! Types and Traits related to task execution. 2 | 3 | use std::fmt::Debug; 4 | 5 | use futures::task::Spawn; 6 | pub use futures::task::SpawnExt; 7 | 8 | cfg_if::cfg_if! { 9 | if #[cfg(target_arch = "wasm32")] { 10 | pub type Executor = local::LocalExecutor; 11 | pub type Spawner = local::LocalSpawner; 12 | } else { 13 | pub type Executor = thread::ThreadExecutor; 14 | pub type Spawner = thread::ThreadSpawner; 15 | } 16 | } 17 | 18 | /// A cloneable spawn handle. 19 | pub trait ClickySpawn: Spawn + Debug + Clone {} 20 | 21 | /// Abstraction over single/multi threaded executors. 22 | pub trait ClickyExecutor: Debug + Sized { 23 | type Spawner: ClickySpawn; 24 | 25 | /// Construct a new executor. 26 | fn new() -> std::io::Result; 27 | 28 | /// Runs all tasks in the pool and returns if no more progress can be made 29 | /// on any task. 30 | /// 31 | /// On multi-threaded executors, this method is a noop. 32 | fn run_until_stalled(&mut self); 33 | 34 | /// Return a cloneable spawn handle. 35 | fn spawner(&self) -> Self::Spawner; 36 | } 37 | 38 | mod local { 39 | use super::*; 40 | 41 | use futures::future::FutureObj; 42 | use futures::task::SpawnError; 43 | 44 | #[derive(Debug, Clone)] 45 | pub struct LocalSpawner(futures_executor::LocalSpawner); 46 | 47 | impl ClickySpawn for LocalSpawner {} 48 | impl Spawn for LocalSpawner { 49 | fn spawn_obj(&self, future: FutureObj<'static, ()>) -> Result<(), SpawnError> { 50 | self.0.spawn_obj(future) 51 | } 52 | 53 | #[inline] 54 | fn status(&self) -> Result<(), SpawnError> { 55 | self.0.status() 56 | } 57 | } 58 | 59 | #[derive(Debug)] 60 | pub struct LocalExecutor(futures_executor::LocalPool); 61 | 62 | impl ClickyExecutor for LocalExecutor { 63 | type Spawner = LocalSpawner; 64 | 65 | fn new() -> std::io::Result { 66 | Ok(LocalExecutor(futures_executor::LocalPool::new())) 67 | } 68 | 69 | fn run_until_stalled(&mut self) { 70 | futures_executor::LocalPool::run_until_stalled(&mut self.0) 71 | } 72 | 73 | fn spawner(&self) -> Self::Spawner { 74 | LocalSpawner(self.0.spawner()) 75 | } 76 | } 77 | } 78 | 79 | mod thread { 80 | use super::*; 81 | 82 | use futures::future::FutureObj; 83 | use futures::task::SpawnError; 84 | 85 | #[derive(Debug, Clone)] 86 | pub struct ThreadSpawner(futures_executor::ThreadPool); 87 | 88 | impl ClickySpawn for ThreadSpawner {} 89 | impl Spawn for ThreadSpawner { 90 | fn spawn_obj(&self, future: FutureObj<'static, ()>) -> Result<(), SpawnError> { 91 | self.0.spawn_obj(future) 92 | } 93 | 94 | #[inline] 95 | fn status(&self) -> Result<(), SpawnError> { 96 | self.0.status() 97 | } 98 | } 99 | 100 | #[derive(Debug)] 101 | pub struct ThreadExecutor(futures_executor::ThreadPool); 102 | 103 | impl ClickyExecutor for ThreadExecutor { 104 | type Spawner = ThreadSpawner; 105 | 106 | fn new() -> std::io::Result { 107 | Ok(ThreadExecutor(futures_executor::ThreadPool::new()?)) 108 | } 109 | 110 | fn run_until_stalled(&mut self) {} 111 | 112 | fn spawner(&self) -> Self::Spawner { 113 | ThreadSpawner(self.0.clone()) 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /clicky-core/src/gui/mod.rs: -------------------------------------------------------------------------------- 1 | //! GUI related types and traits 2 | 3 | /// `RenderCallback` is called with an ARGB Framebuffer, and returns the 4 | /// dimensions of the image. 5 | pub type RenderCallback = 6 | Box) -> (usize, usize) + Send>; 7 | /// `ButtonCallback` should be called whenever a button is pressed and released 8 | /// (passing `true` and `false` respectively) 9 | pub type ButtonCallback = Box; 10 | /// `ScrollCallback` should be called on scroll, passing the delta in both 11 | /// directions. 12 | pub type ScrollCallback = Box; 13 | 14 | pub trait TakeControls { 15 | type Controls; 16 | fn take_controls(&mut self) -> Option; 17 | } 18 | -------------------------------------------------------------------------------- /clicky-core/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate static_assertions; 3 | 4 | #[macro_use] 5 | extern crate log; 6 | 7 | pub mod block; 8 | pub mod devices; 9 | pub mod error; 10 | pub mod executor; 11 | pub mod gui; 12 | pub mod memory; 13 | pub mod signal; 14 | pub mod sys; 15 | -------------------------------------------------------------------------------- /clicky-core/src/memory/access.rs: -------------------------------------------------------------------------------- 1 | /// A value associated with a read/write 2 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 3 | pub enum MemAccessVal { 4 | U8(u8), 5 | U16(u16), 6 | U32(u32), 7 | } 8 | 9 | /// Memory Access Kind (Read or Write) 10 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 11 | pub enum MemAccessKind { 12 | Read, 13 | Execute, 14 | Write, 15 | } 16 | 17 | /// Encodes a memory access (read/write) 18 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 19 | pub struct MemAccess { 20 | pub kind: MemAccessKind, 21 | pub offset: u32, 22 | pub val: MemAccessVal, 23 | } 24 | 25 | /// Utility trait for converting a uX into the corresponding MemAccess 26 | pub trait ToMemAccess: Sized { 27 | fn to_memaccess(self, offset: u32, kind: MemAccessKind) -> MemAccess; 28 | } 29 | 30 | macro_rules! impl_memaccess { 31 | ($(($val:ty, $size:ident)),*) => { 32 | $( 33 | impl ToMemAccess for $val { 34 | fn to_memaccess(self, offset: u32, kind: MemAccessKind) -> MemAccess { 35 | MemAccess { 36 | kind, 37 | offset, 38 | val: MemAccessVal::$size(self), 39 | } 40 | } 41 | } 42 | )* 43 | }; 44 | } 45 | 46 | impl_memaccess! { 47 | (u8, U8), 48 | (u16, U16), 49 | (u32, U32) 50 | } 51 | 52 | impl std::fmt::Display for MemAccessVal { 53 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 54 | match self { 55 | MemAccessVal::U8(val) => write!(f, "{:#04x?}", val), 56 | MemAccessVal::U16(val) => write!(f, "{:#06x?}", val), 57 | MemAccessVal::U32(val) => write!(f, "{:#010x?}", val), 58 | } 59 | } 60 | } 61 | 62 | impl std::fmt::Display for MemAccess { 63 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 64 | match self.kind { 65 | MemAccessKind::Read => write!( 66 | f, 67 | "{}({:#010x?}) // {}", 68 | match self.val { 69 | MemAccessVal::U8(_) => "r8", 70 | MemAccessVal::U16(_) => "r16", 71 | MemAccessVal::U32(_) => "r32", 72 | }, 73 | self.offset, 74 | self.val 75 | ), 76 | MemAccessKind::Write => write!( 77 | f, 78 | "{}({:#010x?}, {})", 79 | match self.val { 80 | MemAccessVal::U8(_) => "w8", 81 | MemAccessVal::U16(_) => "w16", 82 | MemAccessVal::U32(_) => "w32", 83 | }, 84 | self.offset, 85 | self.val 86 | ), 87 | MemAccessKind::Execute => write!( 88 | f, 89 | "{}({:#020x?}, {})", 90 | match self.val { 91 | MemAccessVal::U8(_) => "x8", 92 | MemAccessVal::U16(_) => "x16", 93 | MemAccessVal::U32(_) => "x32", 94 | }, 95 | self.offset, 96 | self.val 97 | ), 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /clicky-core/src/memory/armv4t_adaptor.rs: -------------------------------------------------------------------------------- 1 | use armv4t_emu::Memory as ArmMemory; 2 | 3 | use super::*; 4 | 5 | /// The CPU's Memory interface expects all memory accesses to succeed (i.e: 6 | /// return _some_ sort of value). As such, there needs to be some sort of shim 7 | /// between the emulator's fallible [Memory] interface and the CPU's infallible 8 | /// [ArmMemory] interface. 9 | /// 10 | /// [MemoryAdapter] wraps a [Memory] object, implementing the [ArmMemory] 11 | /// interface such that if an error occurs while accessing memory, the access 12 | /// will still "succeed", while the exception is stashed away until after the 13 | /// CPU cycle is executed. The `take_exception` method is then be used to check 14 | /// if an exception occurred. 15 | pub struct MemoryAdapter<'a, M: Memory> { 16 | pub mem: &'a mut M, 17 | pub exception: Option<(MemAccess, MemException)>, 18 | } 19 | 20 | impl<'a, M: Memory> MemoryAdapter<'a, M> { 21 | pub fn new(mem: &'a mut M) -> Self { 22 | MemoryAdapter { 23 | mem, 24 | exception: None, 25 | } 26 | } 27 | } 28 | 29 | macro_rules! impl_memadapter_r { 30 | ($fn:ident, $ret:ty) => { 31 | fn $fn(&mut self, addr: u32) -> $ret { 32 | use crate::memory::MemAccessKind; 33 | match self.mem.$fn(addr) { 34 | Ok(val) => val, 35 | Err(e) => { 36 | let ret = match e { 37 | // If it's a stubbed-read, pass through the stubbed value 38 | MemException::StubRead(_, v) => v as $ret, 39 | MemException::ContractViolation { 40 | stub_val: Some(v), .. 41 | } => v as $ret, 42 | // otherwise, contents of register undefined 43 | _ => 0x00, 44 | }; 45 | // stash the exception 46 | self.exception = Some((ret.to_memaccess(addr, MemAccessKind::Read), e)); 47 | ret 48 | } 49 | } 50 | } 51 | }; 52 | } 53 | 54 | macro_rules! impl_memadapter_w { 55 | ($fn:ident, $val:ty) => { 56 | fn $fn(&mut self, addr: u32, val: $val) { 57 | use crate::memory::MemAccessKind; 58 | match self.mem.$fn(addr, val) { 59 | Ok(()) => {} 60 | Err(e) => { 61 | // stash the exception 62 | self.exception = Some((val.to_memaccess(addr, MemAccessKind::Write), e)); 63 | } 64 | } 65 | } 66 | }; 67 | } 68 | 69 | macro_rules! impl_memadapter_x { 70 | ($fn:ident, $ret:ty) => { 71 | fn $fn(&mut self, addr: u32) -> $ret { 72 | use crate::memory::MemAccessKind; 73 | match self.mem.$fn(addr) { 74 | Ok(val) => val, 75 | Err(e) => { 76 | let ret = match e { 77 | // If it's a stubbed-read, pass through the stubbed value 78 | MemException::StubRead(_, v) => v as $ret, 79 | MemException::ContractViolation { 80 | stub_val: Some(v), .. 81 | } => v as $ret, 82 | // otherwise, contents of register undefined 83 | _ => 0x00, 84 | }; 85 | // stash the exception 86 | self.exception = Some((ret.to_memaccess(addr, MemAccessKind::Execute), e)); 87 | ret 88 | } 89 | } 90 | } 91 | }; 92 | } 93 | 94 | impl<'a, M: Memory> ArmMemory for MemoryAdapter<'a, M> { 95 | impl_memadapter_r!(r8, u8); 96 | impl_memadapter_r!(r16, u16); 97 | impl_memadapter_r!(r32, u32); 98 | impl_memadapter_w!(w8, u8); 99 | impl_memadapter_w!(w16, u16); 100 | impl_memadapter_w!(w32, u32); 101 | impl_memadapter_x!(x16, u16); 102 | impl_memadapter_x!(x32, u32); 103 | } 104 | -------------------------------------------------------------------------------- /clicky-core/src/memory/mod.rs: -------------------------------------------------------------------------------- 1 | //! Traits and types related to memory access. 2 | 3 | pub mod armv4t_adaptor; 4 | 5 | use crate::error::*; 6 | 7 | mod access; 8 | pub use access::{MemAccess, MemAccessKind, MemAccessVal, ToMemAccess}; 9 | 10 | /// Common memory trait used throughout the emulator. 11 | /// 12 | /// Default implementations for 8-bit and 16-bit read/write return a 13 | /// [MemException::Misaligned] if the address isn't aligned properly. 14 | pub trait Memory { 15 | /// Read a 32 bit value at a given offset 16 | fn r32(&mut self, offset: u32) -> MemResult; 17 | /// Write a 32 bit value to the given offset 18 | fn w32(&mut self, offset: u32, val: u32) -> MemResult<()>; 19 | /// Read a 32 bit value at a given offset for execution 20 | fn x16(&mut self, offset: u32) -> MemResult { 21 | self.r16(offset) 22 | } 23 | fn x32(&mut self, offset: u32) -> MemResult { 24 | self.r32(offset) 25 | } 26 | 27 | /// Read a 8 bit value at a given offset 28 | fn r8(&mut self, offset: u32) -> MemResult { 29 | if offset & 0x3 != 0 { 30 | Err(MemException::Misaligned) 31 | } else { 32 | self.r32(offset).map(|v| v as u8) 33 | } 34 | } 35 | 36 | /// Read a 16 bit value at a given offset 37 | fn r16(&mut self, offset: u32) -> MemResult { 38 | if offset & 0x3 != 0 { 39 | Err(MemException::Misaligned) 40 | } else { 41 | self.r32(offset).map(|v| v as u16) 42 | } 43 | } 44 | 45 | /// Write a 8 bit value to the given offset 46 | fn w8(&mut self, offset: u32, val: u8) -> MemResult<()> { 47 | if offset & 0x3 != 0 { 48 | Err(MemException::Misaligned) 49 | } else { 50 | self.w32(offset, val as u32) 51 | } 52 | } 53 | 54 | /// Write a 16 bit value to the given offset 55 | fn w16(&mut self, offset: u32, val: u16) -> MemResult<()> { 56 | if offset & 0x3 != 0 { 57 | Err(MemException::Misaligned) 58 | } else { 59 | self.w32(offset, val as u32) 60 | } 61 | } 62 | } 63 | 64 | macro_rules! impl_memfwd { 65 | ($type:ty) => { 66 | impl Memory for $type { 67 | 68 | fn r32(&mut self, offset: u32) -> MemResult { 69 | (**self).r32(offset) 70 | } 71 | 72 | fn w32(&mut self, offset: u32, val: u32) -> MemResult<()> { 73 | (**self).w32(offset, val) 74 | } 75 | 76 | fn r8(&mut self, offset: u32) -> MemResult { 77 | (**self).r8(offset) 78 | } 79 | 80 | fn r16(&mut self, offset: u32) -> MemResult { 81 | (**self).r16(offset) 82 | } 83 | 84 | fn w8(&mut self, offset: u32, val: u8) -> MemResult<()> { 85 | (**self).w8(offset, val) 86 | } 87 | 88 | fn w16(&mut self, offset: u32, val: u16) -> MemResult<()> { 89 | (**self).w16(offset, val) 90 | } 91 | 92 | fn x16(&mut self, offset: u32) -> MemResult { 93 | (**self).x16(offset) 94 | } 95 | 96 | fn x32(&mut self, offset: u32) -> MemResult { 97 | (**self).x32(offset) 98 | } 99 | } 100 | }; 101 | } 102 | 103 | impl_memfwd!(Box); 104 | impl_memfwd!(&mut dyn Memory); 105 | -------------------------------------------------------------------------------- /clicky-core/src/signal/gpio.rs: -------------------------------------------------------------------------------- 1 | //! GPIO signaling and notification. 2 | 3 | use super::{new as new_signal, Master, Slave, Trigger, TriggerKind}; 4 | 5 | /// Create a new GPIO line. Updates `notify` whenever the sender updates the 6 | /// signal. 7 | pub fn new(notify: Changed, debug_label: &'static str) -> (Sender, Reciever) { 8 | let (master, slave) = new_signal(notify.trigger, "GPIO", debug_label); 9 | 10 | let sender = Sender { master }; 11 | let reciever = Reciever { slave }; 12 | 13 | (sender, reciever) 14 | } 15 | 16 | /// Tracks GPIO signal changes across one-or-more GPIO lines. 17 | #[derive(Debug, Clone)] 18 | pub struct Changed { 19 | trigger: Trigger, 20 | } 21 | 22 | impl Default for Changed { 23 | fn default() -> Changed { 24 | Changed::new() 25 | } 26 | } 27 | 28 | impl Changed { 29 | pub fn new() -> Changed { 30 | Changed { 31 | trigger: Trigger::new(TriggerKind::Edge), 32 | } 33 | } 34 | 35 | /// Checks if any connected GPIO lines have changed since the last call to 36 | /// `check_and_clear`. 37 | #[inline] 38 | pub fn check_and_clear(&self) -> bool { 39 | self.trigger.check_and_clear() 40 | } 41 | } 42 | 43 | /// The receiving side of a GPIO line. 44 | #[derive(Debug, Clone)] 45 | pub struct Reciever { 46 | slave: Slave, 47 | } 48 | 49 | impl Reciever { 50 | /// Checks if the GPIO line is high. 51 | #[inline] 52 | pub fn is_high(&self) -> bool { 53 | self.slave.asserted() 54 | } 55 | } 56 | 57 | /// The sending side of a GPIO line. Senders can be cloned, whereupon each 58 | /// Sender will share the signal line. The signal is asserted if ANY Sender 59 | /// asserts, and cleared only if ALL Senders have called clear. 60 | #[derive(Debug)] 61 | pub struct Sender { 62 | master: Master, 63 | } 64 | 65 | impl Sender { 66 | /// Set the GPIO line high. 67 | #[inline] 68 | pub fn set_high(&mut self) { 69 | self.master.assert() 70 | } 71 | 72 | /// Set the GPIO line low. 73 | #[inline] 74 | pub fn set_low(&mut self) { 75 | self.master.clear() 76 | } 77 | 78 | /// Check if this sender is setting the signal high. 79 | #[inline] 80 | pub fn is_set_high(&self) -> bool { 81 | self.master.is_asserting() 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /clicky-core/src/signal/irq.rs: -------------------------------------------------------------------------------- 1 | //! IRQ signaling and notification. 2 | 3 | use super::{new as new_signal, Master, Slave, Trigger, TriggerKind}; 4 | 5 | /// Create a new IRQ line. Updates `notify` when the sender asserts the IRQ. 6 | pub fn new(notify: Pending, debug_label: &'static str) -> (Sender, Reciever) { 7 | let (master, slave) = new_signal(notify.trigger, "IRQ", debug_label); 8 | 9 | let sender = Sender { master }; 10 | let reciever = Reciever { slave }; 11 | 12 | (sender, reciever) 13 | } 14 | 15 | /// Tracks IRQ assertions across one-or-more IRQ lines. 16 | #[derive(Debug, Clone)] 17 | pub struct Pending { 18 | trigger: Trigger, 19 | } 20 | 21 | impl Default for Pending { 22 | fn default() -> Pending { 23 | Pending::new() 24 | } 25 | } 26 | 27 | impl Pending { 28 | pub fn new() -> Pending { 29 | Pending { 30 | trigger: Trigger::new(TriggerKind::Hi), 31 | } 32 | } 33 | 34 | /// Checks if any connected IRQs have been fired. 35 | #[inline] 36 | pub fn check(&self) -> bool { 37 | self.trigger.check() 38 | } 39 | 40 | /// Checks if any connected IRQs have been fired since the last call to 41 | /// `check_pending`. 42 | #[inline] 43 | pub fn clear(&self) -> bool { 44 | self.trigger.check() 45 | } 46 | } 47 | 48 | /// The receiving side of an IRQ line. 49 | #[derive(Debug, Clone)] 50 | pub struct Reciever { 51 | slave: Slave, 52 | } 53 | 54 | impl Reciever { 55 | /// Checks if the IRQ has been set. 56 | #[inline] 57 | pub fn asserted(&self) -> bool { 58 | self.slave.asserted() 59 | } 60 | } 61 | 62 | /// The sending side of an IRQ line. Senders can be cloned, whereupon each 63 | /// Sender will share the signal line. The signal is asserted if ANY Sender 64 | /// asserts, and cleared only if ALL Senders have called clear. 65 | #[derive(Debug, Clone)] 66 | pub struct Sender { 67 | master: Master, 68 | } 69 | 70 | impl Sender { 71 | /// Signal an IRQ. 72 | #[inline] 73 | pub fn assert(&mut self) { 74 | self.master.assert() 75 | } 76 | 77 | /// Clears an IRQ. 78 | #[inline] 79 | pub fn clear(&mut self) { 80 | self.master.clear() 81 | } 82 | 83 | /// Check if this sender is setting the signal high. 84 | #[inline] 85 | pub fn is_asserting(&self) -> bool { 86 | self.master.is_asserting() 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /clicky-core/src/signal/mod.rs: -------------------------------------------------------------------------------- 1 | //! General signaling and notification mechanism. Used to implement GPIO, IRQs, 2 | //! etc... 3 | 4 | use std::sync::atomic::{AtomicBool, AtomicIsize, Ordering}; 5 | use std::sync::Arc; 6 | 7 | pub mod gpio; 8 | pub mod irq; 9 | 10 | // TODO: Explore using a less-restrictive ordering. `Ordering::SeqCst` was 11 | // picked just to "play it safe" 12 | 13 | /// Create a new signal. 14 | pub fn new( 15 | trigger: Trigger, 16 | debug_group: &'static str, 17 | debug_label: &'static str, 18 | ) -> (Master, Slave) { 19 | let signal = Arc::new(AtomicIsize::new(0)); 20 | 21 | let sender = Master { 22 | trigger, 23 | own_signal: Arc::new(false.into()), 24 | signal: Arc::clone(&signal), 25 | debug_group, 26 | debug_label, 27 | }; 28 | let reciever = Slave { signal }; 29 | 30 | (sender, reciever) 31 | } 32 | 33 | /// Determines a `Trigger`'s behavior. 34 | #[derive(Debug, Copy, Clone)] 35 | pub enum TriggerKind { 36 | /// Triggered when the signal goes high. 37 | Hi, 38 | /// Triggered when the signal goes low. 39 | Lo, 40 | /// Triggered when the signal changes. 41 | Edge, 42 | } 43 | 44 | /// A way to hook into (one or more) signals and get notified of any changes. 45 | #[derive(Debug, Clone)] 46 | pub struct Trigger { 47 | kind: TriggerKind, 48 | trigger: Arc, 49 | } 50 | 51 | impl Trigger { 52 | fn update(&self, old_val: bool, new_val: bool) { 53 | use TriggerKind::*; 54 | match self.kind { 55 | Hi => { 56 | if !old_val && new_val { 57 | self.trigger.store(true, Ordering::SeqCst) 58 | } 59 | } 60 | Lo => { 61 | if old_val && !new_val { 62 | self.trigger.store(true, Ordering::SeqCst) 63 | } 64 | } 65 | Edge => { 66 | if (old_val && !new_val) || (!old_val && new_val) { 67 | self.trigger.store(true, Ordering::SeqCst) 68 | } 69 | } 70 | } 71 | } 72 | 73 | /// Create a new `Trigger`. 74 | pub fn new(kind: TriggerKind) -> Trigger { 75 | Trigger { 76 | kind, 77 | trigger: Arc::new(AtomicBool::new(false)), 78 | } 79 | } 80 | 81 | /// Retrieves and un-sets the trigger. 82 | #[inline] 83 | pub fn check_and_clear(&self) -> bool { 84 | self.trigger.fetch_and(false, Ordering::SeqCst) 85 | } 86 | 87 | /// Retrieves the trigger. 88 | #[inline] 89 | pub fn check(&self) -> bool { 90 | self.trigger.load(Ordering::SeqCst) 91 | } 92 | 93 | /// Un-sets the trigger. 94 | #[inline] 95 | pub fn clear(&self) { 96 | self.trigger.store(false, Ordering::SeqCst) 97 | } 98 | } 99 | 100 | /// The receiving side of a signal line. Able to query the signal level, but not 101 | /// change it. 102 | #[derive(Debug, Clone)] 103 | pub struct Slave { 104 | signal: Arc, 105 | } 106 | 107 | impl Slave { 108 | /// Checks if the signal is high. 109 | pub fn asserted(&self) -> bool { 110 | self.signal.load(Ordering::SeqCst) != 0 111 | } 112 | } 113 | 114 | /// The sending side of a signal line. Able to assert/clear the signal level, 115 | /// but not read it. Masters can be cloned, whereupon each Master will share the 116 | /// signal line. The signal is asserted if ANY Master asserts, and cleared only 117 | /// if ALL Masters have called clear. 118 | #[derive(Debug, Clone)] 119 | pub struct Master { 120 | trigger: Trigger, 121 | own_signal: Arc, 122 | signal: Arc, 123 | debug_group: &'static str, 124 | debug_label: &'static str, 125 | } 126 | 127 | impl Master { 128 | /// Set the signal high. 129 | pub fn assert(&mut self) { 130 | if self.own_signal.load(Ordering::SeqCst) { 131 | return; 132 | } 133 | 134 | if log_enabled!(target: self.debug_group, log::Level::Trace) { 135 | trace!(target: self.debug_group, "Asserted {}:{}", self.debug_group, self.debug_label); 136 | } 137 | 138 | self.own_signal.store(true, Ordering::SeqCst); 139 | let old_val = self.signal.fetch_add(1, Ordering::SeqCst); 140 | assert!(old_val >= 0); 141 | self.trigger.update(old_val != 0, true); 142 | } 143 | 144 | /// Set the signal low. 145 | pub fn clear(&mut self) { 146 | if !self.own_signal.load(Ordering::SeqCst) { 147 | return; 148 | } 149 | 150 | if log_enabled!(target: self.debug_group, log::Level::Trace) { 151 | trace!(target: self.debug_group, "Cleared {}:{}", self.debug_group, self.debug_label); 152 | } 153 | 154 | self.own_signal.store(false, Ordering::SeqCst); 155 | let old_val = self.signal.fetch_sub(1, Ordering::SeqCst); 156 | assert!(old_val > 0); 157 | self.trigger.update(old_val != 0, false); 158 | } 159 | 160 | /// Check if this Master is asserting the signal. 161 | #[inline] 162 | pub fn is_asserting(&self) -> bool { 163 | self.own_signal.load(Ordering::SeqCst) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /clicky-core/src/sys/ipod4g/controls.rs: -------------------------------------------------------------------------------- 1 | use super::{Ipod4g, Ipod4gControls}; 2 | 3 | use std::collections::HashMap; 4 | 5 | use crate::devices::platform::pp::Controls; 6 | use crate::gui::{ButtonCallback, ScrollCallback, TakeControls}; 7 | 8 | #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] 9 | pub enum Ipod4gKey { 10 | Up, 11 | Down, 12 | Left, 13 | Right, 14 | Action, 15 | Hold, 16 | } 17 | 18 | #[derive(Default)] 19 | pub struct Ipod4gBinds { 20 | pub keys: HashMap, 21 | pub wheel: Option, 22 | } 23 | 24 | impl TakeControls for Ipod4g { 25 | type Controls = Ipod4gBinds; 26 | 27 | fn take_controls(&mut self) -> Option { 28 | let Ipod4gControls { 29 | mut hold, 30 | controls: 31 | Controls { 32 | mut action, 33 | mut up, 34 | mut down, 35 | mut left, 36 | mut right, 37 | wheel: (mut wheel_active, wheel_data), 38 | }, 39 | } = self.controls.take()?; 40 | 41 | let mut controls = Ipod4gBinds::default(); 42 | 43 | controls.keys.insert( 44 | Ipod4gKey::Hold, 45 | Box::new(move |pressed| { 46 | if pressed { 47 | // toggle on and off 48 | match hold.is_set_high() { 49 | false => hold.set_high(), 50 | true => hold.set_low(), 51 | } 52 | } 53 | }), 54 | ); 55 | 56 | macro_rules! connect_controls_btn { 57 | ($key:expr, $signal:expr) => { 58 | controls.keys.insert( 59 | $key, 60 | Box::new(move |pressed| { 61 | if pressed { 62 | $signal.assert() 63 | } else { 64 | $signal.clear() 65 | } 66 | }), 67 | ); 68 | }; 69 | } 70 | 71 | connect_controls_btn!(Ipod4gKey::Up, up); 72 | connect_controls_btn!(Ipod4gKey::Down, down); 73 | connect_controls_btn!(Ipod4gKey::Left, left); 74 | connect_controls_btn!(Ipod4gKey::Right, right); 75 | connect_controls_btn!(Ipod4gKey::Action, action); 76 | 77 | // TODO: make sensitivity adjustable based on user's scroll speed 78 | controls.wheel = Some({ 79 | Box::new(move |(_dx, dy)| { 80 | // HACK: the signal is edge-triggered 81 | // TODO: i really aught to rework how input works... 82 | if wheel_active.is_asserting() { 83 | wheel_active.clear(); 84 | } else { 85 | wheel_active.assert(); 86 | } 87 | 88 | let mut wheel_data = wheel_data.lock().unwrap(); 89 | // from rockbox button-clickwheel.c 90 | // #define WHEELCLICKS_PER_ROTATION 96 /* wheelclicks per full rotation */ 91 | *wheel_data = wheel_data.wrapping_add((-dy * 2.) as i8 as u8) % 96; 92 | }) 93 | }); 94 | 95 | Some(controls) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /clicky-core/src/sys/ipod4g/hle_bootloader/firmware.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Read, Seek, SeekFrom}; 2 | 3 | use byteorder::{BigEndian, ByteOrder, ReadBytesExt, LE}; 4 | 5 | use super::HleBootloaderError; 6 | 7 | /// Firmware image metadata. 8 | /// 9 | /// See http://www.ipodlinux.org/Firmware.html 10 | #[derive(Debug)] 11 | pub struct FirmwareMeta { 12 | pub header: VolumeHeader, 13 | pub images: Vec, 14 | } 15 | 16 | impl FirmwareMeta { 17 | pub fn parse(fw: &mut (impl Read + Seek)) -> Result { 18 | // Volume Header 19 | let header = VolumeHeader::parse(fw)?; 20 | if header.magic_hi != BigEndian::read_u32(b"[hi]") { 21 | return Err(HleBootloaderError::BadMagic); 22 | } 23 | 24 | // TODO: don't assume FW version is 3, as each fw uses slightly-different 25 | // offsets between things 26 | if header.format_version != 3 { 27 | return Err(HleBootloaderError::InvalidVersion(header.format_version)); 28 | } 29 | 30 | // Pull directory entries 31 | fw.seek(SeekFrom::Start(header.dir_offset as u64 + 0x200))?; 32 | 33 | let mut images = Vec::new(); 34 | loop { 35 | let image = ImageInfo::parse(fw)?; 36 | if image.dev == *b"\0\0\0\0" { 37 | break; 38 | } 39 | images.push(image) 40 | } 41 | 42 | Ok(FirmwareMeta { header, images }) 43 | } 44 | } 45 | 46 | #[derive(Debug)] 47 | pub struct VolumeHeader { 48 | pub magic_hi: u32, 49 | pub dir_offset: u32, 50 | pub ext_header_loc: u16, 51 | pub format_version: u16, 52 | } 53 | 54 | impl VolumeHeader { 55 | fn parse(rdr: &mut impl Read) -> io::Result { 56 | // this is just a static string, which we can skip over. we _should_ make sure 57 | // that it matches the expected STOP string, but that's overkill... 58 | let mut stop = vec![0; 256]; 59 | rdr.read_exact(&mut stop)?; 60 | 61 | #[rustfmt::skip] 62 | let header = VolumeHeader { 63 | magic_hi: rdr.read_u32::()?, 64 | dir_offset: rdr.read_u32::()?, 65 | ext_header_loc: rdr.read_u16::()?, 66 | format_version: rdr.read_u16::()?, 67 | }; 68 | 69 | Ok(header) 70 | } 71 | } 72 | 73 | #[derive(Debug)] 74 | pub struct ImageInfo { 75 | pub dev: [u8; 4], 76 | pub name: [u8; 4], 77 | pub id: u32, 78 | pub dev_offset: u32, 79 | pub len: u32, 80 | pub addr: u32, 81 | pub entry_offset: u32, 82 | pub checksum: u32, 83 | pub vers: u32, 84 | pub load_addr: u32, 85 | } 86 | 87 | impl ImageInfo { 88 | fn parse(rdr: &mut impl Read) -> io::Result { 89 | #[rustfmt::skip] 90 | let image = ImageInfo { 91 | dev: rdr.read_u32::()?.to_be_bytes(), 92 | name: rdr.read_u32::()?.to_be_bytes(), 93 | id: rdr.read_u32::()?, 94 | dev_offset: rdr.read_u32::()?, 95 | len: rdr.read_u32::()?, 96 | addr: rdr.read_u32::()?, 97 | entry_offset: rdr.read_u32::()?, 98 | checksum: rdr.read_u32::()?, 99 | vers: rdr.read_u32::()?, 100 | load_addr: rdr.read_u32::()?, 101 | }; 102 | 103 | Ok(image) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /clicky-core/src/sys/ipod4g/hle_bootloader/mod.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, Seek, SeekFrom}; 2 | 3 | use armv4t_emu::{reg, Mode as ArmMode}; 4 | use std::io; 5 | use thiserror::Error; 6 | 7 | use crate::memory::Memory; 8 | 9 | use super::Ipod4g; 10 | 11 | mod firmware; 12 | mod sysinfo; 13 | 14 | use sysinfo::sysinfo_t; 15 | 16 | #[derive(Error, Debug)] 17 | pub enum HleBootloaderError { 18 | #[error("error while reading firmware file: {0}")] 19 | Io(#[from] io::Error), 20 | #[error("magic_hi != \"[hi]\" in the volume header")] 21 | BadMagic, 22 | #[error("HLE boot for firmware version {0} isn't (currently) supported")] 23 | InvalidVersion(u16), 24 | #[error("Couldn't find valid `osos` image")] 25 | MissingOs, 26 | } 27 | 28 | /// Put the system into a state as though the bootloader in Flash ROM was run. 29 | pub(super) fn run_hle_bootloader( 30 | ipod: &mut Ipod4g, 31 | mut fw_file: impl Read + Seek, 32 | ) -> Result<(), HleBootloaderError> { 33 | if !ipod.devices.flash.is_hle() { 34 | warn!("Running HLE bootloader even though the system is using a real Flash ROM dump!"); 35 | } 36 | 37 | let fw_info = firmware::FirmwareMeta::parse(&mut fw_file)?; 38 | 39 | info!("Parsed firmware meta: {:#x?}", fw_info); 40 | 41 | let os_image = fw_info 42 | .images 43 | .iter() 44 | .find(|img| img.name == *b"osos") 45 | .ok_or(HleBootloaderError::MissingOs)?; 46 | 47 | // extract image from firmware file, and copy it into RAM 48 | fw_file.seek(SeekFrom::Start(os_image.dev_offset as u64 + 0x200))?; 49 | let mut os_image_data = vec![0; os_image.len as usize]; 50 | fw_file.read_exact(&mut os_image_data)?; 51 | 52 | ipod.devices.sdram.bulk_write(0, &os_image_data); 53 | 54 | // set the CPU to start execution from the image entry address 55 | ipod.cpu.reg_set( 56 | ArmMode::User, 57 | reg::PC, 58 | os_image.addr + os_image.entry_offset, 59 | ); 60 | ipod.cpu.reg_set(ArmMode::User, reg::CPSR, 0xd3); // supervisor mode 61 | ipod.cop = ipod.cpu; 62 | 63 | // inject some HLE CPU state 64 | ipod.cpu.reg_set(ArmMode::Irq, reg::SP, 0x40017bfc); 65 | 66 | // inject fake sysinfo_t into fastram. 67 | // TODO: look into how this pointer changes between iPod models 68 | const SYSINFO_PTR: u32 = 0x4001_7f1c; 69 | const SYSINFO_LOC: u32 = 0x4000_ff18; 70 | ipod.devices.w32(SYSINFO_PTR, SYSINFO_LOC).unwrap(); // pointer to sysinfo 71 | ipod.devices.fastram.bulk_write( 72 | SYSINFO_LOC - 0x4000_0000, 73 | // FIXME?: this will break on big-endian systems 74 | bytemuck::bytes_of(&sysinfo_t { 75 | IsyS: u32::from_le_bytes(*b"IsyS"), 76 | len: 0x184, 77 | boardHwSwInterfaceRev: 0x50014, 78 | ..Default::default() 79 | }), 80 | ); 81 | 82 | // The bootloader enables the GPIOA:5 pin (i.e: the Hold button) 83 | ipod.devices 84 | .gpio_abcd 85 | .lock() 86 | .unwrap() 87 | .w32(0x00, 0x20) 88 | .unwrap(); 89 | 90 | Ok(()) 91 | } 92 | -------------------------------------------------------------------------------- /clicky-core/src/sys/ipod4g/hle_bootloader/sysinfo.rs: -------------------------------------------------------------------------------- 1 | /// Copied from ipodloader2 source 2 | #[allow(non_snake_case)] 3 | #[repr(C, packed)] 4 | #[derive(Copy, Clone, Default)] 5 | pub struct sysinfo_t { 6 | pub IsyS: u32, /* == "IsyS" */ 7 | pub len: u32, 8 | pub BoardHwName: [u8; 16], 9 | pub pszSerialNumber: [u8; 32], 10 | pub pu8FirewireGuid: [u8; 16], 11 | pub boardHwRev: u32, 12 | pub bootLoaderImageRev: u32, 13 | pub diskModeImageRev: u32, 14 | pub diagImageRev: u32, 15 | pub osImageRev: u32, 16 | pub iram_perhaps: u32, 17 | pub Flsh: u32, 18 | pub flash_zero: u32, 19 | pub flash_base: u32, 20 | pub flash_size: u32, 21 | pub flash_zero2: u32, 22 | pub Sdrm: u32, 23 | pub sdram_zero: u32, 24 | pub sdram_base: u32, 25 | pub sdram_size: u32, 26 | pub sdram_zero2: u32, 27 | pub Frwr: u32, 28 | pub frwr_zero: u32, 29 | pub frwr_base: u32, 30 | pub frwr_size: u32, 31 | pub frwr_zero2: u32, 32 | pub Iram: u32, 33 | pub iram_zero: u32, 34 | pub iram_base: u32, 35 | pub iram_size: u32, 36 | pub iram_zero2: u32, 37 | pub pad7: [u32; 30], 38 | pub boardHwSwInterfaceRev: u32, 39 | /* added in V3 40 | * pub HddFirmwareRev: [u8; 10], 41 | * pub RegionCode: u16, 42 | * pub PolicyFlags: u32, 43 | * pub ModelNumStr: [u8; 16], */ 44 | } 45 | 46 | // Safety: 47 | // - All of `hd_driveid`'s fields are of type `uX` and/or arrays of `uX`, which 48 | // are Pod types themselves 49 | // - `hd_driveid` is repr(C, packed), ensuring no padding 50 | unsafe impl bytemuck::Zeroable for sysinfo_t {} 51 | unsafe impl bytemuck::Pod for sysinfo_t {} 52 | -------------------------------------------------------------------------------- /clicky-core/src/sys/mod.rs: -------------------------------------------------------------------------------- 1 | //! Concrete system implementations. 2 | 3 | pub mod ipod4g; 4 | 5 | #[allow(dead_code)] 6 | mod size_asserts { 7 | use super::*; 8 | 9 | /// Default Rust wasm stack size 10 | const DEFAULT_WASM_STACK_SIZE: usize = 0x100000; 11 | 12 | /// Arbitrary size limit on systems, just to make sure they don't blow out 13 | /// the stack when being constructed. 14 | const MAX_SYS_SIZE: usize = DEFAULT_WASM_STACK_SIZE / 4; 15 | 16 | const_assert!(std::mem::size_of::() < MAX_SYS_SIZE); 17 | } 18 | -------------------------------------------------------------------------------- /clicky-desktop/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "clicky-desktop" 3 | version = "0.1.0" 4 | authors = ["Daniel Prilik "] 5 | edition = "2018" 6 | 7 | [features] 8 | default = ["minifb"] 9 | 10 | [dependencies] 11 | clicky-core = { path = "../clicky-core/" } 12 | 13 | cfg-if = "0.1" 14 | gdbstub = "0.4" 15 | human-size = "0.4" 16 | log = "0.4" 17 | pretty_env_logger = "0.3" 18 | structopt = "0.3" 19 | 20 | minifb = { version = "0.23", optional = true } 21 | -------------------------------------------------------------------------------- /clicky-desktop/README.md: -------------------------------------------------------------------------------- 1 | # clicky-desktop 2 | 3 | A native CLI + GUI for `clicky`. 4 | 5 | ## Controls 6 | 7 | | iPod | `clicky-desktop` | 8 | | ----------- | ---------------- | 9 | | Menu | Up | 10 | | Reverse | Left | 11 | | Forward | Right | 12 | | Play/Pause | Down | 13 | | Select | Enter | 14 | | Click wheel | Scroll wheel | 15 | | Hold | H | 16 | 17 | ## Building 18 | 19 | Building `clicky-desktop` is quite straightforward, and uses the standard `cargo` build flow: 20 | 21 | ```bash 22 | # from the top-level `clicky/` workspace 23 | cargo build --release -p clicky-desktop 24 | ``` 25 | 26 | **Warning:** Building without `--release` will be very slow! 27 | 28 | #### Common build errors 29 | 30 | - Due to a `cargo` limitation ([rust-lang/cargo#5364]), toggling feature flags within sub-packages of a workspace is a bit clunky. 31 | - At the moment, there's only a single feature (`minifb`), so this shouldn't be a problem. 32 | - (Linux) You may encounter some build-script / linker errors related to missing `xkbcommon` and `wayland` libraries. On Debian/Ubuntu, you can install them via `apt install libxkbcommon-dev libwayland-dev`. 33 | 34 | ## Running 35 | 36 | `clicky-desktop` requires various files to run: 37 | 38 | - A HDD image 39 | - A firmware binary (when using HLE) 40 | - (optional) A Flash ROM dump (when using LLE - see `DEVGUIDE.md`) 41 | 42 | See the top-level `README.md` for details on obtaining / creating these files. 43 | 44 | ### Examples 45 | 46 | - Basic end-user 47 | - HLE bootloader 48 | - `--hdd=file` indicates HDD writes are written directly back to the disk image 49 | 50 | ```bash 51 | cargo run -p clicky-desktop --release -- --hdd=file:file=/path/to/ipodhd.img --hle=/path/to/rockbox_fw.bin 52 | ``` 53 | 54 | - Typical dev-cycle 55 | - Use HLE bootloader alongside a real flash-rom dump 56 | - `--hdd=mem` indicates that the image should be loaded into memory, and _not_ written back to disk. This is useful for ensuring reproducible runs. 57 | - The `RUST_LOG` environment variable is used to tweak log levels for various emulator subsystems. 58 | - Spawn a GDB server if a fatal error occurs. 59 | - Run the GDB server over a Unix Domain Sockets (/tmp/clicky). 60 | - Connect to the server using the GDB command `target remote /tmp/clicky`. 61 | 62 | ```bash 63 | RUST_LOG=MMIO=info,GPIO=trace,gdbstub=error \ 64 | cargo run -p clicky-desktop --release -- \ 65 | --flash-rom=/path/to/internal_rom_000000-0FFFFF.bin \ 66 | --hdd=mem:file=/path/to/ipodhd.img \ 67 | --hle=/path/to/rockbox_bootloader_fw.bin \ 68 | -g /tmp/clicky,on-fatal-err 69 | ``` 70 | -------------------------------------------------------------------------------- /clicky-desktop/src/backends/minifb.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::sync::mpsc as chan; 3 | 4 | use minifb::{Key, Window, WindowOptions}; 5 | 6 | use clicky_core::gui::{ButtonCallback, RenderCallback, ScrollCallback}; 7 | 8 | pub struct MinifbControls { 9 | pub keymap: HashMap, 10 | pub on_scroll: Option, 11 | } 12 | 13 | #[derive(Debug)] 14 | pub struct MinifbRenderer {} 15 | 16 | impl MinifbRenderer { 17 | /// (width, height) crops the framebuffer to the specified screen size 18 | /// (starting from the top-left corner) 19 | pub fn run( 20 | title: &'static str, 21 | (width, height): (usize, usize), 22 | mut update_fb: RenderCallback, 23 | controls: impl Into, 24 | kill_rx: chan::Receiver<()>, 25 | ) { 26 | let mut controls = controls.into(); 27 | 28 | let mut buffer: Vec = vec![0; width * height]; 29 | let mut emu_buffer = Vec::new(); 30 | 31 | let mut window = Window::new( 32 | title, 33 | width, 34 | height, 35 | WindowOptions { 36 | scale: minifb::Scale::X4, 37 | resize: true, 38 | ..WindowOptions::default() 39 | }, 40 | ) 41 | .expect("could not create minifb window"); 42 | 43 | // ~60 fps 44 | window.limit_update_rate(Some(std::time::Duration::from_micros(16600))); 45 | 46 | 'ui_loop: while window.is_open() && kill_rx.try_recv().is_err() { 47 | let keys = window.get_keys_pressed(minifb::KeyRepeat::Yes); 48 | for k in keys { 49 | if k == Key::Escape { 50 | break 'ui_loop; 51 | } 52 | 53 | if let Some(cb) = controls.keymap.get_mut(&k) { 54 | cb(true) 55 | } 56 | } 57 | 58 | let keys = window.get_keys_released(); 59 | for k in keys { 60 | if let Some(cb) = controls.keymap.get_mut(&k) { 61 | cb(false) 62 | } 63 | } 64 | 65 | if let Some(scroll) = window.get_scroll_wheel() { 66 | if let Some(ref mut on_scroll) = controls.on_scroll { 67 | on_scroll(scroll) 68 | } 69 | } 70 | 71 | // update the framebuffer 72 | let (w, _h) = update_fb(&mut emu_buffer); 73 | 74 | // crop the emulated buffer 75 | let new_buf = emu_buffer 76 | .chunks_exact(w) 77 | .take(height) 78 | .flat_map(|row| row.iter().take(width)) 79 | .copied(); 80 | buffer.splice(.., new_buf); 81 | 82 | window 83 | .update_with_buffer(&buffer, width, height) 84 | .expect("could not update minifb window"); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /clicky-desktop/src/backends/mod.rs: -------------------------------------------------------------------------------- 1 | //! GUI implementations. 2 | 3 | #[cfg(feature = "minifb")] 4 | pub mod minifb; 5 | -------------------------------------------------------------------------------- /clicky-desktop/src/blockcfg.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | /// Helper struct to parse Block Device configurations. 4 | pub enum BlockCfg { 5 | /// `null:len=` 6 | Null { len: u64 }, 7 | /// `raw:file=/path/` 8 | Raw { path: String }, 9 | /// `mem:file=/path/[,truncate=]` 10 | Mem { path: String, truncate: Option }, 11 | } 12 | 13 | fn parse_capacity(desc: &str) -> Option { 14 | use human_size::{Byte, ParsingError, Size, SpecificSize}; 15 | match desc.parse::() { 16 | Ok(s) => { 17 | let bytes: SpecificSize = s.into(); 18 | Some(bytes.value() as u64) 19 | } 20 | Err(ParsingError::MissingMultiple) => desc.parse::().ok(), 21 | Err(_) => None, 22 | } 23 | } 24 | 25 | impl FromStr for BlockCfg { 26 | type Err = &'static str; 27 | 28 | fn from_str(s: &str) -> Result { 29 | let mut s = s.splitn(2, ':'); 30 | let kind = s.next().unwrap(); 31 | Ok(match kind { 32 | "null" => { 33 | let s = s.next().ok_or("missing required options")?.split(','); 34 | 35 | let mut len = None; 36 | 37 | for arg in s { 38 | let mut s = arg.split('='); 39 | let kind = s.next().unwrap(); 40 | match kind { 41 | "len" => { 42 | len = Some( 43 | parse_capacity(s.next().ok_or("missing argument for `len`")?) 44 | .ok_or("could not parse `len`")?, 45 | ); 46 | } 47 | _ => return Err("unknown `null` option"), 48 | } 49 | } 50 | 51 | BlockCfg::Null { 52 | len: len.ok_or("missing `len` parameter")?, 53 | } 54 | } 55 | "raw" => { 56 | let s = s.next().ok_or("missing required options")?.split(','); 57 | 58 | let mut file = None; 59 | 60 | for arg in s { 61 | let mut s = arg.split('='); 62 | let kind = s.next().unwrap(); 63 | match kind { 64 | "file" => { 65 | file = Some(s.next().ok_or("missing argument for `file`")?.into()) 66 | } 67 | _ => return Err("unknown `len` option"), 68 | } 69 | } 70 | 71 | BlockCfg::Raw { 72 | path: file.ok_or("missing `file` parameter")?, 73 | } 74 | } 75 | "mem" => { 76 | let s = s.next().ok_or("missing required options")?.split(','); 77 | 78 | let mut file = None; 79 | let mut truncate = None; 80 | 81 | for arg in s { 82 | let mut s = arg.split('='); 83 | let kind = s.next().unwrap(); 84 | match kind { 85 | "file" => { 86 | file = Some(s.next().ok_or("missing argument for `file`")?.into()) 87 | } 88 | "truncate" => { 89 | truncate = Some( 90 | parse_capacity(s.next().ok_or("missing argument for `truncate`")?) 91 | .ok_or("could not parse `truncate`")?, 92 | ) 93 | } 94 | _ => return Err("unknown `len` option"), 95 | } 96 | } 97 | 98 | BlockCfg::Mem { 99 | path: file.ok_or("missing `file` parameter")?, 100 | truncate, 101 | } 102 | } 103 | _ => return Err("invalid block kind"), 104 | }) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /clicky-desktop/src/controls/minifb/ipod4g.rs: -------------------------------------------------------------------------------- 1 | use clicky_core::sys::ipod4g::{Ipod4gBinds, Ipod4gKey}; 2 | use minifb::Key; 3 | 4 | use crate::backends::minifb::MinifbControls; 5 | 6 | fn ipod4g_key_to_minifb(key: Ipod4gKey) -> Key { 7 | match key { 8 | Ipod4gKey::Up => Key::Up, 9 | Ipod4gKey::Down => Key::Down, 10 | Ipod4gKey::Left => Key::Left, 11 | Ipod4gKey::Right => Key::Right, 12 | Ipod4gKey::Action => Key::Enter, 13 | Ipod4gKey::Hold => Key::H, 14 | } 15 | } 16 | 17 | impl From for MinifbControls { 18 | fn from(binds: Ipod4gBinds) -> MinifbControls { 19 | let Ipod4gBinds { keys, wheel } = binds; 20 | 21 | MinifbControls { 22 | keymap: keys 23 | .into_iter() 24 | .map(|(k, v)| (ipod4g_key_to_minifb(k), v)) 25 | .collect(), 26 | on_scroll: wheel, 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /clicky-desktop/src/controls/minifb/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod ipod4g; 2 | -------------------------------------------------------------------------------- /clicky-desktop/src/controls/mod.rs: -------------------------------------------------------------------------------- 1 | //! Glue between system-specific controls and backed-specific controls. 2 | 3 | #[cfg(feature = "minifb")] 4 | pub mod minifb; 5 | -------------------------------------------------------------------------------- /clicky-desktop/src/gdb.rs: -------------------------------------------------------------------------------- 1 | use std::net::{TcpListener, TcpStream}; 2 | use std::path::PathBuf; 3 | 4 | #[cfg(unix)] 5 | use std::os::unix::net::{UnixListener, UnixStream}; 6 | 7 | use crate::DynResult; 8 | 9 | use gdbstub::{Connection, GdbStub}; 10 | 11 | /// GDB server configuration. Typically instantiated via StructOpt. 12 | #[derive(Debug, Clone)] 13 | pub struct GdbCfg { 14 | pub kind: ConnKind, 15 | pub on_start: bool, 16 | pub on_fatal_err: bool, 17 | } 18 | 19 | #[derive(Debug, Clone)] 20 | pub enum ConnKind { 21 | Tcp(u16), 22 | Uds(PathBuf), 23 | } 24 | 25 | impl std::str::FromStr for GdbCfg { 26 | type Err = String; 27 | 28 | fn from_str(s: &str) -> Result { 29 | let mut s = s.split(','); 30 | let kind = s.next().unwrap().parse::()?; 31 | 32 | let on_fatal_err = s.next() == Some("on-fatal-err"); 33 | let on_start = if on_fatal_err { 34 | match s.next() { 35 | Some("and-on-start") => true, 36 | Some(o) => return Err(format!("unknown option `{}`", o)), 37 | None => false, 38 | } 39 | } else { 40 | true 41 | }; 42 | 43 | Ok(GdbCfg { 44 | kind, 45 | on_start, 46 | on_fatal_err, 47 | }) 48 | } 49 | } 50 | 51 | impl std::str::FromStr for ConnKind { 52 | type Err = &'static str; 53 | 54 | fn from_str(s: &str) -> Result { 55 | Ok(match s.parse::() { 56 | Ok(port) => ConnKind::Tcp(port), 57 | Err(_) => ConnKind::Uds(s.into()), 58 | }) 59 | } 60 | } 61 | 62 | fn wait_for_tcp(port: u16) -> std::io::Result { 63 | let sockaddr = format!("127.0.0.1:{}", port); 64 | eprintln!("Waiting for a GDB connection on {:?}...", sockaddr); 65 | 66 | let sock = TcpListener::bind(sockaddr)?; 67 | let (stream, addr) = sock.accept()?; 68 | eprintln!("Debugger connected from {}", addr); 69 | 70 | Ok(stream) 71 | } 72 | 73 | #[cfg(unix)] 74 | fn wait_for_uds(path: PathBuf) -> std::io::Result { 75 | match std::fs::remove_file(&path) { 76 | Ok(_) => {} 77 | Err(e) => match e.kind() { 78 | std::io::ErrorKind::NotFound => {} 79 | _ => return Err(e), 80 | }, 81 | } 82 | 83 | eprintln!("Waiting for a GDB connection on {:?}...", path); 84 | 85 | let sock = UnixListener::bind(path)?; 86 | let (stream, addr) = sock.accept()?; 87 | eprintln!("Debugger connected from {:?}", addr); 88 | 89 | Ok(stream) 90 | } 91 | 92 | pub fn make_gdbstub<'a, T>( 93 | cfg: GdbCfg, 94 | ) -> DynResult>>> 95 | where 96 | T: gdbstub::target::Target, 97 | T::Error: 'a, 98 | { 99 | let connection: Box> = match cfg.kind { 100 | ConnKind::Tcp(port) => Box::new(wait_for_tcp(port)?), 101 | ConnKind::Uds(path) => { 102 | #[cfg(not(unix))] 103 | { 104 | let _ = path; 105 | return Err("Unix Domain Sockets can only be used on Unix".into()); 106 | } 107 | #[cfg(unix)] 108 | { 109 | Box::new(wait_for_uds(path)?) 110 | } 111 | } 112 | }; 113 | 114 | Ok(GdbStub::new(connection)) 115 | } 116 | -------------------------------------------------------------------------------- /clicky-web/.appveyor.yml: -------------------------------------------------------------------------------- 1 | install: 2 | - appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe 3 | - if not defined RUSTFLAGS rustup-init.exe -y --default-host x86_64-pc-windows-msvc --default-toolchain nightly 4 | - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin 5 | - rustc -V 6 | - cargo -V 7 | 8 | build: false 9 | 10 | test_script: 11 | - cargo test --locked 12 | -------------------------------------------------------------------------------- /clicky-web/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | bin/ 5 | pkg/ 6 | wasm-pack.log 7 | -------------------------------------------------------------------------------- /clicky-web/.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | sudo: false 3 | 4 | cache: cargo 5 | 6 | matrix: 7 | include: 8 | 9 | # Builds with wasm-pack. 10 | - rust: beta 11 | env: RUST_BACKTRACE=1 12 | addons: 13 | firefox: latest 14 | chrome: stable 15 | before_script: 16 | - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) 17 | - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate) 18 | - cargo install-update -a 19 | - curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f 20 | script: 21 | - cargo generate --git . --name testing 22 | # Having a broken Cargo.toml (in that it has curlies in fields) anywhere 23 | # in any of our parent dirs is problematic. 24 | - mv Cargo.toml Cargo.toml.tmpl 25 | - cd testing 26 | - wasm-pack build 27 | - wasm-pack test --chrome --firefox --headless 28 | 29 | # Builds on nightly. 30 | - rust: nightly 31 | env: RUST_BACKTRACE=1 32 | before_script: 33 | - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) 34 | - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate) 35 | - cargo install-update -a 36 | - rustup target add wasm32-unknown-unknown 37 | script: 38 | - cargo generate --git . --name testing 39 | - mv Cargo.toml Cargo.toml.tmpl 40 | - cd testing 41 | - cargo check 42 | - cargo check --target wasm32-unknown-unknown 43 | - cargo check --no-default-features 44 | - cargo check --target wasm32-unknown-unknown --no-default-features 45 | - cargo check --no-default-features --features console_error_panic_hook 46 | - cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook 47 | - cargo check --no-default-features --features "console_error_panic_hook wee_alloc" 48 | - cargo check --target wasm32-unknown-unknown --no-default-features --features "console_error_panic_hook wee_alloc" 49 | 50 | # Builds on beta. 51 | - rust: beta 52 | env: RUST_BACKTRACE=1 53 | before_script: 54 | - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) 55 | - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate) 56 | - cargo install-update -a 57 | - rustup target add wasm32-unknown-unknown 58 | script: 59 | - cargo generate --git . --name testing 60 | - mv Cargo.toml Cargo.toml.tmpl 61 | - cd testing 62 | - cargo check 63 | - cargo check --target wasm32-unknown-unknown 64 | - cargo check --no-default-features 65 | - cargo check --target wasm32-unknown-unknown --no-default-features 66 | - cargo check --no-default-features --features console_error_panic_hook 67 | - cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook 68 | # Note: no enabling the `wee_alloc` feature here because it requires 69 | # nightly for now. 70 | -------------------------------------------------------------------------------- /clicky-web/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "clicky-web" 3 | version = "0.1.0" 4 | authors = ["Daniel Prilik "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [dependencies] 11 | clicky-core = { path = "../clicky-core/", features = ["wasm-bindgen"] } 12 | 13 | console_error_panic_hook = "0.1" 14 | console_log = { version ="0.2", features = ["color"] } 15 | fern = "0.5" 16 | flate2 = "1.0" 17 | log = "0.4" 18 | wasm-bindgen = "0.2.63" 19 | -------------------------------------------------------------------------------- /clicky-web/README.md: -------------------------------------------------------------------------------- 1 | # clicky-web 2 | 3 | Run `clicky` on the web, using the power of WebAssembly! 4 | 5 | **WARNING:** this port is _incredibly WIP!_ 6 | 7 | ## Port Status 8 | 9 | `clicky-web` is working, but runs _very slowly_. My guess is that `clicky-core` needs to be properly profiled + optimized. 10 | 11 | Also, the HTML5/CSS/JavaScript is absolute spaghetti, and the Rust bindings aren't super clean either... They get the job done, but really aught to be rewritten [in Typescript]. 12 | 13 | ## Controls 14 | 15 | | iPod | Keyboard | Mouse | UI Element status | 16 | | ----------- | -------- | ------------ | ----------------- | 17 | | Menu | Up | | 0% | 18 | | Reverse | Left | | 0% | 19 | | Forward | Right | | 0% | 20 | | Play/Pause | Down | | 0% | 21 | | Select | Enter | | 100% | 22 | | Click wheel | | Scroll wheel | 25%\* | 23 | | Hold | H | | 0% | 24 | 25 | \* only works on desktop (no touch support). Pretty glitchy. 26 | 27 | ## Dependencies 28 | 29 | See https://rustwasm.github.io/book/game-of-life/setup.html for a list of programs and utilities to install. 30 | 31 | Additionally, you'll need to copy a valid firmware and disk image to `clicky-web/www/resources/`, and `gzip` them. See the top-level `README.md` for details on building firmware / disk images. 32 | 33 | ## Building 34 | 35 | Navigate to `clicky-web/` in your terminal, and run: 36 | 37 | ```bash 38 | cargo watch -i .gitignore -i "pkg/*" -i "www/*" -i "../src/*" -s "wasm-pack build --release" 39 | ``` 40 | 41 | Navigate to `clicky-web/www/` in another terminal, and run: 42 | 43 | ```bash 44 | npm install # just once 45 | npm run start 46 | ``` 47 | 48 | Assuming everything went well, you should be able to access `click-web` at `localhost:8080`. 49 | Open the Developer Console to see a whole bunch of debug logs :) 50 | -------------------------------------------------------------------------------- /clicky-web/www/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | resources/*.gz 4 | -------------------------------------------------------------------------------- /clicky-web/www/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: "10" 3 | 4 | script: 5 | - ./node_modules/.bin/webpack 6 | -------------------------------------------------------------------------------- /clicky-web/www/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

create-wasm-app

4 | 5 | An npm init template for kick starting a project that uses NPM packages containing Rust-generated WebAssembly and bundles them with Webpack. 6 | 7 |

8 | Build Status 9 |

10 | 11 |

12 | Usage 13 | | 14 | Chat 15 |

16 | 17 | Built with 🦀🕸 by The Rust and WebAssembly Working Group 18 |
19 | 20 | ## About 21 | 22 | This template is designed for depending on NPM packages that contain 23 | Rust-generated WebAssembly and using them to create a Website. 24 | 25 | * Want to create an NPM package with Rust and WebAssembly? [Check out 26 | `wasm-pack-template`.](https://github.com/rustwasm/wasm-pack-template) 27 | * Want to make a monorepo-style Website without publishing to NPM? Check out 28 | [`rust-webpack-template`](https://github.com/rustwasm/rust-webpack-template) 29 | and/or 30 | [`rust-parcel-template`](https://github.com/rustwasm/rust-parcel-template). 31 | 32 | ## 🚴 Usage 33 | 34 | ``` 35 | npm init wasm-app 36 | ``` 37 | 38 | ## 🔋 Batteries Included 39 | 40 | - `.gitignore`: ignores `node_modules` 41 | - `LICENSE-APACHE` and `LICENSE-MIT`: most Rust projects are licensed this way, so these are included for you 42 | - `README.md`: the file you are reading now! 43 | - `index.html`: a bare bones html document that includes the webpack bundle 44 | - `index.js`: example js file with a comment showing how to import and use a wasm pkg 45 | - `package.json` and `package-lock.json`: 46 | - pulls in devDependencies for using webpack: 47 | - [`webpack`](https://www.npmjs.com/package/webpack) 48 | - [`webpack-cli`](https://www.npmjs.com/package/webpack-cli) 49 | - [`webpack-dev-server`](https://www.npmjs.com/package/webpack-dev-server) 50 | - defines a `start` script to run `webpack-dev-server` 51 | - `webpack.config.js`: configuration file for bundling your js with webpack 52 | 53 | ## License 54 | 55 | Licensed under either of 56 | 57 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 58 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 59 | 60 | at your option. 61 | 62 | ### Contribution 63 | 64 | Unless you explicitly state otherwise, any contribution intentionally 65 | submitted for inclusion in the work by you, as defined in the Apache-2.0 66 | license, shall be dual licensed as above, without any additional terms or 67 | conditions. 68 | -------------------------------------------------------------------------------- /clicky-web/www/bootstrap.js: -------------------------------------------------------------------------------- 1 | // A dependency graph that contains any wasm must all be imported 2 | // asynchronously. This `bootstrap.js` file does the single async import, so 3 | // that no one else needs to worry about it again. 4 | import("./index.js") 5 | .catch(e => console.error("Error importing `index.js`:", e)); 6 | -------------------------------------------------------------------------------- /clicky-web/www/clicky.worker.js: -------------------------------------------------------------------------------- 1 | // lol this is trash 2 | let wasm = null; 3 | import("clicky-web").then((x) => { 4 | wasm = x; 5 | // kick off the state machine 6 | postMessage({ kind: "ready" }); 7 | }); 8 | 9 | let ipod4g = null; 10 | let ipod4g_controls = null; 11 | let cycles_per_tick = 1024; // adjust for responsiveness 12 | 13 | function init_handler({ kind, data }) { 14 | switch (kind) { 15 | case "init": 16 | ipod4g = new wasm.Ipod4gContainer(data.bootloader, data.disk); 17 | ipod4g_controls = ipod4g.take_controls(); 18 | console.log(ipod4g); 19 | console.log(ipod4g_controls); 20 | postMessage({ kind: "init" }); 21 | return true; 22 | break; 23 | default: 24 | console.error("unknown init message sent to webworker: ", { 25 | kind, 26 | data, 27 | }); 28 | postMessage({ kind: "unknown" }); 29 | break; 30 | } 31 | 32 | return false; 33 | } 34 | 35 | let framebuffer = null; 36 | 37 | function send_frame() { 38 | const frame = ipod4g.get_frame(); 39 | const { width, height } = frame; 40 | if (!framebuffer || framebuffer.length !== width * height * 4) { 41 | framebuffer = new Uint8Array(width * height * 4); 42 | } 43 | frame.get_data(framebuffer); 44 | 45 | const data = { 46 | width, 47 | height, 48 | data: framebuffer, 49 | }; 50 | postMessage({ 51 | kind: "frame", 52 | data, 53 | }); 54 | } 55 | 56 | function run_handler({ kind, data }) { 57 | switch (kind) { 58 | case "frame": 59 | send_frame(); 60 | break; 61 | case "keydown": 62 | console.log("pressed ", data); 63 | switch (data) { 64 | case "ArrowDown": 65 | ipod4g_controls.on_keydown(wasm.Ipod4gKeyKind.Down); 66 | break; 67 | case "ArrowUp": 68 | ipod4g_controls.on_keydown(wasm.Ipod4gKeyKind.Up); 69 | break; 70 | case "ArrowLeft": 71 | ipod4g_controls.on_keydown(wasm.Ipod4gKeyKind.Left); 72 | break; 73 | case "ArrowRight": 74 | ipod4g_controls.on_keydown(wasm.Ipod4gKeyKind.Right); 75 | break; 76 | case "Enter": 77 | ipod4g_controls.on_keydown(wasm.Ipod4gKeyKind.Action); 78 | break; 79 | case "H": 80 | ipod4g_controls.on_keydown(wasm.Ipod4gKeyKind.Hold); 81 | break; 82 | } 83 | break; 84 | case "keyup": 85 | console.log("released ", data); 86 | switch (data) { 87 | case "ArrowDown": 88 | ipod4g_controls.on_keyup(wasm.Ipod4gKeyKind.Down); 89 | break; 90 | case "ArrowUp": 91 | ipod4g_controls.on_keyup(wasm.Ipod4gKeyKind.Up); 92 | break; 93 | case "ArrowLeft": 94 | ipod4g_controls.on_keyup(wasm.Ipod4gKeyKind.Left); 95 | break; 96 | case "ArrowRight": 97 | ipod4g_controls.on_keyup(wasm.Ipod4gKeyKind.Right); 98 | break; 99 | case "Enter": 100 | ipod4g_controls.on_keyup(wasm.Ipod4gKeyKind.Action); 101 | break; 102 | case "H": 103 | ipod4g_controls.on_keyup(wasm.Ipod4gKeyKind.Hold); 104 | break; 105 | } 106 | break; 107 | case "scroll": 108 | if (data.deltaY < 0) { 109 | console.log("scolled up"); 110 | ipod4g_controls.on_scroll(0, 2); 111 | } else { 112 | console.log("scolled down"); 113 | ipod4g_controls.on_scroll(0, -2); 114 | } 115 | break; 116 | case "cycles_per_tick": 117 | cycles_per_tick = data; 118 | break; 119 | case "drive": 120 | ipod4g.run(cycles_per_tick); // tweak for different responsiveness 121 | postMessage({ kind: "drive" }); 122 | break; 123 | default: 124 | console.error("unknown message sent to webworker", { 125 | kind, 126 | data, 127 | }); 128 | postMessage({ kind: "unknown" }); 129 | break; 130 | } 131 | 132 | return false; 133 | } 134 | 135 | const STATE_INIT = "INIT"; 136 | const STATE_RUNNING = "RUNNING"; 137 | 138 | let state = STATE_INIT; 139 | 140 | onmessage = function (e) { 141 | if (wasm === null) { 142 | console.error( 143 | "wasm hasn't been loaded yet, why did you send me a message!", 144 | ); 145 | } 146 | if (!e.data.kind || !e.data.data) { 147 | console.error("invalid format."); 148 | return; 149 | } 150 | 151 | switch (state) { 152 | case STATE_INIT: 153 | if (init_handler(e.data)) { 154 | state = STATE_RUNNING; 155 | } 156 | break; 157 | case STATE_RUNNING: 158 | { 159 | run_handler(e.data); 160 | } 161 | break; 162 | } 163 | }; 164 | -------------------------------------------------------------------------------- /clicky-web/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | clicky-web 6 | 7 | 8 |
9 |

clicky

10 | 14 |

15 | This port is incredibly WIP!
At the moment, the controls are the 16 | same as the desktop version.
See the clicky README.md for details. 17 |

18 |

CPU cycles per tick:

19 |

FPS: 0

20 |
21 |
22 | 23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /clicky-web/www/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #efefef; 3 | font-family: sans-serif; 4 | } 5 | 6 | #container { 7 | margin: auto; 8 | width: 80%; 9 | } 10 | 11 | h1 { 12 | margin: 16px 0; 13 | text-align: center; 14 | } 15 | 16 | p { 17 | margin: 16px 0; 18 | text-align: center; 19 | } 20 | 21 | #ipod-container { 22 | zoom: 200%; 23 | } 24 | 25 | #ipod-container:focus { 26 | outline: none; 27 | } 28 | 29 | #ipod-body { 30 | background-color: white; 31 | border-radius: 20px; 32 | height: 400px; 33 | margin: auto; 34 | padding-top: 32px; 35 | width: 250px; 36 | } 37 | 38 | #ipod-screen { 39 | border-radius: 6px; 40 | border: 1px solid #efefef; 41 | display: block; 42 | image-rendering: pixelated; 43 | margin-top: 10px; 44 | margin: auto; 45 | position: relative; 46 | } 47 | 48 | #ipod-clickwheel { 49 | display: flex; 50 | background-color: #dddddd; 51 | width: 160px; 52 | height: 160px; 53 | margin: auto; 54 | margin-top: 32px; 55 | border-radius: 100%; 56 | box-shadow: inset #9c9c9c 0 0 2px 0px; 57 | } 58 | 59 | #ipod-btn-select { 60 | background-color: white; 61 | width: 64px; 62 | height: 64px; 63 | border-radius: 100%; 64 | margin: auto; 65 | box-shadow: #9c9c9c 0 0 2px 0px; 66 | } 67 | -------------------------------------------------------------------------------- /clicky-web/www/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clicky-web", 3 | "version": "0.1.0", 4 | "description": "A clickwheel iPod emulator", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "webpack --config webpack.config.js", 8 | "start": "webpack-dev-server" 9 | }, 10 | "author": "Daniel Prilik ", 11 | "license": "UNLICENSED", 12 | "private": true, 13 | "dependencies": { 14 | "clicky-web": "file:../pkg" 15 | }, 16 | "devDependencies": { 17 | "copy-webpack-plugin": "^5.0.0", 18 | "css-loader": "^4.2.0", 19 | "modern-css-reset": "^1.1.1", 20 | "style-loader": "^1.2.1", 21 | "webpack": "^4.29.3", 22 | "webpack-cli": "^3.1.0", 23 | "webpack-dev-server": "^3.1.5", 24 | "worker-loader": "^3.0.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /clicky-web/www/resources/README.md: -------------------------------------------------------------------------------- 1 | This folder should contain any firmware / disk images that clicky requires, compressed via `gzip`. 2 | 3 | Check `index.js` for the file names that are required. 4 | -------------------------------------------------------------------------------- /clicky-web/www/webpack.config.js: -------------------------------------------------------------------------------- 1 | const CopyWebpackPlugin = require("copy-webpack-plugin"); 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | entry: "./bootstrap.js", 6 | output: { 7 | path: path.resolve(__dirname, "dist"), 8 | filename: "bootstrap.js", 9 | }, 10 | mode: "development", 11 | plugins: [ 12 | new CopyWebpackPlugin(['index.html', 'resources/*']) 13 | ], 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.css$/i, 18 | use: ['style-loader', 'css-loader'], 19 | }, 20 | { 21 | test: /\.worker\.js$/, 22 | use: { loader: 'worker-loader' }, 23 | }, 24 | ], 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /docs/REPO_LAYOUT.md: -------------------------------------------------------------------------------- 1 | # Repo Layout 2 | 3 | This document describes the general layout of the `clicky` project. 4 | 5 | ## Crates 6 | 7 | At the top level, `clicky` is split up into multiple crates: 8 | 9 | | crate | type | | 10 | | ---------------- | ---- | -------------------------------------------------------------------------------- | 11 | | `clicky-core` | lib | Platform agnostic emulator code. | 12 | | `clicky-desktop` | bin | A native CLI + GUI to interact with `clicky-core`. | 13 | | `clicky-web` | bin | Run `clicky` on the web using the power of `wasm`! (_very_ WIP) | 14 | | `relativity` | lib | Cross-platform timers and `Instant` which can be paused/resumed/shifted in time. | 15 | 16 | These crates live in a single [`cargo` workspace](https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html), defined in the top-level `Cargo.toml`. 17 | 18 | ### `clicky-desktop` and `clicky-web` 19 | 20 | At the moment, `clicky` comes with two different frontends: `clicky-desktop` and `clicky-web`. As the names imply, these crates implement the platform-specific code required to run the GUI, load/save files, set up a GDB server, etc... for native clients, and web clients. 21 | 22 | These crates are primarily comprised of easy-to-follow "glue" code, which simply connects the interfaces exposed by `clicky-core` to the outside world. 23 | 24 | See the `README.md` files in the `clicky-desktop` and `clicky-web` directories for more details on how to build and work with the various frontends. 25 | 26 | ### `clicky-core` 27 | 28 | This crate contains the core, platform-agnostic emulation code. It doesn't perform any I/O itself, and must be plugged into a frontend (such as `clicky-desktop`) to function. 29 | 30 | ### `relativity` 31 | 32 | `relativity` is a library which provides cross-platform (read: native + wasm) timers and `Instant`s which can be paused/resumed/shifted in time. See it's `README.md` for more details. 33 | 34 | It doesn't depend on any code from any of the `click-X` crates, and could theoretically be split off into it's own repo entirely. 35 | 36 | ## Documentation + Resources 37 | 38 | Aside from code, the `clicky` repo includes various bits of documentation and resources to aid in development. 39 | 40 | ### `docs` 41 | 42 | Hey, that's where this file lives! 43 | 44 | As the name implies, this folder is where all `clicky`-specific documentation is collected. Things like how to compile/build `clicky`, how to build some basic software, `clicky`'s project structure and architecture, etc... 45 | 46 | ### `resources` 47 | 48 | This folder contains various tidbits of iPod-related documentation and homebrew software which are useful references when working on / testing `clicky`. 49 | 50 | If you stumble across any resources you think might be helpful to preserve "in-tree", feel free to add them here! Just make sure they can be legally distributed! 51 | 52 | ### `scripts` 53 | 54 | This folder is a grab-bag of scripts related to `clicky` that automate certain aspects of development. 55 | 56 | At the time of writing, these scripts are primarily used to create / format / update raw disk image files. 57 | 58 | ### `screenshots` 59 | 60 | Contains screenshots of iPod software referenced in the `README.md`. 61 | -------------------------------------------------------------------------------- /relativity/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "relativity" 3 | version = "0.1.0" 4 | authors = ["Daniel Prilik "] 5 | edition = "2018" 6 | 7 | [features] 8 | wasm-bindgen = [ "instant/wasm-bindgen" ] 9 | 10 | [dependencies] 11 | cfg-if = "0.1" 12 | instant = "0.1" 13 | 14 | # async-timer _should_ work with wasm32, but it doesn't seem to be working... 15 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 16 | async-timer = "1.0.0-beta.5" 17 | 18 | [target.'cfg(target_arch = "wasm32")'.dependencies] 19 | gloo-timers = { version = "0.2", features = ["futures"] } 20 | -------------------------------------------------------------------------------- /relativity/README.md: -------------------------------------------------------------------------------- 1 | # relativity 2 | 3 | Cross-platform timers and `Instant` which can be paused/resumed/shifted in time. 4 | 5 | **NOTE** At the moment, this crate only serves as a place to re-export platform agnostic code. The actual "relativity" parts aren't implemented yet haha. 6 | -------------------------------------------------------------------------------- /relativity/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use instant::Instant; 2 | 3 | cfg_if::cfg_if! { 4 | if #[cfg(target_arch = "wasm32")] { 5 | pub type Timeout = wasm::WasmTimer; 6 | } else { 7 | pub type Timeout = default::Timer; 8 | } 9 | } 10 | 11 | #[cfg(target_arch = "wasm32")] 12 | mod wasm { 13 | use std::future::Future; 14 | use std::pin::Pin; 15 | use std::task::{Context, Poll}; 16 | use std::time::Duration; 17 | 18 | use gloo_timers::future::TimeoutFuture; 19 | 20 | pub struct WasmTimer(TimeoutFuture); 21 | 22 | impl WasmTimer { 23 | pub fn new(timeout: Duration) -> WasmTimer { 24 | WasmTimer(TimeoutFuture::new(timeout.as_millis() as u32)) 25 | } 26 | } 27 | 28 | impl Future for WasmTimer { 29 | type Output = (); 30 | 31 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { 32 | Pin::new(&mut self.0).poll(cx) 33 | } 34 | } 35 | 36 | // XXX: clicky requires Send futures. this is an "okay" hack since wasm is 37 | // single-threaded (for now), but should really be fixed... 38 | unsafe impl Send for WasmTimer {} 39 | } 40 | 41 | #[cfg(not(target_arch = "wasm32"))] 42 | mod default { 43 | use std::future::Future; 44 | use std::pin::Pin; 45 | use std::task::{Context, Poll}; 46 | use std::time::Duration; 47 | 48 | use async_timer::timer::Platform; 49 | 50 | pub struct Timer(Platform); 51 | 52 | impl Timer { 53 | pub fn new(timeout: Duration) -> Timer { 54 | Timer(Platform::new(timeout)) 55 | } 56 | } 57 | 58 | impl Future for Timer { 59 | type Output = (); 60 | 61 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { 62 | Pin::new(&mut self.0).poll(cx) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /resources/.gitignore: -------------------------------------------------------------------------------- 1 | internal_rom_000000-0FFFFF.bin -------------------------------------------------------------------------------- /resources/NOTES.md: -------------------------------------------------------------------------------- 1 | # Notes 2 | 3 | A grab-bag of facts I collect / thoughts I have while working on clicky. 4 | 5 | --- 6 | 7 | Sep 15, 2019 8 | 9 | As it turns out, calling the iPodLinux / Rockbox bootloaders "bootloaders" is a little bit misleading, as they aren't actually the code which runs immediately after power-on! 10 | 11 | As it turns out, execution starts in Flash memory (mapped from 0x0), and the first real code that gets run is the stock Apple bootloader. It's _that_ code which handles reading the firmware _from Disk_, parsing it's contents, and loading an image into memory. 12 | 13 | As far as I can tell, iPodLinux / Rockbox "cheat" and don't overwrite the stock Apple bootloader. Instead, they craft firmware images where the OS image actually points to a custom second-stage bootloader. Thus, once the stock loader dutifully loads what it thinks is the OS, the second-stage bootloader runs, and loads the _actual_ OS. 14 | 15 | Alright, no problem, right? Let's just slap a copy of an iPod's flash ROM at 0x0, run the CPU, and let it run it's course, easy! 16 | 17 | Alas, it's not that easy, and as far as I can tell, no one ever dumped an iPod's flash memory directly. 18 | 19 | So, what's the plan of attack then? 20 | 21 | I'll simulate what I _think_ the stock apple bootloader does directly in my emulator's code (i.e: parse the firmware file according to [it's spec](http://www.ipodlinux.org/Firmware.html), load the image I want into memory, and start the CPU from there directly). 22 | 23 | This ain't _great_, since I'll have to make some assumptions about the system's state (since it won't be a true "cold-start"), and hope that the stock bootloader didn't poke / init _too_ much hardware... 24 | 25 | ... now, here's the sneaky bit: 26 | 27 | Instead of loading the OS image (with the second-stage bootloader), what if I instead loaded an "aupd" image from a legit apple firmware image? That code _should_ then update the flash ROM with a new bootloader image, whereupon I could dump the contents of my emulator's memory, and then do a cold-start with the extracted code! 28 | 29 | This would be awesome, but likely be quite difficult to pull off properly, as I would have to reverse-engineer how the Flash ROM is written to, and implement the actual Flash ROM hardware to get it working correctly. 30 | 31 | As such, I'll likely begin by using my high-level bootloader to load the second-stage bootloader, just to get things going. Once I feel more comfortable with this whole endeavor, I'll revisit this idea... 32 | 33 | --- 34 | 35 | Sep 21, 2019 36 | 37 | Oh shit would you look at that. 38 | 39 | https://www.rockbox.org/wiki/IpodFlash#Apple_39s_flash_code 40 | 41 | Looks like the rockbox devs also wanted to pull the contents of flash ROM from iPods, and wrote a utlity to do so. 42 | 43 | That's great, but unfortunately for me, _I don't have an iPod to rip the flash from!_. Guess I'll just have to keep chugging along with the HLE bootloader approach... 44 | 45 | --- 46 | 47 | Oct 6, 2019 48 | 49 | Guess who finally got an iPod 4g and managed to rip the flash ROM from it??? THIS GUY! 50 | 51 | It's definately nice to have, though now that I have it, I realize that it still makes sense to HLE boot (bypassing the flash ROM bootloader), as I _really_ don't want to start working on HDD emulation. On the bright side, no more guessing the contents of flash ROM on a byte-by-byte basis! 52 | -------------------------------------------------------------------------------- /resources/documentation/LINKS.md: -------------------------------------------------------------------------------- 1 | Most of the documentation in this folder can be found online. It is cloned here for convenience, and as a precaution (if any websites go down). 2 | 3 | Check out these links for more. 4 | 5 | - http://www.ipodlinux.org/ 6 | - https://github.com/Rockbox/rockbox 7 | 8 | - https://github.com/iPodLinux/ipodloader 9 | - https://github.com/iPodLinux/ipodloader2 10 | 11 | - https://www.rockbox.org/wiki/IpodPatcher 12 | - https://www.rockbox.org/wiki/IpodPort 13 | - https://www.rockbox.org/wiki/IpodFlash 14 | -------------------------------------------------------------------------------- /resources/documentation/Linux4NanoReport.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel5151/clicky/c380a7a00148b1ea9ea62da4e30f9282d7b93913/resources/documentation/Linux4NanoReport.pdf -------------------------------------------------------------------------------- /resources/documentation/component_spec_sheets/PCF50606_Philips.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel5151/clicky/c380a7a00148b1ea9ea62da4e30f9282d7b93913/resources/documentation/component_spec_sheets/PCF50606_Philips.pdf -------------------------------------------------------------------------------- /resources/documentation/component_spec_sheets/ipod_4g_display_ehd66753.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel5151/clicky/c380a7a00148b1ea9ea62da4e30f9282d7b93913/resources/documentation/component_spec_sheets/ipod_4g_display_ehd66753.pdf -------------------------------------------------------------------------------- /resources/documentation/component_spec_sheets/ipod_5g_video_BCM2722_brief.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel5151/clicky/c380a7a00148b1ea9ea62da4e30f9282d7b93913/resources/documentation/component_spec_sheets/ipod_5g_video_BCM2722_brief.pdf -------------------------------------------------------------------------------- /resources/documentation/d0948r4c-ATA-2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel5151/clicky/c380a7a00148b1ea9ea62da4e30f9282d7b93913/resources/documentation/d0948r4c-ATA-2.pdf -------------------------------------------------------------------------------- /resources/documentation/iPodLinux/PP5002.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel5151/clicky/c380a7a00148b1ea9ea62da4e30f9282d7b93913/resources/documentation/iPodLinux/PP5002.pdf -------------------------------------------------------------------------------- /resources/documentation/iPodLinux/PP5020.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel5151/clicky/c380a7a00148b1ea9ea62da4e30f9282d7b93913/resources/documentation/iPodLinux/PP5020.pdf -------------------------------------------------------------------------------- /resources/documentation/iPodLinux/firmware_and_boot.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel5151/clicky/c380a7a00148b1ea9ea62da4e30f9282d7b93913/resources/documentation/iPodLinux/firmware_and_boot.pdf -------------------------------------------------------------------------------- /resources/documentation/iPodLinux/flash_decryption.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel5151/clicky/c380a7a00148b1ea9ea62da4e30f9282d7b93913/resources/documentation/iPodLinux/flash_decryption.pdf -------------------------------------------------------------------------------- /resources/documentation/iPodLinux/ipod_games.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel5151/clicky/c380a7a00148b1ea9ea62da4e30f9282d7b93913/resources/documentation/iPodLinux/ipod_games.pdf -------------------------------------------------------------------------------- /resources/documentation/iPodLinux/memory_controller.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel5151/clicky/c380a7a00148b1ea9ea62da4e30f9282d7b93913/resources/documentation/iPodLinux/memory_controller.pdf -------------------------------------------------------------------------------- /resources/flashutils/.gitignore: -------------------------------------------------------------------------------- 1 | flashsplit 2 | diagmode.* 3 | diskmode.* 4 | ipodloader_diagmode.bin 5 | -------------------------------------------------------------------------------- /resources/flashutils/ipodloader_diagmode.sh: -------------------------------------------------------------------------------- 1 | ../ipodloader/make_fw -v -g 4g -o ipodloader_diagmode.bin -l diagmode.bin ../ipodloader/loader.bin 2 | -------------------------------------------------------------------------------- /resources/ipodloader/.gdbinit: -------------------------------------------------------------------------------- 1 | file loader 2 | target remote localhost:9001 3 | -------------------------------------------------------------------------------- /resources/ipodloader/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | loader 3 | loader.bin 4 | loader.objdump 5 | make_fw 6 | 7 | *.bin 8 | -------------------------------------------------------------------------------- /resources/ipodloader/ChangeLog: -------------------------------------------------------------------------------- 1 | 2 | Mon Nov 22 12:53:21 CET 2004 3 | - Make sure the COP is asleep before we try & wake it up 4 | 5 | Wed May 12 21:57:59 CEST 2004 6 | - Added windows support patch from Phyntos 7 | 8 | Sat May 8 21:02:09 CEST 2004 9 | - use new tux.c (our logo) thanks mikey 10 | 11 | ipodloader-0.3.1, Wed Nov 19 12:58:42 UTC 2003 12 | 13 | - big-endian fix for the extract code 14 | 15 | ipodloader-0.3 16 | 17 | - first official release 18 | - code relocates itself to SRAM 19 | - cop handling fixed 20 | - C code no longer needs to be PIC 21 | - tux added 22 | 23 | ipodloader-0.1 24 | 25 | - boot table moved into the loader 26 | - loads from 0x4400 to avoid memmove'ing the first image 27 | - optimized memmove 28 | - patch_fw replaced with make_fw, generating everything except boot sector 29 | - supports up to 5 images 30 | 31 | ipodloader-0.0 32 | 33 | - first brute-force working code 34 | - two images supported. 35 | 36 | -------------------------------------------------------------------------------- /resources/ipodloader/Makefile: -------------------------------------------------------------------------------- 1 | #! /usr/bin/make -f 2 | 3 | CROSS =arm-none-eabi- 4 | 5 | AS =$(CROSS)as 6 | CC =$(CROSS)gcc 7 | OBJDUMP =$(CROSS)objdump 8 | CFLAGS =-Wall -O0 -ffreestanding -fomit-frame-pointer -march=armv4t -g 9 | LDFLAGS =-Wl,-Tarm_elf_40.x -nostartfiles 10 | OBJCOPY =$(CROSS)objcopy 11 | HOSTCC =gcc 12 | HOSTCFLAGS =-Wall -O2 13 | 14 | default: make_fw loader.bin 15 | 16 | %.s: %.c 17 | $(CC) $(CFLAGS) -S $< 18 | 19 | %.o: %.S %.c 20 | 21 | loader: startup.o loader.o tools.o tux.o happymac.o 22 | $(CC) $(LDFLAGS) -o $@ startup.o loader.o tools.o tux.o happymac.o 23 | 24 | loader.objdump: loader 25 | $(OBJDUMP) -ld --prefix-addresses loader > loader.objdump 26 | 27 | loader.bin: loader loader.objdump 28 | $(OBJCOPY) -O binary loader loader.bin 29 | 30 | make_fw: make_fw.c 31 | $(HOSTCC) $(HOSTCFLAGS) -o make_fw make_fw.c 32 | 33 | bin_dist: make_fw loader.bin 34 | mkdir ipodloader-$(VER) 35 | cp README make_fw loader.bin ipodloader-$(VER) 36 | tar zcf ../ipodloader-$(VER).tar.gz ipodloader-$(VER) 37 | rm -rf ipodloader-$(VER) 38 | 39 | src_dist: 40 | mkdir ipodloader-src-$(VER) 41 | cp README Makefile arm_elf_40.x *.[sch] ipodloader-src-$(VER) 42 | tar zcf ../ipodloader-src-$(VER).tar.gz ipodloader-src-$(VER) 43 | rm -rf ipodloader-src-$(VER) 44 | 45 | .PHONY: clean 46 | clean: 47 | rm -f make_fw loader.bin loader startup.o loader.o make_fw.o \ 48 | tools.o tux.o happymac.o 49 | 50 | # vim:ts=8:sts=8:sw=8: 51 | -------------------------------------------------------------------------------- /resources/ipodloader/README: -------------------------------------------------------------------------------- 1 | iPodLinux loader v0.3.2 2 | ----------------------- 3 | 4 | This is a quick and dirty bootloader hack for ipodlinux. It makes it 5 | possible to load either the original firmware or linux without having to 6 | write a new firmware to the harddisk directly. 7 | 8 | +---------+ 9 | | WARNING | 10 | +---------+ 11 | 12 | This is experimental software, so it might eat your iPod. I take no 13 | responsibility whatever happens. But it works for me on a 40GB iPod with 14 | firmware 2.1. Others have tested previous versions of this code on g1/g2 15 | hardware with firmware revisions 1.2x and 1.3. 16 | 17 | The only code running on the host (make_fw) is tested to be 64bit and 18 | endian clean. 19 | 20 | I haven't tested the ipod updater with the modified firmware, neither you 21 | would want it. Simply restore the original firmware before updating. 22 | 23 | Quick HOWTO: 24 | ------------ 25 | 26 | I assume you've already set up firewire with linux, and you know how to make 27 | your linux box see the ipod as a disk. I'll assume your ipod's called 28 | /dev/sda. Also you should have both a native and an arm-elf cross toolchain. 29 | 30 | 0. Build an iPodlinux kernel. I'll assume you have a linux.bin according to 31 | Bernard's docs. 32 | 33 | 1. Build the loader and make_fw 34 | 35 | $ make 36 | 37 | or if your crosscompiler, etc is not called arm-elf-x, but arm-linux-x 38 | for example (this still has to be an arm-elf cross toolchain!): 39 | 40 | $ make CROSS=arm-linux- 41 | 42 | 2. Dump the original firmware and take it away to a safe place: 43 | 44 | $ sudo dd if=/dev/sda1 > firmware_backup.bin 45 | 46 | 3. Extract the Apple firmware 47 | 48 | $ ./make_fw -o apple_sw.bin -e 0 firmware_backup.bin 49 | 50 | 4. Build your new firmware 51 | 52 | To make the Apple firmware the default, and occasionally play with linux, 53 | try this: 54 | 55 | $ ./make_fw -o my_sw.bin -i apple_sw.bin -l linux.bin loader.bin 56 | 57 | To make linux the default: 58 | 59 | $ ./make_fw -o my_sw.bin -l linux.bin -i apple_sw.bin loader.bin 60 | 61 | You can have up to 5 images in any order. 62 | To have Apple the default, and try several different linux images: 63 | 64 | $ ./make_fw -o my_sw.bin -i apple_sw.bin -l lnx1.bin -l lnx2.bin loader.bin 65 | 66 | You can specify the revision with -r rev, (for example 210 for v2.10), I 67 | don't know if this is needed. 68 | 69 | 5. Copy it back to the iPod. The file will be only the required size. This 70 | is about 4.5 MB for the 2.1 Apple sw and one linux image. 71 | 72 | $ sudo dd if=my_sw.bin of=/dev/sda1 73 | 74 | Disconnect your iPod. If it was running the Apple firmware, it should 75 | reboot. If it was in forced disk mode, press and hold menu and play for 76 | about 5 seconds. 77 | 78 | The first image boots by default, the 2., 3., 4. or 5. can be booted by 79 | holding rew, menu, play or ff. 80 | 81 | To install a new version of the kernel (or to change the preferred image to load) simply repeat the steps 4 and 5 above. 82 | 83 | If your iPod doesn't boot: 84 | Reset and then hold rewind and ffwd keys. The iPod should place itself into 85 | forced disk mode. Now you can restore the original firmware and reset again. 86 | 87 | 88 | For developers: 89 | --------------- 90 | 91 | The firmware builder creates a single boot image for the iPod. It loads all 92 | images into memory, with the first image loaded to 0x28000000 (the default 93 | load address for both linux and the Apple firmware). Then the loader 94 | displays tux, decides which image to start, moves the image to its load 95 | address if required, and starts it. The internal boot table and the loader 96 | are at the end of the image. 97 | 98 | Bugs: the code assumes that no firmware would request itself to be loaded to 99 | the SRAM at 0x40000000 that would overwrite the loader. Also there may be 100 | many other assumptions about some address layouts, compiler internals, etc 101 | in the code. 102 | 103 | 104 | Copyright: 105 | ---------- 106 | 107 | This code can be distributed under the GNU GPL v2. 108 | 109 | Copyright (c) 2003, Daniel Palffy (dpalffy (at) rainstorm.org) 110 | Copyright (c) 2003, Bernard Leach (leachbj (at) bouncycastle.org) 111 | - The basic idea and the original implementation are my work. 112 | The code taken from the iPodLinux port (Keyboard initialization, I/O, 113 | original patch_fw, lcd routines), the new head.s and the tux display 114 | code is Bernard's work. 115 | 116 | There are parts taken from other code, these are also copylefted: 117 | 118 | Copyright (C) 1996-2000 Russell King 119 | - inb, outb 120 | Copyright (C) 1991, 1992 Linus Torvalds 121 | - memmove 122 | 123 | Copyright (c) 1987-2002 The Regents of the University of California. 124 | getopt.c, please see the source for licence details 125 | 126 | The tux.c image is Copyright 2003 Travis Winters. 127 | 128 | -- Daniel Palffy (dpalffy rainstorm.org) 129 | -- Bernard Leach (leachbj bouncycastle.org) 130 | 131 | -------------------------------------------------------------------------------- /resources/ipodloader/README.md: -------------------------------------------------------------------------------- 1 | Simple bootloader for iPodLinux. 2 | 3 | --- 4 | 5 | This is a component of iPodLinux that has been split off into it's own separate project. 6 | This project is no longer actively maintained and has been mirrored for archival purposes. 7 | 8 | The iPodLinux project's full source code tree: https://github.com/iPodLinux/iPodLinux-SVN 9 | The original SourceForge project: http://sourceforge.net/projects/ipodlinux/ 10 | The (now dead) website: http://ipodlinux.org/ 11 | 12 | All files are licensed under GNU General Public License v2.0 unless otherwise specified. 13 | http://www.gnu.org/licenses/gpl-2.0.html -------------------------------------------------------------------------------- /resources/ipodloader/arm_elf_40.x: -------------------------------------------------------------------------------- 1 | 2 | OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", 3 | "elf32-littlearm") 4 | OUTPUT_ARCH(arm) 5 | ENTRY(_start) 6 | 7 | SECTIONS 8 | { 9 | . = 0x40000000; 10 | 11 | .text : { *(.text) } 12 | 13 | __data_start__ = . ; 14 | .data : { *(.data) } 15 | 16 | __bss_start__ = .; 17 | .bss : { 18 | *(.bss); 19 | __bss_end__ = . ; 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /resources/ipodloader/getopt.c: -------------------------------------------------------------------------------- 1 | /* 2 | * getopt.c -- 3 | * 4 | * Standard UNIX getopt function. Code is from BSD. 5 | * 6 | * Copyright (c) 1987-2002 The Regents of the University of California. 7 | * All rights reserved. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions are met: 11 | * 12 | * A. Redistributions of source code must retain the above copyright notice, 13 | * this list of conditions and the following disclaimer. 14 | * B. Redistributions in binary form must reproduce the above copyright notice, 15 | * this list of conditions and the following disclaimer in the documentation 16 | * and/or other materials provided with the distribution. 17 | * C. Neither the names of the copyright holders nor the names of its 18 | * contributors may be used to endorse or promote products derived from this 19 | * software without specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS 22 | * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 23 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 24 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE 25 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 26 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 27 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 28 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 30 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | * POSSIBILITY OF SUCH DAMAGE. 32 | */ 33 | 34 | /* #if !defined(lint) 35 | * static char sccsid[] = "@(#)getopt.c 8.2 (Berkeley) 4/2/94"; 36 | * #endif 37 | */ 38 | 39 | #include 40 | #include 41 | #include 42 | 43 | /* declarations to provide consistent linkage */ 44 | extern char *optarg; 45 | extern int optind; 46 | extern int opterr; 47 | 48 | int opterr = 1, /* if error message should be printed */ 49 | optind = 1, /* index into parent argv vector */ 50 | optopt, /* character checked for validity */ 51 | optreset; /* reset getopt */ 52 | char *optarg; /* argument associated with option */ 53 | 54 | #define BADCH (int)'?' 55 | #define BADARG (int)':' 56 | #define EMSG "" 57 | 58 | /* 59 | * getopt -- 60 | * Parse argc/argv argument vector. 61 | */ 62 | int 63 | getopt(nargc, nargv, ostr) 64 | int nargc; 65 | char * const *nargv; 66 | const char *ostr; 67 | { 68 | static char *place = EMSG; /* option letter processing */ 69 | char *oli; /* option letter list index */ 70 | 71 | if (optreset || !*place) { /* update scanning pointer */ 72 | optreset = 0; 73 | if (optind >= nargc || *(place = nargv[optind]) != '-') { 74 | place = EMSG; 75 | return (EOF); 76 | } 77 | if (place[1] && *++place == '-') { /* found "--" */ 78 | ++optind; 79 | place = EMSG; 80 | return (EOF); 81 | } 82 | } /* option letter okay? */ 83 | if ((optopt = (int)*place++) == (int)':' || 84 | !(oli = strchr(ostr, optopt))) { 85 | /* 86 | * if the user didn't specify '-' as an option, 87 | * assume it means EOF. 88 | */ 89 | if (optopt == (int)'-') 90 | return (EOF); 91 | if (!*place) 92 | ++optind; 93 | if (opterr && *ostr != ':') 94 | (void)fprintf(stderr, 95 | "%s: illegal option -- %c\n", __FILE__, optopt); 96 | return (BADCH); 97 | } 98 | if (*++oli != ':') { /* don't need argument */ 99 | optarg = NULL; 100 | if (!*place) 101 | ++optind; 102 | } 103 | else { /* need an argument */ 104 | if (*place) /* no white space */ 105 | optarg = place; 106 | else if (nargc <= ++optind) { /* no arg */ 107 | place = EMSG; 108 | if (*ostr == ':') 109 | return (BADARG); 110 | if (opterr) 111 | (void)fprintf(stderr, 112 | "%s: option requires an argument -- %c\n", 113 | __FILE__, optopt); 114 | return (BADCH); 115 | } 116 | else /* white space */ 117 | optarg = nargv[optind]; 118 | place = EMSG; 119 | ++optind; 120 | } 121 | return (optopt); /* dump back option letter */ 122 | } 123 | 124 | -------------------------------------------------------------------------------- /resources/ipodloader/happymac.c: -------------------------------------------------------------------------------- 1 | /* Generated by xpm2src.pl */ 2 | #include "tools.h" 3 | unsigned char happymac_img_data[] = { 4 | 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 5 | 0x00, 0xd5, 0x55, 0x55, 0x55, 0x55, 0x57, 0x00, 6 | 0x00, 0xd5, 0x55, 0x55, 0x55, 0x55, 0x57, 0x00, 7 | 0x00, 0xd5, 0xaa, 0xaa, 0xaa, 0xaa, 0x57, 0x00, 8 | 0x00, 0xd6, 0x55, 0x55, 0x55, 0x55, 0x17, 0x00, 9 | 0x00, 0xd6, 0x55, 0x55, 0x55, 0x55, 0x17, 0x00, 10 | 0x00, 0xd6, 0x55, 0x55, 0x55, 0x55, 0x17, 0x00, 11 | 0x00, 0xd6, 0x55, 0xd5, 0xd7, 0x55, 0x17, 0x00, 12 | 0x00, 0xd6, 0x55, 0xd5, 0xd7, 0x55, 0x17, 0x00, 13 | 0x00, 0xd6, 0x55, 0x55, 0xd5, 0x55, 0x17, 0x00, 14 | 0x00, 0xd6, 0x55, 0x55, 0xd5, 0x55, 0x17, 0x00, 15 | 0x00, 0xd6, 0x55, 0x57, 0xd5, 0x55, 0x17, 0x00, 16 | 0x00, 0xd6, 0x55, 0x55, 0x55, 0x55, 0x17, 0x00, 17 | 0x00, 0xd6, 0x55, 0x75, 0x5d, 0x55, 0x17, 0x00, 18 | 0x00, 0xd6, 0x55, 0x5f, 0xf5, 0x55, 0x17, 0x00, 19 | 0x00, 0xd6, 0x55, 0x55, 0x55, 0x55, 0x17, 0x00, 20 | 0x00, 0xd6, 0x55, 0x55, 0x55, 0x55, 0x17, 0x00, 21 | 0x00, 0xd5, 0x00, 0x00, 0x00, 0x00, 0x57, 0x00, 22 | 0x00, 0xd5, 0x55, 0x55, 0x55, 0x55, 0x57, 0x00, 23 | 0x00, 0xd5, 0x55, 0x55, 0x55, 0x55, 0x57, 0x00, 24 | 0x00, 0xd5, 0x55, 0x55, 0x55, 0x55, 0x57, 0x00, 25 | 0x00, 0xd5, 0x55, 0x55, 0x55, 0x55, 0x57, 0x00, 26 | 0x00, 0xd5, 0x55, 0x55, 0xff, 0xff, 0x57, 0x00, 27 | 0x00, 0xd5, 0x55, 0x55, 0x55, 0x55, 0x57, 0x00, 28 | 0x00, 0xd5, 0x55, 0x55, 0x55, 0x55, 0x57, 0x00, 29 | 0x00, 0xd5, 0x55, 0x55, 0x55, 0x55, 0x57, 0x00, 30 | 0x00, 0xd5, 0x55, 0x55, 0x55, 0x55, 0x57, 0x00, 31 | 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 32 | 0x00, 0x3a, 0xaa, 0xaa, 0xaa, 0xaa, 0xac, 0x00, 33 | 0x00, 0x3a, 0xaa, 0xaa, 0xaa, 0xaa, 0xac, 0x00, 34 | 0x00, 0x3a, 0xaa, 0xaa, 0xaa, 0xaa, 0xac, 0x00, 35 | 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 36 | 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 37 | }; 38 | img happymac_hdr = { 39 | 0x00, 0x00, 40 | 0x20, 0x20, 41 | 0x08, 42 | 0x02, 43 | 0x00, 44 | 0x100, 45 | happymac_img_data 46 | }; 47 | -------------------------------------------------------------------------------- /resources/ipodloader/loader.c: -------------------------------------------------------------------------------- 1 | /* 2 | * loader.c - iPodLinux loader 3 | * Copyright (c) 2003, Daniel Palffy (dpalffy (at) rainstorm.org) 4 | * Copyright (c) 2003, Bernard Leach (leachbj@bouncycastle.org) 5 | * - Keyboard initialization, I/O 6 | * Copyright (C) 1996-2000 Russell King 7 | * - inb, outb 8 | * Copyright (C) 1991, 1992 Linus Torvalds 9 | * - memmove 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License version 2 as 13 | * published by the Free Software Foundation. 14 | */ 15 | 16 | #include "tools.h" 17 | 18 | typedef struct _image { 19 | unsigned type; /* '' */ 20 | unsigned id; /* */ 21 | unsigned pad1; /* 0000 0000 */ 22 | unsigned devOffset; /* byte offset of start of image code */ 23 | unsigned len; /* length in bytes of image */ 24 | void *addr; /* load address */ 25 | unsigned entryOffset; /* execution start within image */ 26 | unsigned chksum; /* checksum for image */ 27 | unsigned vers; /* image version */ 28 | unsigned loadAddr; /* load address for image */ 29 | } image_t; 30 | 31 | #define TBL ((char **)0x40000000) 32 | #define MASK 0x1 33 | 34 | extern image_t boot_table[]; 35 | extern img tux_hdr; 36 | extern img happymac_hdr; 37 | 38 | int ipod_ver = 0; 39 | 40 | /* black magic */ 41 | static void 42 | init_keyboard(void) 43 | { 44 | if (ipod_ver < 4) { 45 | /* 1..3g keyboard init */ 46 | outb(~inb(0xcf000030), 0xcf000060); 47 | outb(inb(0xcf000040), 0xcf000070); 48 | 49 | outb(inb(0xcf000004) | 0x1, 0xcf000004); 50 | outb(inb(0xcf000014) | 0x1, 0xcf000014); 51 | outb(inb(0xcf000024) | 0x1, 0xcf000024); 52 | 53 | outb(0xff, 0xcf000050); 54 | } else if (ipod_ver == 4) { 55 | /* mini keyboard init */ 56 | outl(inl(0x6000d000) | 0x3f, 0x6000d000); 57 | outl(inl(0x6000d010) & ~0x3f, 0x6000d010); 58 | } else if (ipod_ver >= 5) { 59 | /* 4g/photo keyboard init */ 60 | 61 | /* nothing to do */ 62 | } 63 | } 64 | 65 | static int 66 | key_pressed(void) 67 | { 68 | unsigned char state; 69 | 70 | if (ipod_ver < 4) { 71 | state = inb(0xcf000030); 72 | if ((ipod_ver == 3) && ((state & 0x20) == 0)) return 0; /* hold on */ 73 | if ((state & 0x08) == 0) return 1; 74 | if ((state & 0x10) == 0) return 2; 75 | if ((state & 0x04) == 0) return 3; 76 | if ((state & 0x01) == 0) return 4; 77 | } else if(ipod_ver == 4) { 78 | /* mini buttons */ 79 | state = inb(0x6000d030); 80 | if ((state & 0x10) == 0) return 1; 81 | if ((state & 0x2) == 0) return 2; 82 | if ((state & 0x04) == 0) return 3; 83 | if ((state & 0x08) == 0) return 4; 84 | } else if(ipod_ver >= 5) { 85 | state = opto_keypad_read(); 86 | if ((state & 0x4) == 0) return 1; 87 | if ((state & 0x10) == 0) return 2; 88 | if ((state & 0x8) == 0) return 3; 89 | if ((state & 0x2) == 0) return 4; 90 | } 91 | 92 | return 0; 93 | } 94 | 95 | static void 96 | memmove16(void *dest, const void *src, unsigned count) 97 | { 98 | struct bufstr { 99 | unsigned _buf[4]; 100 | } *d, *s; 101 | 102 | if (src >= dest) { 103 | count = (count + 15) >> 4; 104 | d = (struct bufstr *) dest; 105 | s = (struct bufstr *) src; 106 | while (count--) 107 | *d++ = *s++; 108 | } else { 109 | count = (count + 15) >> 4; 110 | d = (struct bufstr *)(dest + (count <<4)); 111 | s = (struct bufstr *)(src + (count <<4)); 112 | while (count--) 113 | *--d = *--s; 114 | } 115 | } 116 | 117 | void * 118 | loader(void) 119 | { 120 | int imageno = 0; 121 | int padding = 0x4400; 122 | image_t *tblp = boot_table; 123 | void *entry; 124 | 125 | get_ipod_rev(); 126 | if (ipod_ver > 3) padding = 0x4600; 127 | 128 | display_image(&happymac_hdr, 0x0); 129 | 130 | wait_usec(300); 131 | 132 | init_keyboard(); 133 | 134 | imageno = key_pressed(); 135 | if (!tblp[imageno].type) imageno = 0; 136 | 137 | /* for appleOS as default, 0=happymac_hdr, 1=tux_hdr 138 | for linux as default, 0=tux_hdr, 1=happymac_hdr 139 | */ 140 | switch (imageno) { 141 | case 0: 142 | display_image(&happymac_hdr, 0x0); 143 | break; 144 | 145 | case 1: 146 | default: 147 | display_image(&tux_hdr, 0x0); 148 | break; 149 | } 150 | 151 | tblp += imageno; 152 | entry = tblp->addr + tblp->entryOffset; 153 | if (imageno || ((int)tblp->addr & 0xffffff) != 0) { 154 | memmove16(tblp->addr, tblp->addr + tblp->devOffset - padding, 155 | tblp->len); 156 | } 157 | 158 | return entry; 159 | } 160 | 161 | -------------------------------------------------------------------------------- /resources/ipodloader/pics/happymac.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static char *smileymac[] = { 3 | /* columns rows colors chars-per-pixel */ 4 | "32 32 4 1", 5 | " c black", 6 | ". c #555555", 7 | "X c #AAAAAA", 8 | "o c gray100", 9 | /* pixels */ 10 | "ooooo ooooo", 11 | "oooo XXXXXXXXXXXXXXXXXXXXXX oooo", 12 | "oooo XXXXXXXXXXXXXXXXXXXXXX oooo", 13 | "oooo XXX................XXX oooo", 14 | "oooo XX.XXXXXXXXXXXXXXXXoXX oooo", 15 | "oooo XX.XXXXXXXXXXXXXXXXoXX oooo", 16 | "oooo XX.XXXXXXXXXXXXXXXXoXX oooo", 17 | "oooo XX.XXXX XXX XX XXXXoXX oooo", 18 | "oooo XX.XXXX XXX XX XXXXoXX oooo", 19 | "oooo XX.XXXXXXXX XXXXXXXoXX oooo", 20 | "oooo XX.XXXXXXXX XXXXXXXoXX oooo", 21 | "oooo XX.XXXXXXX XXXXXXXoXX oooo", 22 | "oooo XX.XXXXXXXXXXXXXXXXoXX oooo", 23 | "oooo XX.XXXXX XXXX XXXXXoXX oooo", 24 | "oooo XX.XXXXXX XXXXXXoXX oooo", 25 | "oooo XX.XXXXXXXXXXXXXXXXoXX oooo", 26 | "oooo XX.XXXXXXXXXXXXXXXXoXX oooo", 27 | "oooo XXXooooooooooooooooXXX oooo", 28 | "oooo XXXXXXXXXXXXXXXXXXXXXX oooo", 29 | "oooo XXXXXXXXXXXXXXXXXXXXXX oooo", 30 | "oooo XXXXXXXXXXXXXXXXXXXXXX oooo", 31 | "oooo XXXXXXXXXXXXXXXXXXXXXX oooo", 32 | "oooo XXXXXXXXXXX XXX oooo", 33 | "oooo XXXXXXXXXXXXXXXXXXXXXX oooo", 34 | "oooo XXXXXXXXXXXXXXXXXXXXXX oooo", 35 | "oooo XXXXXXXXXXXXXXXXXXXXXX oooo", 36 | "oooo XXXXXXXXXXXXXXXXXXXXXX oooo", 37 | "ooooo ooooo", 38 | "ooooo .................... ooooo", 39 | "ooooo .................... ooooo", 40 | "ooooo .................... ooooo", 41 | "ooooo ooooo" 42 | }; 43 | -------------------------------------------------------------------------------- /resources/ipodloader/startup.s: -------------------------------------------------------------------------------- 1 | /* 2 | * startup.s - iPodLinux loader 3 | * 4 | * Copyright (c) 2003, Daniel Palffy (dpalffy (at) rainstorm.org) 5 | * Copyright (c) 2005, Bernard Leach 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License version 2 as 9 | * published by the Free Software Foundation. 10 | * 11 | * Do not meddle in the affairs of Wizards, for they are subtle 12 | * and quick to anger. 13 | * 14 | * -"The Fellowship of the Ring", J.R.R Tolkien 15 | * 16 | * This code must not compile to more than 0x100 bytes without modifying 17 | * make_fw.c to accomodate the extra room required. 18 | */ 19 | 20 | .equ PP5002_PROC_ID, 0xc4000000 21 | .equ PP5002_COP_CTRL, 0xcf004058 22 | 23 | .equ PP5020_PROC_ID, 0x60000000 24 | .equ PP5020_COP_CTRL, 0x60007004 25 | 26 | .global _start 27 | _start: 28 | /* get the high part of our execute address */ 29 | ldr r0, =0xff000000 30 | and r8, pc, r0 31 | cmp r8, #0x28000000 @ r8 is used later 32 | 33 | moveq r0, #PP5002_PROC_ID 34 | movne r0, #PP5020_PROC_ID 35 | ldr r0, [r0] 36 | and r0, r0, #0xff 37 | cmp r0, #0x55 38 | beq 1f 39 | 40 | /* put us (co-processor) to sleep */ 41 | cmp r8, #0x28000000 42 | ldreq r4, =PP5002_COP_CTRL 43 | moveq r3, #0xca 44 | ldrne r4, =PP5020_COP_CTRL 45 | movne r3, #0x80000000 46 | str r3, [r4] 47 | 48 | ldr pc, =cop_wake_start 49 | 50 | cop_wake_start: 51 | /* jump the COP to startup */ 52 | ldr r0, =startup_loc 53 | ldr pc, [r0] 54 | 55 | 1: 56 | /* setup some stack */ 57 | ldr sp, =0x400177fc 58 | 59 | /* get the high part of our execute address */ 60 | ldr r2, =0xffffff00 61 | and r4, pc, r2 62 | 63 | /* relocate to 0x40000000 */ 64 | mov r5, #0x40000000 65 | ldr r6, =__data_start__ 66 | sub r0, r6, r5 /* lenth of text */ 67 | add r0, r4, r0 /* r0 points to start of text */ 68 | 1: 69 | cmp r5, r6 70 | ldrcc r2, [r4], #4 71 | strcc r2, [r5], #4 72 | bcc 1b 73 | 74 | ldr pc, =start_loc /* jump to the next instruction in 0x4000xxxx */ 75 | 76 | start_loc: 77 | ldr r1, =__data_start__ 78 | ldr r3, =__bss_start__ 79 | cmp r0, r1 80 | beq init_bss 81 | 82 | 1: 83 | cmp r1, r3 84 | ldrcc r2, [r0], #4 85 | strcc r2, [r1], #4 86 | bcc 1b 87 | 88 | init_bss: 89 | ldr r1, =__bss_end__ 90 | mov r2, #0x0 91 | 92 | 1: 93 | cmp r3, r1 94 | strcc r2, [r3], #4 95 | bcc 1b 96 | 97 | /* go to the loader */ 98 | bl loader 99 | /* save the startup address for the COP */ 100 | ldr r1, =startup_loc 101 | str r0, [r1] 102 | 103 | cmp r8, #0x28000000 104 | bne pp5020 105 | 106 | /* make sure COP is sleeping */ 107 | ldr r4, =0xcf004050 108 | 1: 109 | ldr r3, [r4] 110 | ands r3, r3, #0x4000 111 | beq 1b 112 | 113 | /* wake up COP */ 114 | ldr r4, =PP5002_COP_CTRL 115 | mov r3, #0xce 116 | strh r3, [r4] 117 | 118 | /* jump to start location */ 119 | mov pc, r0 120 | 121 | pp5020: 122 | /* make sure COP is sleeping */ 123 | ldr r4, =PP5020_COP_CTRL 124 | 1: 125 | ldr r3, [r4] 126 | ands r3, r3, #0x80000000 127 | beq 1b 128 | 129 | /* wake up COP */ 130 | @ ldr r4, =PP5020_COP_CTRL 131 | mov r3, #0x0 132 | str r3, [r4] 133 | 134 | /* jump to start location */ 135 | mov pc, r0 136 | 137 | startup_loc: 138 | .word 0x0 139 | 140 | .align 8 /* starts at 0x100 */ 141 | .global boot_table 142 | boot_table: 143 | /* here comes the boot table, don't move its offset */ 144 | .space 400 145 | -------------------------------------------------------------------------------- /resources/ipodloader/tools.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef TOOLS_H 3 | #define TOOLS_H 4 | 5 | #define inl(a) (*(volatile unsigned long *) (a)) 6 | #define outl(a,b) (*(volatile unsigned long *) (b) = (a)) 7 | #define inb(a) (*(volatile unsigned char *) (a)) 8 | #define outb(a,b) (*(volatile unsigned char *) (b) = (a)) 9 | 10 | /* find out which ipod revision we're running on */ 11 | void get_ipod_rev(); 12 | 13 | /* get current usec counter */ 14 | int timer_get_current(); 15 | 16 | /* check if number of seconds has past */ 17 | int timer_check(int clock_start, int usecs); 18 | 19 | /* wait for r0 useconds */ 20 | int wait_usec(int usecs); 21 | 22 | /* wait for LCD with timeout */ 23 | void lcd_wait_write(); 24 | 25 | /* send LCD data */ 26 | void lcd_send_data(int data_lo, int data_hi); 27 | 28 | /* send LCD command */ 29 | void lcd_prepare_cmd(int cmd); 30 | 31 | /* send LCD command and data */ 32 | void lcd_cmd_and_data(int cmd, int data_lo, int data_hi); 33 | 34 | typedef struct _img { 35 | unsigned short offy; // #0 36 | unsigned short offx; // #2 37 | unsigned short height; // #4 38 | unsigned short width; // #6 39 | unsigned short data_width; // #8 40 | unsigned short img_type; // #10 41 | unsigned long pad0; // #12 42 | unsigned long len; // #16 43 | unsigned char *data; // #20 44 | } img; 45 | 46 | void display_image(img *img, int draw_bg); 47 | 48 | int opto_keypad_read(); 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /resources/ipodloader/xpm2src.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # 3 | # xpm 2 src 4 | # 5 | # quick and dirty perl script to read in an XPM file, and save 6 | # out a source file to be used with loader.bin 7 | # 8 | # Created 2005-08 By Scott Lawrence 9 | # 10 | # This program is free software; you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License version 2 as 12 | # published by the Free Software Foundation. 13 | # 14 | 15 | 16 | # xpm parsing heuristics: 17 | # ignore anything not in quotes on a line 18 | 19 | sub parseit 20 | { 21 | my( $xpmfile, $cfile, $name, $line, $content, $state ); 22 | my( $w, $h, $c, $p ); 23 | my( $cc, $cj, $cid, $cval ); 24 | my( $curcol ); 25 | my( $byte, $nper ); 26 | 27 | $xpmfile = shift; 28 | $name = shift; 29 | $cfile = $name . ".c"; 30 | 31 | $state = 0; # waiting on parameters 32 | $curcol = 0; # current color = 0; 33 | 34 | open OF, ">$cfile"; 35 | printf OF "/* Generated by xpm2src.pl */\n"; 36 | printf OF "#include \"tools.h\"\n"; 37 | printf OF "unsigned char %s_img_data[] = {\n", $name; 38 | 39 | open IF, "$xpmfile"; 40 | 41 | foreach $line () 42 | { 43 | chomp $line; 44 | $line =~ s/\/\*.*\*\///g; # zot comments 45 | $line =~ s/.*"(.*)".*/$1/; # look at stuff in quotes only 46 | $line = $1; 47 | 48 | next if( 0 == (length $line) ); 49 | 50 | if( $state == 2 ) # image data 51 | { 52 | printf "%s\n", $line; 53 | my( $x, $i ); 54 | 55 | for( $x=0 ; $x<(length $line) ; $x++ ) 56 | { 57 | $i = substr( $line, $x, 1 ); 58 | #printf "%d", $colhash{ $i }; 59 | 60 | $byte = $byte << 2; 61 | $byte += $colhash{ $i }; 62 | 63 | $bytec++; 64 | if( $bytec > 3 ) { 65 | printf OF " 0x%02x,", $byte; 66 | $bytec = 0; 67 | $byte = 0; 68 | } 69 | 70 | } 71 | printf OF "\n"; 72 | } 73 | 74 | if( $state == 1 ) # get colors 75 | { 76 | $cc = substr $line, 0, $p; 77 | $cid = substr $line, $p+3; 78 | $cval = 3; 79 | # some rough assumptions... 80 | if( $cid eq "#555555" ) { $cval = 2; } 81 | if( $cid eq "#AAAAAA" ) { $cval = 1; } 82 | 83 | if( $cid eq "#FFFFFF" ) { $cval = 0; } 84 | if( $cid eq "gray100" ) { $cval = 0; } 85 | if( $cid eq "white" ) { $cval = 0; } 86 | 87 | printf "Color %d is |%s| -> %s %d\n", $curcol, $cc, $cid, $cval; 88 | $colhash{ $cc } = $cval; 89 | 90 | $curcol++; 91 | if( $curcol >= $c ) { 92 | $state = 2; 93 | $nper = 0; 94 | $byte = 0; 95 | $bytec = 0; 96 | } 97 | 98 | } 99 | 100 | if( $state == 0 ) # get dimenstions 101 | { 102 | ($w, $h, $c, $p) = split " ", $line; 103 | $state = 1; 104 | printf "Image is %dx%d, %d colors, %d chars per pixel\n", $w, $h, $c, $p; 105 | } 106 | } 107 | 108 | close IF; 109 | 110 | printf OF "};\n"; 111 | printf OF "img %s_hdr = {\n", $name; 112 | printf OF " 0x00, 0x00,\n"; # offx, offy 113 | printf OF " 0x%02x, 0x%02x,\n", $h, $w; # height, width 114 | printf OF " 0x%02x,\n", ($w/4); # data_width 115 | printf OF " 0x02,\n"; # img_type 116 | printf OF " 0x00,\n"; # padding 117 | printf OF " 0x%02x,\n", ($w * $h)/4; # length 118 | printf OF " %s_img_data\n", $name; # data 119 | printf OF "};\n"; 120 | close OF; 121 | } 122 | 123 | 124 | 125 | 126 | 127 | sub main 128 | { 129 | parseit( $ARGV[0], $ARGV[1] ); 130 | } 131 | 132 | &main; 133 | -------------------------------------------------------------------------------- /resources/ipodloader2/.gdbinit: -------------------------------------------------------------------------------- 1 | file loader.elf 2 | target remote /tmp/clicky 3 | -------------------------------------------------------------------------------- /resources/ipodloader2/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | loader 3 | loader.bin 4 | loader.objdump 5 | make_fw 6 | 7 | *.bin 8 | *.elf 9 | -------------------------------------------------------------------------------- /resources/ipodloader2/Makefile: -------------------------------------------------------------------------------- 1 | # makefile for iPodLoader2 2 | # 3 | # Note by TT: the option "-mstructure-size-boundary=8" is necessary when compiling macpartitions.cc for the structs to get the correct sizes! 4 | 5 | # The next line gets the current svn revision. 6 | 7 | CROSS ?= arm-none-eabi- 8 | CC = $(CROSS)gcc 9 | LD = $(CROSS)ld 10 | MYCFLAGS = -g -Og -Wall -std=gnu99 -ffreestanding -nostdinc -fomit-frame-pointer -DVERSION=\"0\" 11 | # -DDEBUG 12 | MYCPPFLAGS= -g -Og -Wall -nostdinc -fomit-frame-pointer -mstructure-size-boundary=8 13 | MYLDFLAGS = -Tarm_elf_40.x `$(CC) -print-libgcc-file-name` 14 | OBJCOPY = $(CROSS)objcopy 15 | 16 | OBJFILES = startup.o loader.o fb.o ipodhw.o console.o minilibc.o ata2.o vfs.o fat32.o ext2.o fwfs.o keypad.o menu.o config.o macpartitions.o interrupts.o interrupt-entry.o 17 | 18 | all: loader.bin $(OBJFILES) Makefile 19 | # @echo "Building firmware image" 20 | # @./make_fw -g 4g -o my_sw.bin -i apple_os.bin $< 21 | 22 | clean: 23 | @echo "Cleaning up" 24 | @rm -f *.o *~ loader.bin loader.elf nohup.out my_sw.bin $(OBJFILES) 25 | 26 | loader.bin: loader.elf 27 | @echo "Converting $< to binary" 28 | @$(OBJCOPY) -O binary $< $@ 29 | 30 | loader.elf: $(OBJFILES) 31 | @echo "Linking $@" 32 | @$(LD) -o $@ $^ $(MYLDFLAGS) 33 | 34 | %.o:%.cc 35 | @echo "Compiling $<" 36 | @$(CC) $(MYCPPFLAGS) $(CFLAGS) -c $< -o $@ 37 | 38 | %.o:%.c 39 | @echo "Compiling $<" 40 | @$(CC) $(MYCFLAGS) $(CFLAGS) -c $< -o $@ 41 | 42 | %.o:%.s 43 | @echo "Compiling $<" 44 | @$(CC) $(MYCFLAGS) $(CFLAGS) -c $< -o $@ 45 | -------------------------------------------------------------------------------- /resources/ipodloader2/README.md: -------------------------------------------------------------------------------- 1 | Updated bootloader for iPodLinux with more features. 2 | 3 | --- 4 | 5 | This is a component of iPodLinux that has been split off into it's own separate project. 6 | This project is no longer actively maintained and has been mirrored for archival purposes. 7 | 8 | The iPodLinux project's full source code tree: https://github.com/iPodLinux/iPodLinux-SVN 9 | The original SourceForge project: http://sourceforge.net/projects/ipodlinux/ 10 | The (now dead) website: http://ipodlinux.org/ 11 | 12 | All files are licensed under GNU General Public License v2.0 unless otherwise specified. 13 | http://www.gnu.org/licenses/gpl-2.0.html -------------------------------------------------------------------------------- /resources/ipodloader2/TODO: -------------------------------------------------------------------------------- 1 | TODO: 2 | * Make FAT32 use DOS-convention filenames instead of on-disk encoding 3 | * add support for piezo on PP5002 models (ipod_beep) 4 | 5 | Notes about TODOs: 6 | * Make FAT32 use DOS..... 7 | Replace ' ' with 0x0, and do two strncmp() on each dirent, and 8 | is should pretty much work already. Make case-insensitive for 9 | niceness 10 | -------------------------------------------------------------------------------- /resources/ipodloader2/arm_elf_40.x: -------------------------------------------------------------------------------- 1 | 2 | OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", 3 | "elf32-littlearm") 4 | OUTPUT_ARCH(arm) 5 | ENTRY(_start) 6 | 7 | SECTIONS 8 | { 9 | . = 0x40000000; 10 | 11 | .text : { *(.text) } 12 | 13 | __data_start__ = . ; 14 | .data : { *(.data) *(.rodata) } 15 | 16 | __bss_start__ = .; 17 | .bss : { 18 | *(.bss); 19 | __bss_end__ = . ; 20 | } 21 | 22 | __exidx_start = .; 23 | .ARM.exidx : { *(.ARM.exidx* .gnu.linkonce.armexidx.*) } 24 | __exidx_end = .; 25 | } 26 | 27 | -------------------------------------------------------------------------------- /resources/ipodloader2/ata2.h: -------------------------------------------------------------------------------- 1 | #ifndef _ATA2_H_ 2 | #define _ATA2_H_ 3 | 4 | #include "bootloader.h" 5 | 6 | uint32 ata_init(void); 7 | void ata_exit(void); 8 | void ata_identify(void); 9 | void ata_find_transfermode(void); 10 | uint8 ata_get_drivetype (void); 11 | int ata_readblock(void *dst, uint32 sector); // this read get cached 12 | int ata_readblocks(void *dst,uint32 sector,uint32 count); // these reads get cached 13 | int ata_readblocks_uncached(void *dst,uint32 sector,uint32 count); // these reads are uncached 14 | void ata_standby (int cmd_variation); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /resources/ipodloader2/bootloader.h: -------------------------------------------------------------------------------- 1 | #ifndef _BOOTLOADER_H_ 2 | #define _BOOTLOADER_H_ 3 | 4 | typedef unsigned int uint32; 5 | typedef unsigned short uint16; 6 | typedef unsigned char uint8; 7 | typedef signed int int32; 8 | typedef signed short int16; 9 | typedef signed char int8; 10 | 11 | typedef unsigned long size_t; 12 | 13 | #undef NULL 14 | #define NULL ((void*)0x0) 15 | 16 | #define inl(a) (*(volatile unsigned long *) (a)) 17 | #define outl(a,b) (*(volatile unsigned long *) (b) = (a)) 18 | #define inw(a) (*(volatile unsigned short *) (a)) 19 | #define outw(a,b) (*(volatile unsigned short *) (b) = (a)) 20 | #define inb(a) (*(volatile unsigned char *) (a)) 21 | #define outb(a,b) (*(volatile unsigned char *) (b) = (a)) 22 | 23 | typedef struct { 24 | uint8 status; 25 | uint8 chs_start[3]; 26 | uint8 type; /* filesystem type: e.g. 0x0a for FAT32, 0x83 for ext2fs */ 27 | uint8 chs_end[3]; 28 | uint32 lba_offset; 29 | uint32 lba_size; 30 | } __attribute__((__packed__)) pt_entry_t; 31 | 32 | typedef struct { 33 | uint8 code[ 0x018a]; /* MBR Code */ 34 | uint8 ibm_ext_pte[36]; /* 4 9-byte primary partition table entries (some IBM stuff) */ 35 | uint8 unused[10]; /* unused */ 36 | uint32 disk_signature; /* 4 byte disk signature */ 37 | uint16 :16; /* unused */ 38 | pt_entry_t partition_table[4]; /* the partition table */ 39 | uint16 MBR_signature; /* the MBR signature */ 40 | } __attribute__((__packed__)) mbr_t; 41 | 42 | typedef struct { 43 | uint8 unused1[56]; 44 | uint16 ext2magic; /* ext2 magic bytes */ 45 | uint8 unused2[198]; 46 | uint8 fwfsmagic[4]; /* fwfs magic bytes */ 47 | uint8 unused3[250]; 48 | uint16 fat32magic; /* FAT32 magic bytes */ 49 | } __attribute__((__packed__)) fs_header_t; 50 | 51 | 52 | #endif 53 | 54 | 55 | -------------------------------------------------------------------------------- /resources/ipodloader2/config.h: -------------------------------------------------------------------------------- 1 | #ifndef _CONFIG_H_ 2 | #define _CONFIG_H_ 3 | 4 | #include "bootloader.h" 5 | 6 | #define CONFIG_IMAGE_BINARY 0x00 7 | #define CONFIG_IMAGE_SPECIAL 0x01 8 | #define CONFIG_IMAGE_ROCKBOX 0x02 9 | 10 | typedef struct { 11 | uint32 type; 12 | char *title; 13 | char *path; 14 | } config_image_t; 15 | 16 | typedef struct { 17 | config_image_t *image; 18 | int16 timeout; 19 | int16 def; // default item index in menu, 1-based 20 | int16 items; 21 | int16 backlight; 22 | int16 contrast; 23 | uint16 debug; 24 | uint16 usegradient; 25 | uint16 bgcolor; 26 | uint16 hicolor; 27 | uint16 beep_time; // in ms 28 | uint16 beep_period; 29 | int16 ata_standby_code; 30 | } config_t; 31 | 32 | void config_init(void); 33 | config_t *config_get(void); 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /resources/ipodloader2/console.h: -------------------------------------------------------------------------------- 1 | #ifndef _CONSOLE_H_ 2 | #define _CONSOLE_H_ 3 | 4 | #include "bootloader.h" 5 | 6 | extern const uint8 font_large[]; 7 | extern const uint8 font_medium[]; 8 | extern const uint8 font_small[]; 9 | 10 | void console_init(uint16 *fb); 11 | void console_putchar(char ch); 12 | void console_puts(volatile char *str); 13 | void console_putsXY(int x,int y,volatile char *str); 14 | void console_printf (const char *format, ...); 15 | 16 | void console_setcolor(uint16 fg, uint16 bg, uint8 transparent); 17 | void console_getcolor(uint16 *fg, uint16 *bg, uint8 *transparent); 18 | 19 | void console_setfont(const uint8 *font); 20 | const uint8* console_currentfont (void); 21 | void console_home(); 22 | void console_clear(); 23 | int console_suppress_fbupdate (int modify); 24 | 25 | extern int font_width, font_height; 26 | extern int console_printcount; 27 | extern int font_lines; 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /resources/ipodloader2/ext2.h: -------------------------------------------------------------------------------- 1 | #ifndef _EXT2_H_ 2 | #define _EXT2_H_ 3 | 4 | #include "bootloader.h" 5 | 6 | typedef struct { 7 | uint32 s_inodes_count; 8 | uint32 s_blocks_count; 9 | uint32 s_r_blocks_count; 10 | uint32 s_free_blocks_count; 11 | uint32 s_free_inodes_count; 12 | uint32 s_first_data_block; 13 | uint32 s_log_block_size; 14 | uint32 s_log_frag_size; 15 | uint32 s_blocks_per_group; 16 | uint32 s_frags_per_group; 17 | uint32 s_inodes_per_group; 18 | uint32 s_mtime; 19 | uint32 s_wtime; 20 | uint16 s_mnt_count; 21 | uint16 s_max_mnt_count; 22 | uint16 s_magic; 23 | uint16 s_state; 24 | uint16 s_errors; 25 | uint16 s_minor_rev_level; 26 | uint32 s_lastcheck; 27 | uint32 s_checkinterval; 28 | uint32 s_creator_os; 29 | uint32 s_rev_level; 30 | uint16 s_def_resuid; 31 | uint16 s_def_resqid; 32 | uint32 s_first_ino; 33 | uint16 s_inode_size; 34 | uint16 s_block_group_nr; 35 | uint32 s_feature_compat; 36 | uint32 s_feature_incompat; 37 | uint32 s_feature_ro_compat; 38 | uint8 s_uuid[16]; 39 | uint8 s_volume_name[16]; 40 | uint8 s_last_mounted[64]; 41 | uint32 s_algo_bitmap; 42 | uint8 s_prealloc_blocks; 43 | uint8 s_prealloc_dir_blocks; 44 | uint16 align; 45 | uint8 s_journal_uuid[16]; 46 | uint32 s_journal_inum; 47 | uint32 s_journal_dev; 48 | uint32 s_last_orphan; 49 | uint8 padding[788]; 50 | } superblock_t; 51 | 52 | typedef struct { 53 | uint16 i_mode; 54 | uint16 i_uid; 55 | uint32 i_size; 56 | uint32 i_atime; 57 | uint32 i_ctime; 58 | uint32 i_mtime; 59 | uint32 i_dtime; 60 | uint16 i_gid; 61 | uint16 i_links_count; 62 | uint32 i_blocks; 63 | uint32 i_flags; 64 | uint32 i_osdl; 65 | uint32 i_block[15]; 66 | uint32 i_generation; 67 | uint32 i_file_acl; 68 | uint32 i_dir_acl; 69 | uint32 i_faddr; 70 | uint8 i_osd2[12]; 71 | } inode_t; 72 | 73 | typedef struct { 74 | uint32 inode; 75 | uint16 rec_len; 76 | uint8 name_len; 77 | uint8 file_type; 78 | uint8 name[255]; 79 | } dir_t; 80 | 81 | typedef struct _group_desc { 82 | uint32 bg_block_bitmap; 83 | uint32 bg_inode_bitmap; 84 | uint32 bg_inode_table; 85 | uint16 bg_free_blocks_count; 86 | uint16 bg_free_inodes_count; 87 | uint16 bg_used_dirs_count; 88 | uint16 bg_pad; 89 | uint8 bg_reserved[12]; 90 | } group_t; 91 | 92 | typedef struct { 93 | inode_t inode; 94 | uint32 inodeNum; 95 | uint32 length; 96 | uint32 opened; 97 | uint32 position; 98 | } ext2_file; 99 | 100 | void ext2_newfs(uint8 part,uint32 offset); 101 | 102 | #endif 103 | -------------------------------------------------------------------------------- /resources/ipodloader2/fat32.h: -------------------------------------------------------------------------------- 1 | #ifndef _FAT32_H_ 2 | #define _FAT32_H_ 3 | 4 | #include "bootloader.h" 5 | 6 | typedef struct { 7 | uint32 cluster; 8 | uint32 length; 9 | uint32 opened; 10 | uint32 position; 11 | } fat32_file; 12 | 13 | void fat32_newfs(uint8 part,uint32 offset); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /resources/ipodloader2/fb.h: -------------------------------------------------------------------------------- 1 | #ifndef _FB_H_ 2 | #define _FB_H_ 3 | 4 | #include "bootloader.h" 5 | 6 | // commonly used colors: 7 | #define BLACK 0x0000 8 | #define WHITE 0xFFFF 9 | 10 | void fb_init(void); 11 | 12 | void fb_update(uint16 *x); 13 | void fb_cls(uint16 *x,uint16 val); 14 | uint16 fb_rgb(int r, int g, int b); // takes values between 0 and 255 15 | void fb_rgbsplit (uint16 rgb, uint8 *r, uint8 *g, uint8 *b); 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /resources/ipodloader2/fwfs.h: -------------------------------------------------------------------------------- 1 | #ifndef _FWFS_H_ 2 | #define _FWFS_H_ 3 | 4 | #include "bootloader.h" 5 | 6 | typedef struct { 7 | uint32 magic; /* [hi] */ 8 | uint32 bl_table; /* Start location of bootloader table */ 9 | uint16 ext_head; /* Start location of extended header */ 10 | uint16 version; /* Firmware format version (2=Pre 4G, 3=Post 4G) */ 11 | } fwfs_header_t; 12 | 13 | typedef struct { 14 | uint32 dev; 15 | uint32 type; 16 | uint32 id; 17 | uint32 devOffset; 18 | uint32 len; 19 | uint32 addr; 20 | uint32 entryOffset; 21 | uint32 chksum; 22 | uint32 vers; 23 | uint32 loadaddr; 24 | } fwfs_image_t; 25 | 26 | typedef struct { 27 | uint32 devOffset; 28 | uint32 length; 29 | uint32 chksum; 30 | 31 | uint32 position; 32 | } fwfs_file; 33 | 34 | void fwfs_newfs(uint8 part,uint32 offset); 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /resources/ipodloader2/getLoader2Args/Makefile: -------------------------------------------------------------------------------- 1 | getLoader2Args: getLoader2Args.o 2 | arm-elf-gcc getLoader2Args.c -o getLoader2Args -elf2flt 3 | #getLoader2Args.o: getLoader2Args.c 4 | clean: 5 | rm -f getLoader2Args *.o 6 | .PHONY: clean 7 | -------------------------------------------------------------------------------- /resources/ipodloader2/getLoader2Args/getLoader2Args.c: -------------------------------------------------------------------------------- 1 | /* 2 | Author: Thomas Tempelmann (http://ipodlinux.org/User:Tempel) 3 | Last change: 30Mar06 4 | 5 | getLoader2Args is a tool that runs on the iPod under iPodLinux. 6 | It is used in conjunction with iPodLoader2 (http://ipodlinux.org/Loader_2) 7 | 8 | See the "readme.txt" for more info 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | static short calc_checksum2 (char* dest, int size) { 16 | short csum = 0; 17 | while (size-- > 0) { 18 | char b = *dest++; 19 | csum = ((csum << 1) & 0xffff) + ((csum<0)? 1 : 0) + b; // csum-rotation plus b 20 | } 21 | return csum; 22 | } 23 | 24 | static char* getArgs (char* baseAddr) { 25 | // fetch the args 26 | if (strncmp (baseAddr, "Args", 4) == 0) { 27 | int strlen = *(short*)(baseAddr+6); 28 | if (*(short*)(baseAddr+4) == calc_checksum2 (baseAddr+6, strlen+2)) { 29 | return baseAddr + 8; 30 | } 31 | } 32 | return 0; 33 | } 34 | 35 | /* 36 | static void memdump (long addr, int len) { 37 | int i; 38 | while (len > 0) { 39 | for (i = 0; i < 4; ++i) { 40 | printf (" %08x", *(long*)(addr)); 41 | addr += 4; 42 | len -= 4; 43 | } 44 | printf ("\n"); 45 | } 46 | } 47 | */ 48 | 49 | int main (int argc, char **argv) 50 | { 51 | /* look for non-empty spaces: 52 | long *p = 0x24; 53 | while (p < (long*)32768) { 54 | if (*p++) { 55 | printf (" %08lx", (long)p-4); 56 | while (*p++) { } 57 | printf ("-%08lx", (long)p); 58 | } 59 | } 60 | printf ("\n"); 61 | printf ("end: %08lx", (long)p); 62 | printf ("\n"); 63 | 64 | memdump (0x80, 0x20); 65 | */ 66 | char *args; 67 | 68 | args = getArgs ((char*)0x80); 69 | 70 | if (args) { 71 | puts (args); 72 | } 73 | 74 | return 0; 75 | } 76 | -------------------------------------------------------------------------------- /resources/ipodloader2/getLoader2Args/readme.txt: -------------------------------------------------------------------------------- 1 | About "getLoader2Args" 2 | ----------------------- 3 | 4 | Author: Thomas Tempelmann (http://ipodlinux.org/User:Tempel) 5 | Last change: 01Apr06 6 | 7 | Overview 8 | --------- 9 | 10 | getLoader2Args is a tool that runs on the iPod under iPodLinux. 11 | 12 | It is used in conjunction with iPodLoader2 (http://ipodlinux.org/Loader_2) 13 | 14 | It gives you the ability to have multiple choices to launch the same 15 | linux kernel with different options for the "userland". 16 | 17 | Details 18 | -------- 19 | 20 | As of 30Mar06, Loader2 allows to include arguments (text) for the linux 21 | kernel it launches. These arguments can then be read by getLoader2Args. 22 | 23 | To use this feature, you need a use a configuration file (see the docs 24 | for loader2) and add your arguments as text behind the image file name, 25 | separated by a blank (space, not TAB!). 26 | 27 | Example: 28 | 29 | iPodLinux @ (hd0,1)/kernel.bin this is the arg text 30 | 31 | Place the file "getLoader2Args" onto your linux file system (the ext2 32 | or hfs partition), e.g. into /sbin 33 | 34 | When the linux kernel has started, you can put this line into the 35 | /etc/rc file to see these arguments: 36 | 37 | /sbin/getLoader2Args 38 | 39 | When it executes, it should print the text "this is the arg text" 40 | to the console (i.e. the ipod screen). 41 | 42 | If you know your way around with linux, you should be able to do 43 | some more useful with this, e.g. use a shell script that checks the 44 | arg and then either launches an application or does other things. 45 | 46 | Practical example 47 | ------------------ 48 | 49 | Note: the following assumes that you have the improved "minix" shell 50 | installed and not only the simple "sash" shell. How to tell the 51 | difference? Well, if the code below leads just to syntax error messages, 52 | you got the wrong one installed. See http://ipodlinux.org/Minix-sh 53 | for how to install the Minix shell. 54 | 55 | Change your /etc/rc file by removing the last line reading "podzilla" 56 | and add instead these lines: 57 | 58 | if [ -f /bin/getLoader2Args ] ; then 59 | args=`/bin/getLoader2Args` 60 | echo "Args: $args" 61 | fi 62 | if [ "$args" = "" ]; then 63 | podzilla 64 | else 65 | eval $args 66 | fi 67 | 68 | Now, you can define a shell command in the loader2 configuration 69 | file and it will be executed instead of podzilla. E.g, you could 70 | have now these lines inside your config file: 71 | 72 | PodZilla @ (hd0,1)/kernel.bin podzilla 73 | Linux shell @ (hd0,1)/kernel.bin cat /proc/meminfo 74 | 75 | Choosing the first at boot till launch podzilla, while the other 76 | one will show the memory (RAM) info of your Linux OS. 77 | 78 | EOT 79 | -------------------------------------------------------------------------------- /resources/ipodloader2/interrupts.h: -------------------------------------------------------------------------------- 1 | /* 2 | * interrupts.h 3 | * 4 | * Interrupt handling for iPodLoader2 5 | * 6 | * Written 29 Apr 2006 by Thomas Tempelmann (ipod@tempel.org) 7 | */ 8 | 9 | #ifndef INTERRUPTS_H 10 | #define INTERRUPTS_H 11 | 12 | /* PP5002 */ 13 | #define PP5002_IDE_IRQ 1 14 | #define PP5002_SER0_IRQ 4 15 | #define PP5002_I2S_IRQ 5 16 | #define PP5002_SER1_IRQ 7 17 | #define PP5002_TIMER1_IRQ 11 18 | #define PP5002_GPIO_IRQ 14 19 | #define PP5002_DMA_OUT_IRQ 30 20 | #define PP5002_DMA_IN_IRQ 31 21 | 22 | #define PP5002_VALID_IRQ(x) (x==PP5002_IDE_IRQ||x==PP5002_SER0_IRQ||x==PP5002_I2S_IRQ||x==PP5002_SER1_IRQ||x==PP5002_TIMER1_IRQ||x==PP5002_GPIO_IRQ||x==PP5002_DMA_OUT_IRQ||x==PP5002_DMA_IN_IRQ) 23 | 24 | /* PP5020 */ 25 | #define PP5020_TIMER1_IRQ 0 26 | #define PP5020_TIMER2_IRQ 1 27 | #define PP5020_I2S_IRQ 10 28 | #define PP5020_IDE_IRQ 23 29 | #define PP5020_GPIO_IRQ (32+0) 30 | #define PP5020_SER0_IRQ (32+4) 31 | #define PP5020_SER1_IRQ (32+5) 32 | #define PP5020_I2C_IRQ (32+8) 33 | 34 | #define PP5020_VALID_IRQ(x) (x==PP5020_TIMER1_IRQ||x==PP5020_I2S_IRQ||x==PP5020_GPIO_IRQ||x==PP5020_SER0_IRQ||x==PP5020_SER1_IRQ||x==PP5020_I2C_IRQ||x==PP5020_IDE_IRQ) 35 | 36 | 37 | struct pt_regs { 38 | long uregs[17]; 39 | }; 40 | 41 | typedef void (*handle_irq)(int, void *, struct pt_regs *); 42 | 43 | /** 44 | * This call allocates interrupt resources and enables the 45 | * interrupt line and IRQ handling. From the point this 46 | * call is made your handler function may be invoked. Since 47 | * your handler function must clear any interrupt the board 48 | * raises, you must take care both to initialise your hardware 49 | * and to set up the interrupt handler in the right order. 50 | */ 51 | int request_irq (unsigned int irq, handle_irq handler, char is_shared, void *dev_id); 52 | 53 | void disable_irq (unsigned int irq); 54 | void enable_irq (unsigned int irq); 55 | void init_irqs (void); 56 | void exit_irqs (void); 57 | void enable_irqs (void); 58 | int irqs_enabled (); // returns boolean whether the irq system is initialized 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /resources/ipodloader2/ipodhw.h: -------------------------------------------------------------------------------- 1 | #ifndef _IPODHW_H_ 2 | #define _IPODHW_H_ 3 | 4 | #include "bootloader.h" 5 | 6 | #define IPOD_PP5002_RTC 0xCF001110 7 | #define IPOD_PP5020_RTC 0x60005010 8 | #define IPOD_PP5002_LCD_BASE 0xC0001000 9 | #define IPOD_PP5020_LCD_BASE 0x70003000 10 | 11 | #define IPOD_PP5002_IDE_PRIMARY_BASE 0xC00031E0 12 | #define IPOD_PP5002_IDE_PRIMARY_CONTROL 0xC00033F8 13 | #define IPOD_PP5020_IDE_PRIMARY_BASE 0xC30001E0 14 | #define IPOD_PP5020_IDE_PRIMARY_CONTROL 0xC30003F8 15 | 16 | #define IPOD_LCD_FORMAT_2BPP 0x00 17 | #define IPOD_LCD_FORMAT_RGB565 0x01 18 | 19 | typedef struct { 20 | uint32 hw_rev; 21 | uint32 lcd_base, lcd_busy_mask; 22 | uint32 rtc; 23 | uint32 ide_base, ide_control; 24 | uint32 mem_base, mem_size; 25 | uint32 iram_base, iram_full_size, iram_user_end; 26 | int32 lcd_height, lcd_width; 27 | int16 hw_ver; // = hw_rev>>16 28 | uint8 lcd_format; 29 | uint8 lcd_type; 30 | uint8 lcd_is_grayscale; 31 | } ipod_t; 32 | 33 | void ipod_init_hardware(void); 34 | ipod_t *ipod_get_hwinfo(void); 35 | 36 | #define TIMER_SECOND (1000000) 37 | #define TIMER_MINUTE (60000000) 38 | 39 | unsigned long timer_get_current(void); 40 | int timer_passed(unsigned long clock_start, int usecs); 41 | void lcd_wait_ready(void); 42 | void lcd_prepare_cmd(int cmd); 43 | void lcd_send_data(int data_hi, int data_lo); 44 | void lcd_cmd_and_data16(int cmd, uint16 data); 45 | void lcd_cmd_and_data_hi_lo(int cmd, int data_hi, int data_lo); 46 | void lcd_set_contrast(int val); 47 | int lcd_curr_contrast (); 48 | void ipod_set_backlight(int on); 49 | void ipod_reboot (void); 50 | void pcf_standby_mode(void); 51 | void ipod_i2c_init(void); 52 | void ipod_beep(int duration_ms, int period); 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /resources/ipodloader2/keypad.h: -------------------------------------------------------------------------------- 1 | #ifndef _KEYPAD_H_ 2 | #define _KEYPAD_H_ 3 | 4 | /* bitmasks for 4g+; SCRL and SCRR are my own invention. */ 5 | #define IPOD_KEYPAD_SCRL 0x80 6 | #define IPOD_KEYPAD_SCRR 0x40 7 | #define IPOD_KEYPAD_HOLD 0x20 8 | #define IPOD_KEYPAD_MENU 0x10 9 | #define IPOD_KEYPAD_PLAY 0x08 10 | #define IPOD_KEYPAD_PREV 0x04 11 | #define IPOD_KEYPAD_NEXT 0x02 12 | #define IPOD_KEYPAD_ACTION 0x01 13 | 14 | /* buttons returned by keypad_getkey() */ 15 | #define IPOD_KEY_NONE 0 16 | #define IPOD_KEY_SELECT 1 17 | #define IPOD_KEY_FWD 2 18 | #define IPOD_KEY_REW 3 19 | #define IPOD_KEY_PLAY 4 20 | #define IPOD_KEY_MENU 5 21 | 22 | int keypad_getkey(void); 23 | uint8 keypad_getstate(void); 24 | void keypad_init(void); 25 | void keypad_exit(void); 26 | int isHoldEngaged (void); 27 | void keypad_test (void); 28 | void keypad_enable_wheelclicks (int rew_left, int fwd_left); 29 | void keypad_flush(void); 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /resources/ipodloader2/loop.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel5151/clicky/c380a7a00148b1ea9ea62da4e30f9282d7b93913/resources/ipodloader2/loop.bin -------------------------------------------------------------------------------- /resources/ipodloader2/macpartitions.h: -------------------------------------------------------------------------------- 1 | /* 2 | * macpartitions.h 3 | * 4 | * part of "ipodloader2" of the iPodLinux project 5 | * 6 | * purpose: read a MacPod's partition table and create file system handlers 7 | * invoked from vfs.c 8 | * 9 | * written by Thomas Tempelmann (http://ipodlinux.org/User:Tempel) 10 | */ 11 | 12 | void check_mac_partitions (uint8 *blk0); 13 | -------------------------------------------------------------------------------- /resources/ipodloader2/menu.h: -------------------------------------------------------------------------------- 1 | #ifndef _MENU_H_ 2 | #define _MENU_H_ 3 | 4 | #include "bootloader.h" 5 | 6 | #define MAX_MENU_ITEMS 16 // 5G model can show much more than 10 items 7 | 8 | void menu_cls(uint16 *fb); 9 | void menu_init(); 10 | void menu_additem(char *text); 11 | void menu_redraw(uint16 *fb, int selectedItem, char *title, char *countDown, int drawLock); 12 | void menu_drawprogress(uint16 *fb, uint8 completed); 13 | 14 | void menu_drawrect(uint16 *fb, int x1, int y1, int x2, int y2, uint16 color); 15 | void menu_hline (uint16 *fb, int x1, int x2, int y, uint16 color); 16 | void menu_vline (uint16 *fb, int x, int y1, int y2, uint16 color); 17 | void menu_frame (uint16 *fb, int x1, int y1, int x2, int y2, uint16 color); 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /resources/ipodloader2/minilibc.h: -------------------------------------------------------------------------------- 1 | #ifndef _MINILIBC_H_ 2 | #define _MINILIBC_H_ 3 | 4 | #include "bootloader.h" 5 | 6 | /* Assume: width of stack == width of int. Don't use sizeof(char *) or 7 | other pointer because sizeof(char *)==4 for LARGE-model 16-bit code. 8 | Assume: width is a power of 2 */ 9 | #define STACK_WIDTH sizeof(int) 10 | 11 | /* Round up object width so it's an even multiple of STACK_WIDTH. 12 | Using & for division here, so STACK_WIDTH must be a power of 2. */ 13 | #define TYPE_WIDTH(TYPE) \ 14 | ((sizeof(TYPE) + STACK_WIDTH - 1) & ~(STACK_WIDTH - 1)) 15 | 16 | 17 | /* point the va_list pointer to LASTARG, 18 | then advance beyond it to the first variable arg */ 19 | #define mlc_va_start(PTR, LASTARG) \ 20 | PTR = (mlc_va_list)((char *)&(LASTARG) + TYPE_WIDTH(LASTARG)) 21 | 22 | #define mlc_va_end(PTR) /* nothing */ 23 | 24 | /* Increment the va_list pointer, then return 25 | (evaluate to, actually) the previous value of the pointer. 26 | WHEEE! At last; a valid use for the C comma operator! */ 27 | #define mlc_va_arg(PTR, TYPE) ( \ 28 | PTR = (uint8*)PTR + TYPE_WIDTH(TYPE) \ 29 | , \ 30 | *((TYPE *)((char *)(PTR) - TYPE_WIDTH(TYPE))) \ 31 | ) 32 | /* Every other compiler/libc seems to be using 'void *', so... 33 | (I _was_ using 'unsigned char *') */ 34 | typedef void *mlc_va_list; 35 | 36 | int mlc_sprintf(char *buf, const char *fmt, ...); 37 | int mlc_vprintf(const char *fmt, mlc_va_list args); 38 | int mlc_printf(const char *fmt, ...); 39 | 40 | void mlc_malloc_init(void); 41 | void *mlc_malloc(size_t num); 42 | size_t mlc_strlen(const char *); 43 | int mlc_strcmp(const char *s1,const char *s2); 44 | int mlc_strcasecmp(const char *s1,const char *s2); 45 | int mlc_strncmp(const char *s1,const char *s2,size_t maxlen); 46 | int mlc_strncasecmp (const char *s1,const char *s2,size_t maxlen); 47 | size_t mlc_strlcpy(char *dest,const char *src,size_t count); 48 | size_t mlc_strlcat(char *dest,const char *src,size_t count); 49 | void *mlc_memcpy(void *dest,const void *src,size_t n); 50 | void *mlc_memset(void *dest,int c,size_t n); 51 | char *mlc_strchr(const char *s,int c); 52 | char *mlc_strrchr(const char *s,int c); 53 | int mlc_memcmp(const void *sv1,const void *sv2,size_t length); 54 | void mlc_delay_ms (long time_in_ms); 55 | void mlc_delay_us (long time_in_micro_s); 56 | long mlc_atoi (const char *str); 57 | uint16 mlc_atorgb (const char *str, uint16 dft); 58 | void mlc_set_output_options (int buffered, int slow); 59 | void mlc_show_critical_error (); // call this if you can still continue but want to make the user see what you just printed 60 | void mlc_show_fatal_error (); // call this if you can not continue, and want to make the user see what you just printed 61 | void mlc_clear_screen (); 62 | void mlc_hexdump (void* addr, int len); 63 | 64 | #endif 65 | -------------------------------------------------------------------------------- /resources/ipodloader2/unicodecmp.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel5151/clicky/c380a7a00148b1ea9ea62da4e30f9282d7b93913/resources/ipodloader2/unicodecmp.h -------------------------------------------------------------------------------- /resources/ipodloader2/vfs.h: -------------------------------------------------------------------------------- 1 | #ifndef _VFS_H_ 2 | #define _VFS_H_ 3 | 4 | #define VFS_SEEK_CUR 0 5 | #define VFS_SEEK_SET 1 6 | #define VFS_SEEK_END 2 7 | 8 | typedef enum _vfs_type { 9 | FWFS, 10 | EXT2, 11 | FAT32, 12 | HFSPLUS 13 | } vfs_type; 14 | 15 | typedef struct { 16 | int (*open)(void *fsdata,char *fname); 17 | void (*close)(void *fsdata, int fd); 18 | long (*tell)(void *fsdata,int fd); 19 | int (*seek)(void *fsdata,int fd,long offset,int whence); 20 | size_t (*read)(void *fsdata,void *ptr,size_t size,size_t nmemb,int fd); 21 | int (*getinfo)(void *fsdata, int fd, long *out_chksum); 22 | 23 | void *fsdata; 24 | uint8 partnum; 25 | vfs_type type; 26 | } filesystem; 27 | 28 | int vfs_find_part(vfs_type type); 29 | void vfs_init(void); 30 | void vfs_registerfs( filesystem *fs ); 31 | int vfs_open(char *fname); 32 | int vfs_seek(int fd,long offset,int whence); 33 | long vfs_tell(int fd); 34 | size_t vfs_read(void *ptr,size_t size, size_t nmemb,int fd); 35 | int vfs_getinfo(int fd, long *out_chksum); 36 | void vfs_close(int fd); 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /resources/loop.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel5151/clicky/c380a7a00148b1ea9ea62da4e30f9282d7b93913/resources/loop.bin -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | wrap_comments = true -------------------------------------------------------------------------------- /screenshots/clicky-ipodloader2-lle.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel5151/clicky/c380a7a00148b1ea9ea62da4e30f9282d7b93913/screenshots/clicky-ipodloader2-lle.gif -------------------------------------------------------------------------------- /screenshots/clicky-rockbox-boot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel5151/clicky/c380a7a00148b1ea9ea62da4e30f9282d7b93913/screenshots/clicky-rockbox-boot.gif -------------------------------------------------------------------------------- /screenshots/logo-cropped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel5151/clicky/c380a7a00148b1ea9ea62da4e30f9282d7b93913/screenshots/logo-cropped.png -------------------------------------------------------------------------------- /screenshots/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel5151/clicky/c380a7a00148b1ea9ea62da4e30f9282d7b93913/screenshots/logo.png -------------------------------------------------------------------------------- /scripts/rawhd/add_ipodloader_cfg.sh: -------------------------------------------------------------------------------- 1 | DEFAULT_DEBUG_VAL=1 2 | 3 | # requires `mtools` to be installed 4 | if ! [ -x "$(command -v mcopy)" ]; then 5 | echo 'please install `mtools` to use this script' >&2 6 | exit 1 7 | fi 8 | 9 | DEBUG_VAL=${1:-$DEFAULT_DEBUG_VAL} 10 | 11 | cat <ipodloader.conf 12 | # config file 13 | debug = $DEBUG_VAL 14 | timeout = 3 15 | EOM 16 | 17 | IMG_FATPART="ipodhd.img@@$((12288 * 512))" 18 | 19 | mcopy -o -i $IMG_FATPART ipodloader.conf :: 20 | mdir -i $IMG_FATPART 21 | 22 | # cleanup 23 | rm ipodloader.conf 24 | -------------------------------------------------------------------------------- /scripts/rawhd/copy_rockbox.sh: -------------------------------------------------------------------------------- 1 | # takes one argument: a path to the rockbox.zip 2 | # if no argument is given, automatically downloads a rockbox release from the web instead 3 | 4 | if ! [ -x "$(command -v unzip)" ]; then 5 | echo 'please install `unzip` to use this script' >&2 6 | exit 1 7 | fi 8 | 9 | if ! [ -x "$(command -v mcopy)" ]; then 10 | echo 'please install `mtools` to use this script' >&2 11 | exit 1 12 | fi 13 | 14 | ROCKBOX_ZIP_PATH=$1 15 | if [ -z "$1" ]; then 16 | echo "Didn't provide path to self-built rockbox.zip. Downloading a binary instead." 17 | if ! [ -x "$(command -v wget)" ]; then 18 | echo 'please install `wget` to use this script' >&2 19 | exit 1 20 | fi 21 | ROCKBOX_ZIP_PATH=/tmp/rockbox-ipod4g-3.15.zip 22 | wget -nc -P /tmp/ https://download.rockbox.org/release/3.15/rockbox-ipod4g-3.15.zip 23 | fi 24 | 25 | unzip -o $ROCKBOX_ZIP_PATH -d /tmp/rockbox 26 | 27 | IMG_FATPART="ipodhd.img@@$((12288 * 512))" 28 | 29 | mcopy -o -s -i $IMG_FATPART /tmp/rockbox/.rockbox :: 30 | mdir -i $IMG_FATPART 31 | -------------------------------------------------------------------------------- /scripts/rawhd/loopback_delete.sh: -------------------------------------------------------------------------------- 1 | sudo udisksctl loop-delete -b /dev/loop0 2 | -------------------------------------------------------------------------------- /scripts/rawhd/loopback_setup.sh: -------------------------------------------------------------------------------- 1 | sudo udisksctl loop-setup -f ipodhd.img 2 | -------------------------------------------------------------------------------- /scripts/rawhd/make_rawhd.sh: -------------------------------------------------------------------------------- 1 | # makes a 64Mb drive, and optionally copies a firmware binary into the firmware 2 | # partition (if the firmware file is specified as the first arg) 3 | 4 | dd if=/dev/zero of=ipodhd.img count=$((2 * 1024 * 64)) bs=512 5 | sfdisk ipodhd.img << EOM 6 | label: dos 7 | label-id: 0x04206969 8 | device: ipodhd.img 9 | unit: sectors 10 | 11 | ipodhd.img1 : start= 2048, size= 10240, type=0, bootable 12 | ipodhd.img2 : start= 12288, size= 118784, type=b 13 | EOM 14 | 15 | if [ -n "$1" ]; then 16 | dd if=$1 of=ipodhd.img bs=512 seek=2048 conv=notrunc 17 | fi 18 | 19 | dd if=/dev/zero of=ipodhd_fat32.img count=$((118784)) bs=512 20 | mkdosfs -F 32 ipodhd_fat32.img 21 | dd if=ipodhd_fat32.img of=ipodhd.img bs=512 seek=12288 conv=notrunc 22 | 23 | # cleanup 24 | rm ipodhd_fat32.img 25 | -------------------------------------------------------------------------------- /scripts/rawhd/patch_aupd_bit.sh: -------------------------------------------------------------------------------- 1 | # toggles a bit in the firmware to disable running the flash ROM update (which doesn't work rn) 2 | printf "%b" "\001" | dd of=ipodhd.img bs=1 seek=$((0x00104230)) count=1 conv=notrunc 3 | --------------------------------------------------------------------------------