summaryrefslogtreecommitdiffstats
path: root/dev_intelliprop.cpp
blob: 4e5f40a9515609ecde4d7ebbbe5518a52877e9e1 (plain)
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
/*
 * dev_intelliprop.cpp
 *
 * Home page of code is: https://www.smartmontools.org
 *
 * Copyright (C) 2016 Casey Biemiller  <cbiemiller@intelliprop.com>
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "config.h"

#include "atacmds.h" // ATTR_PACKED, STATIC_ASSERT, ata_debugmode
#include "dev_interface.h"
#include "dev_tunnelled.h"
#include <errno.h>

const char * dev_intelliprop_cpp_cvsid = "$Id: dev_intelliprop.cpp 5449 2023-02-05 14:44:07Z chrfranke $";

//Vendor Specific log addresses
#define LOG_C0           0xc0

// VS LOG MODE CONTROL BITS
enum {
  IPROP_VS_LOG_MODE_CTL_AUTO_SUPPORTED   = (0 << 0), // NOTE: Not supported
  IPROP_VS_LOG_MODE_CTL_MANUAL_SUPPORTED = (1 << 1),
  IPROP_VS_LOG_MODE_CTL_AUTO_ENABLED     = (0 << 2), // NOTE: Not supported
  IPROP_VS_LOG_MODE_CTL_MANUAL_ENABLED   = (1 << 3),
};

// VS LOG PORT SETTING BITS
enum {
  IPROP_VS_LOG_PORT_WRITE_ENABLE_MASK  = 0xC000,
  IPROP_VS_LOG_PORT_WRITE_ENABLE_VALID = 0x8000,
  IPROP_VS_LOG_PORT_RX_DC_GAIN_MASK    = 0x3000,
  IPROP_VS_LOG_PORT_RX_DC_GAIN_SHIFT   = 12,
  IPROP_VS_LOG_PORT_RX_EQ_MASK         = 0x0F00,
  IPROP_VS_LOG_PORT_RX_EQ_SHIFT        = 8,
  IPROP_VS_LOG_PORT_TX_PREEMP_MASK     = 0x00F8,
  IPROP_VS_LOG_PORT_TX_PREEMP_SHIFT    = 3,
  IPROP_VS_LOG_PORT_TX_VOD_MASK        = 0x0007,
  IPROP_VS_LOG_PORT_TX_VOD_SHIFT       = 0,
};

//This struct is used for the Vendor Specific log C0 on devices that support it.
#pragma pack(1)
struct iprop_internal_log
{
  uint32_t drive_select;       // Bytes - [  3:  0] of Log C0
  uint32_t obsolete;           // Bytes - [  7:  4] of Log C0
  uint8_t  mode_control;       // Byte  - [      8] of Log C0
  uint8_t  log_passthrough;    // Byte  - [      9] of Log C0
  uint16_t tier_id;            // Bytes - [ 11: 10] of Log C0
  uint32_t hw_version;         // Bytes - [ 15: 12] of Log C0
  uint32_t fw_version;         // Bytes - [ 19: 16] of Log C0
  uint8_t  variant[8];         // Bytes - [ 27: 20] of Log C0
  uint8_t  reserved[228];      // Bytes - [255: 28] of Log C0
  uint16_t port_0_settings[3]; // Bytes - [263:256] of Log C0
  uint16_t port_0_reserved;
  uint16_t port_1_settings[3]; // Bytes - [271:264] of Log C0
  uint16_t port_1_reserved;
  uint16_t port_2_settings[3]; // Bytes - [279:272] of Log C0
  uint16_t port_2_reserved;
  uint16_t port_3_settings[3]; // Bytes - [287:280] of Log C0
  uint16_t port_3_reserved;
  uint16_t port_4_settings[3]; // Bytes - [295:288] of Log C0
  uint16_t port_4_reserved;
  uint8_t  reserved2[214];     // Bytes - [509:296] of Log C0
  uint16_t crc;                // Bytes - [511:510] of Log C0
} ATTR_PACKED;
#pragma pack()
STATIC_ASSERT(sizeof(iprop_internal_log) == 512);

/**
 * buffer is a pointer to a buffer of bytes, which should include data and
 *   also CRC if the function is being used to check CRC
 * len is the number of bytes in the buffer (including CRC if it is present)
 * check_crc is a boolean value, set true to check an existing CRC, false
 *   to calculate a new CRC
 */
