firewire-dice-protocols 0.1.1

Implementation of protocols defined by TC Applied Technologies for ASICs of Digital Interface Communication Engine (DICE) as well as hardware vendors.
Documentation
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright (c) 2020 Takashi Sakamoto

use {
    firewire_dice_protocols as protocols,
    glib::{Error, FileError, MainContext, MainLoop},
    hinawa::{prelude::FwNodeExt, FwNode, FwNodeError, FwReq},
    protocols::tcat::extension::{
        caps_section::*, cmd_section::*, current_config_section::*, mixer_section::*,
        peak_section::*, standalone_section::*, *,
    },
    std::{sync::Arc, thread},
};

const TIMEOUT_MS: u32 = 20;

fn print_sections(sections: &ExtensionSections) {
    println!("Extension sections:");
    println!(
        "  caps:           offset 0x{:x}, size: 0x{:x}",
        sections.caps.offset, sections.caps.size
    );
    println!(
        "  cmd:            offset 0x{:x}, size: 0x{:x}",
        sections.cmd.offset, sections.cmd.size
    );
    println!(
        "  mixer:          offset 0x{:x}, size: 0x{:x}",
        sections.mixer.offset, sections.mixer.size
    );
    println!(
        "  peak:           offset 0x{:x}, size: 0x{:x}",
        sections.peak.offset, sections.peak.size
    );
    println!(
        "  router:         offset 0x{:x}, size: 0x{:x}",
        sections.router.offset, sections.router.size
    );
    println!(
        "  stream_format:  offset 0x{:x}, size: 0x{:x}",
        sections.stream_format.offset, sections.stream_format.size
    );
    println!(
        "  config:         offset 0x{:x}, size: 0x{:x}",
        sections.current_config.offset, sections.current_config.size
    );
    println!(
        "  standalone:     offset 0x{:x}, size: 0x{:x}",
        sections.standalone.offset, sections.standalone.size
    );
    println!(
        "  application:    offset 0x{:x}, size: 0x{:x}",
        sections.application.offset, sections.application.size
    );
}

fn print_caps(caps: &ExtensionCaps) {
    println!("Caps:");
    println!("  Router:");
    println!("    is_exposed:       {}", caps.router.is_exposed);
    println!("    is_readonly:      {}", caps.router.is_readonly);
    println!("    is_storable:      {}", caps.router.is_storable);
    println!(
        "    maximum_entry_count: {}",
        caps.router.maximum_entry_count
    );
    println!("  Mixer:");
    println!("    is_exposed:       {}", caps.mixer.is_exposed);
    println!("    is_readonly:      {}", caps.mixer.is_readonly);
    println!("    is_storable:      {}", caps.mixer.is_storable);
    println!("    input_device_id:  {}", caps.mixer.input_device_id);
    println!("    output_device_id: {}", caps.mixer.output_device_id);
    println!("    input_count:      {}", caps.mixer.input_count);
    println!("    output_count:     {}", caps.mixer.output_count);
    println!("  General:");
    println!(
        "    dynamic_stream_format: {}",
        caps.general.dynamic_stream_format
    );
    println!("    storage_avail:    {}", caps.general.storage_avail);
    println!("    peak_avail:       {}", caps.general.peak_avail);
    println!("    max_tx_streams:   {}", caps.general.max_tx_streams);
    println!("    max_rx_streams:   {}", caps.general.max_rx_streams);
    println!(
        "    stream_format_is_storable: {}",
        caps.general.stream_format_is_storable
    );

    let label = match caps.general.asic_type {
        AsicType::DiceII => "DiceII".to_string(),
        AsicType::Tcd2210 => "TCD2210".to_string(),
        AsicType::Tcd2220 => "TCD2220".to_string(),
        AsicType::Reserved(val) => format!("Reserved({})", val),
    };
    println!("    asic_type:        {}", label);
}

fn print_mixer(
    req: &mut FwReq,
    node: &mut FwNode,
    sections: &ExtensionSections,
    caps: &ExtensionCaps,
) -> Result<(), Error> {
    println!("Mixer:");
    println!("  Saturation:");
    let entries = MixerSectionProtocol::read_saturation(req, node, sections, caps, TIMEOUT_MS)?;
    entries.iter().enumerate().for_each(|(i, saturation)| {
        println!("    dst {}: {}", i, saturation);
    });

    println!("  Coefficiency:");
    (0..(caps.mixer.output_count as usize)).try_for_each(|dst| {
        (0..(caps.mixer.input_count as usize)).try_for_each(|src| {
            MixerSectionProtocol::read_coef(req, node, sections, caps, dst, src, TIMEOUT_MS)
                .map(|coef| println!("    dst {} <- src {}: {}", dst, src, coef))
        })
    })
}

fn print_peak(
    req: &mut FwReq,
    node: &mut FwNode,
    sections: &ExtensionSections,
    caps: &ExtensionCaps,
) -> Result<(), Error> {
    PeakSectionProtocol::read_peak_entries(req, node, sections, caps, TIMEOUT_MS).map(|entries| {
        println!("Peak:");
        entries.iter().enumerate().for_each(|(i, entry)| {
            println!("  entry {}: 0x{:04x}", i, entry.peak);
        })
    })
}

const RATE_MODES: [RateMode; 3] = [RateMode::Low, RateMode::Middle, RateMode::High];

