1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
//! This module defines the layout of the initialization segment as well as access functions.
//! (PRM Section 5.4: Initialization Segment)

use command_queue::{Command, CmdState};
use memory::PhysicalAddress;
use volatile::{Volatile, ReadOnly};
use bit_field::BitField;
use zerocopy::{U16, U32, U64, FromBytes};
use byteorder::BigEndian;
use core::fmt;


/// The initialization segment is located at offset 0 of PCI BAR0.
/// It is used in the initialization procedure of the device,
/// and it contains the 32-bit command doorbell vector used to inform the HW when a command is ready to be processed.
#[derive(FromBytes)]
#[repr(packed)]
pub struct InitializationSegment {
    /// Firmware Revision - Minor
    fw_rev_minor:               ReadOnly<U16<BigEndian>>,
    /// Firmware Revision - Major
    fw_rev_major:               ReadOnly<U16<BigEndian>>,
    /// Command Interface Interpreter Revision ID
    cmd_interface_rev:          ReadOnly<U16<BigEndian>>,
    /// Firmware Sub-minor version (Patch level)
    fw_rev_subminor:            ReadOnly<U16<BigEndian>>,
    _padding1:                  [u8; 8],
    /// MSBs of the physical address of the command queue record.
    cmdq_phy_addr_high:         Volatile<U32<BigEndian>>,
    /// LSBs of the physical address of the command queue record.
    cmdq_phy_addr_low:          Volatile<U32<BigEndian>>,
    /// Bit per command in the cmdq.
    /// When the bit is set, that command entry in the queue is moved to HW ownership.
    command_doorbell_vector:    Volatile<U32<BigEndian>>,
    _padding2:                  [u8; 480],
    /// If bit 31 is set, the device is still initializing and driver should not post commands
    initializing_state:         ReadOnly<U32<BigEndian>>,
    /// Advanced debug information.
    _health_buffer:              Volatile<[u8; 64]>,
    /// The offset in bytes, inside the initialization segment, where the NODNIC registers can be found.
    _no_dram_nic_offset:         ReadOnly<U32<BigEndian>>,
    _padding3:                  [u8; 3516],
    /// MSBs of the current internal timer value
    _internal_timer_h:           ReadOnly<U32<BigEndian>>,
    /// LSBs of the current internal timer value
    _internal_timer_l:           ReadOnly<U32<BigEndian>>,
    _padding4:                  [u8; 8],
    /// Advanced debug information
    _health_counter:             ReadOnly<U32<BigEndian>>,
    _padding5:                  [u8; 44],
    _real_time:                  ReadOnly<U64<BigEndian>>,
    _padding6:                  [u8; 12228],
}

const _: () = assert!(core::mem::size_of::<InitializationSegment>() == 16396);

impl InitializationSegment {
    /// Returns the maximum number of entries that can be in the command queue
    pub fn num_cmdq_entries(&self) -> u8 {
        let log = (self.cmdq_phy_addr_low.read().get() >> 4) & 0x0F;
        2_u8.pow(log)
    }

    /// Returns the required stride of command queue entries (bytes between the start of consecutive entries)
    pub fn cmdq_entry_stride(&self) -> u8 {
        let val = self.cmdq_phy_addr_low.read().get() & 0x0F;
        2_u8.pow(val)
    }
    
    /// Sets the physical address of the command queue within the initialization segment.
    ///
    /// # Arguments
    /// * `cmdq_physical_addr`: the starting physical address of the command queue, the lower 12 bits of which must be zero. 
    pub fn set_physical_address_of_cmdq(&mut self, cmdq_physical_addr: PhysicalAddress) -> Result<(), &'static str> {
        if cmdq_physical_addr.value() & 0xFFF != 0 {
            return Err("cmdq physical address lower 12 bits must be zero.");
        }

        self.cmdq_phy_addr_high.write(U32::new((cmdq_physical_addr.value() >> 32) as u32));
        let val = self.cmdq_phy_addr_low.read().get() & 0xFFF;
        self.cmdq_phy_addr_low.write(U32::new(cmdq_physical_addr.value() as u32 | val));
        Ok(())
    }

    /// Returns true if the device is still initializing, and driver should not pass any commands to the device.
    pub fn device_is_initializing(&self) -> bool {
        self.initializing_state.read().get().get_bit(31)
    }

    /// Sets a bit in the [`InitializationSegment::command_doorbell_vector`] to inform HW that the command needs to be executed.
    ///
    /// # Arguments
    /// * `command bit`: the command entry that needs to be executed. (e.g. bit 0 corresponds to entry at index 0).
    pub(crate) fn post_command(&mut self, command: &Command<{CmdState::Initialized}>) {
        let val = self.command_doorbell_vector.read().get();
        self.command_doorbell_vector.write(U32::new(val | (1 << command.entry_num)));
    }
}

impl fmt::Debug for InitializationSegment {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("InitializationSegment")
            .field("Firmware Rev Major", &self.fw_rev_major.read().get()) 
            .field("Firmware Rev Minor",&self.fw_rev_minor.read().get())
            .field("Firmware Rev Subminor",&self.fw_rev_subminor.read().get())
            .field("Command Interface Rev",&self.cmd_interface_rev.read().get())
            .field("Command queue address high", &self.cmdq_phy_addr_high.read().get())
            .field("Command queue address low", &self.cmdq_phy_addr_low.read().get())
            .field("Command doorbell vector", &self.command_doorbell_vector.read().get())
            .field("Initializing state", &self.initializing_state.read().get())
            .finish()
    }
}

/// The possible values of the initialization state of the device as taken from the intialization segment.
pub enum InitializingState {
    NotAllowed = 0,
    WaitingPermetion = 1,
    WaitingResources = 2,
    Abort = 3
}