bm1397_protocol/
command.rs

1use crate::crc::{crc16, crc5};
2use crate::registers::Register;
3use byteorder::{BigEndian, ByteOrder, LittleEndian};
4
5/// Some command can be send to All chip in the chain or to a specific one
6pub enum Destination {
7    All,
8    Chip(u8),
9}
10
11pub struct Command;
12
13pub type Midstate = [u8; 32];
14
15impl Command {
16    const CMD_ALL_CHIP: u8 = 0x10;
17    const CMD_SEND_JOB: u8 = 0x21;
18    const CMD_SET_CHIP_ADDR: u8 = 0x40;
19    const CMD_WRITE_REGISTER: u8 = 0x41;
20    const CMD_READ_REGISTER: u8 = 0x42;
21    const CMD_CHAIN_INACTIVE: u8 = 0x43;
22
23    /// # Chain Inactive Command
24    ///
25    /// This disable the relay ability of every chip on the chain (CI signal is no more relayed to CO pin).
26    /// Usually done before setting each chip address individually using `Command::set_chip_addr`.
27    ///
28    /// ## Example
29    ///
30    /// ```
31    /// use bm1397_protocol::Command;
32    ///
33    /// let cmd = Command::chain_inactive();
34    /// assert_eq!(cmd, [0x55, 0xAA, 0x53, 0x05, 0x00, 0x00, 0x03]);
35    /// ```
36    pub fn chain_inactive() -> [u8; 7] {
37        let mut data: [u8; 7] = [
38            0x55,
39            0xAA,
40            Self::CMD_CHAIN_INACTIVE + Self::CMD_ALL_CHIP,
41            5,
42            0,
43            0,
44            0,
45        ];
46        data[6] = crc5(&data[2..6]);
47        data
48    }
49
50    /// # Set Chip Address Command
51    ///
52    /// Give a logical `ChipAddress` to the chip on the chain that does not have one yet.
53    /// Usually done after sending `Command::chain_inactive` so only the first chip in the
54    /// chain will receive it's chip address, then it will start relaying CI signal to
55    /// CO pin and subsequent `Command::set_chip_addr` will be received by the next chip only.
56    /// This way all chips will be addressed one by one and will received differents `ChipAddress`.
57    ///
58    /// This logical `ChipAddress` have 2 utilities :
59    /// - sending command to a specific chip on the chain, using `Destination::Chip(ChipAddress)`.
60    /// - when mining, the nonce space (u32) will be divided evenly according to `ChipAddress` :
61    /// each chip will add it's own `ChipAddress` to the MSB of the starting nonce for a job.
62    ///
63    /// ## Example
64    ///
65    /// ```
66    /// use bm1397_protocol::Command;
67    ///
68    /// let cmd = Command::set_chip_addr(0x00);
69    /// assert_eq!(cmd, [0x55, 0xAA, 0x40, 0x05, 0x00, 0x00, 0x1C]);
70    ///
71    /// let cmd = Command::set_chip_addr(0x08);
72    /// assert_eq!(cmd, [0x55, 0xAA, 0x40, 0x05, 0x08, 0x00, 0x07]);
73    /// ```
74    pub fn set_chip_addr(addr: u8) -> [u8; 7] {
75        let mut data: [u8; 7] = [0x55, 0xAA, Self::CMD_SET_CHIP_ADDR, 5, addr, 0, 0];
76        data[6] = crc5(&data[2..6]);
77        data
78    }
79
80    /// # Read Register Command
81    ///
82    /// Used to send a Read Register command on the chain.
83    ///
84    /// All chips on the chain or only a specific one can be addressed by this command
85    /// using the `dest` parameter.
86    ///
87    /// Usually the first command sent by a driver to the chain is the Read Register
88    /// command of `Register::ChipAddress` to all chips on the chain, this is usefull to
89    /// enumerate all chips on the chain.
90    ///
91    /// ## Example
92    ///
93    /// ```
94    /// use bm1397_protocol::{Command, Destination, Register};
95    ///
96    /// // Enumerate the chain
97    /// let cmd = Command::read_reg(Register::ChipAddress, Destination::All);
98    /// assert_eq!(cmd, [0x55, 0xAA, 0x52, 0x05, 0x00, 0x00, 0x0A]);
99    ///
100    /// // Read I2CControl on chip with ChipAddress@64
101    /// let cmd = Command::read_reg(Register::I2CControl, Destination::Chip(64));
102    /// assert_eq!(cmd, [0x55, 0xAA, 0x42, 0x05, 0x40, 0x1C, 0x0B]);
103    /// ```
104    pub fn read_reg(reg: Register, dest: Destination) -> [u8; 7] {
105        let mut data: [u8; 7] = [0x55, 0xAA, Self::CMD_READ_REGISTER, 5, 0, reg as u8, 0];
106        match dest {
107            Destination::All => data[2] += Self::CMD_ALL_CHIP,
108            Destination::Chip(c) => data[4] = c,
109        }
110        data[6] = crc5(&data[2..6]);
111        data
112    }
113
114    /// # Write Register Command
115    ///
116    /// Used to send a Write Register command on the chain.
117    ///
118    /// All chips on the chain or only a specific one can be addressed by this command
119    /// using the `dest` parameter.
120    ///
121    /// ## Example
122    ///
123    /// ```
124    /// use bm1397_protocol::{Command, Destination, Register};
125    ///
126    /// // Write ClockOrderControl0 value 0x0000_0000 on All chip of the chain
127    /// let cmd = Command::write_reg(Register::ClockOrderControl0, Destination::All, 0x0000_0000);
128    /// assert_eq!(cmd, [0x55, 0xAA, 0x51, 0x09, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1C]);
129    ///
130    /// // Write MiscControl value 0x0000_7A31 on chip with ChipAddress@64
131    /// let cmd = Command::write_reg(Register::MiscControl, Destination::Chip(64), 0x0000_7A31);
132    /// assert_eq!(cmd, [0x55, 0xAA, 0x41, 0x09, 0x40, 0x18, 0x00, 0x00, 0x7A, 0x31, 0x11]);
133    /// ```
134    pub fn write_reg(reg: Register, dest: Destination, val: u32) -> [u8; 11] {
135        let mut data: [u8; 11] = [
136            0x55,
137            0xAA,
138            Self::CMD_WRITE_REGISTER,
139            9,
140            0,
141            reg as u8,
142            0,
143            0,
144            0,
145            0,
146            0,
147        ];
148        match dest {
149            Destination::All => data[2] += 0x10,
150            Destination::Chip(c) => data[4] = c,
151        }
152        BigEndian::write_u32(&mut data[6..], val);
153        data[10] = crc5(&data[2..10]);
154        data
155    }
156
157    /// # Job with 1 Midstate Command
158    ///
159    /// ## Example
160    ///
161    /// ```
162    /// use bm1397_protocol::{Command, Midstate};
163    ///
164    /// let midstates: [&Midstate; 1] = [
165    ///     &[
166    ///         0xDE, 0x60, 0x4A, 0x09, 0xE9, 0x30, 0x1D, 0xE1, 0x25, 0x6D, 0x7E, 0xB8, 0x0E, 0xA1,
167    ///         0xE6, 0x43, 0x82, 0xDF, 0x61, 0x14, 0x15, 0x03, 0x96, 0x6C, 0x18, 0x5F, 0x50, 0x2F,
168    ///         0x55, 0x74, 0xD4, 0xBA,
169    ///     ],
170    /// ];
171    /// let cmd = Command::job_1_midstate(0, 0x1707_9E15, 0x638E_3275, 0x706A_B3A2, midstates);
172    /// assert_eq!(
173    ///     cmd,
174    ///     [
175    ///         0x55, 0xAA, 0x21, 0x36, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x15, 0x9E, 0x07, 0x17,
176    ///         0x75, 0x32, 0x8E, 0x63, 0xA2, 0xB3, 0x6A, 0x70, 0xDE, 0x60, 0x4A, 0x09, 0xE9, 0x30,
177    ///         0x1D, 0xE1, 0x25, 0x6D, 0x7E, 0xB8, 0x0E, 0xA1, 0xE6, 0x43, 0x82, 0xDF, 0x61, 0x14,
178    ///         0x15, 0x03, 0x96, 0x6C, 0x18, 0x5F, 0x50, 0x2F, 0x55, 0x74, 0xD4, 0xBA, 0xD3, 0xDC
179    ///     ]
180    /// );
181    /// ```
182    pub fn job_1_midstate(
183        job_id: u8,
184        n_bits: u32,
185        n_time: u32,
186        merkle_root: u32,
187        midstates: [&Midstate; 1],
188    ) -> [u8; 56] {
189        let mut data: [u8; 56] = [0; 56];
190        data[0] = 0x55;
191        data[1] = 0xAA;
192        data[2] = Self::CMD_SEND_JOB;
193        // data[3] = 22 + (midstates.len() * 32) as u8;
194        data[3] = data.len() as u8 - 2;
195        data[4] = job_id;
196        data[5] = midstates.len() as u8;
197        // LittleEndian::write_u32(&mut data[6..], 0u32); // starting_nonce ?
198        LittleEndian::write_u32(&mut data[10..], n_bits);
199        LittleEndian::write_u32(&mut data[14..], n_time);
200        LittleEndian::write_u32(&mut data[18..], merkle_root);
201        let mut offset = 22;
202        for ms in midstates.into_iter() {
203            data[offset..offset + ms.len()].clone_from_slice(ms);
204            offset += ms.len();
205        }
206        let crc = crc16(&data[2..offset]);
207        BigEndian::write_u16(&mut data[offset..], crc);
208        data
209    }
210
211    /// # Job with 4 Midstate Command
212    ///
213    /// ## Example
214    ///
215    /// ```
216    /// use bm1397_protocol::{Command, Midstate};
217    ///
218    /// let midstates: [&Midstate; 4] = [
219    ///     &[
220    ///         0xDE, 0x60, 0x4A, 0x09, 0xE9, 0x30, 0x1D, 0xE1, 0x25, 0x6D, 0x7E, 0xB8, 0x0E, 0xA1,
221    ///         0xE6, 0x43, 0x82, 0xDF, 0x61, 0x14, 0x15, 0x03, 0x96, 0x6C, 0x18, 0x5F, 0x50, 0x2F,
222    ///         0x55, 0x74, 0xD4, 0xBA,
223    ///     ],
224    ///     &[
225    ///         0xAE, 0x2F, 0x3F, 0xC6, 0x02, 0xD9, 0xCD, 0x3B, 0x9E, 0x39, 0xAD, 0x97, 0x9C, 0xFD,
226    ///         0xFF, 0x3A, 0x40, 0x49, 0x4D, 0xB6, 0xD7, 0x8D, 0xA4, 0x51, 0x34, 0x99, 0x29, 0xD1,
227    ///         0xAD, 0x36, 0x66, 0x1D,
228    ///     ],
229    ///     &[
230    ///         0xDF, 0xFF, 0xC1, 0xCC, 0x89, 0x33, 0xEA, 0xF3, 0xE8, 0x3A, 0x91, 0x58, 0xA6, 0xD6,
231    ///         0xFA, 0x02, 0x0D, 0xCF, 0x60, 0xF8, 0xC1, 0x0E, 0x99, 0x36, 0xDE, 0x71, 0xDB, 0xD3,
232    ///         0xF7, 0xD2, 0x86, 0xAF,
233    ///     ],
234    ///     &[
235    ///         0xAD, 0x62, 0x59, 0x3A, 0x8D, 0xA3, 0x28, 0xAF, 0xEC, 0x09, 0x6D, 0x86, 0xB9, 0x8E,
236    ///         0x30, 0xE5, 0x79, 0xAE, 0xA4, 0x35, 0xE1, 0x4B, 0xB5, 0xD7, 0x09, 0xCC, 0xE1, 0x74,
237    ///         0x04, 0x3A, 0x7C, 0x2D,
238    ///     ],
239    /// ];
240    /// let cmd = Command::job_4_midstate(0, 0x1707_9E15, 0x638E_3275, 0x706A_B3A2, midstates);
241    /// assert_eq!(
242    ///     cmd,
243    ///     [
244    ///         0x55, 0xAA, 0x21, 0x96, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x15, 0x9E, 0x07, 0x17,
245    ///         0x75, 0x32, 0x8E, 0x63, 0xA2, 0xB3, 0x6A, 0x70, 0xDE, 0x60, 0x4A, 0x09, 0xE9, 0x30,
246    ///         0x1D, 0xE1, 0x25, 0x6D, 0x7E, 0xB8, 0x0E, 0xA1, 0xE6, 0x43, 0x82, 0xDF, 0x61, 0x14,
247    ///         0x15, 0x03, 0x96, 0x6C, 0x18, 0x5F, 0x50, 0x2F, 0x55, 0x74, 0xD4, 0xBA, 0xAE, 0x2F,
248    ///         0x3F, 0xC6, 0x02, 0xD9, 0xCD, 0x3B, 0x9E, 0x39, 0xAD, 0x97, 0x9C, 0xFD, 0xFF, 0x3A,
249    ///         0x40, 0x49, 0x4D, 0xB6, 0xD7, 0x8D, 0xA4, 0x51, 0x34, 0x99, 0x29, 0xD1, 0xAD, 0x36,
250    ///         0x66, 0x1D, 0xDF, 0xFF, 0xC1, 0xCC, 0x89, 0x33, 0xEA, 0xF3, 0xE8, 0x3A, 0x91, 0x58,
251    ///         0xA6, 0xD6, 0xFA, 0x02, 0x0D, 0xCF, 0x60, 0xF8, 0xC1, 0x0E, 0x99, 0x36, 0xDE, 0x71,
252    ///         0xDB, 0xD3, 0xF7, 0xD2, 0x86, 0xAF, 0xAD, 0x62, 0x59, 0x3A, 0x8D, 0xA3, 0x28, 0xAF,
253    ///         0xEC, 0x09, 0x6D, 0x86, 0xB9, 0x8E, 0x30, 0xE5, 0x79, 0xAE, 0xA4, 0x35, 0xE1, 0x4B,
254    ///         0xB5, 0xD7, 0x09, 0xCC, 0xE1, 0x74, 0x04, 0x3A, 0x7C, 0x2D, 0x1B, 0x5C
255    ///     ]
256    /// );
257    /// ```
258    pub fn job_4_midstate(
259        job_id: u8,
260        n_bits: u32,
261        n_time: u32,
262        merkle_root: u32,
263        midstates: [&Midstate; 4],
264    ) -> [u8; 152] {
265        let mut data: [u8; 152] = [0; 152];
266        data[0] = 0x55;
267        data[1] = 0xAA;
268        data[2] = Self::CMD_SEND_JOB;
269        // data[3] = 22 + (midstates.len() * 32) as u8;
270        data[3] = data.len() as u8 - 2;
271        data[4] = job_id;
272        data[5] = midstates.len() as u8;
273        // LittleEndian::write_u32(&mut data[6..], 0u32); // starting_nonce ?
274        LittleEndian::write_u32(&mut data[10..], n_bits);
275        LittleEndian::write_u32(&mut data[14..], n_time);
276        LittleEndian::write_u32(&mut data[18..], merkle_root);
277        let mut offset = 22;
278        for ms in midstates.into_iter() {
279            data[offset..offset + ms.len()].clone_from_slice(ms);
280            offset += ms.len();
281        }
282        let crc = crc16(&data[2..offset]);
283        BigEndian::write_u16(&mut data[offset..], crc);
284        data
285    }
286}