static uint16_t iprop_crc16_1(uint8_t * buffer, uint32_t len, bool check_crc)
{
  uint8_t crc[16];
  uint16_t crc_final = 0;
  uint8_t crc_msb;
  uint8_t data_msb;
  uint32_t total_len;

  // Initialize CRC array
  for (uint32_t ii = 0; ii < 16; ii++) {
    crc[ii] = 0;
    //crc[ii] = (crc_in >> ii) & 1;
  }

  // If calculating a new CRC, we need to pad the data with extra zeroes
  total_len = check_crc ? len : len + 2;

  // Loop for each byte, plus extra for the CRC itself
  for (uint32_t ii = 0; ii < total_len; ii++) {
    uint8_t data = (ii < len) ? buffer[ii] : 0;

    // Loop for each bit
    for (uint32_t jj = 0; jj < 8; jj++) {
      crc_msb = crc[15];
      data_msb = (data >> (8 - jj - 1)) & 1;

      crc[15] = crc[14] ^ crc_msb;
      crc[14] = crc[13];
      crc[13] = crc[12];
      crc[12] = crc[11];
      crc[11] = crc[10] ^ crc_msb;
      crc[10] = crc[9];
      crc[9] = crc[8] ^ crc_msb;
      crc[8] = crc[7] ^ crc_msb;
      crc[7] = crc[6] ^ crc_msb;
      crc[6] = crc[5];
      crc[5] = crc[4] ^ crc_msb;
      crc[4] = crc[3] ^ crc_msb;
      crc[3] = crc[2];
      crc[2] = crc[1] ^ crc_msb;
      crc[1] = crc[0] ^ crc_msb;
      crc[0] = data_msb ^ crc_msb;
    }
  }

  // Convert CRC array to final value
  for (uint32_t ii = 0; ii < 16; ii++) {
    if (crc[ii] == 1) {
      crc_final |= (1 << ii);
    } else {
      crc_final &= ~(1 << ii);
    }
  }

  return crc_final;
}

static void iprop_dump_log_structure(struct iprop_internal_log const * const log)
{
  pout("Dumping LOG Structure:\n");
  pout("  drive_select:           0x%08x\n", log->drive_select);
  pout("  obsolete:               0x%08x\n", log->obsolete);
  pout("  mode_control:           0x%02x\n", log->mode_control);
  pout("  log_passthrough:        0x%02x\n", log->log_passthrough);
  pout("  tier_id:                0x%04x\n", log->tier_id);
  pout("  hw_version:             0x%08x\n", log->hw_version);
  pout("  fw_version:             0x%08x\n", log->fw_version);
  pout("  variant:                \"");
  for (int ii = 0; ii < 8; ii++) {
    pout("%c", (char)log->variant[ii]);
  }
  pout("\"\n");
  pout("  port_0_settings(Gen 1): 0x%08x\n", log->port_0_settings[0]);
  pout("  port_0_settings(Gen 2): 0x%08x\n", log->port_0_settings[1]);
  pout("  port_0_settings(Gen 3): 0x%08x\n", log->port_0_settings[2]);
  pout("  port_1_settings(Gen 1): 0x%08x\n", log->port_1_settings[0]);
  pout("  port_1_settings(Gen 2): 0x%08x\n", log->port_1_settings[1]);
  pout("  port_1_settings(Gen 3): 0x%08x\n", log->port_1_settings[2]);
  pout("  port_2_settings(Gen 1): 0x%08x\n", log->port_2_settings[0]);
  pout("  port_2_settings(Gen 2): 0x%08x\n", log->port_2_settings[1]);
  pout("  port_2_settings(Gen 3): 0x%08x\n", log->port_2_settings[2]);
  pout("  port_3_settings(Gen 1): 0x%08x\n", log->port_3_settings[0]);
  pout("  port_3_settings(Gen 2): 0x%08x\n", log->port_3_settings[1]);
  pout("  port_3_settings(Gen 3): 0x%08x\n", log->port_3_settings[2]);
  pout("  port_4_settings(Gen 1): 0x%08x\n", log->port_4_settings[0]);
  pout("  port_4_settings(Gen 2): 0x%08x\n", log->port_4_settings[1]);
  pout("  port_4_settings(Gen 3): 0x%08x\n", log->port_4_settings[2]);
  pout("  crc:                    0x%04x\n", log->crc);
  pout("\n");
}

