>From 59d5f4c18187a0b20ab4e1b93f4619e48f909df8 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 2 Aug 2012 14:56:05 +0200 Subject: [PATCH] scsi: add AMD PCscsi driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a driver for an AMD PCscsi (am53c974) SCSI card. It can be used together with DOS or old operating systems such as Windows NT 3.1, Windows 3.1 or Windows 98. Cc: Hervé Poussineau Signed-off-by: Paolo Bonzini --- Makefile | 2 +- src/Kconfig | 6 ++ src/block.c | 1 + src/blockcmd.c | 3 + src/disk.h | 1 + src/esp-scsi.c | 231 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/post.c | 2 + 7 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 src/esp-scsi.c diff --git a/Makefile b/Makefile index 1167799..4dce77b 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ SRCBOTH=misc.c stacks.c pmm.c output.c util.c block.c floppy.c ata.c mouse.c \ pnpbios.c pirtable.c vgahooks.c ramdisk.c pcibios.c blockcmd.c \ usb.c usb-uhci.c usb-ohci.c usb-ehci.c usb-hid.c usb-msc.c \ virtio-ring.c virtio-pci.c virtio-blk.c virtio-scsi.c apm.c ahci.c \ - usb-uas.c lsi-scsi.c + usb-uas.c lsi-scsi.c esp-scsi.c SRC16=$(SRCBOTH) system.c disk.c font.c SRC32FLAT=$(SRCBOTH) post.c shadow.c memmap.c coreboot.c boot.c \ acpi.c smm.c mptable.c smbios.c pciinit.c optionroms.c mtrr.c \ diff --git a/src/Kconfig b/src/Kconfig index bc343ee..6de3e71 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -119,6 +119,12 @@ menu "Hardware support" default y help Support boot from virtio-scsi storage. + config ESP_SCSI + depends on DRIVES && !COREBOOT + bool "AMD PCscsi controllers" + default y + help + Support boot from AMD PCscsi storage. config LSI_SCSI depends on DRIVES && !COREBOOT bool "lsi53c895a scsi controllers" diff --git a/src/block.c b/src/block.c index 1c200cc..243428e 100644 --- a/src/block.c +++ b/src/block.c @@ -335,6 +335,7 @@ process_op(struct disk_op_s *op) case DTYPE_UAS: case DTYPE_VIRTIO_SCSI: case DTYPE_LSI_SCSI: + case DTYPE_ESP_SCSI: return process_scsi_op(op); default: op->count = 0; diff --git a/src/blockcmd.c b/src/blockcmd.c index 4365650..97c72a6 100644 --- a/src/blockcmd.c +++ b/src/blockcmd.c @@ -15,6 +15,7 @@ #include "usb-uas.h" // usb_cmd_data #include "virtio-scsi.h" // virtio_scsi_cmd_data #include "lsi-scsi.h" // lsi_scsi_cmd_data +#include "esp-scsi.h" // esp_scsi_cmd_data #include "boot.h" // boot_add_hd // Route command to low-level handler. @@ -35,6 +36,8 @@ cdb_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize) return virtio_scsi_cmd_data(op, cdbcmd, blocksize); case DTYPE_LSI_SCSI: return lsi_scsi_cmd_data(op, cdbcmd, blocksize); + case DTYPE_ESP_SCSI: + return esp_scsi_cmd_data(op, cdbcmd, blocksize); default: op->count = 0; return DISK_RET_EPARAM; diff --git a/src/disk.h b/src/disk.h index 2b2511f..3d07372 100644 --- a/src/disk.h +++ b/src/disk.h @@ -234,6 +234,7 @@ struct drive_s { #define DTYPE_USB 0x0a #define DTYPE_UAS 0x0b #define DTYPE_LSI_SCSI 0x0c +#define DTYPE_ESP_SCSI 0x0d #define MAXDESCSIZE 80 diff --git a/src/esp-scsi.c b/src/esp-scsi.c new file mode 100644 index 0000000..daafa1e --- /dev/null +++ b/src/esp-scsi.c @@ -0,0 +1,231 @@ +// AMD PCscsi boot support. +// +// Copyright (C) 2012 Red Hat Inc. +// +// Authors: +// Paolo Bonzini +// +// based on lsi-scsi.c which is written by: +// Gerd Hoffman +// +// This file may be distributed under the terms of the GNU LGPLv3 license. + +#include "util.h" // dprintf +#include "pci.h" // foreachpci +#include "config.h" // CONFIG_* +#include "biosvar.h" // GET_GLOBAL +#include "pci_ids.h" // PCI_DEVICE_ID +#include "pci_regs.h" // PCI_VENDOR_ID +#include "boot.h" // bootprio_find_scsi_device +#include "blockcmd.h" // scsi_init_drive +#include "disk.h" + +#define ESP_TCLO 0x00 +#define ESP_TCMID 0x04 +#define ESP_FIFO 0x08 +#define ESP_CMD 0x0c +#define ESP_WBUSID 0x10 +#define ESP_TCHI 0x38 + +#define ESP_RSTAT 0x10 +#define ESP_RINTR 0x14 +#define ESP_RFLAGS 0x1c + +#define ESP_DMA_CMD 0x40 +#define ESP_DMA_STC 0x44 +#define ESP_DMA_SPA 0x48 +#define ESP_DMA_WBC 0x4c +#define ESP_DMA_WAC 0x50 +#define ESP_DMA_STAT 0x54 +#define ESP_DMA_SMDLA 0x58 +#define ESP_DMA_WMAC 0x58c + +#define ESP_CMD_DMA 0x80 +#define ESP_CMD_RESET 0x02 +#define ESP_CMD_TI 0x10 +#define ESP_CMD_ICCS 0x11 +#define ESP_CMD_SELATN 0x42 + +#define ESP_STAT_DI 0x01 +#define ESP_STAT_CD 0x02 +#define ESP_STAT_MSG 0x04 +#define ESP_STAT_TC 0x10 + +#define ESP_INTR_DC 0x20 + +struct esp_lun_s { + struct drive_s drive; + struct pci_device *pci; + u32 iobase; + u8 target; + u8 lun; +}; + +static void +esp_scsi_dma(u32 iobase, u32 buf, u32 len, int read) +{ + outb(len & 0xff, iobase + ESP_TCLO); + outb((len >> 8) & 0xff, iobase + ESP_TCMID); + outb((len >> 16) & 0xff, iobase + ESP_TCHI); + outl(buf, iobase + ESP_DMA_SPA); + outl(len, iobase + ESP_DMA_STC); + outb(read ? 0x83 : 0x03, iobase + ESP_DMA_CMD); +} + +static int +esp_scsi_cmd(struct esp_lun_s *llun, struct disk_op_s *op, + u8 *cdbcmd, u16 target, u16 lun, u16 blocksize) +{ + u32 iobase = GET_GLOBAL(llun->iobase); + int i, state; + u8 status; + + outb(target, iobase + ESP_WBUSID); + + /* + * We need to pass the LUN at the beginning of the command, and the FIFO + * is only 16 bytes, so we cannot support 16-byte CDBs. The alternative + * would be to use DMA for the 17-byte command too, which is quite + * overkill. + */ + outb(lun, iobase + ESP_FIFO); + cdbcmd[1] &= 0x1f; + cdbcmd[1] |= lun << 5; + for (i = 0; i < 12; i++) + outb(cdbcmd[i], iobase + ESP_FIFO); + outb(ESP_CMD_SELATN, iobase + ESP_CMD); + + for (state = 0;;) { + u8 stat = inb(iobase + ESP_RSTAT); + + /* Detect disconnected device. */ + if (state == 0 && (inb(iobase + ESP_RINTR) & ESP_INTR_DC)) { + return DISK_RET_ENOTREADY; + } + + /* HBA reads command, clears CD, sets TC -> do DMA if needed. */ + if (state == 0 && (stat & ESP_STAT_TC)) { + state++; + if (op->count && blocksize) { + /* Data phase. */ + u32 count = (u32)op->count * blocksize; + esp_scsi_dma(iobase, (u32)op->buf_fl, count, + cdb_is_read(cdbcmd, blocksize)); + outb(ESP_CMD_TI | ESP_CMD_DMA, iobase + ESP_CMD); + continue; + } + } + + /* At end of DMA TC is set again -> complete command. */ + if (state == 1 && (stat & ESP_STAT_TC)) { + state++; + outb(ESP_CMD_ICCS, iobase + ESP_CMD); + continue; + } + + /* Finally read data from the message in phase. */ + if (state == 2 && (stat & ESP_STAT_MSG)) { + state++; + status = inb(iobase + ESP_FIFO); + inb(iobase + ESP_FIFO); + break; + } + usleep(5); + } + + if (status == 0) { + return DISK_RET_SUCCESS; + } + + return DISK_RET_EBADTRACK; +} + +int +esp_scsi_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize) +{ + if (!CONFIG_ESP_SCSI) + return DISK_RET_EBADTRACK; + + struct esp_lun_s *llun = + container_of(op->drive_g, struct esp_lun_s, drive); + + return esp_scsi_cmd(llun, op, cdbcmd, + GET_GLOBAL(llun->target), GET_GLOBAL(llun->lun), + blocksize); +} + +static int +esp_scsi_add_lun(struct pci_device *pci, u32 iobase, u8 target, u8 lun) +{ + struct esp_lun_s *llun = malloc_fseg(sizeof(*llun)); + if (!llun) { + warn_noalloc(); + return -1; + } + memset(llun, 0, sizeof(*llun)); + llun->drive.type = DTYPE_ESP_SCSI; + llun->drive.cntl_id = pci->bdf; + llun->pci = pci; + llun->target = target; + llun->lun = lun; + llun->iobase = iobase; + + char *name = znprintf(16, "esp %02x:%02x.%x %d:%d", + pci_bdf_to_bus(pci->bdf), pci_bdf_to_dev(pci->bdf), + pci_bdf_to_fn(pci->bdf), target, lun); + int prio = bootprio_find_scsi_device(pci, target, lun); + int ret = scsi_init_drive(&llun->drive, name, prio); + free(name); + if (ret) + goto fail; + return 0; + +fail: + free(llun); + return -1; +} + +static void +esp_scsi_scan_target(struct pci_device *pci, u32 iobase, u8 target) +{ + esp_scsi_add_lun(pci, iobase, target, 0); +} + +static void +init_esp_scsi(struct pci_device *pci) +{ + u16 bdf = pci->bdf; + u32 iobase = pci_config_readl(pci->bdf, PCI_BASE_ADDRESS_0) + & PCI_BASE_ADDRESS_IO_MASK; + + dprintf(1, "found esp at %02x:%02x.%x, io @ %x\n", + pci_bdf_to_bus(bdf), pci_bdf_to_dev(bdf), + pci_bdf_to_fn(bdf), iobase); + + // reset + outb(ESP_CMD_RESET, iobase + ESP_CMD); + + int i; + for (i = 0; i <= 7; i++) + esp_scsi_scan_target(pci, iobase, i); + + return; +} + +void +esp_scsi_setup(void) +{ + ASSERT32FLAT(); + if (!CONFIG_ESP_SCSI) + return; + + dprintf(3, "init esp\n"); + + struct pci_device *pci; + foreachpci(pci) { + if (pci->vendor != PCI_VENDOR_ID_AMD + || pci->device != PCI_DEVICE_ID_AMD_SCSI) + continue; + init_esp_scsi(pci); + } +} diff --git a/src/post.c b/src/post.c index 0f31b4c..924b311 100644 --- a/src/post.c +++ b/src/post.c @@ -28,6 +28,7 @@ #include "virtio-blk.h" // virtio_blk_setup #include "virtio-scsi.h" // virtio_scsi_setup #include "lsi-scsi.h" // lsi_scsi_setup +#include "esp-scsi.h" // esp_scsi_setup /**************************************************************** @@ -196,6 +197,7 @@ init_hw(void) virtio_blk_setup(); virtio_scsi_setup(); lsi_scsi_setup(); + esp_scsi_setup(); } // Begin the boot process by invoking an int0x19 in 16bit mode. -- 1.7.10.4