Skip to content

Commit

Permalink
Documentation updates WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
timower committed Oct 3, 2024
1 parent b26e9c2 commit 5457c8d
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 2 deletions.
4 changes: 2 additions & 2 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Collection of reMarkable related apps, utilities and libraries.
Projects
--------

### rm2fb
### [rm2fb](libs/rm2fb)
[![2.15: supported](https://img.shields.io/badge/2.15-supported-brightgreen)](https://support.remarkable.com/s/article/Software-release-2-15-October-2022)
[![3.3: supported](https://img.shields.io/badge/3.3-supported-brightgreen)](https://support.remarkable.com/s/article/Software-release-3-3)
[![3.5: supported](https://img.shields.io/badge/3.5-supported-brightgreen)](https://support.remarkable.com/s/article/Software-release-3-5)
Expand All @@ -31,7 +31,7 @@ More usage information can be found in the yaft [Readme](apps/yaft).

### Rocket

Launcher that uses the power button to show.
Launcher that uses the power button to toggle.

<img src="doc/rocket.png" width=500/>

Expand Down
File renamed without changes.
167 changes: 167 additions & 0 deletions libs/rm2fb/Readme.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,173 @@
reMarkable 2 - Framebuffer
==========================

SWTCON
------

The SWTCON is the software timing controller for the e-ink display in the
remarkable. It's used instead of a hardware one that's embedded in the iMX
processor. Its main job is to take in updates to the framebuffer and drive
the e-ink display with the correct voltages in the correct order to achieve
that change. These sequences are called waveforms, they depend on the
temperature, current state of the display and required speed.

The SWTCON is embedded inside of xochtil, which means we have to 'hook' into it
to be able to draw to the screen from other apps. There are a few functions of
interest in xochitl:
* `createInstance`: Creates the QT EPDObject that owns the SWTCON state and the
software framebuffer (stored inside a QImage).
* `createThreads`: Called by `createInstance`, allocates the required buffers,
locks and semaphores and then starts the generator and vsync threads.
* `generatorThreadRoutine`: A thread that's responsible of taking in
framebuffer updates and generating update frames that will get sent to the
display.
* `vsyncThreadRoutine`: Takes in generated update frames and 'pans' the display
to show the with the correct timing.
* `waitForStartup`: Function called by create instance to wait for the SWTCON
threads to start.

Of course I just made up the names of these functions based on their behaviour.
The exact implementation of these functions differs between versions, and
some are inlined into each other in later (3.5+) xochitl versions.

<details>
<summary>
In pseudo code these functions look like:
</summary>
```cpp
EPDObject*
createInstance() {
if (!QLockFile::tryLock("/tmp/epframebuffer.lock")) {
LogError();
}

QImage screenBuffer;
createThreads(screenBuffer.bits());

wait_for_startup();

puts("SWTCON initialized \\o/");

return new EPDObject(screenBuffer);
}

void
wait_for_startup() {
// clear the framebuffer, not in later xochitl versions.
QImage::fill(param_1 + 0x20);

// Post and wait until the global is flipped.
initGlobal = 1;
pthread_mutex_lock((pthread_mutex_t *)&DAT_008e4a6c);
pthread_cond_broadcast((pthread_cond_t *)&DAT_008e4a88);
pthread_mutex_unlock((pthread_mutex_t *)&DAT_008e4a6c);

while (initGlobal == 1) {
usleep(1000);
}
}

void
createThreads(void* buffer) {
allocateBuffers(buffer); // mallocs a backbuffer, memsets it, ...
loadWaveforms();

int fd = open("/dev/fb0");
mmap(fd, ...);
ioctl(fd); // Set var info, get fix info, ...

updateTemperature(); // Used to select correct waveform

pthread_mutex_init(&VsyncMutex1, NULL);
pthread_mutex_init(&VsyncMutex2, NULL);
pthread_cond_init(&VsyncCond1, NULL);

pthread_create(&VsyncPThread, NULL, vsyncThreadRoutine, NULL);
pthread_setname_np(VsyncPThread,"vsync-flip");
setPriority(VsyncPThread,99);

pthread_mutex_init(&GenMutex1,(pthread_mutexattr_t *)0x0);
pthread_mutex_init(&GenMutex2,(pthread_mutexattr_t *)0x0);
pthread_cond_init(&GenCond1,(pthread_condattr_t *)0x0);

pthread_create(&GenPThread, NULL, generatorThreadRoutine, NULL);
pthread_setname_np(GenPThread,"framegen");
setPriority(GenPThread,0x62);
}

// Inlined into 'do_update'
void
updateGeneratorThread(void)

{
pthread_mutex_lock((pthread_mutex_t *)&GenMutex2);
updateGlobal = 0;
pthread_cond_broadcast((pthread_cond_t *)&GenCond1);
pthread_mutex_unlock((pthread_mutex_t *)&GenMutex2);
return;
}


int
do_update(UpdateParams* params) {
auto updateListElem = makeUpdate(params);
// There's some processing of the update here, and the affected area
// is copied to a separate buffer.

bool syncFlagSet = params->updateFlags & 0x2;
pthread_mutex_lock(&GenMutex1);
// There's some of processing here. I suspect to combine consequtive
// overlapping updates.
std::list::push_back(GlobalList, updateListElem);
pthread_mutex_unlock(&GenMutex1);

updateGeneratorThread();

if (syncFlagSet != 0) {
while (!std::list::empty()) {
usleep(1000);
}
}

return 1;
}

void
EPDObject::update(EPDObject *this, QRect *area, int waveform, int flags)
{
auto rect = this->image.rect();
rect &= area;
if ((rect.x0 <= rect.x1) && (rect.y0 <= rect.y1)) {
UpdateParams params;
switch(waveform) {
case 0:
case 3:
params.waveform = 2;
break;
default:
params.waveform = 0;
break;
case 2:
params.waveform = 3;
break;
case 4:
if (this->someFlag) {
params.waveform = 0;
} else {
params.waveform = 8;
}
break;
case 5:
params.waveform = 1;
}
params.updateFlags = flags;
do_update(&params);
}
}
```
</details>
Waveform modes
--------------
Expand Down

0 comments on commit 5457c8d

Please sign in to comment.