| /* |
| * 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; |
| } |