static bool iprop_switch_routed_drive(ata_device * device, int drive_select)
{
  // Declare a log page buffer and initialize it with what is on the drive currently
  iprop_internal_log write_payload;
  if (!ataReadLogExt(device, LOG_C0, 0, 0, &write_payload, 1))
    return device->set_err(EIO, "intelliprop: Initial Read Log failed: %s", device->get_errmsg());

  // Check the returned data is good
  uint16_t const crc_check = iprop_crc16_1((uint8_t *)&write_payload,
                                           sizeof(struct iprop_internal_log),
                                           false);


   //If this first read fails the crc check, the log can be still sent with routing information
   //as long as everything else in the log is zeroed. So there is no need to return false.
  if (crc_check != 0) {
    if (ata_debugmode)
      pout("Intelliprop WARNING: Received log crc(0x%04X) is invalid!\n", crc_check);
    iprop_dump_log_structure(&write_payload);
    memset(&write_payload, 0, sizeof(struct iprop_internal_log));
  }

  //The option to read the log, even if successful, could be useful
  if (ata_debugmode)
    iprop_dump_log_structure(&write_payload);

  // Modify the current drive select to what we were given
  write_payload.drive_select = (uint32_t)drive_select;
  if (ata_debugmode)
    pout("Intelliprop - Change to port 0x%08X.\n", write_payload.drive_select);
  write_payload.log_passthrough = 0; // TEST (Set to 1, non hydra member drive will abort --> test error handling)
  write_payload.tier_id = 0; // TEST (Set to non-zero, non hydra member drive will abort --> test error handling)

  // Update the CRC area
  uint16_t const crc_new = iprop_crc16_1((uint8_t *)&write_payload,
                                         sizeof(struct iprop_internal_log) - sizeof(uint16_t),
                                         false);
  write_payload.crc = (crc_new >> 8) | (crc_new << 8);

  // Check our CRC work
  uint16_t const crc_check2 = iprop_crc16_1((uint8_t *)&write_payload,
                                            sizeof(struct iprop_internal_log),
                                            false);
  if (crc_check2 != 0)
    return device->set_err(EIO, "intelliprop: Re-calculated log crc(0x%04X) is invalid!", crc_check2);

  // Apply the Write LOG
  if (!ataWriteLogExt(device, LOG_C0, 0, &write_payload, 1))
    return device->set_err(EIO, "intelliprop: Write Log failed: %s", device->get_errmsg());

  // Check that the Write LOG was applied
  iprop_internal_log check_payload;
  if (!ataReadLogExt(device, LOG_C0, 0, 0, &check_payload, 1))
    return device->set_err(EIO, "intelliprop: Secondary Read Log failed: %s", device->get_errmsg());

  if (check_payload.drive_select != write_payload.drive_select) {
    if (ata_debugmode > 1)
      iprop_dump_log_structure(&check_payload);
    return device->set_err(EIO, "intelliprop: Current drive select val(0x%08X) is not expected(0x%08X)",
         check_payload.drive_select,
         write_payload.drive_select);
  }

  return true;
}

namespace intelliprop {

class intelliprop_device
: public tunnelled_device<
    /*implements*/ ata_device,
    /*by using an*/ ata_device
  >
{
public:
  intelliprop_device(smart_interface * intf, unsigned phydrive, ata_device * atadev);

  virtual ~intelliprop_device();

  virtual bool open() override;

  virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) override;

private:
  unsigned m_phydrive;
};


intelliprop_device::intelliprop_device(smart_interface * intf, unsigned phydrive, ata_device * atadev)
: smart_device(intf, atadev->get_dev_name(), "intelliprop", "intelliprop"),
  tunnelled_device<ata_device, ata_device>(atadev),
  m_phydrive(phydrive)
{
  set_info().info_name = strprintf("%s [intelliprop_disk_%u]", atadev->get_info_name(), phydrive);
}

intelliprop_device::~intelliprop_device()
{
}

bool intelliprop_device::open()
{
  if (!tunnelled_device<ata_device, ata_device>::open())
    return false;

  ata_device * atadev = get_tunnel_dev();
  if (!iprop_switch_routed_drive(atadev, m_phydrive)) {
    close();
    return set_err(atadev->get_err());
  }

  return true;
}

bool intelliprop_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
{
  return get_tunnel_dev()->ata_pass_through(in, out);
}
}//namespace

ata_device * smart_interface::get_intelliprop_device(const char * type, ata_device * atadev)
{
  // Take temporary ownership of 'atadev' to delete it on error
  ata_device_auto_ptr atadev_holder(atadev);

  unsigned phydrive = ~0; int n = -1;
  sscanf(type, "intelliprop,%u,force%n", &phydrive, &n);
  // TODO: Remove after smartmontools 7.4
  if (n != (int)strlen(type))
    return set_err_np(EINVAL,
      "The device type 'intelliprop' is deprecated and will be removed in a\n"
      "future version of smartmontools.  If this device type is still needed, please\n"
      "use '-d intelliprop,N,force' and inform " PACKAGE_BUGREPORT                     );
  if (phydrive > 3)
    return set_err_np(EINVAL, "Option '-d intelliprop,N,force' must have 0 <= N <= 3");

  ata_device * itldev = new intelliprop::intelliprop_device(this, phydrive, atadev);
  // 'atadev' is now owned by 'itldev'
  atadev_holder.release();
  return itldev;
}