Skip to main content

Linux environment

note

The host perspective section describes in detail the cartesi-machine command-line utility and the general structure of Cartesi Machines. In order to avoid repetition, this section assumes familiarity with the material presented there.

The most direct way for target developers to familiarize themselves with the embedded Linux environment is to run the Cartesi Machine emulator in interactive mode. The cartesi/playground Docker image comes pre-installed with the emulator and all its support files. Inside the playground, the following command instructs the emulator to load the default machine configuration and run a shell in interactive mode

cartesi-machine -i -- sh

Once executed, the Cartesi Machine boots Linux and drops into an interactive shell (The sh argument in the command-line.)

Running in interactive mode!

.
/ \
/ \
\---/---\ /----\
\ X \
\----/ \---/---\
\ / CARTESI
\ / MACHINE
'

cartesi-machine:~ # cd /bin
cartesi-machine:/bin # ls
arch dmesg linux64 nuke stty
ash dnsdomainname ln pidof su
base32 dumpkmap login ping sync
base64 echo ls pipe_progress tar
busybox egrep lsattr printenv touch
cat false lsblk ps true
chattr fdflush mk_cmds pwd umount
chgrp fgrep mkdir resume uname
chmod findmnt mknod rm usleep
chown getopt mktemp rmdir vi
compile_et grep more run-parts watch
cp gunzip mount sed wdctl
cpio gzip mountpoint setarch zcat
cttyhack hostname mt setpriv
date kill mv setserial
dd link netstat sh
df linux32 nice sleep
cartesi-machine:/bin # cd /usr/bin
cartesi-machine:/usr/bin # ls
7zr fdtget lzcat sqlite3
[ fdtoverlay lzcmp strace
[[ fdtput lzdiff strace-log-merge
ar filecap lzegrep strings
ascii fileop lzfgrep svc
attr fincore lzgrep svok
awk find lzip tail
basename flask lzless taskset
bc flock lzma tee
brotli fold lzmadec telnet
bunzip2 free lzmainfo test
bzcat fuser lzmore tftp
bzcmp genext2fs lzop time
bzdiff getfacl lzopcat tinymembench
bzegrep getfattr make tjs
bzfgrep getopt mcookie top
bzgrep gpg md5sum tr
bzip2 gpg-agent mesg traceroute
bzip2recover gpg-connect-agent microcom truncate
bzless gpg-error mkfifo ts
bzmore gpg-wks-server mkpasswd tty
captest gpgconf mpicalc uclampset
ccat gpgparsemail namei ul
ccdecrypt gpgrt-config netcap uname26
ccencrypt gpgscm newgrp uniq
ccguess gpgsm nl unix2dos
ccrypt gpgsplit nohup unlink
chacl gpgtar normalizer unlzma
chardetect gsl-histogram nproc unlzop
choom gsl-randist npth-config unrar
chrt gunicorn nsenter unshare
chvt head nslookup unxz
cksum hexdump od unzip
clear hexedit openvt uptime
cmp hmac256 passwd utmpdump
col hostid paste uudecode
colcrt id patch uuencode
colrm install printf uuidgen
column ionice prlimit uuidparse
convert-dtsv0 iozone pscap vlock
crc32 ipcmk python w
crontab ipcrm python3 wall
cut ipcs python3.10 watchgnupg
dc irqtop ramspeed wc
deallocvt isosize readlink wget
devio jq realpath whereis
dhrystone kbxutil rename whetstone
dieharder killall renice which
diff ksba-config reset who
dirmngr last resize whoami
dirmngr-client lastb rev write
dirname ldd script xargs
dos2unix less scriptlive xxd
dtc line scriptreplay xz
du linux32 seq xzcat
dumpsexp linux64 setarch xzcmp
e2cp logger setfacl xzdec
e2ln logname setfattr xzdiff
e2ls look setkeycodes xzegrep
e2mkdir lscpu setpriv xzfgrep
e2mv lsipc setsid xzgrep
e2rm lsirq setterm xzless
e2tail lslocks sha1sum xzmore
eject lsns sha256sum yes
em lsof sha3sum zip
env lspci sha512sum zipcloak
expr lsscsi shred zipnote
factor lsusb sl zipsplit
fallocate lua smtpd.py.10
fdtdump luac sort
cartesi-machine:/usr/bin # exit

Halted
Cycles: 194196795

The session shows a user changing the working directory to /bin/ and listing its contents. The user then does the same with directory /usr/bin/, before finally leaving the emulator with the exit command. The point of the exercise is that, from the inside, the environment will be familiar to any regular Unix user.

One of the key differences is that, unlike stand-alone systems, most embedded systems are not self-hosting. None of the utilities visible inside the /usr/bin/ and /bin/ directories were built with a compiler that ran inside a Cartesi Machine. They were built in a separate host system, on which a cross-compiling toolchain for the target architecture has been installed. In the case of Linux, the key elements in the toolchain are the GNU Compiler Collection and the GNU C Library. Support for RISC-V is upstream in the official GCC compiler collection. Nevertheless, building a cross-compiler is time-consuming, even with the help of specialized tools such as crosstool-ng. The Emulator SDK includes a Docker image cartesi/toolchain with the toolchain pre-installed. The same toolchain is available in the cartesi/playground Docker image.

Target "Hello world!"

Other than using a cross-compiler in the host to create executables for a different target platform, cross-development is not that different from hosted development. As an example, consider the simple task of compiling the ubiquitous Hello world! program in the C++ programming language to run in the target. (Printing 5 lines, to at least offer a taste of the programming language.)

hello.cpp
#include <iostream>

int main(int argc, char *argv[]) {
for (int i = 0; i < 5; i++) {
std::cout << i+1 << ": Hello world from C++!\n";
}
return 0;
}

To produce the binary in the playground, run

riscv64-cartesi-linux-gnu-g++ -O2 -o hello-cpp hello.cpp

Note the prefix riscv64-cartesi-linux-gnu- to the typical g++ command. This prefix identifies the cross-compiler. The resulting file is a RISC-V executable suitable for running on the target. This can be see by running the command

file hello-cpp

which produces

hello-cpp: ELF 64-bit LSB executable, UCB RISC-V, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-riscv64-lp64.so.1, for GNU/Linux 5.5.19, with debug_info, not stripped

If the bare gcc command was used instead, the resulting binary would be suitable for running on the host.

The executable can now be placed inside a new hello.ext2 file-system:

mkdir hello
cp hello-cpp hello
genext2fs -b 1024 -d hello hello.ext2

The hello-cpp program can then be run from using the cartesi-machine command-line utility as follows:

cartesi-machine \
--flash-drive=label:hello,filename:hello.ext2 \
-- /mnt/hello/hello-cpp

The output is


.
/ \
/ \
\---/---\ /----\
\ X \
\----/ \---/---\
\ / CARTESI
\ / MACHINE
'

1: Hello world from C++!
2: Hello world from C++!
3: Hello world from C++!
4: Hello world from C++!
5: Hello world from C++!

Halted
Cycles: 76503434

To create the Hello world! program in Rust, first create a Cargo project:

cargo new hello-rust

Then, edit the hello-rust/src/main.rs file so it contains our modified program:

main.rs
fn main() {
for i in 1..6 {
println!("{}: Hello world from Rust!", i);
}
}

Cross-compiling in Rust requires an additional configuration step. To that end, create a file with the following JSON object:

riscv64ima-cartesi-linux-gnu.json
{
"arch": "riscv64",
"code-model": "medium",
"cpu": "generic-rv64",
"crt-static-respected": true,
"data-layout": "e-m:e-p:64:64-i64:64-i128:128-n64-S128",
"dynamic-linking": true,
"env": "gnu",
"executables": true,
"features": "+m,+a",
"has-rpath": true,
"is-builtin": false,
"llvm-abiname": "lp64",
"llvm-target": "riscv64",
"max-atomic-width": 64,
"os": "linux",
"position-independent-executables": true,
"relro-level": "full",
"target-family": ["unix"],
"linker-flavor": "gcc",
"linker": "riscv64-cartesi-linux-gnu-gcc",
"pre-link-args": {
"gcc": []
},
"post-link-args": {
"gcc": [
"-Wl,--allow-multiple-definition",
"-Wl,--start-group,-lc,-lm,-lgcc,-lstdc++,-lsupc++,--end-group"
]
},
"target-pointer-width": "64",
"panic-strategy": "abort"
}

Now invoke Cargo with with the following command-line:

cargo build -Z build-std=std,core,alloc,panic_abort,proc_macro --target riscv64ima-cartesi-linux-gnu.json --release

The compiled program should appear in hello-rust/target/riscv64ima-cartesi-linux-gnu/release/hello-rust.

One of the advantages of running Linux is the large number of well-established software development tools available. By default, the rootfs.ext2 root file-system includes the ash shell, and a Lua interpreter, both of which can be used for scripting.

For example, to run the shell script version of the Hello world! program:

hello.sh
#!/bin/sh

for i in $(seq 1 5); do
echo "$i: Hello world from sh!"
done
cp hello.sh hello
chmod +x hello/hello.sh
genext2fs -b 1024 -d hello hello.ext2
cartesi-machine \
--flash-drive=label:hello,filename:hello.ext2 \
-- /mnt/hello/hello.sh

Running these commands produce an output that is very similar to the C++ version.

The root file-system

The fs/ submodule in the Emulator SDK uses the Buildroot tool to create the root file-system rootfs.ext2 (mounted as /). Buildroot is a highly configurable tool, and an explanation of how to use it to its full potential is beyond the scope of this documentation. Please refer to its manual.

Even relative to other embedded Linux root file-systems, the Cartesi-provided rootfs.ext2 is very simple. The only significant customization is the Cartesi-provided /sbin/init script, which performs a few initialization tasks before handing control to the application chosen by the developer to run inside the Cartesi Machine, and finally shuts down after the application exits.

As is typical in the field, rootfs.ext2 uses BusyBox to consolidate tiny versions of many common UNIX utilities (ls, cd, rm, etc) into a single binary. It also includes a variety of typical command-line utilities, as can be seen in the listings of directories /bin/ and /usr/bin/ above.

Using Buildroot, it is rather easy to add new packages, or to remove unnecessary ones. Hundreds of packages are available for installation. To that end, from inside the Emulator SDK, change into the fs/ directory and run make config. This will bring up a textual menu interface, from which the option Target packages can be selected.

For example, additional scripting languages are available from the Interpreter languages and scripting section. After selecting the options for 4th, lua, qjs, perl, php, python3, ruby, and tcl and replacing the old rootfs.ext2 with the freshly generated one, all these scripting languages become available for use inside the Cartesi Machine.

Here are Hello world! programs for each of these languages:

hello.4th
6 1 do i <# # #> type ." : Hello world from Forth!" cr loop
hello.js
#!/usr/bin/env qjs

for (var i = 1; i <= 5; i++) {
console.log(i + ": Hello world from JavaScript!");
}
hello.lua
#!/usr/bin/env lua

for i = 1, 5 do
print(i .. ": Hello world from Lua!")
end
hello.pl
#!/usr/bin/env perl

for my $i (1..5){
print("$i: Hello from Perl!\n");
}
hello.php
#!/usr/bin/env php
<?php
for ($i = 1; $i <= 5; $i++) {
print "$i: Hello world from PHP!\n";
}
?>
hello.py
#!/usr/bin/env python3

for i in range(1,6):
print("{}: Hello world from Python3".format(i))
hello.rb
#!/usr/bin/env ruby

for i in 1..5 do
puts "%d: Hello world from Ruby!" % i
end
hello.tcl
#!/usr/bin/env tclsh

for {set i 1} {$i <= 5} {incr i} {
puts "$i: Hello world from TCL!"
}

The following shell script invokes all of them:

all.sh
#!/bin/sh

cd $(dirname $0)

./hello-cpp
./hello-rust
4th cxq hello.4th
./hello.lua
./hello.js
./hello.pl
./hello.php
./hello.py
./hello.rb
./hello.sh
./hello.tcl

After adding all these files to hello.ext2 (with execute permissions), the result of the command line

cartesi-machine \
--flash-drive=label:hello,filename:hello.ext2 \
-- "/mnt/hello/all.sh"

is as follows:


.
/ \
/ \
\---/---\ /----\
\ X \
\----/ \---/---\
\ / CARTESI
\ / MACHINE
'

1: Hello world from C++!
2: Hello world from C++!
3: Hello world from C++!
4: Hello world from C++!
5: Hello world from C++!
1: Hello world from Rust!
2: Hello world from Rust!
3: Hello world from Rust!
4: Hello world from Rust!
5: Hello world from Rust!
1: Hello world from Forth!
2: Hello world from Forth!
3: Hello world from Forth!
4: Hello world from Forth!
5: Hello world from Forth!
1: Hello world from Lua!
2: Hello world from Lua!
3: Hello world from Lua!
4: Hello world from Lua!
5: Hello world from Lua!
1: Hello world from JavaScript!
2: Hello world from JavaScript!
3: Hello world from JavaScript!
4: Hello world from JavaScript!
5: Hello world from JavaScript!
1: Hello world from Perl!
2: Hello world from Perl!
3: Hello world from Perl!
4: Hello world from Perl!
5: Hello world from Perl!
1: Hello world from PHP!
2: Hello world from PHP!
3: Hello world from PHP!
4: Hello world from PHP!
5: Hello world from PHP!
1: Hello world from Python3
2: Hello world from Python3
3: Hello world from Python3
4: Hello world from Python3
5: Hello world from Python3
1: Hello world from Ruby!
2: Hello world from Ruby!
3: Hello world from Ruby!
4: Hello world from Ruby!
5: Hello world from Ruby!
1: Hello world from sh!
2: Hello world from sh!
3: Hello world from sh!
4: Hello world from sh!
5: Hello world from sh!
1: Hello world from TCL!
2: Hello world from TCL!
3: Hello world from TCL!
4: Hello world from TCL!
5: Hello world from TCL!

Halted
Cycles: 205939605

The take-away message is that developers can use the tools they are most familiar with to accomplish the task at hand.

note

Note that your cycle count may vary, since your new rootfs.ext2 may differ from the one used to produce the results above.

note

As of version 0.4.0 of the rootfs.ext2, the Ruby interpreter does not compile. We are working on a fix.

Flash drives

Flash drives are simply regions of physical memory under the control of Linux's mtd-ram driver. The flash drives 07 receive device names flash.0flash.7, and the driver makes them accessible as block devices /dev/mtdblock0/dev/mtdblock7.

The kernel command-line parameters rootfstype=ext2 root=/dev/mtdblock0 rw declare that the root file-system is of type ext2, that it resides in device /dev/mtdblock0, i.e., flash drive 0, and that it should be mounted read-write. Partitioning information for flash drives and, in particular, custom labels can be specified with the mtdparts parameter in the Linux kernel command line. The format for the parameter is documented in the source-code for the kernel module responsible for parsing it. For example, the parameter mtdparts=flash.0:-(root) specifies a single partition with label root for flash.0.

A flash drive holds whatever data is made available by the emulator in the corresponding target physical memory region. The data can come from an image file specified during machine instantiation, from an image file specified after instantiation via the machine:replace_memory_range(<memory_range_config>), or through external state access method machine:write_memory().

The Cartesi-provided /sbin/init script scans flash drives 17 for valid file-systems. When a valid file-system is detected, the script automatically mounts the file-system at /mnt/<label>, using the corresponding <label> from the mtdparts kernel parameter. In this fashion, file-systems present in all flash drives are available for use right after Linux boots.

This was the case with the command

cartesi-machine \
--flash-drive=label:hello,filename:hello.ext2 \
-- "/mnt/hello/all.sh"

The cartesi-machine command-line utility instructed the emulator to add a new flash drive, initialized with the contents of the hello.ext2 image file. It gave the label hello to that flash drive using the kernel command-line parameter mtdparts=flash.0:-(root);flash.1:-(hello). The /sbin/init script identified a valid file-system in the device, and used its label to mount it at /mnt/hello. It then executed the command /mnt/hello/all.sh, causing all the Hello world! messages to be printed to screen.

Raw flash drives

Raw flash drives, i.e., flash drives containing free-format data, are not mounted. Instead, the data in raw flash drives are read from/written to directly by accessing the underlying block device. The layout and contents of data written to raw flash drives is completely up to application developers.

Depending on the layout and contents, it may be simple or difficult to read from/write to raw flash drives from the command line. The most popular tool for reading and writing block devices is the dd command-line utility. Another alternative is the devio tool. Some scripting languages, like the Lua programming language, have packing and unpacking libraries that can be very helpful.

For example, consider the previously discussed Cartesi Machine that operates as an arbitrary-precision calculator

\rm -f output.raw
truncate -s 4K output.raw
echo "6*2^1024 + 3*2^512" > input.raw
truncate -s 4K input.raw
cartesi-machine \
--flash-drive="label:input,length:1<<12,filename:input.raw" \
--flash-drive="label:output,length:1<<12,filename:output.raw,shared" \
-- $'dd status=none if=$(flashdrive input) | lua -e \'print((string.unpack("z", io.read("a"))))\' | bc | dd status=none of=$(flashdrive output)'
luapp5.3 -e 'print((string.unpack("z", io.read("a"))))' < output.raw

The input is a null-terminated string containing the expression to be evaluated. This string is stored inside a raw flash drive with label input. The output is once again a null-terminated string with the result, this time stored inside a raw flash drive with label output.

The command executed inside the machine is

dd status=none if=$(flashdrive input) | \
lua -e 'print((string.unpack("z", io.read("a"))))' | \
bc | \
dd status=none of=$(flashdrive output)

The flashdrive command-line utility prints the name of the device corresponding to a given label. In this case, flashdrive input prints /dev/mtdblock1 and flashdrive output prints /dev/mtdblock2 (recall /dev/mtdblock0 is the root file-system, defined by default to load the rootfs.ext2 image).

The first command, dd status=none if=$(flashdrive input) therefore reads the entire 4KiB of the raw input flash drive and sends it to the standard output. The second command, lua -e 'print((string.unpack("z", io.read("a"))))' extracts the first null-terminated string and prints it to standard out. This is the meaning of the "z" format argument to the string.unpack() function. There are a variety of other formats available, including reading integers of different sizes, big- or little-endian etc. Please see the documentation for the string.unpack() function for more details. The string is received by the bc command-line utility. In the example, that string is 6*2^1024 + 3*2^512\n. The bc command-line utility computes the value of the expression and sends it to standard out. This is finally received by the last command, dd status=none of=$(flashdrive output), which writes it to the raw output flash drive. (No need to null-terminate, since the drive is already completely filled with zeros.)

Initialization

By default, a Cartesi Machine starts its execution from the image loaded into ROM. In order to boot Linux, the Cartesi-provided rom.bin image first builds a devicetree describing the hardware. The organization of a Cartesi Machine is defined during machine instantiation from its configuration. This includes the number, starts, and lengths of all flash drives and rollup memory ranges, the amount of RAM, and which HTIF commands are supported (yield manual, yield automatic, console getchar etc). The rom.bin program reads a Cartesi-specific low-level description of this organization from special machine registers and translates it into a devicetree that Linux can understand. The configuration also includes the initial contents of ROM, RAM, all flash drives and rollup memory ranges, all registers, and the command-line parameters to be passed to the Linux kernel. The latter is also added to the devicetree.

Once the devicetree is ready, rom.bin jumps to the image loaded into RAM, passing the address of the devicetree (which resides at the end of RAM) in a register. The Cartesi-provided linux.bin image is composed of the Linux kernel linked with the Berkeley Boot Loader (BBL). BBL is a thin abstraction layer that isolates Linux from details of the particular RISC-V machine on which it is running. The abstraction layer gives Linux the ability to perform tasks such as powering the machine down and outputting a character to the console. Once this functionality has been installed, BBL jumps to the kernel entrypoint. The Linux kernel reads the devicetree to find out about the machine organization, loads the appropriate drivers, and performs its own initialization.

When the kernel initialization is complete, it tries to mount a root file-system. The information of where this root file-system resides comes from the kernel command-line parameter. In normal situations, this will reside in /dev/mtdblock0. Once the root file-system is mounted, the kernel executes /sbin/init.

The Cartesi-provided /sbin/init script in rootfs.ext2 sets up a basic Linux environment on which applications can run. In particular, it goes over the available flash drive devices (/dev/mtdblock1/dev/mtdblock7) looking for valid file-systems, and mounting them at the appropriate /mnt/<label> mount points. The Linux kernel passes to /sbin/init, unmodified, everything after the separator -- in its own command-line. Once its initialization tasks are complete, the Cartesi-provided /sbin/init concatenates all its arguments into a string and executes them in a shell.

This is how the commands passed to cartesi-machine come to be executed in the Linux environment that runs inside the Cartesi Machine. Given a proper rootfs.ext2 and an appropriate command-line, the applications can run any general computation, consuming input from any flash drives, and writing outputs to any flash drives. Once the application exits, control returns to /sbin/init. The script then unmounts all file-systems and gracefully halts the machine.

Cartesi-specific devices

The Linux kernel produced in the kernel/ directory of the Cartesi Machine SDK includes two Cartesi-specific device drivers, accessible via /dev/yield and /dev/rollup. The /dev/yield device allows target applications to return control back to the host, while signaling a variety of conditions that may require its attention. The /dev/rollup device allows target applications to interact with Cartesi Rollups, receiving requests and returning responses. (Internally, the /dev/rollup device uses the /dev/yield device.)

ioctl for /dev/yield

The /dev/yield device can be controlled directly via its ioctl interface. This is how the /opt/cartesi/bin/yield command-line utility operates. The only ioctl request code exported is IOCTL_YIELD. It takes as argument a structure yield_request defined as follows:

struct yield_request {
__u8 dev;
__u8 cmd;
__u16 reason;
__u32 data;
};

The dev field must take the value HTIF_DEVICE_YIELD.

The cmd field can take one of two values: HTIF_YIELD_MANUAL or HTIF_YIELD_AUTOMATIC. Sending either a manual yield or an automatic yield command to the device causes the emulator to return control to the host, giving it access to the reason and data fields.

Manual yields are used when the target application needs some kind of manual intervention from the host that modifies the machine state before resuming, typically when it needs some kind of input or throws an exception. In that case, the Y bit in the iflags CSR will be set, and must be externally reset before the machine can continue executing. Automatic yields are used when the target application has produced some data for the host, and can be resumed without further action, automatically. The X bit in the iflags CSR will be set instead, and will be automatically reset when the machine is resumed.

The HTIF_YIELD_REASON_PROGRESS value for the reason field is used with automatic yields. In this case, the value of the data field should contain an integer with the progress in parts per thousand.

The remaining values for the reason field are in conjunction with the /dev/rollup device.

ioctl for /dev/rollup

The /dev/rollup device also exposes an ioctl interface. It exports a variety of ioctl requests. These are used, for example, by the /opt/cartesi/bin/rollup and /opt/cartesi/bin/rollup-http-server command-line utilities.

Recall that Cartesi Rollups is a mechanism by which a target application receives requests for processing and produces responses to those requests. There are two types of rollup requests: advance-state requests and inspect-state requests. There are four types of rollup responses: vouchers, notices, reports, and exceptions.

The ioctl request IOCTL_ROLLUP_FINISH is used to transition between one rollup request to the next. It takes as argument a structure rollup_finish defined as follows:

struct rollup_finish {
/* True if previous request should be accepted */
/* False if previous request should be rejected */
bool accept_previous_request;

int next_request_type; /* either CARTESI_ROLLUP_ADVANCE or CARTESI_ROLLUP_INSPECT */
int next_request_payload_length;
};

The accept_previous_request field is set to true when accepting the previous request, or to false when rejecting it. As a result, the /dev/rollup device will issue an yield manual command to the /dev/yield device, passing as reason field, respectively, HTIF_YIELD_REASON_RX_ACCEPTED or HTIF_YIELD_REASON_RX_REJECTED. Upon return, the value of field next_request_type will contain CARTESI_ROLLUP_ADVANCE if the next request is an advance-state request, or CARTESI_ROLLUP_INSPECT if the next request is an inspect-state request. Moreover, the next_request_payload_length field will contain the length of the request payload.

To obtain the advance-state request data, the target application should then use the ioctl request IOCTL_ROLLUP_READ_ADVANCE_STATE. It takes as argument a structure rollup_advance_state defined as follows:

struct rollup_bytes {
unsigned char *data;
uint64_t length;
};

struct rollup_input_metadata {
uint8_t msg_sender[CARTESI_ROLLUP_ADDRESS_SIZE];
uint64_t block_number;
uint64_t timestamp;
uint64_t epoch_index;
uint64_t input_index;
};

struct rollup_advance_state {
struct rollup_input_metadata metadata;
struct rollup_bytes payload;
};

The payload field should contain a rollup_bytes structure, where the payload.data points to a buffer that can hold payload.length bytes. Note that the value of payload.length should be no less than the value of next_request_payload_length returned in the rollup_finish argument to the previous ioctl request IOCTL_ROLLUP_FINISH. Upon return, the payload.data buffer will contain the advance-state request payload. This data comes from what the host wrote to the rollup.rx_buffer memory range. In addition, the metadata field will contain all the associated input metadata. This data comes from what the host wrote to the rollup.input_metadata memory range.

To obtain the inspect-state request data, the target application should then use the ioctl request IOCTL_ROLLUP_READ_ADVANCE_STATE. It takes as argument a structure rollup_advance_state defined as follows:

struct rollup_inspect_state {
struct rollup_bytes payload;
};

The payload field should contain a rollup_bytes structure, where the payload.data points to a buffer that can hold payload.length bytes. Note that the value of payload.length should be no less than the value of next_request_payload_length returned in the rollup_finish argument to the previous ioctl request IOCTL_ROLLUP_FINISH. Upon return, the payload.data buffer will contain the inspect-state request payload. This data comes from what the host wrote to the rollup.rx_buffer memory range.

While processing a request, to produce a voucher, the target application should use the ioctl request IOCTL_ROLLUP_WRITE_VOUCHER. It takes as argument a structure rollup_voucher defined as follows:

struct rollup_voucher {
uint8_t address[CARTESI_ROLLUP_ADDRESS_SIZE];
struct rollup_bytes payload;
uint64_t index;
};

The address field should contain the desired voucher address. The payload field should contain a rollup_bytes structure with the desired voucher payload. The /dev/rollup device copies this data to the rollup.tx_buffer memory range for the host to read. Then, the /dev/rollup device issues an yield automatic command to the /dev/yield device, passing as reason field HTIF_YIELD_REASON_TX_VOUCHER. Upon return, the index field contains the index of the emitted voucher.

While processing a request, to produce a rollup notice, the target application should use the ioctl request IOCTL_ROLLUP_WRITE_NOTICE. It takes as argument a structure rollup_notice defined as follows:

struct rollup_notice {
struct rollup_bytes payload;
uint64_t index;
};

The payload field should contain a rollup_bytes structure with the desired notice payload. The /dev/rollup device copies this data to the rollup.tx_buffer memory range for the host to read. Then, the /dev/rollup device issues an yield automatic command to the /dev/yield device, passing as reason field HTIF_YIELD_REASON_TX_NOTICE. Upon return, the index field contains the index of the emitted notice.

While processing a request, to produce a rollup report, the target application should use the ioctl request IOCTL_ROLLUP_WRITE_REPORT. It takes as argument a structure rollup_report defined as follows:

struct rollup_report {
struct rollup_bytes payload;
};

The payload field should contain a rollup_bytes structure with the desired report payload. The /dev/rollup device copies this data to the rollup.tx_buffer memory range for the host to read. Then, the /dev/rollup device issues an yield automatic command to the /dev/yield device, passing as reason field HTIF_YIELD_REASON_TX_REPORT.

Finally, to throw a rollup exception, the target application should use the ioctl request IOCTL_ROLLUP_THROW_EXCEPTION. It takes as argument a structure rollup_exception defined as follows:

struct rollup_exception {
struct rollup_bytes payload;
};

The payload field should contain a rollup_bytes structure with the desired exception payload. The /dev/rollup device copies this data to the rollup.tx_buffer memory range for the host to read. Then, the /dev/rollup device issues an yield manual command to the /dev/yield device, passing as reason field HTIF_YIELD_REASON_TX_EXCEPTION.

© 2024 Cartesi Foundation Ltd. All rights reserved.

The Cartesi Project is commissioned by the Cartesi Foundation.

We use cookies to ensure that we give you the best experience on our website. By using the website, you agree to the use of cookies.