Post by kasper on Sept 22, 2009 10:01:56 GMT -5
As Linux users know, there is no support for querying Netcell Revolution RAID information on Linux. After debugging the windows utility I came to conclusion that the card is controlled using vendor specific ATA SMART commands to a separate pseudo disk named "Netcell Comm Channel <don't use>". Initializing this control device needs some special i/o. Here is a patch for Linux kernel. It should apply to newer versions also.
*** linux-2.6.28/drivers/ata/pata_netcell.c Thu Dec 25 01:26:37 2008
--- linux-2.6.27/drivers/ata/pata_netcell.c Sun Oct 12 07:08:49 2008
***************
*** 30,35 ****
--- 30,148 ----
};
+ static DEFINE_SPINLOCK(netcell_lock);
+
+ static unsigned int init_control_device(struct pci_dev *dev)
+ {
+ unsigned long flags;
+ unsigned char c;
+ unsigned long cmd = 0; // 0x9800;
+ unsigned long ctl = 0; // 0x9402;
+ unsigned long bmdma = 0; // 0x9008;
+ int status = -1;
+
+ /* In my system:
+ I/O ports at a400 <- Normal ide
+ I/O ports at a000 <- Normal ide
+ I/O ports at 9800 <- Control interface
+ I/O ports at 9400 <- Control interface
+ I/O ports at 9000 <- dma both
+
+ Revolution: simplex device: DMA forced
+ ide2: BM-DMA at 0x9000-0x9007, BIOS settings: hde:DMA, hdf:DMA
+ Revolution: simplex device: DMA forced
+ ide3: BM-DMA at 0x9008-0x900f, BIOS settings: hdg:pio, hdh:pio
+
+ Probing IDE interface ide2...
+ hde: NetCell SyncRAID(TM) SR5000 R1-2, ATA DISK drive
+ ide2 at 0xa400-0xa407,0xa002 on irq 10
+
+ Probing IDE interface ide3...
+ hdh: NetCell Configuration Device <don't use, ATA DISK drive
+ ide3 at 0x9800-0x9807,0x9402 on irq 10
+
+ */
+
+ if (pci_resource_len(dev, 2) != 8 ||
+ pci_resource_len(dev, 3) != 4 ||
+ pci_resource_len(dev, 4) != 16) {
+ printk (DRV_NAME ": ERROR: cannot find io ports!\n");
+ goto out;
+ }
+
+ cmd = pci_resource_start(dev, 2);
+ ctl = pci_resource_start(dev, 3);
+ bmdma = pci_resource_start(dev, 4);
+
+ if (cmd == 0 || ctl == 0 || bmdma == 0) {
+ printk (DRV_NAME ": ERROR: cannot map io ports!\n");
+ goto out;
+ }
+
+ ctl = ctl + 2;
+ bmdma = bmdma + 8;
+
+ spin_lock_irqsave(&netcell_lock, flags);
+
+ c = inb(cmd + ATA_REG_STATUS);
+ if (c != 0) {
+ printk (DRV_NAME ": WARNING: status 0 != %X\n", c);
+ }
+ outb(ATA_HOB, ctl);
+
+ /* Some extra checks, may be unnecessary */
+ c = inb(cmd + ATA_REG_LBAL);
+ if (c != 0) {
+ printk (DRV_NAME ": WARNING: sector 0 != %X\n", c);
+ }
+ c = inb(cmd + ATA_REG_NSECT);
+ if (c != 0) {
+ printk (DRV_NAME ": WARNING: nsector 0 != %X\n", c);
+ }
+ c = inb(cmd + ATA_REG_LBAH);
+ if (c != 0) {
+ printk (DRV_NAME ": WARNING: hcyl 0 != %X\n", c);
+ }
+ c = inb(cmd + ATA_REG_LBAM);
+ if (c != 0) {
+ printk (DRV_NAME ": WARNING: lcyl 0 != %X\n", c);
+ }
+ outb(0x00, ctl);
+
+ // Reset
+ outb(ATA_SRST, ctl);
+ mdelay(40);
+ outb(0x00, ctl);
+
+ c = inb(bmdma + ATA_DMA_STATUS);
+ // 0xe0 ok
+ // 0x86 something fishy ?
+ // 0x80 initialized already ?
+ if (c != 0xe0) {
+ printk (DRV_NAME ": WARNING: ioport status = %X\n", c);
+ }
+
+ // Wakeup sequence
+ outl(0xeba40306, bmdma + 4);
+ outb(0x06, bmdma + ATA_DMA_STATUS);
+ outb(0x00, bmdma + ATA_DMA_CMD);
+
+ mdelay(10);
+ c = inb(cmd + ATA_REG_STATUS);
+ // 0x52 ok (BUSY=0, DRDY =0x40)
+ if ((c & (ATA_DRDY | ATA_BUSY)) == ATA_DRDY) {
+ status = 0;
+ printk (DRV_NAME ": Configuration device initilized\n");
+ } else {
+ printk (DRV_NAME ": ERROR: Could not initialize configuration device: status %X not ok\n", c);
+ }
+
+ spin_unlock_irqrestore(&netcell_lock, flags);
+
+ out:
+ return status;
+ }
+
/**
* netcell_init_one - Register Netcell ATA PCI device with kernel services
* @pdev: PCI device to register
***************
*** 68,73 ****
--- 181,187 ----
return rc;
/* Any chip specific setup/optimisation/messages here */
+ init_control_device(pdev);
ata_pci_bmdma_clear_simplex(pdev);
/* And let the library code do the work */
After applying the patch and running the new kernel , you should see a boot messages like:
[ 1.000017] pata_netcell: Configuration device initilized
...
[ 1.220334] ata1.00: ATA-6: Netcell Revolution SR5000 R1-2, , max UDMA7
[ 1.220338] ata1.00: 488397168 sectors, multi 128: LBA48
[ 1.260358] ata1.00: configured for UDMA/100
[ 1.260513] scsi 0:0:0:0: Direct-Access ATA Netcell Revoluti n/a PQ: 0 ANSI: 5
[ 1.440322] ata2.01: ATA-6: Netcell Comm Channel <don't use>, , max UDMA7
[ 1.440327] ata2.01: 1008 sectors, multi 128: LBA48
[ 1.440364] ata2.01: simplex DMA is claimed by other device, disabling DMA
[ 1.460349] ata2.01: configured for PIO4
[ 1.460491] scsi 1:0:1:0: Direct-Access ATA Netcell Comm Cha n/a PQ: 0 ANSI: 5
Next thing to do is start poking the card with some carefully crafted ata commands. a small program follows.
WARNING: this command may corrupt your data (yet it hasn't done it for me)
/*
* based on Simple Disk Sleep Monitor
* by Bartek Kania
* Licenced under the GPL
*/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <string.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <linux/hdreg.h>
#include <scsi/sg.h>
#ifdef DEBUG
#define D(x) x
#else
#define D(x)
#endif
#define INPUT_SIZE 512
#define OUTPUT_SIZE 512
#define ATA_SMART_CMD 0xb0
#define ATA_IDENTIFY_DEVICE 0xec
#define ATA_16 0x85 /* 16-byte pass-thru */
#define ATA_12 0xa1 /* 12-byte pass-thru */
void print_task(u_int8_t *args, char *title) {
/*
printf ("-- %s\n", title);
printf ("COMMAND %02X\n", args[0]);
printf ("FEATURE %02X\n", args[1]);
printf ("NSECTOR %02X\n", args[2]);
printf ("SECTOR %02X\n", args[3]);
printf ("LCYL %02X\n", args[4]);
printf ("HCYL %02X\n", args[5]);
printf ("SELECT %02X\n", args[6]);
*/
printf ("%02X %02X %02X %02X %02X %02X %02X %s\n",
args[1], args[2], args[3], args[4], args[5], args[6], args[0],
title);
}
void print_buffer(u_int8_t *buf, int size) {
char ascz[17];
int i,j;
for (i = 0; i < size; i += 16) {
printf ("%04X: ", i);
for (j = 0; j < 16; j++) {
if (i + j < size) {
ascz[j] = (buf[j+i] >= ' ' && buf[j+i] < 0x7f) ? buf[j+i] : '.';
printf ("%02X ", buf[j+i]);
} else {
printf (" ");
ascz[j] = ' ';
}
}
ascz[j] = 0;
printf ("%s\n", ascz);
}
}
int cmd_e180 (int fd)
{
u_int8_t args[7];
int retval = 0;
args[0] = ATA_SMART_CMD; /* COMMAND */
args[1] = 0xE1; /* FEATURE */
args[2] = 0x00; /* NSECTOR */
args[3] = 0x80; /* SECTOR */
args[4] = 0x4F; /* LCYL */
args[5] = 0xC2; /* HCYL */
args[6] = 0xB0; /* SELECT */
print_task(args, "E180 req");
if ((retval=ioctl(fd, HDIO_DRIVE_TASK, args))) {
perror("cmd_e180 ioctl");
exit (-1);
}
print_task(args, "E180 res");
return retval;
}
int cmd (int fd, int feature, int nsector, int sector, int head)
{
u_int8_t args[7];
int retval = 0;
args[0] = ATA_SMART_CMD; /* COMMAND */
args[1] = feature;
args[2] = nsector;
args[3] = sector;
args[4] = 0x4F; /* LCYL */
args[5] = 0xC2; /* HCYL */
args[6] = head;
printf ("%02X %02X %02X %02X %02X %02X %02X - %02X%02X %02X %02X req\n",
args[1], args[2], args[3], args[4], args[5], args[6], args[0],
feature, sector, nsector, head);
if ((retval=ioctl(fd, HDIO_DRIVE_TASK, args))) {
perror("cmd ioctl");
exit (-1);
}
printf ("%02X %02X %02X %02X %02X %02X %02X - %02X%02X %02X %02X res\n",
args[1], args[2], args[3], args[4], args[5], args[6], args[0],
feature, sector, nsector, head);
return retval;
}
int cmd2(int fd, int feature, int nsector, int sector, int head)
{
struct {
ide_task_request_t req_task;
u_int8_t outbuf[OUTPUT_SIZE];
u_int8_t inbuf[INPUT_SIZE];
} task;
ide_task_request_t *reqtask=(ide_task_request_t *) &(task.req_task);
task_struct_t *taskfile=(task_struct_t *) reqtask->io_ports;
int retval;
memset(&task, 0, sizeof(task));
reqtask->data_phase = TASKFILE_NO_DATA;
reqtask->req_cmd = IDE_DRIVE_TASK_NO_DATA;
reqtask->out_size = 0;
reqtask->out_size = 0;
reqtask->in_size = 0;
taskfile->data = 0;
taskfile->feature = feature;
taskfile->sector_count = nsector;
taskfile->sector_number = sector;
taskfile->low_cylinder = 0x4f; /* SMART constant */
taskfile->high_cylinder = 0xc2; /* SMART constant */
taskfile->device_head = head;
taskfile->command = ATA_SMART_CMD;
reqtask->in_flags.all=0;
reqtask->in_flags.b.error_feature = 1;
reqtask->in_flags.b.sector = 1;
reqtask->in_flags.b.nsector = 1;
reqtask->in_flags.b.lcyl = 1;
reqtask->in_flags.b.hcyl = 1;
reqtask->in_flags.b.select = 1;
reqtask->in_flags.b.status_command = 1;
reqtask->out_flags.all=0;
reqtask->out_flags.b.error_feature = 1;
reqtask->out_flags.b.sector = 1;
reqtask->out_flags.b.nsector = 1;
reqtask->out_flags.b.lcyl = 1;
reqtask->out_flags.b.hcyl = 1;
reqtask->out_flags.b.select = 1;
reqtask->out_flags.b.status_command = 1;
// copy user data into the task request structure
/*memcpy(&task.inbuf, data, 512);*/
/*
printf ("data %02X\n", taskfile->data);
printf ("feature %02X\n", taskfile->feature);
printf ("sector_count %02X\n", taskfile->sector_count);
printf ("sector_number %02X\n", taskfile->sector_number);
printf ("low_cylinder %02X\n", taskfile->low_cylinder);
printf ("high_cylinder %02X\n", taskfile->high_cylinder);
printf ("device_head %02X\n", taskfile->device_head);
printf ("command %02X\n", taskfile->command);
*/
printf ("CMD: %02X %02X %02X %02X %02X %02X %02X - %02X%02X %02X %02X\n",
taskfile->feature, taskfile->sector_count,
taskfile->sector_number, taskfile->low_cylinder,
taskfile->high_cylinder, taskfile->device_head,
taskfile->command, feature, sector, nsector, head);
if ((retval=ioctl(fd, HDIO_DRIVE_TASKFILE, &task))) {
if (errno==EINVAL) {
printf ("Kernel lacks HDIO_DRIVE_TASKFILE support; compile kernel with CONFIG_IDE_TASKFILE_IO set\n");
return -1;
}
perror("cmd2 ioctl");
exit (-1);
}
printf ("RES: %02X %02X %02X %02X %02X %02X %02X - %02X%02X %02X %02X\n",
taskfile->feature, taskfile->sector_count,
taskfile->sector_number, taskfile->low_cylinder,
taskfile->high_cylinder, taskfile->device_head,
taskfile->command, feature, sector, nsector, head);
return 0;
}
int cmd_data(int fd, int feature, int nsector, int sector, int head, u_int8_t *data)
{
int retval;
int i;
unsigned sum = 0;
u_int8_t inbuf[INPUT_SIZE];
unsigned char sense_b[32];
unsigned char * ret = sense_b + 8;
unsigned char cdb[16];
struct sg_io_hdr io_hdr;
int k;
memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
memset (&cdb, 0, sizeof(cdb));
io_hdr.interface_id = 'S';
io_hdr.cmdp = cdb;
io_hdr.cmd_len = sizeof(cdb);
io_hdr.mx_sb_len = sizeof(sense_b);
io_hdr.dxferp = inbuf;
io_hdr.dxfer_len = sizeof (inbuf);
io_hdr.resid = 0;
io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
io_hdr.sbp = sense_b;
io_hdr.timeout = 30000;
int command_id = ATA_SMART_CMD;
int lcyl = 0x4f; /* SMART constant */
int hcyl = 0xc2; /* SMART constant */
cdb[0] = ATA_16;; // pass-through ATA16 command (no translation)
cdb[1] = (4 << 1); // data-in
cdb[2] = 0x2e; // data-in + generate sense
cdb[4] = feature; // ATA feature ID
cdb[6] = nsector; // number of sectors
cdb[8] = sector;
cdb[10] = lcyl;
cdb[12] = hcyl;
cdb[13] = head;
cdb[14] = command_id; // ATA command ID
/*
cdb[0] = ATA_12;; // pass-through ATA12 command (no translation)
cdb[1] = (4 << 1); // data-in PIO
cdb[2] = 0x2e; // data-in + generate sense
cdb[3] = feature; // ATA feature ID
cdb[4] = nsector; // number of sectors
cdb[5] = sector;
cdb[6] = lcyl;
cdb[7] = hcyl;
cdb[8] = head;
cdb[9] = command_id; // ATA command ID
*/
// WARNING: ata/libata-scsi.c may force the head/device bits
/*
printf ("data %02X\n", cdb[0]);
printf ("feature %02X\n", cdb[3]);
printf ("sector_count %02X\n", cdb[4]);
printf ("sector_number %02X\n", cdb[5]);
printf ("low_cylinder %02X\n", cdb[6]);
printf ("high_cylinder %02X\n", cdb[7]);
printf ("device_head %02X\n", cdb[8]);
printf ("command %02X\n", cdb[9]);
*/
printf ("CMD: %02X %02X %02X %02X %02X %02X %02X - %02X%02X %02X %02X\n",
//cdb[3], cdb[4], cdb[5], cdb[6], cdb[7], cdb[8], cdb[9],
cdb[4], cdb[6], cdb[8], cdb[10], cdb[12], cdb[13], cdb[14],
feature, sector, nsector, head);
if ((retval = ioctl(fd, SG_IO, &io_hdr)) < 0) {
perror("cmd_data ioctl");
exit (-1);
} else {
if (data) {
memcpy (data, inbuf, sizeof inbuf);
}
}
print_buffer (sense_b, sizeof(sense_b));
/*
E1 00 80 4F C2 B0 B0 - E180 00 B0 req
00 00 80 4F C2 10 50 - E180 00 B0 res
CMD: EA 00 C0 4F C2 B0 B0 - EAC0 00 B0
0000: 72 00 00 00 00 00 00 0E 09 0C 00 00 00 00 00 C0 r...............
0010: 00 4F 00 C2 30 50 00 00 E1 00 00 00 00 00 00 00 .O..0P..........
*/
if (retval == 0) {
printf ("RES: %02X %02X %02X %02X %02X %02X %02X - %02X%02X %02X %02X\n",
ret[3], ret[5], ret[7], ret[9], ret[11], ret[12],ret[13],
feature, sector, nsector, head);
for (i = 0; i < sizeof(inbuf); i++) {
sum += (unsigned)inbuf;
}
sum &= 0xFF;
printf ("DATA: checksum %s\n", sum ? "ERROR" : "OK");
print_buffer (inbuf, sizeof(inbuf));
}
return 0;
}
int cmd_data2 (int fd, int feature, int nsector, int sector, int head, u_int8_t *data)
{
struct {
ide_task_request_t req_task;
u_int8_t outbuf[OUTPUT_SIZE];
u_int8_t inbuf[INPUT_SIZE];
} task;
ide_task_request_t *reqtask=(ide_task_request_t *) &(task.req_task);
task_struct_t *taskfile=(task_struct_t *) reqtask->io_ports;
int retval;
int i;
unsigned sum = 0;
memset(&task, 0, sizeof(task));
if (data) {
reqtask->data_phase = TASKFILE_IN;
reqtask->req_cmd = IDE_DRIVE_TASK_IN;
reqtask->out_size = 0;
reqtask->out_size = sizeof(task.outbuf);
reqtask->in_size = sizeof(task.inbuf);
} else {
reqtask->data_phase = TASKFILE_NO_DATA;
reqtask->req_cmd = IDE_DRIVE_TASK_NO_DATA;
reqtask->out_size = 0;
reqtask->out_size = 0;
reqtask->in_size = 0;
}
taskfile->data = 0;
taskfile->feature = feature;
taskfile->sector_count = nsector;
taskfile->sector_number = sector;
taskfile->low_cylinder = 0x4f; /* SMART constant */
taskfile->high_cylinder = 0xc2; /* SMART constant */
taskfile->device_head = head;
taskfile->command = ATA_SMART_CMD;
reqtask->in_flags.all=0;
reqtask->in_flags.b.error_feature = 1;
reqtask->in_flags.b.sector = 1;
reqtask->in_flags.b.nsector = 1;
reqtask->in_flags.b.lcyl = 1;
reqtask->in_flags.b.hcyl = 1;
reqtask->in_flags.b.select = 1;
reqtask->in_flags.b.status_command = 1;
reqtask->out_flags.all=0;
reqtask->out_flags.b.error_feature = 1;
reqtask->out_flags.b.sector = 1;
reqtask->out_flags.b.nsector = 1;
reqtask->out_flags.b.lcyl = 1;
reqtask->out_flags.b.hcyl = 1;
reqtask->out_flags.b.select = 1;
reqtask->out_flags.b.status_command = 1;
// copy user data into the task request structure
/*memcpy(&task.inbuf, data, 512);*/
/*
printf ("data %02X\n", taskfile->data);
printf ("feature %02X\n", taskfile->feature);
printf ("sector_count %02X\n", taskfile->sector_count);
printf ("sector_number %02X\n", taskfile->sector_number);
printf ("low_cylinder %02X\n", taskfile->low_cylinder);
printf ("high_cylinder %02X\n", taskfile->high_cylinder);
printf ("device_head %02X\n", taskfile->device_head);
printf ("command %02X\n", taskfile->command);
*/
printf ("CMD: %02X %02X %02X %02X %02X %02X %02X - %02X%02X %02X %02X\n",
taskfile->feature, taskfile->sector_count,
taskfile->sector_number, taskfile->low_cylinder,
taskfile->high_cylinder, taskfile->device_head,
taskfile->command, feature, sector, nsector, head);
if ((retval=ioctl(fd, HDIO_DRIVE_TASKFILE, &task))) {
if (errno==EINVAL) {
printf ("Kernel lacks HDIO_DRIVE_TASKFILE support; compile kernel with CONFIG_IDE_TASKFILE_IO set\n");
return -1;
}
perror("cmd_data ioctl");
exit (-1);
} else {
if (data) {
memcpy (data, task.inbuf, sizeof task.inbuf);
}
}
if (retval == 0) {
printf ("RES: %02X %02X %02X %02X %02X %02X %02X - %02X%02X %02X %02X\n",
taskfile->feature, taskfile->sector_count,
taskfile->sector_number, taskfile->low_cylinder,
taskfile->high_cylinder, taskfile->device_head,
taskfile->command, feature, sector, nsector, head);
for (i = 0; i < sizeof(task.inbuf); i++) {
sum += (unsigned)task.inbuf;
}
sum &= 0xFF;
printf ("DATA: checksum %s\n", sum ? "ERROR" : "OK");
print_buffer (task.inbuf, sizeof(task.inbuf));
}
return 0;
}
void usage()
{
puts("usage: revo64 <disk>");
exit(0);
}
int main(int argc, char **argv)
{
int fd;
char *disk = 0;
u_int8_t data[INPUT_SIZE];
unsigned char identity[512];
int retval;
struct sg_io_hdr io_hdr;
int k;
/* Parse the simple command-line */
if (argc == 2) {
disk = argv[1];
} else {
usage();
}
if (!(fd = open(disk, O_RDWR|O_NONBLOCK))) {
printf("Can't open %s, because: %s\n", disk, strerror(errno));
exit(-1);
}
/*
if ((retval=ioctl(fd, HDIO_GET_IDENTITY, identity))) {
perror("main ioctl");
exit(-1);
} else {
print_buffer (identity, sizeof(identity));
}
*/
/* It is prudent to check we have a sg device by trying an ioctl */
if ((ioctl(fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) {
printf("%s is not an sg device, or old sg driver\n", argv[1]);
exit(-1);
}
//cmd_data(fd, 0xED, 0x80, 0xE8, 0xB0, data);
//exit(0);
/*cmd_data(fd, 0xED, 0x00, 0xA5, 0xB0);*/
cmd (fd, 0xEB, 0x00, 0xDA, 0xB0);
cmd (fd, 0xEB, 0x00, 0xDA, 0xB0);
cmd (fd, 0xE1, 0xC0, 0xBF, 0xB0);
cmd (fd, 0xE1, 0x00, 0x80, 0xB0);
cmd_data(fd, 0xEA, 0x00, 0xC0, 0xB0, data);
/* board info */
cmd_data(fd, 0xEB, 0x00, 0xA2, 0xB0, data);
cmd_data(fd, 0xEB, 0x00, 0xA5, 0xB0, data);
cmd_data(fd, 0xE8, 0x00, 0xA2, 0xB0, data);
cmd_data(fd, 0xE8, 0x00, 0xA2, 0xB1, data);
cmd_data(fd, 0xE8, 0x00, 0xA2, 0xB2, data);
cmd_data(fd, 0xE8, 0x00, 0xA2, 0xB3, data);
cmd (fd, 0xE8, 0x00, 0xA6, 0xF0);
/* disk 0*/
cmd_data(fd, 0xED, 0x00, 0xA5, 0xB0, data);
cmd_data(fd, 0xED, 0x80, 0xE8, 0xB0, data);
/* disk 1*/
cmd_data(fd, 0xED, 0x00, 0xA5, 0xB1, data);
cmd_data(fd, 0xED, 0x80, 0xE8, 0xB1, data);
/* disk 2*/
cmd_data(fd, 0xED, 0x00, 0xA5, 0xB2, data);
cmd_data(fd, 0xED, 0x80, 0xE8, 0xB2, data);
cmd_data(fd, 0xED, 0x00, 0xA1, 0xB0, data);
cmd_data(fd, 0xED, 0x00, 0xA1, 0xB1, data);
cmd (fd, 0xE1, 0xC0, 0x9F, 0xB0);
cmd (fd, 0xE1, 0x80, 0x40, 0xB0);
cmd_data(fd, 0xEA, 0x00, 0xC0, 0xB0, data);
/* identify array */
cmd_data(fd, 0xE5, 0x00, 0xA5, 0xB0, data);
cmd (fd, 0xE5, 0x00, 0xDA, 0xB0);
cmd_data(fd, 0xED, 0x00, 0xA1, 0xB0, data);
cmd_data(fd, 0xED, 0x00, 0xA1, 0xB1, data);
cmd_data(fd, 0xE8, 0x00, 0xA2, 0xB0, data);
cmd_data(fd, 0xE5, 0x00, 0xA5, 0xB0, data);
/* pause */
cmd (fd, 0xE8, 0x00, 0xA6, 0xF0);
cmd (fd, 0xE1, 0xC0, 0xBF, 0xB0);
cmd (fd, 0xE1, 0x00, 0x80, 0xB0);
cmd_data(fd, 0xEA, 0x00, 0xC0, 0xB0, data);
/* board info */
cmd_data(fd, 0xEB, 0x00, 0xA2, 0xB0, data);
cmd_data(fd, 0xEB, 0x00, 0xA5, 0xB0, data);
close(fd);
return 0;
}
The output will contain some data that may be identified and some that cannot.
Let's see if someone follows this far.
*** linux-2.6.28/drivers/ata/pata_netcell.c Thu Dec 25 01:26:37 2008
--- linux-2.6.27/drivers/ata/pata_netcell.c Sun Oct 12 07:08:49 2008
***************
*** 30,35 ****
--- 30,148 ----
};
+ static DEFINE_SPINLOCK(netcell_lock);
+
+ static unsigned int init_control_device(struct pci_dev *dev)
+ {
+ unsigned long flags;
+ unsigned char c;
+ unsigned long cmd = 0; // 0x9800;
+ unsigned long ctl = 0; // 0x9402;
+ unsigned long bmdma = 0; // 0x9008;
+ int status = -1;
+
+ /* In my system:
+ I/O ports at a400 <- Normal ide
+ I/O ports at a000 <- Normal ide
+ I/O ports at 9800 <- Control interface
+ I/O ports at 9400 <- Control interface
+ I/O ports at 9000 <- dma both
+
+ Revolution: simplex device: DMA forced
+ ide2: BM-DMA at 0x9000-0x9007, BIOS settings: hde:DMA, hdf:DMA
+ Revolution: simplex device: DMA forced
+ ide3: BM-DMA at 0x9008-0x900f, BIOS settings: hdg:pio, hdh:pio
+
+ Probing IDE interface ide2...
+ hde: NetCell SyncRAID(TM) SR5000 R1-2, ATA DISK drive
+ ide2 at 0xa400-0xa407,0xa002 on irq 10
+
+ Probing IDE interface ide3...
+ hdh: NetCell Configuration Device <don't use, ATA DISK drive
+ ide3 at 0x9800-0x9807,0x9402 on irq 10
+
+ */
+
+ if (pci_resource_len(dev, 2) != 8 ||
+ pci_resource_len(dev, 3) != 4 ||
+ pci_resource_len(dev, 4) != 16) {
+ printk (DRV_NAME ": ERROR: cannot find io ports!\n");
+ goto out;
+ }
+
+ cmd = pci_resource_start(dev, 2);
+ ctl = pci_resource_start(dev, 3);
+ bmdma = pci_resource_start(dev, 4);
+
+ if (cmd == 0 || ctl == 0 || bmdma == 0) {
+ printk (DRV_NAME ": ERROR: cannot map io ports!\n");
+ goto out;
+ }
+
+ ctl = ctl + 2;
+ bmdma = bmdma + 8;
+
+ spin_lock_irqsave(&netcell_lock, flags);
+
+ c = inb(cmd + ATA_REG_STATUS);
+ if (c != 0) {
+ printk (DRV_NAME ": WARNING: status 0 != %X\n", c);
+ }
+ outb(ATA_HOB, ctl);
+
+ /* Some extra checks, may be unnecessary */
+ c = inb(cmd + ATA_REG_LBAL);
+ if (c != 0) {
+ printk (DRV_NAME ": WARNING: sector 0 != %X\n", c);
+ }
+ c = inb(cmd + ATA_REG_NSECT);
+ if (c != 0) {
+ printk (DRV_NAME ": WARNING: nsector 0 != %X\n", c);
+ }
+ c = inb(cmd + ATA_REG_LBAH);
+ if (c != 0) {
+ printk (DRV_NAME ": WARNING: hcyl 0 != %X\n", c);
+ }
+ c = inb(cmd + ATA_REG_LBAM);
+ if (c != 0) {
+ printk (DRV_NAME ": WARNING: lcyl 0 != %X\n", c);
+ }
+ outb(0x00, ctl);
+
+ // Reset
+ outb(ATA_SRST, ctl);
+ mdelay(40);
+ outb(0x00, ctl);
+
+ c = inb(bmdma + ATA_DMA_STATUS);
+ // 0xe0 ok
+ // 0x86 something fishy ?
+ // 0x80 initialized already ?
+ if (c != 0xe0) {
+ printk (DRV_NAME ": WARNING: ioport status = %X\n", c);
+ }
+
+ // Wakeup sequence
+ outl(0xeba40306, bmdma + 4);
+ outb(0x06, bmdma + ATA_DMA_STATUS);
+ outb(0x00, bmdma + ATA_DMA_CMD);
+
+ mdelay(10);
+ c = inb(cmd + ATA_REG_STATUS);
+ // 0x52 ok (BUSY=0, DRDY =0x40)
+ if ((c & (ATA_DRDY | ATA_BUSY)) == ATA_DRDY) {
+ status = 0;
+ printk (DRV_NAME ": Configuration device initilized\n");
+ } else {
+ printk (DRV_NAME ": ERROR: Could not initialize configuration device: status %X not ok\n", c);
+ }
+
+ spin_unlock_irqrestore(&netcell_lock, flags);
+
+ out:
+ return status;
+ }
+
/**
* netcell_init_one - Register Netcell ATA PCI device with kernel services
* @pdev: PCI device to register
***************
*** 68,73 ****
--- 181,187 ----
return rc;
/* Any chip specific setup/optimisation/messages here */
+ init_control_device(pdev);
ata_pci_bmdma_clear_simplex(pdev);
/* And let the library code do the work */
After applying the patch and running the new kernel , you should see a boot messages like:
[ 1.000017] pata_netcell: Configuration device initilized
...
[ 1.220334] ata1.00: ATA-6: Netcell Revolution SR5000 R1-2, , max UDMA7
[ 1.220338] ata1.00: 488397168 sectors, multi 128: LBA48
[ 1.260358] ata1.00: configured for UDMA/100
[ 1.260513] scsi 0:0:0:0: Direct-Access ATA Netcell Revoluti n/a PQ: 0 ANSI: 5
[ 1.440322] ata2.01: ATA-6: Netcell Comm Channel <don't use>, , max UDMA7
[ 1.440327] ata2.01: 1008 sectors, multi 128: LBA48
[ 1.440364] ata2.01: simplex DMA is claimed by other device, disabling DMA
[ 1.460349] ata2.01: configured for PIO4
[ 1.460491] scsi 1:0:1:0: Direct-Access ATA Netcell Comm Cha n/a PQ: 0 ANSI: 5
Next thing to do is start poking the card with some carefully crafted ata commands. a small program follows.
WARNING: this command may corrupt your data (yet it hasn't done it for me)
/*
* based on Simple Disk Sleep Monitor
* by Bartek Kania
* Licenced under the GPL
*/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <string.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <linux/hdreg.h>
#include <scsi/sg.h>
#ifdef DEBUG
#define D(x) x
#else
#define D(x)
#endif
#define INPUT_SIZE 512
#define OUTPUT_SIZE 512
#define ATA_SMART_CMD 0xb0
#define ATA_IDENTIFY_DEVICE 0xec
#define ATA_16 0x85 /* 16-byte pass-thru */
#define ATA_12 0xa1 /* 12-byte pass-thru */
void print_task(u_int8_t *args, char *title) {
/*
printf ("-- %s\n", title);
printf ("COMMAND %02X\n", args[0]);
printf ("FEATURE %02X\n", args[1]);
printf ("NSECTOR %02X\n", args[2]);
printf ("SECTOR %02X\n", args[3]);
printf ("LCYL %02X\n", args[4]);
printf ("HCYL %02X\n", args[5]);
printf ("SELECT %02X\n", args[6]);
*/
printf ("%02X %02X %02X %02X %02X %02X %02X %s\n",
args[1], args[2], args[3], args[4], args[5], args[6], args[0],
title);
}
void print_buffer(u_int8_t *buf, int size) {
char ascz[17];
int i,j;
for (i = 0; i < size; i += 16) {
printf ("%04X: ", i);
for (j = 0; j < 16; j++) {
if (i + j < size) {
ascz[j] = (buf[j+i] >= ' ' && buf[j+i] < 0x7f) ? buf[j+i] : '.';
printf ("%02X ", buf[j+i]);
} else {
printf (" ");
ascz[j] = ' ';
}
}
ascz[j] = 0;
printf ("%s\n", ascz);
}
}
int cmd_e180 (int fd)
{
u_int8_t args[7];
int retval = 0;
args[0] = ATA_SMART_CMD; /* COMMAND */
args[1] = 0xE1; /* FEATURE */
args[2] = 0x00; /* NSECTOR */
args[3] = 0x80; /* SECTOR */
args[4] = 0x4F; /* LCYL */
args[5] = 0xC2; /* HCYL */
args[6] = 0xB0; /* SELECT */
print_task(args, "E180 req");
if ((retval=ioctl(fd, HDIO_DRIVE_TASK, args))) {
perror("cmd_e180 ioctl");
exit (-1);
}
print_task(args, "E180 res");
return retval;
}
int cmd (int fd, int feature, int nsector, int sector, int head)
{
u_int8_t args[7];
int retval = 0;
args[0] = ATA_SMART_CMD; /* COMMAND */
args[1] = feature;
args[2] = nsector;
args[3] = sector;
args[4] = 0x4F; /* LCYL */
args[5] = 0xC2; /* HCYL */
args[6] = head;
printf ("%02X %02X %02X %02X %02X %02X %02X - %02X%02X %02X %02X req\n",
args[1], args[2], args[3], args[4], args[5], args[6], args[0],
feature, sector, nsector, head);
if ((retval=ioctl(fd, HDIO_DRIVE_TASK, args))) {
perror("cmd ioctl");
exit (-1);
}
printf ("%02X %02X %02X %02X %02X %02X %02X - %02X%02X %02X %02X res\n",
args[1], args[2], args[3], args[4], args[5], args[6], args[0],
feature, sector, nsector, head);
return retval;
}
int cmd2(int fd, int feature, int nsector, int sector, int head)
{
struct {
ide_task_request_t req_task;
u_int8_t outbuf[OUTPUT_SIZE];
u_int8_t inbuf[INPUT_SIZE];
} task;
ide_task_request_t *reqtask=(ide_task_request_t *) &(task.req_task);
task_struct_t *taskfile=(task_struct_t *) reqtask->io_ports;
int retval;
memset(&task, 0, sizeof(task));
reqtask->data_phase = TASKFILE_NO_DATA;
reqtask->req_cmd = IDE_DRIVE_TASK_NO_DATA;
reqtask->out_size = 0;
reqtask->out_size = 0;
reqtask->in_size = 0;
taskfile->data = 0;
taskfile->feature = feature;
taskfile->sector_count = nsector;
taskfile->sector_number = sector;
taskfile->low_cylinder = 0x4f; /* SMART constant */
taskfile->high_cylinder = 0xc2; /* SMART constant */
taskfile->device_head = head;
taskfile->command = ATA_SMART_CMD;
reqtask->in_flags.all=0;
reqtask->in_flags.b.error_feature = 1;
reqtask->in_flags.b.sector = 1;
reqtask->in_flags.b.nsector = 1;
reqtask->in_flags.b.lcyl = 1;
reqtask->in_flags.b.hcyl = 1;
reqtask->in_flags.b.select = 1;
reqtask->in_flags.b.status_command = 1;
reqtask->out_flags.all=0;
reqtask->out_flags.b.error_feature = 1;
reqtask->out_flags.b.sector = 1;
reqtask->out_flags.b.nsector = 1;
reqtask->out_flags.b.lcyl = 1;
reqtask->out_flags.b.hcyl = 1;
reqtask->out_flags.b.select = 1;
reqtask->out_flags.b.status_command = 1;
// copy user data into the task request structure
/*memcpy(&task.inbuf, data, 512);*/
/*
printf ("data %02X\n", taskfile->data);
printf ("feature %02X\n", taskfile->feature);
printf ("sector_count %02X\n", taskfile->sector_count);
printf ("sector_number %02X\n", taskfile->sector_number);
printf ("low_cylinder %02X\n", taskfile->low_cylinder);
printf ("high_cylinder %02X\n", taskfile->high_cylinder);
printf ("device_head %02X\n", taskfile->device_head);
printf ("command %02X\n", taskfile->command);
*/
printf ("CMD: %02X %02X %02X %02X %02X %02X %02X - %02X%02X %02X %02X\n",
taskfile->feature, taskfile->sector_count,
taskfile->sector_number, taskfile->low_cylinder,
taskfile->high_cylinder, taskfile->device_head,
taskfile->command, feature, sector, nsector, head);
if ((retval=ioctl(fd, HDIO_DRIVE_TASKFILE, &task))) {
if (errno==EINVAL) {
printf ("Kernel lacks HDIO_DRIVE_TASKFILE support; compile kernel with CONFIG_IDE_TASKFILE_IO set\n");
return -1;
}
perror("cmd2 ioctl");
exit (-1);
}
printf ("RES: %02X %02X %02X %02X %02X %02X %02X - %02X%02X %02X %02X\n",
taskfile->feature, taskfile->sector_count,
taskfile->sector_number, taskfile->low_cylinder,
taskfile->high_cylinder, taskfile->device_head,
taskfile->command, feature, sector, nsector, head);
return 0;
}
int cmd_data(int fd, int feature, int nsector, int sector, int head, u_int8_t *data)
{
int retval;
int i;
unsigned sum = 0;
u_int8_t inbuf[INPUT_SIZE];
unsigned char sense_b[32];
unsigned char * ret = sense_b + 8;
unsigned char cdb[16];
struct sg_io_hdr io_hdr;
int k;
memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
memset (&cdb, 0, sizeof(cdb));
io_hdr.interface_id = 'S';
io_hdr.cmdp = cdb;
io_hdr.cmd_len = sizeof(cdb);
io_hdr.mx_sb_len = sizeof(sense_b);
io_hdr.dxferp = inbuf;
io_hdr.dxfer_len = sizeof (inbuf);
io_hdr.resid = 0;
io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
io_hdr.sbp = sense_b;
io_hdr.timeout = 30000;
int command_id = ATA_SMART_CMD;
int lcyl = 0x4f; /* SMART constant */
int hcyl = 0xc2; /* SMART constant */
cdb[0] = ATA_16;; // pass-through ATA16 command (no translation)
cdb[1] = (4 << 1); // data-in
cdb[2] = 0x2e; // data-in + generate sense
cdb[4] = feature; // ATA feature ID
cdb[6] = nsector; // number of sectors
cdb[8] = sector;
cdb[10] = lcyl;
cdb[12] = hcyl;
cdb[13] = head;
cdb[14] = command_id; // ATA command ID
/*
cdb[0] = ATA_12;; // pass-through ATA12 command (no translation)
cdb[1] = (4 << 1); // data-in PIO
cdb[2] = 0x2e; // data-in + generate sense
cdb[3] = feature; // ATA feature ID
cdb[4] = nsector; // number of sectors
cdb[5] = sector;
cdb[6] = lcyl;
cdb[7] = hcyl;
cdb[8] = head;
cdb[9] = command_id; // ATA command ID
*/
// WARNING: ata/libata-scsi.c may force the head/device bits
/*
printf ("data %02X\n", cdb[0]);
printf ("feature %02X\n", cdb[3]);
printf ("sector_count %02X\n", cdb[4]);
printf ("sector_number %02X\n", cdb[5]);
printf ("low_cylinder %02X\n", cdb[6]);
printf ("high_cylinder %02X\n", cdb[7]);
printf ("device_head %02X\n", cdb[8]);
printf ("command %02X\n", cdb[9]);
*/
printf ("CMD: %02X %02X %02X %02X %02X %02X %02X - %02X%02X %02X %02X\n",
//cdb[3], cdb[4], cdb[5], cdb[6], cdb[7], cdb[8], cdb[9],
cdb[4], cdb[6], cdb[8], cdb[10], cdb[12], cdb[13], cdb[14],
feature, sector, nsector, head);
if ((retval = ioctl(fd, SG_IO, &io_hdr)) < 0) {
perror("cmd_data ioctl");
exit (-1);
} else {
if (data) {
memcpy (data, inbuf, sizeof inbuf);
}
}
print_buffer (sense_b, sizeof(sense_b));
/*
E1 00 80 4F C2 B0 B0 - E180 00 B0 req
00 00 80 4F C2 10 50 - E180 00 B0 res
CMD: EA 00 C0 4F C2 B0 B0 - EAC0 00 B0
0000: 72 00 00 00 00 00 00 0E 09 0C 00 00 00 00 00 C0 r...............
0010: 00 4F 00 C2 30 50 00 00 E1 00 00 00 00 00 00 00 .O..0P..........
*/
if (retval == 0) {
printf ("RES: %02X %02X %02X %02X %02X %02X %02X - %02X%02X %02X %02X\n",
ret[3], ret[5], ret[7], ret[9], ret[11], ret[12],ret[13],
feature, sector, nsector, head);
for (i = 0; i < sizeof(inbuf); i++) {
sum += (unsigned)inbuf;
}
sum &= 0xFF;
printf ("DATA: checksum %s\n", sum ? "ERROR" : "OK");
print_buffer (inbuf, sizeof(inbuf));
}
return 0;
}
int cmd_data2 (int fd, int feature, int nsector, int sector, int head, u_int8_t *data)
{
struct {
ide_task_request_t req_task;
u_int8_t outbuf[OUTPUT_SIZE];
u_int8_t inbuf[INPUT_SIZE];
} task;
ide_task_request_t *reqtask=(ide_task_request_t *) &(task.req_task);
task_struct_t *taskfile=(task_struct_t *) reqtask->io_ports;
int retval;
int i;
unsigned sum = 0;
memset(&task, 0, sizeof(task));
if (data) {
reqtask->data_phase = TASKFILE_IN;
reqtask->req_cmd = IDE_DRIVE_TASK_IN;
reqtask->out_size = 0;
reqtask->out_size = sizeof(task.outbuf);
reqtask->in_size = sizeof(task.inbuf);
} else {
reqtask->data_phase = TASKFILE_NO_DATA;
reqtask->req_cmd = IDE_DRIVE_TASK_NO_DATA;
reqtask->out_size = 0;
reqtask->out_size = 0;
reqtask->in_size = 0;
}
taskfile->data = 0;
taskfile->feature = feature;
taskfile->sector_count = nsector;
taskfile->sector_number = sector;
taskfile->low_cylinder = 0x4f; /* SMART constant */
taskfile->high_cylinder = 0xc2; /* SMART constant */
taskfile->device_head = head;
taskfile->command = ATA_SMART_CMD;
reqtask->in_flags.all=0;
reqtask->in_flags.b.error_feature = 1;
reqtask->in_flags.b.sector = 1;
reqtask->in_flags.b.nsector = 1;
reqtask->in_flags.b.lcyl = 1;
reqtask->in_flags.b.hcyl = 1;
reqtask->in_flags.b.select = 1;
reqtask->in_flags.b.status_command = 1;
reqtask->out_flags.all=0;
reqtask->out_flags.b.error_feature = 1;
reqtask->out_flags.b.sector = 1;
reqtask->out_flags.b.nsector = 1;
reqtask->out_flags.b.lcyl = 1;
reqtask->out_flags.b.hcyl = 1;
reqtask->out_flags.b.select = 1;
reqtask->out_flags.b.status_command = 1;
// copy user data into the task request structure
/*memcpy(&task.inbuf, data, 512);*/
/*
printf ("data %02X\n", taskfile->data);
printf ("feature %02X\n", taskfile->feature);
printf ("sector_count %02X\n", taskfile->sector_count);
printf ("sector_number %02X\n", taskfile->sector_number);
printf ("low_cylinder %02X\n", taskfile->low_cylinder);
printf ("high_cylinder %02X\n", taskfile->high_cylinder);
printf ("device_head %02X\n", taskfile->device_head);
printf ("command %02X\n", taskfile->command);
*/
printf ("CMD: %02X %02X %02X %02X %02X %02X %02X - %02X%02X %02X %02X\n",
taskfile->feature, taskfile->sector_count,
taskfile->sector_number, taskfile->low_cylinder,
taskfile->high_cylinder, taskfile->device_head,
taskfile->command, feature, sector, nsector, head);
if ((retval=ioctl(fd, HDIO_DRIVE_TASKFILE, &task))) {
if (errno==EINVAL) {
printf ("Kernel lacks HDIO_DRIVE_TASKFILE support; compile kernel with CONFIG_IDE_TASKFILE_IO set\n");
return -1;
}
perror("cmd_data ioctl");
exit (-1);
} else {
if (data) {
memcpy (data, task.inbuf, sizeof task.inbuf);
}
}
if (retval == 0) {
printf ("RES: %02X %02X %02X %02X %02X %02X %02X - %02X%02X %02X %02X\n",
taskfile->feature, taskfile->sector_count,
taskfile->sector_number, taskfile->low_cylinder,
taskfile->high_cylinder, taskfile->device_head,
taskfile->command, feature, sector, nsector, head);
for (i = 0; i < sizeof(task.inbuf); i++) {
sum += (unsigned)task.inbuf;
}
sum &= 0xFF;
printf ("DATA: checksum %s\n", sum ? "ERROR" : "OK");
print_buffer (task.inbuf, sizeof(task.inbuf));
}
return 0;
}
void usage()
{
puts("usage: revo64 <disk>");
exit(0);
}
int main(int argc, char **argv)
{
int fd;
char *disk = 0;
u_int8_t data[INPUT_SIZE];
unsigned char identity[512];
int retval;
struct sg_io_hdr io_hdr;
int k;
/* Parse the simple command-line */
if (argc == 2) {
disk = argv[1];
} else {
usage();
}
if (!(fd = open(disk, O_RDWR|O_NONBLOCK))) {
printf("Can't open %s, because: %s\n", disk, strerror(errno));
exit(-1);
}
/*
if ((retval=ioctl(fd, HDIO_GET_IDENTITY, identity))) {
perror("main ioctl");
exit(-1);
} else {
print_buffer (identity, sizeof(identity));
}
*/
/* It is prudent to check we have a sg device by trying an ioctl */
if ((ioctl(fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) {
printf("%s is not an sg device, or old sg driver\n", argv[1]);
exit(-1);
}
//cmd_data(fd, 0xED, 0x80, 0xE8, 0xB0, data);
//exit(0);
/*cmd_data(fd, 0xED, 0x00, 0xA5, 0xB0);*/
cmd (fd, 0xEB, 0x00, 0xDA, 0xB0);
cmd (fd, 0xEB, 0x00, 0xDA, 0xB0);
cmd (fd, 0xE1, 0xC0, 0xBF, 0xB0);
cmd (fd, 0xE1, 0x00, 0x80, 0xB0);
cmd_data(fd, 0xEA, 0x00, 0xC0, 0xB0, data);
/* board info */
cmd_data(fd, 0xEB, 0x00, 0xA2, 0xB0, data);
cmd_data(fd, 0xEB, 0x00, 0xA5, 0xB0, data);
cmd_data(fd, 0xE8, 0x00, 0xA2, 0xB0, data);
cmd_data(fd, 0xE8, 0x00, 0xA2, 0xB1, data);
cmd_data(fd, 0xE8, 0x00, 0xA2, 0xB2, data);
cmd_data(fd, 0xE8, 0x00, 0xA2, 0xB3, data);
cmd (fd, 0xE8, 0x00, 0xA6, 0xF0);
/* disk 0*/
cmd_data(fd, 0xED, 0x00, 0xA5, 0xB0, data);
cmd_data(fd, 0xED, 0x80, 0xE8, 0xB0, data);
/* disk 1*/
cmd_data(fd, 0xED, 0x00, 0xA5, 0xB1, data);
cmd_data(fd, 0xED, 0x80, 0xE8, 0xB1, data);
/* disk 2*/
cmd_data(fd, 0xED, 0x00, 0xA5, 0xB2, data);
cmd_data(fd, 0xED, 0x80, 0xE8, 0xB2, data);
cmd_data(fd, 0xED, 0x00, 0xA1, 0xB0, data);
cmd_data(fd, 0xED, 0x00, 0xA1, 0xB1, data);
cmd (fd, 0xE1, 0xC0, 0x9F, 0xB0);
cmd (fd, 0xE1, 0x80, 0x40, 0xB0);
cmd_data(fd, 0xEA, 0x00, 0xC0, 0xB0, data);
/* identify array */
cmd_data(fd, 0xE5, 0x00, 0xA5, 0xB0, data);
cmd (fd, 0xE5, 0x00, 0xDA, 0xB0);
cmd_data(fd, 0xED, 0x00, 0xA1, 0xB0, data);
cmd_data(fd, 0xED, 0x00, 0xA1, 0xB1, data);
cmd_data(fd, 0xE8, 0x00, 0xA2, 0xB0, data);
cmd_data(fd, 0xE5, 0x00, 0xA5, 0xB0, data);
/* pause */
cmd (fd, 0xE8, 0x00, 0xA6, 0xF0);
cmd (fd, 0xE1, 0xC0, 0xBF, 0xB0);
cmd (fd, 0xE1, 0x00, 0x80, 0xB0);
cmd_data(fd, 0xEA, 0x00, 0xC0, 0xB0, data);
/* board info */
cmd_data(fd, 0xEB, 0x00, 0xA2, 0xB0, data);
cmd_data(fd, 0xEB, 0x00, 0xA5, 0xB0, data);
close(fd);
return 0;
}
The output will contain some data that may be identified and some that cannot.
Let's see if someone follows this far.