mirror of
https://github.com/Rockbox/rockbox.git
synced 2025-11-09 13:12:37 -05:00
iPod Classic: reads HDD S.M.A.R.T. data
Adds ata_read_smart() function to storage ATA driver, current SMART data can be displayed and optionally written to hard disk using System->Debug menu. Change-Id: Ie8817bb311d5d956df2f0fbfaf554e2d53e89a93
This commit is contained in:
parent
32b4558511
commit
d20185ac96
5 changed files with 373 additions and 0 deletions
|
|
@ -1507,6 +1507,205 @@ static int disk_callback(int btn, struct gui_synclist *lists)
|
||||||
#endif /* HAVE_ATA_DMA */
|
#endif /* HAVE_ATA_DMA */
|
||||||
return btn;
|
return btn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_ATA_SMART
|
||||||
|
static struct ata_smart_values *smart_data = NULL;
|
||||||
|
|
||||||
|
static const char * ata_smart_get_attr_name(unsigned char id)
|
||||||
|
{
|
||||||
|
switch (id)
|
||||||
|
{
|
||||||
|
case 1: return "Raw Read Error Rate";
|
||||||
|
case 2: return "Throughput Performance";
|
||||||
|
case 3: return "Spin-Up Time";
|
||||||
|
case 4: return "Start/Stop Count";
|
||||||
|
case 5: return "Reallocated Sector Count";
|
||||||
|
case 7: return "Seek Error Rate";
|
||||||
|
case 8: return "Seek Time Performance";
|
||||||
|
case 9: return "Power-On Hours Count";
|
||||||
|
case 10: return "Spin-Up Retry Count";
|
||||||
|
case 12: return "Power Cycle Count";
|
||||||
|
case 192: return "Power-Off Retract Count";
|
||||||
|
case 193: return "Load/Unload Cycle Count";
|
||||||
|
case 194: return "HDA Temperature";
|
||||||
|
case 196: return "Reallocated Event Count";
|
||||||
|
case 197: return "Current Pending Sector Count";
|
||||||
|
case 198: return "Uncorrectable Sector Count";
|
||||||
|
case 199: return "UltraDMA CRC Error Count";
|
||||||
|
case 220: return "Disk Shift";
|
||||||
|
case 222: return "Loaded Hours";
|
||||||
|
case 223: return "Load/Unload Retry Count";
|
||||||
|
case 224: return "Load Friction";
|
||||||
|
case 226: return "Load-In Time";
|
||||||
|
case 240: return "Transfer Error Rate"; /* Fujitsu */
|
||||||
|
/*case 240: return "Head Flying Hours";*/
|
||||||
|
default: return "Unknown Attribute";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static int ata_smart_get_attr_rawfmt(unsigned char id)
|
||||||
|
{
|
||||||
|
switch (id)
|
||||||
|
{
|
||||||
|
case 3: /* Spin-up time */
|
||||||
|
return RAWFMT_RAW16_OPT_AVG16;
|
||||||
|
|
||||||
|
case 5: /* Reallocated sector count */
|
||||||
|
case 196: /* Reallocated event count */
|
||||||
|
return RAWFMT_RAW16_OPT_RAW16;
|
||||||
|
|
||||||
|
case 190: /* Airflow Temperature */
|
||||||
|
case 194: /* HDA Temperature */
|
||||||
|
return RAWFMT_TEMPMINMAX;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return RAWFMT_RAW48;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static int ata_smart_attr_to_string(
|
||||||
|
struct ata_smart_attribute *attr, char *str, int size)
|
||||||
|
{
|
||||||
|
uint16_t w[3]; /* 3 words to store 6 bytes of raw data */
|
||||||
|
char buf[size]; /* temp string to store attribute data */
|
||||||
|
int len, slen;
|
||||||
|
int id = attr->id;
|
||||||
|
|
||||||
|
if (id == 0)
|
||||||
|
return 0; /* null attribute */
|
||||||
|
|
||||||
|
/* align and convert raw data */
|
||||||
|
memcpy(w, attr->raw, 6);
|
||||||
|
w[0] = letoh16(w[0]);
|
||||||
|
w[1] = letoh16(w[1]);
|
||||||
|
w[2] = letoh16(w[2]);
|
||||||
|
|
||||||
|
len = snprintf(buf, size, ": %u,%u ", attr->current, attr->worst);
|
||||||
|
|
||||||
|
switch (ata_smart_get_attr_rawfmt(id))
|
||||||
|
{
|
||||||
|
case RAWFMT_RAW16_OPT_RAW16:
|
||||||
|
len += snprintf(buf+len, size-len, "%u", w[0]);
|
||||||
|
if ((w[1] || w[2]) && (len < size))
|
||||||
|
len += snprintf(buf+len, size-len, " %u %u", w[1],w[2]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RAWFMT_RAW16_OPT_AVG16:
|
||||||
|
len += snprintf(buf+len, size-len, "%u", w[0]);
|
||||||
|
if (w[1] && (len < size))
|
||||||
|
len += snprintf(buf+len, size-len, " Avg: %u", w[1]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RAWFMT_TEMPMINMAX:
|
||||||
|
len += snprintf(buf+len, size-len, "%u -/+: %u/%u", w[0],w[1],w[2]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RAWFMT_RAW48:
|
||||||
|
default:
|
||||||
|
/* shows first 4 bytes of raw data as uint32 LE,
|
||||||
|
and the ramaining 2 bytes as uint16 LE */
|
||||||
|
len += snprintf(buf+len, size-len, "%lu", letoh32(*((uint32_t*)w)));
|
||||||
|
if (w[2] && (len < size))
|
||||||
|
len += snprintf(buf+len, size-len, " %u", w[2]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* ignore trailing \0 when truncated */
|
||||||
|
if (len >= size) len = size-1;
|
||||||
|
|
||||||
|
/* fill return string; when max. size is exceded: first truncate
|
||||||
|
attribute name, then attribute data and finally attribute id */
|
||||||
|
slen = snprintf(str, size, "%d ", id);
|
||||||
|
if (slen < size) {
|
||||||
|
/* maximum space disponible for attribute name,
|
||||||
|
including initial space separator */
|
||||||
|
int name_sz = size - (slen + len);
|
||||||
|
if (name_sz > 1) {
|
||||||
|
len = snprintf(str+slen, name_sz, " %s",
|
||||||
|
ata_smart_get_attr_name(id));
|
||||||
|
if (len >= name_sz) len = name_sz-1;
|
||||||
|
slen += len;
|
||||||
|
}
|
||||||
|
snprintf(str+slen, size-slen, "%s", buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1; /* ok */
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ata_smart_dump(void)
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
fd = creat("/smart_data.bin", 0666);
|
||||||
|
if(fd >= 0)
|
||||||
|
{
|
||||||
|
write(fd, smart_data, sizeof(struct ata_smart_values));
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
fd = creat("/smart_data.txt", 0666);
|
||||||
|
if(fd >= 0)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
char buf[128];
|
||||||
|
for (i = 0; i < NUMBER_ATA_SMART_ATTRIBUTES; i++)
|
||||||
|
{
|
||||||
|
if (ata_smart_attr_to_string(
|
||||||
|
&smart_data->vendor_attributes[i], buf, sizeof(buf)))
|
||||||
|
{
|
||||||
|
write(fd, buf, strlen(buf));
|
||||||
|
write(fd, "\n", 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ata_smart_callback(int btn, struct gui_synclist *lists)
|
||||||
|
{
|
||||||
|
(void)lists;
|
||||||
|
|
||||||
|
if (btn == ACTION_STD_CANCEL)
|
||||||
|
{
|
||||||
|
smart_data = NULL;
|
||||||
|
return btn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* read S.M.A.R.T. data only on first redraw */
|
||||||
|
if (!smart_data)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
char buf[SIMPLELIST_MAX_LINELENGTH];
|
||||||
|
smart_data = ata_read_smart();
|
||||||
|
simplelist_set_line_count(0);
|
||||||
|
simplelist_addline("Id Name: Current,Worst Raw");
|
||||||
|
for (i = 0; i < NUMBER_ATA_SMART_ATTRIBUTES; i++) {
|
||||||
|
if (ata_smart_attr_to_string(
|
||||||
|
&smart_data->vendor_attributes[i], buf, sizeof(buf)))
|
||||||
|
simplelist_addline(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (btn == ACTION_STD_CONTEXT)
|
||||||
|
{
|
||||||
|
ata_smart_dump();
|
||||||
|
splashf(HZ, "S.M.A.R.T. data dumped");
|
||||||
|
}
|
||||||
|
|
||||||
|
return btn;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool dbg_ata_smart(void)
|
||||||
|
{
|
||||||
|
struct simplelist_info info;
|
||||||
|
simplelist_info_init(&info, "S.M.A.R.T. Data [CONTEXT to dump]", 1, NULL);
|
||||||
|
info.action_callback = ata_smart_callback;
|
||||||
|
info.hide_selection = true;
|
||||||
|
info.scroll_all = true;
|
||||||
|
return simplelist_show_list(&info);
|
||||||
|
}
|
||||||
|
#endif /* HAVE_ATA_SMART */
|
||||||
#else /* No SD, MMC or ATA */
|
#else /* No SD, MMC or ATA */
|
||||||
static int disk_callback(int btn, struct gui_synclist *lists)
|
static int disk_callback(int btn, struct gui_synclist *lists)
|
||||||
{
|
{
|
||||||
|
|
@ -2383,6 +2582,9 @@ static const struct {
|
||||||
{ "View disk info", dbg_disk_info },
|
{ "View disk info", dbg_disk_info },
|
||||||
#if (CONFIG_STORAGE & STORAGE_ATA)
|
#if (CONFIG_STORAGE & STORAGE_ATA)
|
||||||
{ "Dump ATA identify info", dbg_identify_info},
|
{ "Dump ATA identify info", dbg_identify_info},
|
||||||
|
#ifdef HAVE_ATA_SMART
|
||||||
|
{ "View/Dump S.M.A.R.T. data", dbg_ata_smart},
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
{ "Metadata log", dbg_metadatalog },
|
{ "Metadata log", dbg_metadatalog },
|
||||||
|
|
|
||||||
|
|
@ -257,7 +257,11 @@ struct simplelist_info {
|
||||||
};
|
};
|
||||||
|
|
||||||
#define SIMPLELIST_MAX_LINES 32
|
#define SIMPLELIST_MAX_LINES 32
|
||||||
|
#ifdef HAVE_ATA_SMART
|
||||||
|
#define SIMPLELIST_MAX_LINELENGTH 48
|
||||||
|
#else
|
||||||
#define SIMPLELIST_MAX_LINELENGTH 32
|
#define SIMPLELIST_MAX_LINELENGTH 32
|
||||||
|
#endif
|
||||||
|
|
||||||
/** The next three functions are used if the text is mostly static.
|
/** The next three functions are used if the text is mostly static.
|
||||||
These should be called in the action callback for the list.
|
These should be called in the action callback for the list.
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,107 @@
|
||||||
#include "config.h" /* for HAVE_MULTIVOLUME or not */
|
#include "config.h" /* for HAVE_MULTIVOLUME or not */
|
||||||
#include "mv.h" /* for IF_MV() and friends */
|
#include "mv.h" /* for IF_MV() and friends */
|
||||||
|
|
||||||
|
#ifdef HAVE_ATA_SMART
|
||||||
|
/* S.M.A.R.T. headers from smartmontools-5.42 */
|
||||||
|
#define NUMBER_ATA_SMART_ATTRIBUTES 30
|
||||||
|
|
||||||
|
struct ata_smart_attribute {
|
||||||
|
unsigned char id;
|
||||||
|
/* meaning of flag bits: see MACROS just below */
|
||||||
|
/* WARNING: MISALIGNED! */
|
||||||
|
unsigned short flags;
|
||||||
|
unsigned char current;
|
||||||
|
unsigned char worst;
|
||||||
|
unsigned char raw[6];
|
||||||
|
unsigned char reserv;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
/* MACROS to interpret the flags bits in the previous structure. */
|
||||||
|
/* These have not been implemented using bitflags and a union, to make */
|
||||||
|
/* it portable across bit/little endian and different platforms. */
|
||||||
|
|
||||||
|
/* 0: Prefailure bit */
|
||||||
|
|
||||||
|
/* From SFF 8035i Revision 2 page 19: Bit 0 (pre-failure/advisory bit) */
|
||||||
|
/* - If the value of this bit equals zero, an attribute value less */
|
||||||
|
/* than or equal to its corresponding attribute threshold indicates an */
|
||||||
|
/* advisory condition where the usage or age of the device has */
|
||||||
|
/* exceeded its intended design life period. If the value of this bit */
|
||||||
|
/* equals one, an attribute value less than or equal to its */
|
||||||
|
/* corresponding attribute threshold indicates a prefailure condition */
|
||||||
|
/* where imminent loss of data is being predicted. */
|
||||||
|
#define ATTRIBUTE_FLAGS_PREFAILURE(x) (x & 0x01)
|
||||||
|
|
||||||
|
/* 1: Online bit */
|
||||||
|
|
||||||
|
/* From SFF 8035i Revision 2 page 19: Bit 1 (on-line data collection */
|
||||||
|
/* bit) - If the value of this bit equals zero, then the attribute */
|
||||||
|
/* value is updated only during off-line data collection */
|
||||||
|
/* activities. If the value of this bit equals one, then the attribute */
|
||||||
|
/* value is updated during normal operation of the device or during */
|
||||||
|
/* both normal operation and off-line testing. */
|
||||||
|
#define ATTRIBUTE_FLAGS_ONLINE(x) (x & 0x02)
|
||||||
|
|
||||||
|
/* The following are (probably) IBM's, Maxtors and Quantum's definitions for the */
|
||||||
|
/* vendor-specific bits: */
|
||||||
|
/* 2: Performance type bit */
|
||||||
|
#define ATTRIBUTE_FLAGS_PERFORMANCE(x) (x & 0x04)
|
||||||
|
|
||||||
|
/* 3: Errorrate type bit */
|
||||||
|
#define ATTRIBUTE_FLAGS_ERRORRATE(x) (x & 0x08)
|
||||||
|
|
||||||
|
/* 4: Eventcount bit */
|
||||||
|
#define ATTRIBUTE_FLAGS_EVENTCOUNT(x) (x & 0x10)
|
||||||
|
|
||||||
|
/* 5: Selfpereserving bit */
|
||||||
|
#define ATTRIBUTE_FLAGS_SELFPRESERVING(x) (x & 0x20)
|
||||||
|
|
||||||
|
/* 6-15: Reserved for future use */
|
||||||
|
#define ATTRIBUTE_FLAGS_OTHER(x) ((x) & 0xffc0)
|
||||||
|
|
||||||
|
struct ata_smart_values
|
||||||
|
{
|
||||||
|
unsigned short int revnumber;
|
||||||
|
struct ata_smart_attribute vendor_attributes [NUMBER_ATA_SMART_ATTRIBUTES];
|
||||||
|
unsigned char offline_data_collection_status;
|
||||||
|
unsigned char self_test_exec_status;
|
||||||
|
unsigned short int total_time_to_complete_off_line;
|
||||||
|
unsigned char vendor_specific_366;
|
||||||
|
unsigned char offline_data_collection_capability;
|
||||||
|
unsigned short int smart_capability;
|
||||||
|
unsigned char errorlog_capability;
|
||||||
|
unsigned char vendor_specific_371;
|
||||||
|
unsigned char short_test_completion_time;
|
||||||
|
unsigned char extend_test_completion_time;
|
||||||
|
unsigned char conveyance_test_completion_time;
|
||||||
|
unsigned char reserved_375_385[11];
|
||||||
|
unsigned char vendor_specific_386_510[125];
|
||||||
|
unsigned char chksum;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
/* Raw attribute value print formats */
|
||||||
|
enum ata_attr_raw_format
|
||||||
|
{
|
||||||
|
RAWFMT_DEFAULT,
|
||||||
|
RAWFMT_RAW8,
|
||||||
|
RAWFMT_RAW16,
|
||||||
|
RAWFMT_RAW48,
|
||||||
|
RAWFMT_HEX48,
|
||||||
|
RAWFMT_RAW64,
|
||||||
|
RAWFMT_HEX64,
|
||||||
|
RAWFMT_RAW16_OPT_RAW16,
|
||||||
|
RAWFMT_RAW16_OPT_AVG16,
|
||||||
|
RAWFMT_RAW24_DIV_RAW24,
|
||||||
|
RAWFMT_RAW24_DIV_RAW32,
|
||||||
|
RAWFMT_SEC2HOUR,
|
||||||
|
RAWFMT_MIN2HOUR,
|
||||||
|
RAWFMT_HALFMIN2HOUR,
|
||||||
|
RAWFMT_MSEC24_HOUR32,
|
||||||
|
RAWFMT_TEMPMINMAX,
|
||||||
|
RAWFMT_TEMP10X,
|
||||||
|
};
|
||||||
|
#endif /* HAVE_ATA_SMART */
|
||||||
|
|
||||||
struct storage_info;
|
struct storage_info;
|
||||||
|
|
||||||
void ata_enable(bool on);
|
void ata_enable(bool on);
|
||||||
|
|
@ -69,4 +170,9 @@ int ata_spinup_time(void); /* ticks */
|
||||||
int ata_get_dma_mode(void);
|
int ata_get_dma_mode(void);
|
||||||
#endif /* HAVE_ATA_DMA */
|
#endif /* HAVE_ATA_DMA */
|
||||||
|
|
||||||
|
#ifdef HAVE_ATA_SMART
|
||||||
|
/* Returns current S.M.A.R.T. data */
|
||||||
|
void* ata_read_smart(void);
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif /* __ATA_H__ */
|
#endif /* __ATA_H__ */
|
||||||
|
|
|
||||||
|
|
@ -201,6 +201,8 @@
|
||||||
|
|
||||||
#define STORAGE_NEEDS_ALIGN
|
#define STORAGE_NEEDS_ALIGN
|
||||||
|
|
||||||
|
#define HAVE_ATA_SMART
|
||||||
|
|
||||||
/* define this if the device has larger sectors when accessed via USB */
|
/* define this if the device has larger sectors when accessed via USB */
|
||||||
/* (only relevant in disk.c, fat.c now always supports large virtual sectors) */
|
/* (only relevant in disk.c, fat.c now always supports large virtual sectors) */
|
||||||
//#define MAX_LOG_SECTOR_SIZE 4096
|
//#define MAX_LOG_SECTOR_SIZE 4096
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,9 @@
|
||||||
/** static, private data **/
|
/** static, private data **/
|
||||||
static uint8_t ceata_taskfile[16] STORAGE_ALIGN_ATTR;
|
static uint8_t ceata_taskfile[16] STORAGE_ALIGN_ATTR;
|
||||||
static uint16_t ata_identify_data[0x100] STORAGE_ALIGN_ATTR;
|
static uint16_t ata_identify_data[0x100] STORAGE_ALIGN_ATTR;
|
||||||
|
#ifdef HAVE_ATA_SMART
|
||||||
|
static uint16_t ata_smart_data[0x100] STORAGE_ALIGN_ATTR;
|
||||||
|
#endif
|
||||||
static bool ceata;
|
static bool ceata;
|
||||||
static bool ata_swap;
|
static bool ata_swap;
|
||||||
static bool ata_lba48;
|
static bool ata_lba48;
|
||||||
|
|
@ -1211,6 +1214,62 @@ int ata_init(void)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_ATA_SMART
|
||||||
|
static int ata_smart(uint16_t* buf)
|
||||||
|
{
|
||||||
|
mutex_lock(&ata_mutex);
|
||||||
|
ata_power_up();
|
||||||
|
|
||||||
|
if (ceata)
|
||||||
|
{
|
||||||
|
memset(ceata_taskfile, 0, 16);
|
||||||
|
ceata_taskfile[0xc] = 0x4f;
|
||||||
|
ceata_taskfile[0xd] = 0xc2;
|
||||||
|
ceata_taskfile[0xe] = 0x40; /* Device/Head Register, bit6: 0->CHS, 1->LBA */
|
||||||
|
ceata_taskfile[0xf] = 0xb0;
|
||||||
|
PASS_RC(ceata_wait_idle(), 3, 0);
|
||||||
|
if (((uint8_t*)ata_identify_data)[54] != 'A') /* Model != aAmsung */
|
||||||
|
{
|
||||||
|
ceata_taskfile[0x9] = 0xd8; /* SMART enable operations */
|
||||||
|
PASS_RC(ceata_write_multiple_register(0, ceata_taskfile, 16), 3, 1);
|
||||||
|
PASS_RC(ceata_check_error(), 3, 2);
|
||||||
|
}
|
||||||
|
ceata_taskfile[0x9] = 0xd0; /* SMART read data */
|
||||||
|
PASS_RC(ceata_write_multiple_register(0, ceata_taskfile, 16), 3, 3);
|
||||||
|
PASS_RC(ceata_rw_multiple_block(false, buf, 1, CEATA_COMMAND_TIMEOUT * HZ / 1000000), 3, 4);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
uint32_t old = ATA_CFG;
|
||||||
|
ATA_CFG |= BIT(6); /* 16bit big-endian */
|
||||||
|
PASS_RC(ata_wait_for_not_bsy(10000000), 3, 5);
|
||||||
|
ata_write_cbr(&ATA_PIO_DAD, 0);
|
||||||
|
ata_write_cbr(&ATA_PIO_FED, 0xd0);
|
||||||
|
ata_write_cbr(&ATA_PIO_SCR, 0);
|
||||||
|
ata_write_cbr(&ATA_PIO_LLR, 0);
|
||||||
|
ata_write_cbr(&ATA_PIO_LMR, 0x4f);
|
||||||
|
ata_write_cbr(&ATA_PIO_LHR, 0xc2);
|
||||||
|
ata_write_cbr(&ATA_PIO_DVR, BIT(6));
|
||||||
|
ata_write_cbr(&ATA_PIO_CSD, 0xb0);
|
||||||
|
PASS_RC(ata_wait_for_start_of_transfer(10000000), 3, 6);
|
||||||
|
for (i = 0; i < 0x100; i++) buf[i] = ata_read_cbr(&ATA_PIO_DTR);
|
||||||
|
ATA_CFG = old;
|
||||||
|
}
|
||||||
|
|
||||||
|
ata_set_active();
|
||||||
|
mutex_unlock(&ata_mutex);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* ata_read_smart(void)
|
||||||
|
{
|
||||||
|
ata_smart(ata_smart_data);
|
||||||
|
return ata_smart_data;
|
||||||
|
}
|
||||||
|
#endif /* HAVE_ATA_SMART */
|
||||||
|
|
||||||
#ifdef CONFIG_STORAGE_MULTI
|
#ifdef CONFIG_STORAGE_MULTI
|
||||||
static int ata_num_drives(int first_drive)
|
static int ata_num_drives(int first_drive)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue