diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6e9e778..43edea4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,8 +1,7 @@ on: [push, pull_request] - name: Continuous integration - jobs: + check: name: Check runs-on: ubuntu-latest @@ -11,33 +10,47 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: nightly - target: armv7-unknown-linux-gnueabihf + toolchain: stable + target: ${{ env.TARGET }} override: true components: rustfmt - uses: actions-rs/cargo@v1 with: command: check use-cross: true - args: --target armv7-unknown-linux-gnueabihf + args: --target ${{ env.TARGET }} test: - name: Test Suite + name: Test Suite on gnueabihf runs-on: ubuntu-latest + env: + TARGET: armv7-unknown-linux-gnueabihf steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: nightly - target: armv7-unknown-linux-gnueabihf + toolchain: stable + target: ${{ env.TARGET }} override: true components: rustfmt - uses: actions-rs/cargo@v1 with: command: test use-cross: true - args: --target armv7-unknown-linux-gnueabihf + args: --target ${{ env.TARGET }} + test-local: + name: Test Suite + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + - uses: actions-rs/cargo@v1 + with: + command: test fmt: name: Rustfmt @@ -47,8 +60,8 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: nightly - target: armv7-unknown-linux-gnueabihf + toolchain: stable + target: ${{ env.TARGET }} override: true components: rustfmt - uses: actions-rs/cargo@v1 @@ -65,11 +78,14 @@ jobs: with: profile: minimal toolchain: stable - target: armv7-unknown-linux-gnueabihf + target: ${{ env.TARGET }} override: true components: rustfmt, clippy - uses: actions-rs/cargo@v1 with: command: clippy use-cross: true - args: --target armv7-unknown-linux-gnueabihf -- -D warnings + args: --target ${{ env.TARGET }} -- -D warnings + +env: + TARGET: armv7-unknown-linux-musleabihf diff --git a/.github/workflows/demo.yml b/.github/workflows/demo.yml index 03f33a7..698a677 100644 --- a/.github/workflows/demo.yml +++ b/.github/workflows/demo.yml @@ -3,7 +3,7 @@ name: Build for reMarkable jobs: demo: - name: Demo + name: Demo with gnueabihf runs-on: ubuntu-latest env: TARGET: armv7-unknown-linux-gnueabihf @@ -11,7 +11,8 @@ jobs: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: - toolchain: nightly + profile: minimal + toolchain: stable target: ${{ env.TARGET }} override: true components: rustfmt @@ -20,13 +21,6 @@ jobs: use-cross: true command: build args: --target ${{ env.TARGET }} --release --example demo - - uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') - with: - files: | - ./target/${{ env.TARGET }}/release/demo - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} demo-with-musl: name: Demo with musl @@ -37,7 +31,8 @@ jobs: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: - toolchain: nightly + profile: minimal + toolchain: stable target: ${{ env.TARGET }} override: true components: rustfmt @@ -46,3 +41,10 @@ jobs: use-cross: true command: build args: --target ${{ env.TARGET }} --release --example demo + - uses: softprops/action-gh-release@v1 + if: startsWith(github.ref, 'refs/tags/') + with: + files: | + ./target/${{ env.TARGET }}/release/demo + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/Cargo.toml b/Cargo.toml index c4d8c71..3527e19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,11 @@ name = "spy" path = "examples/spy.rs" crate-type = ["dylib"] +[[example]] +name = "swtfb_sysv_spy" +path = "examples/swtfb_sysv_spy.rs" +crate-type = ["dylib"] + [[example]] name = "demo" path = "examples/demo.rs" diff --git a/Makefile b/Makefile index e242b70..ab55f4f 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ -# For musl, use: armv7-unknown-linux-musleabihf -TARGET ?= armv7-unknown-linux-gnueabihf +# For non-musl, use: armv7-unknown-linux-gnueabihf +TARGET ?= armv7-unknown-linux-musleabihf DEVICE_IP ?= '10.11.99.1' DEVICE_HOST ?= root@$(DEVICE_IP) @@ -8,10 +8,10 @@ all: library examples .PHONY: examples examples: - cargo build --examples --release --target=$(TARGET) + cargo build --examples --release --target=armv7-unknown-linux-gnueabihf demo: - cargo build --example demo --release --target=$(TARGET) + cargo build --example demo --release --target=armv7-unknown-linux-gnueabihf x-demo: cross build --example demo --release --target=$(TARGET) @@ -22,7 +22,7 @@ deploy-x-demo: x-demo ssh $(DEVICE_HOST) 'RUST_BACKTRACE=1 RUST_LOG=debug ./demo' bench: - cargo build --examples --release --target=$(TARGET) --features "enable-runtime-benchmarking" + cargo build --examples --release --target=armv7-unknown-linux-gnueabihf --features "enable-runtime-benchmarking" .PHONY: docker-env docker-env: @@ -42,10 +42,10 @@ examples-docker: docker-env -v cargo-registry:/home/builder/.cargo/registry \ -w /home/builder/libremarkable \ rust-build-remarkable:latest \ - cargo build --examples --release --target=$(TARGET) + cargo build --examples --release --target=armv7-unknown-linux-gnueabihf library: - cargo build --release --target=$(TARGET) + cargo build --release --target=armv7-unknown-linux-gnueabihf test: # Notice we aren't using the armv7 target here diff --git a/README.md b/README.md index cbc975a..c8e0f7d 100644 --- a/README.md +++ b/README.md @@ -77,9 +77,11 @@ The `--release` argument is important as this enables optimizations and without #### Building with [`cross`](https://github.com/rust-embedded/cross) *Building this way does not require reMarkable's toolchain nor building on Ubuntu 16.04 with Docker so setting up should be easier.* +Install `cross` with `cargo install cross`. Make sure the reMarkable toolchain is not in use first. + To build, deploy and run the `demo`, simply: ```shell -make deploy-x-demo +make TARGET=armv7-unknown-linux-gnueabihf deploy-x-demo # This builds with # cross build --example demo --release --target=armv7-unknown-linux-gnueabihf # then deploys the demo @@ -87,8 +89,7 @@ make deploy-x-demo ##### Using [`musl`](https://musl.libc.org/) Make sure to build with `lto = true` otherwise `musl` symbols may be improperly resolved (call to `mmap` fails). -1. Install `cross` with `cargo install cross` (make sure the reMarkable toolchain is not in use first) -1. Compile with `cross build --example demo --release --target=armv7-unknown-linux-musleabihf` (or `TARGET=armv7-unknown-linux-musleabihf make x-demo`) -1. Run the demo: `TARGET=armv7-unknown-linux-musleabihf make deploy-x-demo` +1. Compile with `cross build --example demo --release --target=armv7-unknown-linux-musleabihf` (or `make x-demo`) +1. Run the demo: `make deploy-x-demo` -**Regarding apps for the rM2**: `musl`-built apps do not write to the framebuffer of rM2 devices as some work is required regarding `LD_PRELOAD` to enable the use of [`rm2fb`](https://github.com/ddvk/remarkable2-framebuffer). In the meantime you should use the `gnueabihf` target. +**Regarding apps for the rM2**: you will need the [display](https://github.com/ddvk/remarkable2-framebuffer) package from [Toltec](https://toltec-dev.org/). Only the server part though as the client is built into this lib. diff --git a/examples/swtfb_sysv_spy.rs b/examples/swtfb_sysv_spy.rs new file mode 100644 index 0000000..7297862 --- /dev/null +++ b/examples/swtfb_sysv_spy.rs @@ -0,0 +1,49 @@ +use libc::{c_int, c_void, key_t, msqid_ds, size_t}; +use redhook::{hook, real}; + +use libremarkable::framebuffer::swtfb_client; + +hook! { + unsafe fn msgget(key: key_t, msgflg: c_int) -> c_int => msgget_spy { + let res = real!(msgget)(key, msgflg); + eprintln!("Spy: msgget({:#x}, {:#x}) => {:#x}", key, msgflg, res); + res + } +} + +hook! { + unsafe fn msgctl(msqid: c_int, cmd: c_int, buf: *mut msqid_ds) -> c_int => msgctl_spy { + let res = real!(msgctl)(msqid, cmd, buf); + eprintln!("Spy: msgctl({:#x}, {:#x}, {:?}) => {}", msqid, cmd, buf, res); + res + } +} + +hook! { + unsafe fn msgsnd(msqid: c_int, msgp: *const c_void, msgsz: size_t, msgflg: c_int) -> c_int => msgsnd_spy { + let res = real!(msgsnd)(msqid, msgp, msgsz, msgflg); + eprintln!("Spy: msgsnd({:#x}, {:?}, {}, {:#x}) => {}", msqid, msgp, msgsz, msgflg, res); + if msgsz == std::mem::size_of::() { + let msg = &*(msgp as *const swtfb_client::swtfb_update); + eprintln!("Spy: msgsnd: Message: swt_update.mtype: {:?}, data: ... }}", msg.mtype); + let data_str_formatted = match msg.mtype { + swtfb_client::MSG_TYPE::INIT_t => { + format!("...") + }, + swtfb_client::MSG_TYPE::UPDATE_t => { + format!("{:?}", msg.data.update) + }, + swtfb_client::MSG_TYPE::XO_t => { + format!("{:?}", msg.data.xochitl_update) + }, + swtfb_client::MSG_TYPE::WAIT_t => { + format!("{:?}", msg.data.wait_update) + } + }; + eprintln!("Spy: msgsnd: Message: swt_update.data: {}", data_str_formatted); + }else { + eprintln!("Spy: msgsnd: Error: Message is not sizeof(swtfb_update) (expected {}, got {})", std::mem::size_of::(), msgsz) + } + res + } +} diff --git a/src/appctx.rs b/src/appctx.rs index 40217e2..1fd488a 100644 --- a/src/appctx.rs +++ b/src/appctx.rs @@ -86,7 +86,9 @@ impl<'a> ApplicationContext<'a> { on_wacom: fn(&mut ApplicationContext<'_>, WacomEvent), on_touch: fn(&mut ApplicationContext<'_>, MultitouchEvent), ) -> ApplicationContext<'static> { - let framebuffer = Box::new(core::Framebuffer::from_path("/dev/fb0")); + let framebuffer = Box::new(core::Framebuffer::from_path( + crate::device::CURRENT_DEVICE.get_framebuffer_path(), + )); let yres = framebuffer.var_screen_info.yres; let xres = framebuffer.var_screen_info.xres; diff --git a/src/device.rs b/src/device.rs index e81e296..d8acfc9 100644 --- a/src/device.rs +++ b/src/device.rs @@ -31,6 +31,22 @@ impl Model { Err(ErrorKind::UnknownVersion(machine_name.to_owned())) } } + + /// Path for gen 1 can be used as long as the rm2fb shim is active: + /// LD_PRELOAD="/opt/lib/librm2fb_client.so.1" or + /// rm2fb-client YOUR_BINARY + /// https://github.com/ddvk/remarkable2-framebuffer/ + /// If `/dev/shm/swtfb.01` is used for the framebuffer, the + /// internal swtfb_client will be used. This enables the use + /// of musl builds. But the rm2fb server must still be installed. + /// + /// TODO: Use proper path (needs breaking change for FramebufferBase::from_path() !) + pub fn framebuffer_path(&self) -> &'static str { + match self { + Model::Gen1 => "/dev/fb0", + Model::Gen2 => "/dev/shm/swtfb.01", + } + } } pub static CURRENT_DEVICE: Lazy = Lazy::new(Device::new); @@ -118,4 +134,8 @@ impl Device { Model::Gen2 => "max77818_battery", } } + + pub fn get_framebuffer_path(&self) -> &'static str { + self.model.framebuffer_path() + } } diff --git a/src/framebuffer/core.rs b/src/framebuffer/core.rs index 525ea1d..6b023ed 100644 --- a/src/framebuffer/core.rs +++ b/src/framebuffer/core.rs @@ -12,6 +12,7 @@ use crate::framebuffer::common::{ MXCFB_ENABLE_EPDC_ACCESS, MXCFB_SET_AUTO_UPDATE_MODE, MXCFB_SET_UPDATE_SCHEME, }; use crate::framebuffer::screeninfo::{FixScreeninfo, VarScreeninfo}; +use crate::framebuffer::swtfb_client::SwtfbClient; /// Framebuffer struct containing the state (latest update marker etc.) /// along with the var/fix screeninfo structs. @@ -25,6 +26,7 @@ pub struct Framebuffer<'a> { /// like it has been done in `Framebuffer::new(..)`. pub var_screen_info: VarScreeninfo, pub fix_screen_info: FixScreeninfo, + pub swtfb_client: Option, } unsafe impl<'a> Send for Framebuffer<'a> {} @@ -32,13 +34,27 @@ unsafe impl<'a> Sync for Framebuffer<'a> {} impl<'a> framebuffer::FramebufferBase<'a> for Framebuffer<'a> { fn from_path(path_to_device: &str) -> Framebuffer<'_> { - let device = OpenOptions::new() - .read(true) - .write(true) - .open(path_to_device) - .unwrap(); + let swtfb_client = if path_to_device == crate::device::Model::Gen2.framebuffer_path() { + Some(SwtfbClient::default()) + } else { + None + }; + + let (device, mem_map) = if let Some(ref swtfb_client) = swtfb_client { + let (device, mem_map) = swtfb_client + .open_buffer() + .expect("Failed to open swtfb shared buffer"); + (device, Some(mem_map)) + } else { + let device = OpenOptions::new() + .read(true) + .write(true) + .open(path_to_device) + .unwrap(); + (device, None) + }; - let mut var_screen_info = Framebuffer::get_var_screeninfo(&device); + let mut var_screen_info = Framebuffer::get_var_screeninfo(&device, swtfb_client.as_ref()); var_screen_info.xres = 1404; var_screen_info.yres = 1872; var_screen_info.rotate = 1; @@ -55,15 +71,19 @@ impl<'a> framebuffer::FramebufferBase<'a> for Framebuffer<'a> { var_screen_info.vmode = 0; // FB_VMODE_NONINTERLACED var_screen_info.accel_flags = 0; - Framebuffer::put_var_screeninfo(&device, &mut var_screen_info); + Framebuffer::put_var_screeninfo(&device, swtfb_client.as_ref(), &mut var_screen_info); - let fix_screen_info = Framebuffer::get_fix_screeninfo(&device); + let fix_screen_info = Framebuffer::get_fix_screeninfo(&device, swtfb_client.as_ref()); let frame_length = (fix_screen_info.line_length * var_screen_info.yres) as usize; - let mem_map = MmapOptions::new() - .len(frame_length) - .map_raw(&device) - .expect("Unable to map provided path"); + let mem_map = if let Some(mem_map) = mem_map { + mem_map + } else { + MmapOptions::new() + .len(frame_length) + .map_raw(&device) + .expect("Unable to map provided path") + }; // Load the font let font_data = include_bytes!("../../assets/Roboto-Regular.ttf"); @@ -75,10 +95,16 @@ impl<'a> framebuffer::FramebufferBase<'a> for Framebuffer<'a> { default_font, var_screen_info, fix_screen_info, + swtfb_client, } } fn set_epdc_access(&mut self, state: bool) { + if self.swtfb_client.is_some() { + // Not caught/handled in rm2fb => noop + return; + } + unsafe { libc::ioctl( self.device.as_raw_fd(), @@ -92,6 +118,12 @@ impl<'a> framebuffer::FramebufferBase<'a> for Framebuffer<'a> { } fn set_autoupdate_mode(&mut self, mode: u32) { + if self.swtfb_client.is_some() { + // https://github.com/ddvk/remarkable2-framebuffer/blob/1e288aa9/src/client/main.cpp#L137 + // Is a noop in rm2fb + return; + } + let m = mode.to_owned(); unsafe { libc::ioctl( @@ -103,6 +135,11 @@ impl<'a> framebuffer::FramebufferBase<'a> for Framebuffer<'a> { } fn set_update_scheme(&mut self, scheme: u32) { + if self.swtfb_client.is_some() { + // Not caught/handled in rm2fb => noop + return; + } + let s = scheme.to_owned(); unsafe { libc::ioctl( @@ -113,26 +150,48 @@ impl<'a> framebuffer::FramebufferBase<'a> for Framebuffer<'a> { }; } - fn get_fix_screeninfo(device: &File) -> FixScreeninfo { + fn get_fix_screeninfo(device: &File, swtfb_client: Option<&SwtfbClient>) -> FixScreeninfo { + if let Some(swtfb_client) = swtfb_client { + return swtfb_client.get_fix_screeninfo(); + } + let mut info: FixScreeninfo = Default::default(); let result = unsafe { ioctl(device.as_raw_fd(), FBIOGET_FSCREENINFO, &mut info) }; assert!(result == 0, "FBIOGET_FSCREENINFO failed"); info } - fn get_var_screeninfo(device: &File) -> VarScreeninfo { + fn get_var_screeninfo(device: &File, swtfb_client: Option<&SwtfbClient>) -> VarScreeninfo { + if let Some(swtfb_client) = swtfb_client { + return swtfb_client.get_var_screeninfo(); + } + let mut info: VarScreeninfo = Default::default(); let result = unsafe { ioctl(device.as_raw_fd(), FBIOGET_VSCREENINFO, &mut info) }; assert!(result == 0, "FBIOGET_VSCREENINFO failed"); info } - fn put_var_screeninfo(device: &File, var_screen_info: &mut VarScreeninfo) -> bool { + fn put_var_screeninfo( + device: &std::fs::File, + swtfb_client: Option<&SwtfbClient>, + var_screen_info: &mut VarScreeninfo, + ) -> bool { + if swtfb_client.is_some() { + // https://github.com/ddvk/remarkable2-framebuffer/blob/1e288aa9/src/client/main.cpp#L214 + // Is a noop in rm2fb + return true; + } + let result = unsafe { ioctl(device.as_raw_fd(), FBIOPUT_VSCREENINFO, var_screen_info) }; result == 0 } fn update_var_screeninfo(&mut self) -> bool { - Self::put_var_screeninfo(&self.device, &mut self.var_screen_info) + Self::put_var_screeninfo( + &self.device, + self.swtfb_client.as_ref(), + &mut self.var_screen_info, + ) } } diff --git a/src/framebuffer/mod.rs b/src/framebuffer/mod.rs index 797548b..c157093 100644 --- a/src/framebuffer/mod.rs +++ b/src/framebuffer/mod.rs @@ -6,6 +6,8 @@ pub mod storage; pub mod io; +pub mod swtfb_client; + pub use cgmath; pub trait FramebufferIO { @@ -119,14 +121,21 @@ pub trait FramebufferBase<'a> { /// Toggles update scheme fn set_update_scheme(&mut self, scheme: u32); /// Creates a FixScreeninfo struct and fills it using ioctl - fn get_fix_screeninfo(device: &std::fs::File) -> screeninfo::FixScreeninfo; + fn get_fix_screeninfo( + device: &std::fs::File, + swtfb_client: Option<&swtfb_client::SwtfbClient>, + ) -> screeninfo::FixScreeninfo; /// Creates a VarScreeninfo struct and fills it using ioctl - fn get_var_screeninfo(device: &std::fs::File) -> screeninfo::VarScreeninfo; + fn get_var_screeninfo( + device: &std::fs::File, + swtfb_client: Option<&swtfb_client::SwtfbClient>, + ) -> screeninfo::VarScreeninfo; /// Makes the proper ioctl call to set the VarScreenInfo. /// You must first update the contents of self.var_screen_info /// and then call this function. fn put_var_screeninfo( device: &std::fs::File, + swtfb_client: Option<&swtfb_client::SwtfbClient>, var_screen_info: &mut screeninfo::VarScreeninfo, ) -> bool; diff --git a/src/framebuffer/mxcfb.rs b/src/framebuffer/mxcfb.rs index fd05cbf..44ea3b5 100644 --- a/src/framebuffer/mxcfb.rs +++ b/src/framebuffer/mxcfb.rs @@ -33,7 +33,7 @@ impl ::std::default::Default for mxcfb_update_marker_data { } } -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] #[repr(C)] pub struct mxcfb_alt_buffer_data { pub phys_addr: u32, @@ -48,7 +48,7 @@ impl ::std::default::Default for mxcfb_alt_buffer_data { } } -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] #[repr(C)] pub struct mxcfb_update_data { pub update_region: mxcfb_rect, diff --git a/src/framebuffer/refresh.rs b/src/framebuffer/refresh.rs index 42fa838..506e59d 100644 --- a/src/framebuffer/refresh.rs +++ b/src/framebuffer/refresh.rs @@ -42,28 +42,22 @@ impl<'a> framebuffer::FramebufferRefresh for core::Framebuffer<'a> { ..Default::default() }; - let pt: *const mxcfb_update_data = &whole; - unsafe { - libc::ioctl(self.device.as_raw_fd(), common::MXCFB_SEND_UPDATE, pt); + let update_succeeded = if let Some(ref swtfb_client) = self.swtfb_client { + swtfb_client.send_mxcfb_update(&whole) + } else { + let pt: *const mxcfb_update_data = &whole; + (unsafe { libc::ioctl(self.device.as_raw_fd(), common::MXCFB_SEND_UPDATE, pt) }) >= 0 + }; + + if !update_succeeded { + warn!("Sending full_refresh update failed!") } if wait_completion { - let mut markerdata = mxcfb_update_marker_data { - update_marker: whole.update_marker, - collision_test: 0, - }; - unsafe { - if libc::ioctl( - self.device.as_raw_fd(), - common::MXCFB_WAIT_FOR_UPDATE_COMPLETE, - &mut markerdata, - ) < 0 - { - warn!("WAIT_FOR_UPDATE_COMPLETE failed after a full_refresh(..)"); - } - } + self.wait_refresh_complete(whole.update_marker) + } else { + whole.update_marker } - whole.update_marker } fn partial_refresh( @@ -126,48 +120,46 @@ impl<'a> framebuffer::FramebufferRefresh for core::Framebuffer<'a> { ..Default::default() }; - let pt: *const mxcfb_update_data = &whole; - unsafe { - libc::ioctl(self.device.as_raw_fd(), common::MXCFB_SEND_UPDATE, pt); + let update_succeeded = if let Some(ref swtfb_client) = self.swtfb_client { + swtfb_client.send_mxcfb_update(&whole) + } else { + let pt: *const mxcfb_update_data = &whole; + (unsafe { libc::ioctl(self.device.as_raw_fd(), common::MXCFB_SEND_UPDATE, pt) }) >= 0 + }; + + if !update_succeeded { + warn!("Sending partial_refresh update failed!") } match mode { PartialRefreshMode::Wait | PartialRefreshMode::DryRun => { - let mut markerdata = mxcfb_update_marker_data { - update_marker: whole.update_marker, - collision_test: 0, - }; - unsafe { - if libc::ioctl( - self.device.as_raw_fd(), - common::MXCFB_WAIT_FOR_UPDATE_COMPLETE, - &mut markerdata, - ) < 0 - { - warn!("WAIT_FOR_UPDATE_COMPLETE failed after a partial_refresh(..)"); - } - } - markerdata.collision_test + self.wait_refresh_complete(whole.update_marker) } PartialRefreshMode::Async => whole.update_marker, } } - fn wait_refresh_complete(&self, marker: u32) -> u32 { + fn wait_refresh_complete(&self, update_marker: u32) -> u32 { + if let Some(ref swtfb_client) = self.swtfb_client { + swtfb_client.wait_for_update_complete(); + // Assume success + return 0; + } + let mut markerdata = mxcfb_update_marker_data { - update_marker: marker, + update_marker, collision_test: 0, }; - unsafe { - if libc::ioctl( + if (unsafe { + libc::ioctl( self.device.as_raw_fd(), common::MXCFB_WAIT_FOR_UPDATE_COMPLETE, &mut markerdata, - ) < 0 - { - warn!("WAIT_FOR_UPDATE_COMPLETE failed"); - } - }; + ) + }) < 0 + { + warn!("WAIT_FOR_UPDATE_COMPLETE failed"); + } markerdata.collision_test } } diff --git a/src/framebuffer/swtfb_client.rs b/src/framebuffer/swtfb_client.rs new file mode 100644 index 0000000..9ba5192 --- /dev/null +++ b/src/framebuffer/swtfb_client.rs @@ -0,0 +1,225 @@ +//! This implements the IPC part of a rM2Framebuffer client to interact with this server: +//! https://github.com/ddvk/remarkable2-framebuffer +//! +//! The client is developed according to the spec here: +//! https://github.com/ddvk/remarkable2-framebuffer/issues/11 + +use super::mxcfb::mxcfb_update_data; +use crate::device; +use crate::framebuffer::screeninfo::{FixScreeninfo, VarScreeninfo}; +use log::warn; +use memmap2::{MmapOptions, MmapRaw}; +use std::ffi::{c_void, CString}; +use std::fs::{File, OpenOptions}; +use std::io::Error as IoError; +use std::os::unix::prelude::AsRawFd; +use std::{env, mem, ptr}; + +const SWTFB_MESSAGE_QUEUE_ID: i32 = 0x2257c; + +pub const WIDTH: i32 = crate::framebuffer::common::DISPLAYWIDTH as i32; +pub const HEIGHT: i32 = crate::framebuffer::common::DISPLAYHEIGHT as i32; + +pub const BUF_SIZE: i32 = WIDTH * HEIGHT * std::mem::size_of::() as i32; // hardcoded size of display mem for rM2 +const SEM_WAIT_TIMEOUT_NS: libc::c_long = 200_000_000; + +/// long on 32 bit is 4 bytes as well!! +#[derive(Debug, Clone, Copy)] +#[repr(i32)] +#[allow(non_camel_case_types)] +#[allow(dead_code)] +pub enum MSG_TYPE { + INIT_t = 1, + UPDATE_t = 2, + XO_t = 3, + WAIT_t = 4, +} + +#[derive(Debug, Clone, Copy)] +#[repr(C)] +#[allow(non_camel_case_types, dead_code)] +pub struct xochitl_data { + pub x1: i32, + pub y1: i32, + pub x2: i32, + pub y2: i32, + + pub waveform: i32, + pub flags: i32, +} + +#[derive(Debug, Clone, Copy)] +#[repr(C)] +#[allow(non_camel_case_types, dead_code)] +pub struct wait_sem_data { + /// C string + pub sem_name: [u8; 512], +} + +/// MSG_TYPE has to match swtfb_update_data !!! +#[derive(Clone, Copy)] +#[repr(C)] +#[allow(non_camel_case_types, dead_code)] +pub struct swtfb_update { + pub mtype: MSG_TYPE, + pub data: swtfb_update_data, + //ms: u64, +} + +#[derive(Clone, Copy)] +#[repr(C)] +#[allow(non_camel_case_types, dead_code)] +pub union swtfb_update_data { + pub xochitl_update: xochitl_data, + pub update: mxcfb_update_data, + pub wait_update: wait_sem_data, +} + +pub struct SwtfbClient { + msqid: i32, + do_wait_ioctl: bool, +} + +impl Default for SwtfbClient { + fn default() -> Self { + assert!( + device::CURRENT_DEVICE.model == device::Model::Gen2, + "SWTFB is not supported on devices other than rM 2" + ); + + let msqid = unsafe { + libc::msgget( + SWTFB_MESSAGE_QUEUE_ID, + libc::IPC_CREAT | libc::SHM_R | libc::SHM_W, + ) + }; + assert!(msqid >= 0); + + Self { + msqid, + do_wait_ioctl: env::var("RM2FB_NO_WAIT_IOCTL").is_err(), + } + } +} + +impl SwtfbClient { + pub fn open_buffer(&self) -> Result<(File, MmapRaw), IoError> { + let device = OpenOptions::new() + .read(true) + .write(true) + .open(crate::device::Model::Gen2.framebuffer_path())?; + let ret = unsafe { libc::ftruncate(device.as_raw_fd(), BUF_SIZE as libc::off_t) }; + if ret < 0 { + return Err(IoError::last_os_error()); + } + let mem_map = MmapOptions::new().len(BUF_SIZE as usize).map_raw(&device)?; + Ok((device, mem_map)) + } + + pub fn send(&self, update: &swtfb_update) -> bool { + unsafe { + let ptr = ptr::addr_of!(*update) as *const c_void; + libc::msgsnd(self.msqid, ptr, mem::size_of::(), 0) == 0 + } + } + + pub fn send_mxcfb_update(&self, update: &mxcfb_update_data) -> bool { + self.send(&swtfb_update { + mtype: MSG_TYPE::UPDATE_t, + data: swtfb_update_data { update: *update }, + }) + } + + /// This function seems to be meant for internal use only. + #[allow(dead_code)] + fn send_xochitl_update(&self, data: &xochitl_data) -> bool { + self.send(&swtfb_update { + mtype: MSG_TYPE::XO_t, + data: swtfb_update_data { + xochitl_update: *data, + }, + }) + } + + pub fn wait_for_update_complete(&self) { + if !self.do_wait_ioctl { + return; + } + + // https://github.com/ddvk/remarkable2-framebuffer/blob/1e288aa9/src/client/main.cpp#L149 + + let sem_name_str = format!("/rm2fb.wait.{}", unsafe { libc::getpid() }); + let mut sem_name = [0u8; 512]; + for (i, byte) in sem_name_str.as_bytes().iter().enumerate() { + sem_name[i] = *byte; + } + self.send_wait_update(&wait_sem_data { sem_name }); + let sem_name_c = CString::new(sem_name_str.as_str()).unwrap(); + let sem = + unsafe { libc::sem_open(sem_name_c.as_ptr() as *const libc::c_char, libc::O_CREAT) }; + + let mut timeout = libc::timespec { + tv_nsec: 0, + tv_sec: 0, + }; + unsafe { + libc::clock_gettime(libc::CLOCK_REALTIME, &mut timeout); + } + timeout.tv_nsec += SEM_WAIT_TIMEOUT_NS; + // Move overflow ns to secs + timeout.tv_sec += timeout.tv_nsec / 1_000_000_000; + timeout.tv_nsec %= 1_000_000_000; + + unsafe { + libc::sem_timedwait(sem, &timeout); + libc::sem_unlink(sem_name_c.as_ptr() as *const libc::c_char); + } + } + + pub fn send_wait_update(&self, wait_update: &wait_sem_data) -> bool { + self.send(&swtfb_update { + mtype: MSG_TYPE::WAIT_t, + data: swtfb_update_data { + wait_update: *wait_update, + }, + }) + } + + pub fn get_fix_screeninfo(&self) -> FixScreeninfo { + // https://github.com/ddvk/remarkable2-framebuffer/blob/1e288aa9/src/client/main.cpp#L217 + let mut screeninfo: FixScreeninfo = unsafe { std::mem::zeroed() }; + //screeninfo.smem_start = mem_map.as_ptr() as u32; // Not used anyway. TODO: Consider adding properly + screeninfo.smem_len = super::swtfb_client::BUF_SIZE as u32; + screeninfo.line_length = + super::swtfb_client::WIDTH as u32 * std::mem::size_of::() as u32; + screeninfo + } + + pub fn get_var_screeninfo(&self) -> VarScreeninfo { + // https://github.com/ddvk/remarkable2-framebuffer/blob/1e288aa9/src/client/main.cpp#L194 + let mut screeninfo: VarScreeninfo = unsafe { std::mem::zeroed() }; + screeninfo.xres = super::swtfb_client::WIDTH as u32; + screeninfo.yres = super::swtfb_client::HEIGHT as u32; + screeninfo.grayscale = 0; + screeninfo.bits_per_pixel = 8 * std::mem::size_of::() as u32; + screeninfo.xres_virtual = super::swtfb_client::WIDTH as u32; + screeninfo.yres_virtual = super::swtfb_client::HEIGHT as u32; + + //set to RGB565 + screeninfo.red.offset = 11; + screeninfo.red.length = 5; + screeninfo.green.offset = 5; + screeninfo.green.length = 6; + screeninfo.blue.offset = 0; + screeninfo.blue.length = 5; + screeninfo + } +} + +impl Drop for SwtfbClient { + fn drop(&mut self) { + if unsafe { libc::msgctl(self.msqid, libc::IPC_RMID, ptr::null_mut()) } != 0 { + warn!("Got an error when attempting to close an ipc queue!") + } + } +}