/* * OpenBIOS ESP driver * * Copyright (C) 2004 Jens Axboe * 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 "openbios/config.h" #include "openbios/bindings.h" #include "openbios/kernel.h" #include "libc/byteorder.h" #include "libc/vsprintf.h" #include "openbios/drivers.h" #include "asm/io.h" #include "scsi.h" #include "asm/dma.h" #include "esp.h" #define BUFSIZE 4096 #define REGISTER_NAMED_NODE( name, path ) do { \ bind_new_node( name##_flags_, name##_size_, \ path, name##_m, sizeof(name##_m)/sizeof(method_t)); \ } while(0) #define REGISTER_NODE_METHODS( name, path ) do { \ const char *paths[1]; \ \ paths[0] = path; \ bind_node( name##_flags_, name##_size_, \ paths, 1, name##_m, sizeof(name##_m)/sizeof(method_t)); \ } while(0) #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; char *media_str; 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; 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); 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 esp->espdma.regs->cond_reg = 0; // 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]; DPRINTF("do_command: id %d, cmd[0] 0x%x, status 0x%x\n", sd->id, esp->buffer[0], status); // Target didn't want all command data or went to status phase // instead of data phase? if ((status & ESP_STAT_TCNT) != ESP_STAT_TCNT || (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; // 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]; 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 %lx sector=%d\n", sd->id, (unsigned long)dest, offset); // Setup command = Read(10) memset(esp->buffer, 0, 10); 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, 10, 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; 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); return 1; } static unsigned int inquiry(esp_private_t *esp, sd_private_t *sd) { char *media = "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 = "disk"; break; case TYPE_ROM: media = "cdrom"; break; } sd->media_str = media; 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 ); 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 *)map_io(base + (uint64_t)offset, 0x10); if (espdma->regs == 0) { 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"); 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) { 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; 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 *)map_io(base + (uint64_t)espoffset, sizeof(struct esp_regs)); if (esp->ll == 0) { 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])) continue; 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"); sprintf(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); } sprintf(aliasbuff, "%s%d", esp->sd[id].media_str, *counter_ptr); add_alias(nodebuff, aliasbuff); sprintf(aliasbuff, "sd(0,%d,0)", id); add_alias(nodebuff, aliasbuff); sprintf(aliasbuff, "sd(0,%d,0)@0,0", id); add_alias(nodebuff, aliasbuff); (*counter_ptr)++; } DPRINTF("done\n"); return 0; }