650 lines
16 KiB
C
650 lines
16 KiB
C
/*
|
|
* OpenBIOS ESP driver
|
|
*
|
|
* Copyright (C) 2004 Jens Axboe <axboe@suse.de>
|
|
* Copyright (C) 2005 Stefan Reinauer
|
|
*
|
|
* Credit goes to Hale Landis for his excellent ata demo software
|
|
* OF node handling and some fixes by Stefan Reinauer
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* version 2
|
|
*
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "libopenbios/bindings.h"
|
|
#include "kernel/kernel.h"
|
|
#include "libc/byteorder.h"
|
|
#include "libc/vsprintf.h"
|
|
|
|
#include "drivers/drivers.h"
|
|
#include "asm/io.h"
|
|
#include "scsi.h"
|
|
#include "asm/dma.h"
|
|
#include "esp.h"
|
|
#include "libopenbios/ofmem.h"
|
|
|
|
#define BUFSIZE 4096
|
|
|
|
#ifdef CONFIG_DEBUG_ESP
|
|
#define DPRINTF(fmt, args...) \
|
|
do { printk(fmt , ##args); } while (0)
|
|
#else
|
|
#define DPRINTF(fmt, args...)
|
|
#endif
|
|
|
|
struct esp_dma {
|
|
volatile struct sparc_dma_registers *regs;
|
|
enum dvma_rev revision;
|
|
};
|
|
|
|
typedef struct sd_private {
|
|
unsigned int bs;
|
|
const char *media_str[2];
|
|
uint32_t sectors;
|
|
uint8_t media;
|
|
uint8_t id;
|
|
uint8_t present;
|
|
char model[40];
|
|
} sd_private_t;
|
|
|
|
struct esp_regs {
|
|
unsigned char regs[ESP_REG_SIZE];
|
|
};
|
|
|
|
typedef struct esp_private {
|
|
volatile struct esp_regs *ll;
|
|
uint32_t buffer_dvma;
|
|
unsigned int irq; /* device IRQ number */
|
|
struct esp_dma espdma;
|
|
unsigned char *buffer;
|
|
sd_private_t sd[8];
|
|
} esp_private_t;
|
|
|
|
static esp_private_t *global_esp;
|
|
|
|
/* DECLARE data structures for the nodes. */
|
|
DECLARE_UNNAMED_NODE(ob_sd, INSTALL_OPEN, sizeof(sd_private_t *));
|
|
DECLARE_UNNAMED_NODE(ob_esp, INSTALL_OPEN, sizeof(esp_private_t *));
|
|
|
|
#ifdef CONFIG_DEBUG_ESP
|
|
static void dump_drive(sd_private_t *drive)
|
|
{
|
|
printk("SCSI DRIVE @%lx:\n", (unsigned long)drive);
|
|
printk("id: %d\n", drive->id);
|
|
printk("media: %s\n", drive->media_str[0]);
|
|
printk("media: %s\n", drive->media_str[1]);
|
|
printk("model: %s\n", drive->model);
|
|
printk("sectors: %d\n", drive->sectors);
|
|
printk("present: %d\n", drive->present);
|
|
printk("bs: %d\n", drive->bs);
|
|
}
|
|
#endif
|
|
|
|
static int
|
|
do_command(esp_private_t *esp, sd_private_t *sd, int cmdlen, int replylen)
|
|
{
|
|
int status;
|
|
|
|
// Set SCSI target
|
|
esp->ll->regs[ESP_BUSID] = sd->id & 7;
|
|
// Set DMA address
|
|
esp->espdma.regs->st_addr = esp->buffer_dvma;
|
|
// Set DMA length
|
|
esp->ll->regs[ESP_TCLOW] = cmdlen & 0xff;
|
|
esp->ll->regs[ESP_TCMED] = (cmdlen >> 8) & 0xff;
|
|
// Set DMA direction and enable DMA
|
|
esp->espdma.regs->cond_reg = DMA_ENABLE;
|
|
// Set ATN, issue command
|
|
esp->ll->regs[ESP_CMD] = ESP_CMD_SELA | ESP_CMD_DMA;
|
|
// Wait for DMA to complete. Can this fail?
|
|
while ((esp->espdma.regs->cond_reg & DMA_HNDL_INTR) == 0) /* no-op */;
|
|
// Check status
|
|
status = esp->ll->regs[ESP_STATUS];
|
|
// Clear interrupts to avoid guests seeing spurious interrupts
|
|
(void)esp->ll->regs[ESP_INTRPT];
|
|
|
|
DPRINTF("do_command: id %d, cmd[0] 0x%x, status 0x%x\n", sd->id, esp->buffer[1], status);
|
|
|
|
/* Target didn't want all command data? */
|
|
if ((status & ESP_STAT_TCNT) != ESP_STAT_TCNT) {
|
|
return status;
|
|
}
|
|
if (replylen == 0) {
|
|
return 0;
|
|
}
|
|
/* Target went to status phase instead of data phase? */
|
|
if ((status & ESP_STAT_PMASK) == ESP_STATP) {
|
|
return status;
|
|
}
|
|
|
|
// Get reply
|
|
// Set DMA address
|
|
esp->espdma.regs->st_addr = esp->buffer_dvma;
|
|
// Set DMA length
|
|
esp->ll->regs[ESP_TCLOW] = replylen & 0xff;
|
|
esp->ll->regs[ESP_TCMED] = (replylen >> 8) & 0xff;
|
|
// Set DMA direction
|
|
esp->espdma.regs->cond_reg = DMA_ST_WRITE | DMA_ENABLE;
|
|
// Transfer
|
|
esp->ll->regs[ESP_CMD] = ESP_CMD_TI | ESP_CMD_DMA;
|
|
// Wait for DMA to complete
|
|
while ((esp->espdma.regs->cond_reg & DMA_HNDL_INTR) == 0) /* no-op */;
|
|
// Check status
|
|
status = esp->ll->regs[ESP_STATUS];
|
|
// Clear interrupts to avoid guests seeing spurious interrupts
|
|
(void)esp->ll->regs[ESP_INTRPT];
|
|
|
|
DPRINTF("do_command_reply: status 0x%x\n", status);
|
|
|
|
if ((status & ESP_STAT_TCNT) != ESP_STAT_TCNT)
|
|
return status;
|
|
else
|
|
return 0; // OK
|
|
}
|
|
|
|
// offset is in sectors
|
|
static int
|
|
ob_sd_read_sector(esp_private_t *esp, sd_private_t *sd, int offset)
|
|
{
|
|
DPRINTF("ob_sd_read_sector id %d sector=%d\n",
|
|
sd->id, offset);
|
|
|
|
// Setup command = Read(10)
|
|
memset(esp->buffer, 0, 11);
|
|
esp->buffer[0] = 0x80;
|
|
esp->buffer[1] = READ_10;
|
|
|
|
esp->buffer[3] = (offset >> 24) & 0xff;
|
|
esp->buffer[4] = (offset >> 16) & 0xff;
|
|
esp->buffer[5] = (offset >> 8) & 0xff;
|
|
esp->buffer[6] = offset & 0xff;
|
|
|
|
esp->buffer[8] = 0;
|
|
esp->buffer[9] = 1;
|
|
|
|
if (do_command(esp, sd, 11, sd->bs))
|
|
return 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int
|
|
read_capacity(esp_private_t *esp, sd_private_t *sd)
|
|
{
|
|
// Setup command = Read Capacity
|
|
memset(esp->buffer, 0, 11);
|
|
esp->buffer[0] = 0x80;
|
|
esp->buffer[1] = READ_CAPACITY;
|
|
|
|
if (do_command(esp, sd, 11, 8)) {
|
|
sd->sectors = 0;
|
|
sd->bs = 0;
|
|
DPRINTF("read_capacity id %d failed\n", sd->id);
|
|
return 0;
|
|
}
|
|
sd->bs = (esp->buffer[4] << 24) | (esp->buffer[5] << 16) | (esp->buffer[6] << 8) | esp->buffer[7];
|
|
sd->sectors = ((esp->buffer[0] << 24) | (esp->buffer[1] << 16) | (esp->buffer[2] << 8) | esp->buffer[3]) * (sd->bs / 512);
|
|
|
|
DPRINTF("read_capacity id %d bs %d sectors %d\n", sd->id, sd->bs,
|
|
sd->sectors);
|
|
return 1;
|
|
}
|
|
|
|
static unsigned int
|
|
test_unit_ready(esp_private_t *esp, sd_private_t *sd)
|
|
{
|
|
/* Setup command = Test Unit Ready */
|
|
memset(esp->buffer, 0, 7);
|
|
esp->buffer[0] = 0x80;
|
|
esp->buffer[1] = TEST_UNIT_READY;
|
|
|
|
if (do_command(esp, sd, 7, 0)) {
|
|
DPRINTF("test_unit_ready id %d failed\n", sd->id);
|
|
return 0;
|
|
}
|
|
|
|
DPRINTF("test_unit_ready id %d success\n", sd->id);
|
|
return 1;
|
|
}
|
|
|
|
static unsigned int
|
|
inquiry(esp_private_t *esp, sd_private_t *sd)
|
|
{
|
|
const char *media[2] = { "UNKNOWN", "UNKNOWN"};
|
|
|
|
// Setup command = Inquiry
|
|
memset(esp->buffer, 0, 7);
|
|
esp->buffer[0] = 0x80;
|
|
esp->buffer[1] = INQUIRY;
|
|
|
|
esp->buffer[5] = 36;
|
|
|
|
if (do_command(esp, sd, 7, 36)) {
|
|
sd->present = 0;
|
|
sd->media = -1;
|
|
return 0;
|
|
}
|
|
sd->present = 1;
|
|
sd->media = esp->buffer[0];
|
|
|
|
switch (sd->media) {
|
|
case TYPE_DISK:
|
|
media[0] = "disk";
|
|
media[1] = "hd";
|
|
break;
|
|
case TYPE_ROM:
|
|
media[0] = "cdrom";
|
|
media[1] = "cd";
|
|
break;
|
|
}
|
|
sd->media_str[0] = media[0];
|
|
sd->media_str[1] = media[1];
|
|
memcpy(sd->model, &esp->buffer[16], 16);
|
|
sd->model[17] = '\0';
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
ob_esp_dma_alloc(__attribute__((unused)) esp_private_t **esp)
|
|
{
|
|
call_parent_method("dma-alloc");
|
|
}
|
|
|
|
static void
|
|
ob_esp_dma_free(__attribute__((unused)) esp_private_t **esp)
|
|
{
|
|
call_parent_method("dma-free");
|
|
}
|
|
|
|
static void
|
|
ob_esp_dma_map_in(__attribute__((unused)) esp_private_t **esp)
|
|
{
|
|
call_parent_method("dma-map-in");
|
|
}
|
|
|
|
static void
|
|
ob_esp_dma_map_out(__attribute__((unused)) esp_private_t **esp)
|
|
{
|
|
call_parent_method("dma-map-out");
|
|
}
|
|
|
|
static void
|
|
ob_esp_dma_sync(__attribute__((unused)) esp_private_t **esp)
|
|
{
|
|
call_parent_method("dma-sync");
|
|
}
|
|
|
|
static void
|
|
ob_sd_read_blocks(sd_private_t **sd)
|
|
{
|
|
cell n = POP(), cnt = n;
|
|
ucell blk = POP();
|
|
char *dest = (char*)POP();
|
|
int pos, spb, sect_offset;
|
|
|
|
DPRINTF("ob_sd_read_blocks id %d %lx block=%d n=%d\n", (*sd)->id, (unsigned long)dest, blk, n );
|
|
|
|
if ((*sd)->bs == 0) {
|
|
PUSH(0);
|
|
return;
|
|
}
|
|
spb = (*sd)->bs / 512;
|
|
while (n) {
|
|
sect_offset = blk / spb;
|
|
pos = (blk - sect_offset * spb) * 512;
|
|
|
|
if (ob_sd_read_sector(global_esp, *sd, sect_offset)) {
|
|
DPRINTF("ob_sd_read_blocks: error\n");
|
|
RET(0);
|
|
}
|
|
while (n && pos < spb * 512) {
|
|
memcpy(dest, global_esp->buffer + pos, 512);
|
|
pos += 512;
|
|
dest += 512;
|
|
n--;
|
|
blk++;
|
|
}
|
|
}
|
|
PUSH(cnt);
|
|
}
|
|
|
|
static void
|
|
ob_sd_block_size(__attribute__((unused))sd_private_t **sd)
|
|
{
|
|
PUSH(512);
|
|
}
|
|
|
|
static void
|
|
ob_sd_open(__attribute__((unused))sd_private_t **sd)
|
|
{
|
|
int ret = 1, id;
|
|
phandle_t ph;
|
|
|
|
fword("my-unit");
|
|
id = POP();
|
|
POP(); // unit id is 2 ints but we only need one.
|
|
*sd = &global_esp->sd[id];
|
|
|
|
#ifdef CONFIG_DEBUG_ESP
|
|
{
|
|
char *args;
|
|
|
|
fword("my-args");
|
|
args = pop_fstr_copy();
|
|
DPRINTF("opening drive %d args %s\n", id, args);
|
|
free(args);
|
|
}
|
|
#endif
|
|
|
|
selfword("open-deblocker");
|
|
|
|
/* interpose disk-label */
|
|
ph = find_dev("/packages/disk-label");
|
|
fword("my-args");
|
|
PUSH_ph( ph );
|
|
fword("interpose");
|
|
|
|
RET ( -ret );
|
|
}
|
|
|
|
static void
|
|
ob_sd_close(__attribute__((unused)) sd_private_t **sd)
|
|
{
|
|
selfword("close-deblocker");
|
|
}
|
|
|
|
NODE_METHODS(ob_sd) = {
|
|
{ "open", ob_sd_open },
|
|
{ "close", ob_sd_close },
|
|
{ "read-blocks", ob_sd_read_blocks },
|
|
{ "block-size", ob_sd_block_size },
|
|
{ "dma-alloc", ob_esp_dma_alloc },
|
|
{ "dma-free", ob_esp_dma_free },
|
|
{ "dma-map-in", ob_esp_dma_map_in },
|
|
{ "dma-map-out", ob_esp_dma_map_out },
|
|
{ "dma-sync", ob_esp_dma_sync },
|
|
};
|
|
|
|
|
|
static int
|
|
espdma_init(unsigned int slot, uint64_t base, unsigned long offset,
|
|
struct esp_dma *espdma)
|
|
{
|
|
espdma->regs = (void *)ofmem_map_io(base + (uint64_t)offset, 0x10);
|
|
|
|
if (espdma->regs == NULL) {
|
|
DPRINTF("espdma_init: cannot map registers\n");
|
|
return -1;
|
|
}
|
|
|
|
DPRINTF("dma1: ");
|
|
|
|
switch ((espdma->regs->cond_reg) & DMA_DEVICE_ID) {
|
|
case DMA_VERS0:
|
|
espdma->revision = dvmarev0;
|
|
DPRINTF("Revision 0 ");
|
|
break;
|
|
case DMA_ESCV1:
|
|
espdma->revision = dvmaesc1;
|
|
DPRINTF("ESC Revision 1 ");
|
|
break;
|
|
case DMA_VERS1:
|
|
espdma->revision = dvmarev1;
|
|
DPRINTF("Revision 1 ");
|
|
break;
|
|
case DMA_VERS2:
|
|
espdma->revision = dvmarev2;
|
|
DPRINTF("Revision 2 ");
|
|
break;
|
|
case DMA_VERHME:
|
|
espdma->revision = dvmahme;
|
|
DPRINTF("HME DVMA gate array ");
|
|
break;
|
|
case DMA_VERSPLUS:
|
|
espdma->revision = dvmarevplus;
|
|
DPRINTF("Revision 1 PLUS ");
|
|
break;
|
|
default:
|
|
DPRINTF("unknown dma version %x",
|
|
(espdma->regs->cond_reg) & DMA_DEVICE_ID);
|
|
/* espdma->allocated = 1; */
|
|
break;
|
|
}
|
|
DPRINTF("\n");
|
|
|
|
push_str("/iommu/sbus/espdma");
|
|
fword("find-device");
|
|
|
|
/* set reg */
|
|
PUSH(slot);
|
|
fword("encode-int");
|
|
PUSH(offset);
|
|
fword("encode-int");
|
|
fword("encode+");
|
|
PUSH(0x00000010);
|
|
fword("encode-int");
|
|
fword("encode+");
|
|
push_str("reg");
|
|
fword("property");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
ob_esp_decodeunit(__attribute__((unused)) esp_private_t **esp)
|
|
{
|
|
fword("decode-unit-scsi");
|
|
}
|
|
|
|
|
|
static void
|
|
ob_esp_encodeunit(__attribute__((unused)) esp_private_t **esp)
|
|
{
|
|
fword("encode-unit-scsi");
|
|
}
|
|
|
|
NODE_METHODS(ob_esp) = {
|
|
{ "decode-unit", ob_esp_decodeunit },
|
|
{ "encode-unit", ob_esp_encodeunit },
|
|
{ "dma-alloc", ob_esp_dma_alloc },
|
|
{ "dma-free", ob_esp_dma_free },
|
|
{ "dma-map-in", ob_esp_dma_map_in },
|
|
{ "dma-map-out", ob_esp_dma_map_out },
|
|
{ "dma-sync", ob_esp_dma_sync },
|
|
};
|
|
|
|
static void
|
|
add_alias(const char *device, const char *alias)
|
|
{
|
|
DPRINTF("add_alias dev \"%s\" = alias \"%s\"\n", device, alias);
|
|
push_str("/aliases");
|
|
fword("find-device");
|
|
push_str(device);
|
|
fword("encode-string");
|
|
push_str(alias);
|
|
fword("property");
|
|
}
|
|
|
|
int
|
|
ob_esp_init(unsigned int slot, uint64_t base, unsigned long espoffset,
|
|
unsigned long dmaoffset)
|
|
{
|
|
int id, diskcount = 0, cdcount = 0, *counter_ptr;
|
|
char nodebuff[256], aliasbuff[256];
|
|
esp_private_t *esp;
|
|
ucell addr;
|
|
unsigned int i;
|
|
|
|
DPRINTF("Initializing SCSI...");
|
|
|
|
esp = malloc(sizeof(esp_private_t));
|
|
if (!esp) {
|
|
DPRINTF("Can't allocate ESP private structure\n");
|
|
return -1;
|
|
}
|
|
|
|
global_esp = esp;
|
|
|
|
if (espdma_init(slot, base, dmaoffset, &esp->espdma) != 0) {
|
|
return -1;
|
|
}
|
|
/* Get the IO region */
|
|
esp->ll = (void *)ofmem_map_io(base + (uint64_t)espoffset,
|
|
sizeof(struct esp_regs));
|
|
if (esp->ll == NULL) {
|
|
DPRINTF("Can't map ESP registers\n");
|
|
return -1;
|
|
}
|
|
|
|
push_str("/iommu/sbus/espdma");
|
|
fword("find-device");
|
|
fword("new-device");
|
|
|
|
push_str("esp");
|
|
fword("device-name");
|
|
|
|
/* set device type */
|
|
push_str("scsi");
|
|
fword("device-type");
|
|
|
|
/* QEMU's ESP emulation does not support mixing DMA and FIFO messages. By
|
|
setting this attribute, we prevent the Solaris ESP kernel driver from
|
|
trying to use this feature when booting a disk image (and failing) */
|
|
PUSH(0x58);
|
|
fword("encode-int");
|
|
push_str("scsi-options");
|
|
fword("property");
|
|
|
|
PUSH(0x24);
|
|
fword("encode-int");
|
|
PUSH(0);
|
|
fword("encode-int");
|
|
fword("encode+");
|
|
push_str("intr");
|
|
fword("property");
|
|
|
|
PUSH(slot);
|
|
fword("encode-int");
|
|
PUSH(espoffset);
|
|
fword("encode-int");
|
|
fword("encode+");
|
|
PUSH(0x00000010);
|
|
fword("encode-int");
|
|
fword("encode+");
|
|
push_str("reg");
|
|
fword("property");
|
|
|
|
PUSH(0x02625a00);
|
|
fword("encode-int");
|
|
push_str("clock-frequency");
|
|
fword("property");
|
|
|
|
REGISTER_NODE_METHODS(ob_esp, "/iommu/sbus/espdma/esp");
|
|
|
|
fword("finish-device");
|
|
|
|
fword("my-self");
|
|
push_str("/iommu/sbus/espdma/esp");
|
|
feval("open-dev to my-self");
|
|
PUSH(BUFSIZE);
|
|
feval("dma-alloc");
|
|
addr = POP();
|
|
esp->buffer = cell2pointer(addr);
|
|
|
|
PUSH(addr);
|
|
PUSH(BUFSIZE);
|
|
PUSH(1);
|
|
feval("dma-map-in");
|
|
addr = POP();
|
|
esp->buffer_dvma = addr;
|
|
feval("to my-self");
|
|
|
|
if (!esp->buffer || !esp->buffer_dvma) {
|
|
DPRINTF("Can't get a DVMA buffer\n");
|
|
return -1;
|
|
}
|
|
|
|
// Chip reset
|
|
esp->ll->regs[ESP_CMD] = ESP_CMD_RC;
|
|
|
|
DPRINTF("ESP at 0x%lx, buffer va 0x%lx dva 0x%lx\n", (unsigned long)esp,
|
|
(unsigned long)esp->buffer, (unsigned long)esp->buffer_dvma);
|
|
DPRINTF("done\n");
|
|
DPRINTF("Initializing SCSI devices...");
|
|
|
|
for (id = 0; id < 8; id++) {
|
|
esp->sd[id].id = id;
|
|
if (!inquiry(esp, &esp->sd[id])) {
|
|
DPRINTF("Unit %d not present\n", id);
|
|
continue;
|
|
}
|
|
/* Clear Unit Attention condition from reset */
|
|
for (i = 0; i < 5; i++) {
|
|
if (test_unit_ready(esp, &esp->sd[id])) {
|
|
break;
|
|
}
|
|
}
|
|
if (i == 5) {
|
|
DPRINTF("Unit %d present but won't become ready\n", id);
|
|
continue;
|
|
}
|
|
DPRINTF("Unit %d present\n", id);
|
|
read_capacity(esp, &esp->sd[id]);
|
|
|
|
#ifdef CONFIG_DEBUG_ESP
|
|
dump_drive(&esp->sd[id]);
|
|
#endif
|
|
}
|
|
|
|
for (id = 0; id < 8; id++) {
|
|
if (!esp->sd[id].present)
|
|
continue;
|
|
push_str("/iommu/sbus/espdma/esp");
|
|
fword("find-device");
|
|
fword("new-device");
|
|
push_str("sd");
|
|
fword("device-name");
|
|
push_str("block");
|
|
fword("device-type");
|
|
fword("is-deblocker");
|
|
PUSH(id);
|
|
fword("encode-int");
|
|
PUSH(0);
|
|
fword("encode-int");
|
|
fword("encode+");
|
|
push_str("reg");
|
|
fword("property");
|
|
fword("finish-device");
|
|
snprintf(nodebuff, sizeof(nodebuff), "/iommu/sbus/espdma/esp/sd@%d,0",
|
|
id);
|
|
REGISTER_NODE_METHODS(ob_sd, nodebuff);
|
|
if (esp->sd[id].media == TYPE_ROM) {
|
|
counter_ptr = &cdcount;
|
|
} else {
|
|
counter_ptr = &diskcount;
|
|
}
|
|
if (*counter_ptr == 0) {
|
|
add_alias(nodebuff, esp->sd[id].media_str[0]);
|
|
add_alias(nodebuff, esp->sd[id].media_str[1]);
|
|
}
|
|
snprintf(aliasbuff, sizeof(aliasbuff), "%s%d",
|
|
esp->sd[id].media_str[0], *counter_ptr);
|
|
add_alias(nodebuff, aliasbuff);
|
|
snprintf(aliasbuff, sizeof(aliasbuff), "%s%d",
|
|
esp->sd[id].media_str[1], *counter_ptr);
|
|
add_alias(nodebuff, aliasbuff);
|
|
snprintf(aliasbuff, sizeof(aliasbuff), "sd(0,%d,0)", id);
|
|
add_alias(nodebuff, aliasbuff);
|
|
snprintf(aliasbuff, sizeof(aliasbuff), "sd(0,%d,0)@0,0", id);
|
|
add_alias(nodebuff, aliasbuff);
|
|
(*counter_ptr)++;
|
|
}
|
|
DPRINTF("done\n");
|
|
|
|
return 0;
|
|
}
|