fn print_current_router_entries(
    req: &mut FwReq,
    node: &mut FwNode,
    sections: &ExtensionSections,
    caps: &ExtensionCaps,
) -> Result<(), Error> {
    println!("Current router entries:");
    RATE_MODES.iter().try_for_each(|&mode| {
        CurrentConfigSectionProtocol::read_current_router_entries(
            req, node, sections, caps, mode, TIMEOUT_MS,
        )
        .map(|entries| {
            println!("  {}:", mode);
            entries.iter().enumerate().for_each(|(i, entry)| {
                println!("    entry {}: {:?} <- {:?}", i, entry.dst, entry.src);
            });
        })
    })
}

fn print_stream_format_entry(entry: &FormatEntry) {
    println!("      pcm:      {}", entry.pcm_count);
    println!("      midi:     {}", entry.midi_count);
    println!("      channel names:");
    entry.labels.iter().enumerate().for_each(|(i, label)| {
        println!("        ch {}:   {}", i, label);
    });
    println!("      AC3 capabilities:");
    entry
        .enable_ac3
        .iter()
        .enumerate()
        .filter(|&(_, enabled)| *enabled)
        .for_each(|(i, enabled)| {
            println!("        ch {}: {}", i, enabled);
        });
}

fn print_current_stream_format_entries(
    req: &mut FwReq,
    node: &mut FwNode,
    sections: &ExtensionSections,
    caps: &ExtensionCaps,
) -> Result<(), Error> {
    println!("Current stream format entries:");
    RATE_MODES.iter().try_for_each(|&mode| {
        CurrentConfigSectionProtocol::read_current_stream_format_entries(
            req, node, sections, caps, mode, TIMEOUT_MS,
        )
        .map(|(tx_entries, rx_entries)| {
            println!("  {}:", mode);
            tx_entries.iter().enumerate().for_each(|(i, entry)| {
                println!("    Tx stream {}:", i);
                print_stream_format_entry(entry);
            });
            rx_entries.iter().enumerate().for_each(|(i, entry)| {
                println!("    Rx stream {}:", i);
                print_stream_format_entry(entry);
            });
        })
    })
}

fn print_standalone_config(
    req: &mut FwReq,
    node: &mut FwNode,
    sections: &ExtensionSections,
) -> Result<(), Error> {
    println!("Standalone configurations:");
    let src =
        StandaloneSectionProtocol::read_standalone_clock_source(req, node, sections, TIMEOUT_MS)?;
    println!("  clock source: {}", src);
    let mode =
        StandaloneSectionProtocol::read_standalone_aes_high_rate(req, node, sections, TIMEOUT_MS)?;
    println!("  AES high rate: {}", mode);
    let mode =
        StandaloneSectionProtocol::read_standalone_adat_mode(req, node, sections, TIMEOUT_MS)?;
    println!("  ADAT mode: {}", mode);
    let params = StandaloneSectionProtocol::read_standalone_word_clock_param(
        req, node, sections, TIMEOUT_MS,
    )?;
    println!(
        "  Word clock params: {}, {} / {}",
        params.mode, params.rate.numerator, params.rate.denominator
    );
    let rate =
        StandaloneSectionProtocol::read_standalone_internal_rate(req, node, sections, TIMEOUT_MS)?;
    println!("  Internal rate: {}", rate);
    Ok(())
}

fn main() {
    let code = std::env::args().nth(1)
        .ok_or("At least one argument is required for path to special file of firewire character device".to_string())
        .and_then(|path| {
            let node = FwNode::new();
            node.open(&path)
                .map_err(|e| {
                    let cause = if let Some(error) = e.kind::<FileError>() {
                        match error {
                            FileError::Isdir => "is directory",
                            FileError::Acces => "access permission",
                            FileError::Noent => "not exists",
                            _ => "unknown",
                        }.to_string()
                    } else if let Some(error) = e.kind::<FwNodeError>() {
                        match error {
                            FwNodeError::Disconnected => "disconnected",
                            FwNodeError::Failed => "ioctl error",
                            _ => "unknown",
                        }.to_string()
                    } else {
                        e.to_string()
                    };
                    format!("Fail to open firewire character device {}: {} {}", path, cause, e)
                })
                .and_then(|_| {
                    node.create_source()
                        .map_err(|e| e.to_string())
                        .map(|src| (node, src))
                })
        })
        .and_then(|(mut node, src)| {
            let ctx = MainContext::new();
            let _ = src.attach(Some(&ctx));
            let dispatcher = Arc::new(MainLoop::new(Some(&ctx), false));
            let d = dispatcher.clone();
            let th = thread::spawn(move || d.run());

            let mut req = FwReq::new();
            let result = ProtocolExtension::read_extension_sections(&mut req, &mut node, TIMEOUT_MS)
                .and_then(|sections| {
                    print_sections(&sections);
                    let caps = CapsSectionProtocol::read_caps(
                        &mut req,
                        &mut node,
                        &sections,
                        TIMEOUT_MS
                    )?;
                    print_caps(&caps);
                    print_mixer(&mut req, &mut node, &sections, &caps)?;
                    print_peak(&mut req, &mut node, &sections, &caps)?;
                    print_current_router_entries(&mut req, &mut node, &sections, &caps)?;
                    print_current_stream_format_entries(&mut req, &mut node, &sections, &caps)?;
                    print_standalone_config(&mut req, &mut node, &sections)?;
                    Ok(())
                })
                .map_err(|e| e.to_string());

            dispatcher.quit();
            th.join().unwrap();
            result
        })
        .map(|_| 0)
        .unwrap_or_else(|msg| {
            eprintln!("{}", msg);
            print_help();
            1
        });

    std::process::exit(code)
}

fn print_help() {
    print!(
        r###"
Usage:
  tcat-extension-parser CDEV

  where:
    CDEV:       The path to special file of firewire character device, typically '/dev/fw1'.
"###
    );
}