RPMsg inter-partition messaging
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(openamp,rpmsg): add remoteproc control and RPMsg IPC libraries

Add ocaml-openamp: wraps the Linux remoteproc sysfs interface for
managing remote processors. On Zynq UltraScale+ K26, controls the R5
co-processor from SpaceOS (Linux on A53). Includes testability hooks
for overriding sysfs/firmware paths.

Add ocaml-rpmsg: wraps the Linux RPMsg character device interface for
inter-partition messaging via virtio vrings. Defines the
rpmsg_endpoint_info wire format using Wire.Codec. Supports Jailhouse,
Xen, and Zynq UltraScale+ platforms.

+566
+1
.ocamlformat
··· 1 + version=0.28.1
+22
dune-project
··· 1 + (lang dune 3.21) 2 + 3 + (name rpmsg) 4 + 5 + (generate_opam_files true) 6 + 7 + (license ISC) 8 + (authors "Thomas Gazagnaire <thomas@gazagnaire.org>") 9 + (maintainers "Thomas Gazagnaire <thomas@gazagnaire.org>") 10 + 11 + (package 12 + (name rpmsg) 13 + (synopsis "RPMsg inter-partition messaging for SpaceOS") 14 + (description 15 + "OCaml bindings to the Linux RPMsg character device interface for inter-partition messaging. Used for IPC between SpaceOS (Linux) and co-processor partitions via virtio vrings on Jailhouse, Xen, and Zynq UltraScale+ platforms.") 16 + (depends 17 + (ocaml (>= 5.1)) 18 + (eio (>= 1.0)) 19 + (eio_main (>= 1.0)) 20 + (fmt (>= 0.9)) 21 + wire 22 + (alcotest :with-test)))
+10
fuzz/dune
··· 1 + (executable 2 + (name fuzz_rpmsg) 3 + (modules fuzz_rpmsg fuzz_common fuzz_wire fuzz_endpoint) 4 + (libraries rpmsg eio eio_main crowbar fmt wire)) 5 + 6 + (rule 7 + (alias fuzz) 8 + (deps fuzz_rpmsg.exe) 9 + (action 10 + (run %{exe:fuzz_rpmsg.exe})))
+4
fuzz/fuzz_common.ml
··· 1 + (** Common utilities for RPMsg fuzz tests. *) 2 + 3 + let truncate s = String.sub s 0 (min (String.length s) 512) 4 + let run () = ()
+58
fuzz/fuzz_endpoint.ml
··· 1 + (** Fuzz tests for Rpmsg.Endpoint module. 2 + 3 + Tests message transport via Eio pipes (simulating /dev/rpmsgN). Ioctl-based 4 + Ctrl operations require Linux and are not fuzzed. *) 5 + 6 + open Crowbar 7 + 8 + (** Send/recv roundtrip via Eio pipe - arbitrary messages must round-trip. *) 9 + let test_pipe_roundtrip buf = 10 + let buf = Fuzz_common.truncate buf in 11 + if String.length buf = 0 then () 12 + else 13 + Eio_main.run @@ fun env -> 14 + Eio.Switch.run @@ fun sw -> 15 + let source, sink = Eio.Process.pipe ~sw (Eio.Stdenv.process_mgr env) in 16 + let ep = Rpmsg.Endpoint.of_source_sink ~source ~sink in 17 + Rpmsg.Endpoint.send ep buf; 18 + match Rpmsg.Endpoint.recv ep ~max_size:512 with 19 + | None -> fail "expected message" 20 + | Some received -> if received <> buf then fail "roundtrip mismatch" 21 + 22 + (** Max-size messages must round-trip. *) 23 + let test_max_size_message buf = 24 + let max = Rpmsg.Endpoint.max_message_size in 25 + let buf = String.sub buf 0 (min (String.length buf) max) in 26 + if String.length buf = 0 then () 27 + else 28 + Eio_main.run @@ fun env -> 29 + Eio.Switch.run @@ fun sw -> 30 + let source, sink = Eio.Process.pipe ~sw (Eio.Stdenv.process_mgr env) in 31 + let ep = Rpmsg.Endpoint.of_source_sink ~source ~sink in 32 + Rpmsg.Endpoint.send ep buf; 33 + match Rpmsg.Endpoint.recv ep ~max_size:max with 34 + | None -> fail "expected message" 35 + | Some received -> 36 + if received <> buf then fail "max size roundtrip mismatch" 37 + 38 + (** Binary payloads with null bytes must round-trip. *) 39 + let test_binary_payload b1 b2 b3 b4 = 40 + let buf = 41 + String.init 4 (fun i -> 42 + Char.chr ((match i with 0 -> b1 | 1 -> b2 | 2 -> b3 | _ -> b4) mod 256)) 43 + in 44 + Eio_main.run @@ fun env -> 45 + Eio.Switch.run @@ fun sw -> 46 + let source, sink = Eio.Process.pipe ~sw (Eio.Stdenv.process_mgr env) in 47 + let ep = Rpmsg.Endpoint.of_source_sink ~source ~sink in 48 + Rpmsg.Endpoint.send ep buf; 49 + match Rpmsg.Endpoint.recv ep ~max_size:4 with 50 + | None -> fail "expected message" 51 + | Some received -> if received <> buf then fail "binary roundtrip mismatch" 52 + 53 + let run () = 54 + add_test ~name:"endpoint: pipe roundtrip" [ bytes ] test_pipe_roundtrip; 55 + add_test ~name:"endpoint: max size message" [ bytes ] test_max_size_message; 56 + add_test ~name:"endpoint: binary payload" 57 + [ uint8; uint8; uint8; uint8 ] 58 + test_binary_payload
+6
fuzz/fuzz_rpmsg.ml
··· 1 + (** Main entry point for RPMsg fuzz tests. *) 2 + 3 + let () = 4 + Fuzz_common.run (); 5 + Fuzz_wire.run (); 6 + Fuzz_endpoint.run ()
+26
fuzz/fuzz_wire.ml
··· 1 + (** Fuzz tests for Rpmsg wire codec. *) 2 + 3 + open Crowbar 4 + 5 + (** Codec roundtrip - encode then decode must preserve values. *) 6 + let test_codec_roundtrip name src dst = 7 + let info = Rpmsg.{ name; src; dst } in 8 + let buf = Bytes.create Rpmsg.endpoint_info_size in 9 + Wire.Codec.encode Rpmsg.endpoint_info_codec info buf 0; 10 + let decoded = Wire.Codec.decode Rpmsg.endpoint_info_codec buf 0 in 11 + if decoded.src <> src then fail "src mismatch"; 12 + if decoded.dst <> dst then fail "dst mismatch" 13 + 14 + (** Decode arbitrary bytes - must not crash. *) 15 + let test_decode_crash_safety buf = 16 + if String.length buf < Rpmsg.endpoint_info_size then () 17 + else 18 + let b = Bytes.of_string buf in 19 + let _ = Wire.Codec.decode Rpmsg.endpoint_info_codec b 0 in 20 + () 21 + 22 + let run () = 23 + add_test ~name:"wire: codec roundtrip" 24 + [ bytes; range 65536; range 65536 ] 25 + test_codec_roundtrip; 26 + add_test ~name:"wire: decode crash safety" [ bytes ] test_decode_crash_safety
+7
lib/dune
··· 1 + (library 2 + (name rpmsg) 3 + (public_name rpmsg) 4 + (foreign_stubs 5 + (language c) 6 + (names rpmsg_stubs)) 7 + (libraries eio fmt unix wire))
+104
lib/rpmsg.ml
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Thomas Gazagnaire. All rights reserved. 3 + SPDX-License-Identifier: ISC 4 + ---------------------------------------------------------------------------*) 5 + 6 + (** {1 RPMsg endpoint info struct} 7 + 8 + Matches Linux kernel [struct rpmsg_endpoint_info]: 9 + {v 10 + struct rpmsg_endpoint_info { 11 + char name[32]; /* endpoint name */ 12 + __u32 src; /* source address */ 13 + __u32 dst; /* destination address */ 14 + }; 15 + v} 16 + 17 + Encoded via Wire.Codec to avoid manual C struct packing. *) 18 + 19 + type endpoint_info = { name : string; src : int; dst : int } 20 + 21 + let endpoint_info_codec = 22 + let open Wire.Codec in 23 + record "rpmsg_endpoint_info" (fun name src dst -> { name; src; dst }) 24 + |+ field "name" 25 + (Wire.byte_array ~size:(Wire.int 32)) 26 + (fun t -> 27 + (* Pad/truncate name to exactly 32 bytes *) 28 + let b = Bytes.make 32 '\x00' in 29 + let len = min (String.length t.name) 31 in 30 + Bytes.blit_string t.name 0 b 0 len; 31 + Bytes.to_string b) 32 + |+ field "src" Wire.uint32 (fun t -> t.src) 33 + |+ field "dst" Wire.uint32 (fun t -> t.dst) 34 + |> seal 35 + 36 + let endpoint_info_size = Wire.Codec.wire_size endpoint_info_codec 37 + 38 + (** RPMSG ioctl commands. 39 + 40 + [RPMSG_CREATE_EPT_IOCTL = _IOW(0xb5, 0x1, struct rpmsg_endpoint_info)] 41 + [RPMSG_DESTROY_EPT_IOCTL = _IOW(0xb5, 0x2, struct rpmsg_endpoint_info)] 42 + 43 + _IOW(type, nr, size) = (1 << 30) | (size << 16) | (type << 8) | nr *) 44 + let _iow typ nr size = (1 lsl 30) lor (size lsl 16) lor (typ lsl 8) lor nr 45 + 46 + let rpmsg_create_ept_ioctl = _iow 0xb5 0x1 endpoint_info_size 47 + let rpmsg_destroy_ept_ioctl = _iow 0xb5 0x2 endpoint_info_size 48 + 49 + external rpmsg_ioctl : Unix.file_descr -> int -> bytes -> int 50 + = "caml_rpmsg_ioctl" 51 + 52 + module Ctrl = struct 53 + type t = { fd : Unix.file_descr } 54 + 55 + let open_ ?(index = 0) () = 56 + let path = Fmt.str "/dev/rpmsg_ctrl%d" index in 57 + let fd = Unix.openfile path [ Unix.O_RDWR ] 0 in 58 + { fd } 59 + 60 + let encode_info ~name ~src ~dst = 61 + let info = { name; src; dst } in 62 + let buf = Bytes.create endpoint_info_size in 63 + Wire.Codec.encode endpoint_info_codec info buf 0; 64 + buf 65 + 66 + let create_endpoint t ~name ~src ~dst = 67 + let buf = encode_info ~name ~src ~dst in 68 + rpmsg_ioctl t.fd rpmsg_create_ept_ioctl buf 69 + 70 + let destroy_endpoint t ~name ~src ~dst = 71 + let buf = encode_info ~name ~src ~dst in 72 + ignore (rpmsg_ioctl t.fd rpmsg_destroy_ept_ioctl buf) 73 + 74 + let close t = Unix.close t.fd 75 + end 76 + 77 + module Endpoint = struct 78 + type t = { 79 + recv_fn : max_size:int -> string option; 80 + send_fn : string -> unit; 81 + close_fn : unit -> unit; 82 + } 83 + 84 + let max_message_size = 496 85 + 86 + let of_source_sink ~source ~sink = 87 + let recv_fn ~max_size = 88 + let buf = Cstruct.create max_size in 89 + match Eio.Flow.single_read source buf with 90 + | n -> Some (Cstruct.to_string ~len:n buf) 91 + | exception End_of_file -> None 92 + in 93 + let send_fn msg = Eio.Flow.copy_string msg sink in 94 + { recv_fn; send_fn; close_fn = ignore } 95 + 96 + let open_ index = 97 + let path = Fmt.str "/dev/rpmsg%d" index in 98 + let fd = Unix.openfile path [ Unix.O_RDWR ] 0 in 99 + fd 100 + 101 + let recv t = t.recv_fn 102 + let send t msg = t.send_fn msg 103 + let close t = t.close_fn () 104 + end
+71
lib/rpmsg.mli
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Thomas Gazagnaire. All rights reserved. 3 + SPDX-License-Identifier: ISC 4 + ---------------------------------------------------------------------------*) 5 + 6 + (** RPMsg inter-partition messaging. 7 + 8 + Wraps the Linux RPMsg character device interface for message-based IPC 9 + between partitions. Used on Jailhouse, Xen, and Zynq UltraScale+ platforms 10 + for communication between SpaceOS (Linux) and co-processor partitions via 11 + virtio vrings. 12 + 13 + The [rpmsg_endpoint_info] struct layout is described via {!Wire.Codec}, 14 + avoiding manual C struct packing. On macOS, endpoint creation via {!Ctrl} 15 + raises [Failure] (RPMsg does not exist outside Linux). *) 16 + 17 + (** {1 Endpoint info wire format} *) 18 + 19 + type endpoint_info = { name : string; src : int; dst : int } 20 + (** Matches Linux kernel [struct rpmsg_endpoint_info]. *) 21 + 22 + val endpoint_info_codec : endpoint_info Wire.Codec.t 23 + (** Wire codec for [rpmsg_endpoint_info]: [name[32]], [src:u32], [dst:u32]. *) 24 + 25 + val endpoint_info_size : int 26 + (** Wire size of [rpmsg_endpoint_info] (40 bytes). *) 27 + 28 + (** {1 Control device} *) 29 + 30 + module Ctrl : sig 31 + type t 32 + (** A handle to [/dev/rpmsg_ctrlN]. *) 33 + 34 + val open_ : ?index:int -> unit -> t 35 + (** [open_ ?index ()] opens [/dev/rpmsg_ctrlN] (default [N=0]). *) 36 + 37 + val create_endpoint : t -> name:string -> src:int -> dst:int -> int 38 + (** [create_endpoint t ~name ~src ~dst] creates an RPMsg endpoint. Returns the 39 + [/dev/rpmsgN] index. *) 40 + 41 + val destroy_endpoint : t -> name:string -> src:int -> dst:int -> unit 42 + (** [destroy_endpoint t ~name ~src ~dst] destroys an RPMsg endpoint. *) 43 + 44 + val close : t -> unit 45 + (** [close t] closes the control device. *) 46 + end 47 + 48 + (** {1 Message endpoint} *) 49 + 50 + module Endpoint : sig 51 + type t 52 + (** An RPMsg endpoint backed by Eio flows. *) 53 + 54 + val max_message_size : int 55 + (** 496 bytes (typical RPMsg limit: 512 - 16 byte virtio header). *) 56 + 57 + val of_source_sink : source:_ Eio.Flow.source -> sink:_ Eio.Flow.sink -> t 58 + (** [of_source_sink ~source ~sink] creates an endpoint from Eio flows. *) 59 + 60 + val open_ : int -> Unix.file_descr 61 + (** [open_ n] opens [/dev/rpmsgN] by index, returning the raw fd. *) 62 + 63 + val recv : t -> max_size:int -> string option 64 + (** [recv t ~max_size] receives a message. Returns [None] on EOF. *) 65 + 66 + val send : t -> string -> unit 67 + (** [send t msg] sends a message. *) 68 + 69 + val close : t -> unit 70 + (** [close t] closes the endpoint. *) 71 + end
+35
lib/rpmsg_stubs.c
··· 1 + /*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Thomas Gazagnaire. All rights reserved. 3 + SPDX-License-Identifier: ISC 4 + ---------------------------------------------------------------------------*/ 5 + 6 + #include <caml/mlvalues.h> 7 + #include <caml/memory.h> 8 + #include <caml/alloc.h> 9 + #include <caml/fail.h> 10 + 11 + #ifdef __linux__ 12 + #include <sys/ioctl.h> 13 + #include <string.h> 14 + #include <errno.h> 15 + #endif 16 + 17 + /* Generic ioctl(fd, cmd, buf) where buf is an OCaml bytes. 18 + The struct is encoded/decoded in OCaml via Wire.Codec. 19 + Returns the ioctl return value. 20 + On non-Linux platforms, raises Failure. */ 21 + CAMLprim value caml_rpmsg_ioctl(value v_fd, value v_cmd, value v_buf) 22 + { 23 + CAMLparam3(v_fd, v_cmd, v_buf); 24 + #ifdef __linux__ 25 + int ret = ioctl(Int_val(v_fd), (unsigned long)Long_val(v_cmd), 26 + Bytes_val(v_buf)); 27 + if (ret < 0) 28 + caml_failwith(strerror(errno)); 29 + CAMLreturn(Val_int(ret)); 30 + #else 31 + (void)v_fd; (void)v_cmd; (void)v_buf; 32 + caml_failwith("rpmsg: not supported on this platform"); 33 + CAMLreturn(Val_int(-1)); 34 + #endif 35 + }
+33
rpmsg.opam
··· 1 + # This file is generated by dune, edit dune-project instead 2 + opam-version: "2.0" 3 + synopsis: "RPMsg inter-partition messaging for SpaceOS" 4 + description: 5 + "OCaml bindings to the Linux RPMsg character device interface for inter-partition messaging. Used for IPC between SpaceOS (Linux) and co-processor partitions via virtio vrings on Jailhouse, Xen, and Zynq UltraScale+ platforms." 6 + maintainer: ["Thomas Gazagnaire <thomas@gazagnaire.org>"] 7 + authors: ["Thomas Gazagnaire <thomas@gazagnaire.org>"] 8 + license: "ISC" 9 + depends: [ 10 + "dune" {>= "3.21"} 11 + "ocaml" {>= "5.1"} 12 + "eio" {>= "1.0"} 13 + "eio_main" {>= "1.0"} 14 + "fmt" {>= "0.9"} 15 + "wire" 16 + "alcotest" {with-test} 17 + "odoc" {with-doc} 18 + ] 19 + build: [ 20 + ["dune" "subst"] {dev} 21 + [ 22 + "dune" 23 + "build" 24 + "-p" 25 + name 26 + "-j" 27 + jobs 28 + "@install" 29 + "@runtest" {with-test} 30 + "@doc" {with-doc} 31 + ] 32 + ] 33 + x-maintenance-intent: ["(latest)"]
+3
test/dune
··· 1 + (test 2 + (name test) 3 + (libraries rpmsg alcotest eio eio_main fmt wire))
+3
test/test.ml
··· 1 + (** Main test entry point for RPMsg tests. *) 2 + 3 + let () = Alcotest.run "rpmsg" (Test_wire.suite @ Test_endpoint.suite)
+97
test/test_endpoint.ml
··· 1 + (** Tests for Rpmsg.Endpoint module. 2 + 3 + Tests message transport using Eio pipes (simulating /dev/rpmsgN). The 4 + ioctl-based Ctrl operations require Linux RPMsg devices and are not testable 5 + on macOS. 6 + 7 + Upstream test vectors from Linux kernel: 8 + - Max message size: 496 bytes (512 - 16 byte virtio header) 9 + - RPMsg endpoint info struct: name[32], src u32, dst u32 10 + - Device paths: /dev/rpmsg_ctrlN, /dev/rpmsgN *) 11 + 12 + let with_eio f () = Eio_main.run @@ fun _env -> f () 13 + 14 + let with_pipe f () = 15 + Eio_main.run @@ fun env -> 16 + Eio.Switch.run @@ fun sw -> 17 + let source, sink = Eio.Process.pipe ~sw (Eio.Stdenv.process_mgr env) in 18 + f ~source ~sink 19 + 20 + (* -- constants -- *) 21 + 22 + let test_max_message_size = 23 + with_eio @@ fun () -> 24 + Alcotest.(check int) "max_message_size" 496 Rpmsg.Endpoint.max_message_size 25 + 26 + let test_max_message_size_virtio = 27 + with_eio @@ fun () -> 28 + (* 512 byte virtio buffer - 16 byte virtio header = 496 payload *) 29 + Alcotest.(check int) 30 + "virtio calculation" (512 - 16) Rpmsg.Endpoint.max_message_size 31 + 32 + (* -- endpoint send/recv via Eio pipe -- *) 33 + 34 + let test_send_recv = 35 + with_pipe @@ fun ~source ~sink -> 36 + let sender = Rpmsg.Endpoint.of_source_sink ~source ~sink in 37 + let msg = "hello rpmsg" in 38 + Rpmsg.Endpoint.send sender msg; 39 + match Rpmsg.Endpoint.recv sender ~max_size:256 with 40 + | None -> Alcotest.fail "expected message" 41 + | Some received -> Alcotest.(check string) "received" msg received 42 + 43 + let test_send_recv_binary = 44 + with_pipe @@ fun ~source ~sink -> 45 + let ep = Rpmsg.Endpoint.of_source_sink ~source ~sink in 46 + let msg = "\x00\x01\x02\xff\xfe\xfd" in 47 + Rpmsg.Endpoint.send ep msg; 48 + match Rpmsg.Endpoint.recv ep ~max_size:256 with 49 + | None -> Alcotest.fail "expected message" 50 + | Some received -> 51 + Alcotest.(check int) 52 + "recv len" (String.length msg) (String.length received); 53 + Alcotest.(check string) "binary payload" msg received 54 + 55 + let test_send_max_size = 56 + with_pipe @@ fun ~source ~sink -> 57 + let ep = Rpmsg.Endpoint.of_source_sink ~source ~sink in 58 + let msg = String.make Rpmsg.Endpoint.max_message_size 'X' in 59 + Rpmsg.Endpoint.send ep msg; 60 + match Rpmsg.Endpoint.recv ep ~max_size:512 with 61 + | None -> Alcotest.fail "expected message" 62 + | Some received -> 63 + Alcotest.(check int) 64 + "recv max" Rpmsg.Endpoint.max_message_size (String.length received) 65 + 66 + (* -- ctrl platform check -- *) 67 + 68 + let test_ctrl_open_fails_macos = 69 + with_eio @@ fun () -> 70 + if Sys.file_exists "/dev/rpmsg_ctrl0" then () 71 + else begin 72 + try 73 + ignore (Rpmsg.Ctrl.open_ ()); 74 + Alcotest.fail "should have raised" 75 + with Unix.Unix_error (Unix.ENOENT, _, _) -> () 76 + end 77 + 78 + let suite = 79 + [ 80 + ( "constants", 81 + [ 82 + Alcotest.test_case "max_message_size" `Quick test_max_message_size; 83 + Alcotest.test_case "virtio calculation" `Quick 84 + test_max_message_size_virtio; 85 + ] ); 86 + ( "endpoint", 87 + [ 88 + Alcotest.test_case "send/recv" `Quick test_send_recv; 89 + Alcotest.test_case "binary" `Quick test_send_recv_binary; 90 + Alcotest.test_case "max size" `Quick test_send_max_size; 91 + ] ); 92 + ( "ctrl", 93 + [ 94 + Alcotest.test_case "open fails on macOS" `Quick 95 + test_ctrl_open_fails_macos; 96 + ] ); 97 + ]
+86
test/test_wire.ml
··· 1 + (** Tests for Rpmsg wire codec. 2 + 3 + Upstream test vectors from Linux kernel: 4 + - struct rpmsg_endpoint_info: name[32], __u32 src, __u32 dst 5 + - Total size: 40 bytes 6 + - RPMSG_CREATE_EPT_IOCTL = _IOW(0xb5, 0x1, struct rpmsg_endpoint_info) 7 + - RPMSG_DESTROY_EPT_IOCTL = _IOW(0xb5, 0x2, struct rpmsg_endpoint_info) *) 8 + 9 + (* -- struct size -- *) 10 + 11 + let test_endpoint_info_size () = 12 + (* name[32] + src(u32) + dst(u32) = 40 bytes *) 13 + Alcotest.(check int) "size" 40 Rpmsg.endpoint_info_size 14 + 15 + (* -- codec roundtrip -- *) 16 + 17 + let test_codec_roundtrip () = 18 + let info = Rpmsg.{ name = "rpmsg-test-channel"; src = 1024; dst = 1025 } in 19 + let buf = Bytes.create Rpmsg.endpoint_info_size in 20 + Wire.Codec.encode Rpmsg.endpoint_info_codec info buf 0; 21 + let decoded = Wire.Codec.decode Rpmsg.endpoint_info_codec buf 0 in 22 + (* Name is padded to 32 bytes with nulls *) 23 + let expected_name = "rpmsg-test-channel" in 24 + Alcotest.(check bool) 25 + "name starts with" true 26 + (String.length decoded.name >= String.length expected_name 27 + && String.sub decoded.name 0 (String.length expected_name) = expected_name); 28 + Alcotest.(check int) "src" 1024 decoded.src; 29 + Alcotest.(check int) "dst" 1025 decoded.dst 30 + 31 + let test_codec_short_name () = 32 + let info = Rpmsg.{ name = "a"; src = 0; dst = 0 } in 33 + let buf = Bytes.create Rpmsg.endpoint_info_size in 34 + Wire.Codec.encode Rpmsg.endpoint_info_codec info buf 0; 35 + let decoded = Wire.Codec.decode Rpmsg.endpoint_info_codec buf 0 in 36 + Alcotest.(check char) "first byte" 'a' (String.get decoded.name 0); 37 + (* Rest should be null-padded *) 38 + Alcotest.(check char) "second byte" '\x00' (String.get decoded.name 1); 39 + Alcotest.(check int) "src" 0 decoded.src; 40 + Alcotest.(check int) "dst" 0 decoded.dst 41 + 42 + let test_codec_max_name () = 43 + let info = Rpmsg.{ name = String.make 31 'X'; src = 100; dst = 200 } in 44 + let buf = Bytes.create Rpmsg.endpoint_info_size in 45 + Wire.Codec.encode Rpmsg.endpoint_info_codec info buf 0; 46 + let decoded = Wire.Codec.decode Rpmsg.endpoint_info_codec buf 0 in 47 + (* Name field is 32 bytes, last byte should be null terminator *) 48 + Alcotest.(check char) "last name byte" '\x00' (String.get decoded.name 31); 49 + Alcotest.(check int) "src" 100 decoded.src; 50 + Alcotest.(check int) "dst" 200 decoded.dst 51 + 52 + let test_codec_name_truncation () = 53 + (* Name longer than 31 chars should be truncated *) 54 + let info = Rpmsg.{ name = String.make 50 'Z'; src = 42; dst = 43 } in 55 + let buf = Bytes.create Rpmsg.endpoint_info_size in 56 + Wire.Codec.encode Rpmsg.endpoint_info_codec info buf 0; 57 + let decoded = Wire.Codec.decode Rpmsg.endpoint_info_codec buf 0 in 58 + (* Last byte must be null *) 59 + Alcotest.(check char) "null terminator" '\x00' (String.get decoded.name 31); 60 + Alcotest.(check int) "src" 42 decoded.src; 61 + Alcotest.(check int) "dst" 43 decoded.dst 62 + 63 + (* -- ioctl command values -- *) 64 + 65 + let test_ioctl_create_cmd () = 66 + (* _IOW(0xb5, 0x1, 40) = (1 << 30) | (40 << 16) | (0xb5 << 8) | 1 *) 67 + let expected = (1 lsl 30) lor (40 lsl 16) lor (0xb5 lsl 8) lor 1 in 68 + Alcotest.(check int) "create cmd" expected 0x4028b501 69 + 70 + let test_ioctl_destroy_cmd () = 71 + let expected = (1 lsl 30) lor (40 lsl 16) lor (0xb5 lsl 8) lor 2 in 72 + Alcotest.(check int) "destroy cmd" expected 0x4028b502 73 + 74 + let suite = 75 + [ 76 + ( "wire", 77 + [ 78 + Alcotest.test_case "endpoint_info size" `Quick test_endpoint_info_size; 79 + Alcotest.test_case "codec roundtrip" `Quick test_codec_roundtrip; 80 + Alcotest.test_case "short name" `Quick test_codec_short_name; 81 + Alcotest.test_case "max name" `Quick test_codec_max_name; 82 + Alcotest.test_case "name truncation" `Quick test_codec_name_truncation; 83 + Alcotest.test_case "ioctl create cmd" `Quick test_ioctl_create_cmd; 84 + Alcotest.test_case "ioctl destroy cmd" `Quick test_ioctl_destroy_cmd; 85 + ] ); 86 + ]