diff options
Diffstat (limited to 'qemu/roms/openbios/drivers/esp.c')
-rw-r--r-- | qemu/roms/openbios/drivers/esp.c | 598 |
1 files changed, 598 insertions, 0 deletions
diff --git a/qemu/roms/openbios/drivers/esp.c b/qemu/roms/openbios/drivers/esp.c new file mode 100644 index 000000000..ad3db280d --- /dev/null +++ b/qemu/roms/openbios/drivers/esp.c @@ -0,0 +1,598 @@ +/* + * OpenBIOS ESP driver + * + * Copyright (C) 2004 Jens Axboe <axboe@suse.de> + * Copyright (C) 2005 Stefan Reinauer <stepan@openbios.org> + * + * 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_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 }, +}; + + +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_initialize(__attribute__((unused)) esp_private_t **esp) +{ + phandle_t ph = get_cur_dev(); + + set_int_property(ph, "#address-cells", 2); + set_int_property(ph, "#size-cells", 0); + + /* 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"); +} + +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) = { + { NULL, ob_esp_initialize }, + { "decode-unit", ob_esp_decodeunit }, + { "encode-unit", ob_esp_encodeunit }, +}; + +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; + 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; + } + + esp->buffer = (void *)dvma_alloc(BUFSIZE, &esp->buffer_dvma); + 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 + } + + REGISTER_NAMED_NODE(ob_esp, "/iommu/sbus/espdma/esp"); + device_end(); + /* set reg */ + push_str("/iommu/sbus/espdma/esp"); + fword("find-device"); + 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"); + + 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; +} |