e-tipsmemo

ごった煮

Rust memmap2 LED blinking with Zynq

ZyboにUbuntu22.04のファイルシステムを入れて起動した。
PL部分にはAXI GPIOがつながっておりLEDが接続されている。
rotors.tar.goのファイルシステムにはdevmemがインストールされいた。
しかしUbuntuには入っていなかったのでdevmem2.cをどこからかダウンロードしてきてビルドする事で使える。
しかしこのままではBusyboxの時と何も変わらないのでRustで書いたアプリでdev/memを操作してみる。

環境構築

Zynq上でRustをビルドするわけにもいかないので
WSL2上でCrossコンパイルする

必要なものは

  • WSL2
  • Docker for WSL
  • Rust

これらは解説記事がとても多いのでスキップして

  • cross

cross で Rust のクロスコンパイル - Qiita
に沿ってやる
ターゲットのアーキテクチャ

~/d/r/z/devmem-rs (master)> rustup target list | grep arm
arm-linux-androideabi
arm-unknown-linux-gnueabi
arm-unknown-linux-gnueabihf
arm-unknown-linux-musleabi
arm-unknown-linux-musleabihf
armebv7r-none-eabi
armebv7r-none-eabihf
armv5te-unknown-linux-gnueabi
armv5te-unknown-linux-musleabi
armv7-linux-androideabi
armv7-unknown-linux-gnueabi
armv7-unknown-linux-gnueabihf
armv7-unknown-linux-musleabi
armv7-unknown-linux-musleabihf
armv7a-none-eabi
armv7r-none-eabi
armv7r-none-eabihf

zynqはarmv7なのでarmv7-unknown-linux-gnueabihfを入れる

memmap2

cargo new devmem-rs --bin
cd devmem-rs
cargo add memmap2

ソースコード

こんな感じで

use clap::{arg, command};
use memmap2::{MmapMut, MmapOptions};
use std::env;
use std::error::Error;
use std::fs::OpenOptions;
use std::path::PathBuf;
use std::u64;

fn mapping(addr: u64, len: usize) -> Result<MmapMut, Box<dyn Error>> {
    let file = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open(PathBuf::from("/dev/mem"))?;

    let m = unsafe { MmapOptions::new().offset(addr).len(len).map_mut(&file)? };

    Ok(m)
}
fn main() {
    let matches = command!()
        .arg(arg!([ADDRESS]))
        .arg(arg!([TYPE]).default_value("b"))
        .arg(arg!([DATA]))
        .get_matches();

    let adr = match matches.get_one::<String>("ADDRESS") {
        Some(adr) => {
            let adr = adr.trim_start_matches("0x");
            u64::from_str_radix(&adr, 16).unwrap_or_default()
        }
        _ => {
            println!(
                "Usage:  devmem {{ address }} [ type [ data ] ]
        address : memory address to act upon
        type    : access operation type : [b]yte, [h]alfword, [w]ord
        data    : data to be written\n"
            );
            std::process::exit(0)
        }
    };

    let width = match matches.get_one::<String>("TYPE") {
        Some(t) => match t.as_str() {
            "b" | "B" => 1,
            "h" | "H" => 2,
            "w" | "W" => 4,
            _ => {
                println!("illigal data type '{}'", t);
                std::process::exit(0)
            }
        },
        _ => {
            unreachable!()
        }
    };

    let mut m = mapping(adr, 4).unwrap();

    let rdata = u32::from_ne_bytes(m[0..4].try_into().unwrap());
    println!("Value at address 0x{:X}: 0x{:X}", adr, rdata);

    match matches.get_one::<String>("DATA") {
        Some(s) => {
            //Write Mode
            let data = if s.starts_with("0x") {
                let s = s.trim_start_matches("0x");
                u32::from_str_radix(s, 16).unwrap_or_default()
            } else {
                u32::from_str_radix(s, 10).unwrap_or_default()
            };

            let bytes = data.to_ne_bytes();

            for i in 0..width {
                m[i] = bytes[i];
            }

            let rbdata = u32::from_ne_bytes(m[0..4].try_into().unwrap());
            println!("Written 0x{:X}: readback: 0x{:X}", data, rbdata);
        }
        None => {}
    };
}

使用


Clapによって簡単にヘルプ表示できる。

Vivadoのほうでbase addrを確認する。

AXI GPIOはbase + 0x0004に入出力方向を決めるレジスタがあり、デフォルトでは入力に設定されているので、そこに0x0を書きこむ。
LogiCORE IP AXI GPIO (v1.01b) Data Sheet (AXI) - 1.01b English

LED点灯


これでLEDが交互に光っているはず。

スイッチ入力

同様に、スイッチ入力を読み取ることもできる。