2010-06-02 Aleš Nesrsta <starous@volny.cz>
Finally make USB usable. * bus/usb/ohci.c (grub_ohci_reg_t): Add missing values. (GRUB_OHCI_RHUB_PORT_POWER_MASK): New macro. (GRUB_OHCI_RHUB_PORT_ALL_POWERED): Likewise. (GRUB_OHCI_REG_FRAME_INTERVAL_FSMPS_MASK): Likewise. (GRUB_OHCI_REG_FRAME_INTERVAL_FSMPS_SHIFT): Likewise. (GRUB_OHCI_REG_FRAME_INTERVAL_FI_SHIFT): Likewise. (GRUB_OHCI_FSMPS): Likewise. (GRUB_OHCI_PERIODIC_START): Likewise. (GRUB_OHCI_FRAME_INTERVAL): Likewise. (GRUB_OHCI_SET_PORT_ENABLE): Likewise. (GRUB_OHCI_CLEAR_PORT_ENABLE): Likewise. (GRUB_OHCI_SET_PORT_RESET): Likewise. (GRUB_OHCI_SET_PORT_RESET_STATUS_CHANGE): Likewise. * bus/usb/ohci.c (grub_ohci_pci_iter): Various important fixups. (grub_ohci_transaction): Likewise. (grub_ohci_transfer): Improve condition detection algorithms. Handle toggle property. Program the transactions correctly. Improve error handling. Various important fixups. (grub_ohci_portstatus): Put register writes in right order. * bus/usb/uhci.c (grub_free_queue): Compute last_trans. (grub_uhci_transfer): Don't show "failed" message on success. * bus/usb/usb.c (grub_usb_set_configuration): Zero-fill whole "toggle" array. (grub_usb_device_initialize): Read first 8 bytes of descriptor to determine its size. * bus/usb/usbtrans.c (grub_usb_control_msg): Use descdev.maxsize0 even before initialization is completed. Use IN direction for empty transfers. Use last_trans and compute toggle. * include/grub/usbtrans.h (grub_usb_transfer): New field last_trans. (GRUB_USB_FEATURE_ENDP_HALT): Correct the value. (GRUB_USB_FEATURE_DEV_REMOTE_WU): Likewise. (GRUB_USB_FEATURE_TEST_MODE): Likewise. * include/grub/usb.h (grub_usb_err_t): New value GRUB_USB_ERR_UNRECOVERABLE. (grub_usb_device): Increase toggle to 256. (grub_usbms_subclass_t): New values GRUB_USBMS_SUBCLASS_RBC, GRUB_USBMS_SUBCLASS_MMC2, GRUB_USBMS_SUBCLASS_UFI and GRUB_USBMS_SUBCLASS_SFF8070. * include/grub/scsicmd.h (grub_scsi_test_unit_ready): New structure. (grub_scsi_inquiry): New member page and alloc_length. (grub_scsi_request_sense): New structure. (grub_scsi_request_sense_data): Likewise. (grub_scsi_read_capacity): New fields logical_block_addr, PMI and control. * disk/scsi.c (grub_scsi_request_sense): New function. (grub_scsi_test_unit_ready): Likewise. (grub_scsi_inquiry): Fill new fields. (grub_scsi_read_capacity): Likewise. (grub_scsi_read10): Add request sense at the end. (grub_scsi_read12): Likewise. (grub_scsi_write10): Likewise. (grub_scsi_write12): Likewise. (grub_scsi_open): Add Test Unit Ready. * disk/usbms.c (grub_usbms_finddevs): Check configcnt. Support additional subclasses. Con't clear halt yet. Activate the proper config. Calculate LUNs correctly. (grub_usbms_transfer): Various important fixups. 2010-06-02 Vladimir Serbinenko <phcoder@gmail.com> * bus/pci.c (grub_pci_iterate) [GRUB_MACHINE_MIPS_YEELOONG]: Skip ghosts. * bus/usb/ohci.c (grub_ohci_portstatus): Handle R/WC correctly. (grub_ohci_fini_hw): New function. (grub_ohci_restore_hw): Likewise. (GRUB_MOD_INIT(ohci)): Register preboot hook. (GRUB_MOD_FINI(ohci)): Shutdown OHCI. * term/usb_keyboard.c: Remove include of grub/machine/console.h. 2010-06-02 Vladimir Serbinenko <phcoder@gmail.com> Dedicated DMA allocations. * bus/pci.c (grub_memalign_dma32): New function (grub_dma_free): Likewise. (grub_dma_get_virt): Likewise. (grub_dma_get_phys): Likewise. * bus/usb/ohci.c (grub_ohci): New members hcca_addr and hcca_chunk. (grub_ohci_pci_iter): Use dma32_alloc. (grub_ohci_transfer): Likewise. * bus/usb/usbtrans.c (grub_usb_control_msg): Likewise. (grub_usb_bulk_readwrite): Likewise. * include/grub/pci.h: Add declarations. 2010-06-02 Vladimir Serbinenko <phcoder@gmail.com> CS5536 support. * bus/cs5536.c: New file. * bus/usb/ohci.c (grub_ohci_pci_iter): Check for CS5536. * conf/i386.rmk (pkglib_MODULES): Add cs5536.mod. (cs5536_mod_SOURCES): New variable. (cs5536_mod_CFLAGS): Likewise. (cs5536_mod_LDFLAGS): Likewise. * conf/mips-yeeloong.rmk (kernel_img_HEADERS): Add cs5536.h and machine/pci.h. (kernel_img_SOURCES): Add bus/cs5536.c. (pkglib_MODULES): Add usb.mod, usbtest.mod, ohci.mod, usbms.mod and usb_keyboard.mod. (usb_mod_SOURCES): New variable. (usb_mod_CFLAGS): New variable. (usb_mod_LDFLAGS): New variable. (usbtest_mod_SOURCES): New variable. (usbtest_mod_CFLAGS): New variable. (usbtest_mod_LDFLAGS): New variable. (ohci_mod_SOURCES): New variable. (ohci_mod_CFLAGS): New variable. (ohci_mod_LDFLAGS): New variable. (usbms_mod_SOURCES): New variable. (usbms_mod_CFLAGS): New variable. (usbms_mod_LDFLAGS): New variable. (usb_keyboard_mod_SOURCES): New variable. (usb_keyboard_mod_CFLAGS): New variable. (usb_keyboard_mod_LDFLAGS): New variable. * include/grub/smbus.h: New file. * include/grub/cs5536.h: New file. Also-By: Vladimir Serbinenko <phcoder@gmail.com>
This commit is contained in:
commit
1515ec8684
20 changed files with 1771 additions and 255 deletions
215
bus/cs5536.c
Normal file
215
bus/cs5536.c
Normal file
|
@ -0,0 +1,215 @@
|
|||
/*
|
||||
* GRUB -- GRand Unified Bootloader
|
||||
* Copyright (C) 2010 Free Software Foundation, Inc.
|
||||
*
|
||||
* GRUB is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GRUB is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <grub/types.h>
|
||||
#include <grub/cs5536.h>
|
||||
#include <grub/pci.h>
|
||||
#include <grub/time.h>
|
||||
#include <grub/ata.h>
|
||||
|
||||
int
|
||||
grub_cs5536_find (grub_pci_device_t *devp)
|
||||
{
|
||||
int found = 0;
|
||||
auto int NESTED_FUNC_ATTR hook (grub_pci_device_t dev,
|
||||
grub_pci_id_t pciid);
|
||||
|
||||
int NESTED_FUNC_ATTR hook (grub_pci_device_t dev,
|
||||
grub_pci_id_t pciid)
|
||||
{
|
||||
if (pciid == GRUB_CS5536_PCIID)
|
||||
{
|
||||
*devp = dev;
|
||||
found = 1;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
grub_pci_iterate (hook);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
grub_uint64_t
|
||||
grub_cs5536_read_msr (grub_pci_device_t dev, grub_uint32_t addr)
|
||||
{
|
||||
grub_uint64_t ret = 0;
|
||||
grub_pci_write (grub_pci_make_address (dev, GRUB_CS5536_MSR_MAILBOX_ADDR),
|
||||
addr);
|
||||
ret = (grub_uint64_t)
|
||||
grub_pci_read (grub_pci_make_address (dev, GRUB_CS5536_MSR_MAILBOX_DATA0));
|
||||
ret |= (((grub_uint64_t)
|
||||
grub_pci_read (grub_pci_make_address (dev,
|
||||
GRUB_CS5536_MSR_MAILBOX_DATA1)))
|
||||
<< 32);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
grub_cs5536_write_msr (grub_pci_device_t dev, grub_uint32_t addr,
|
||||
grub_uint64_t val)
|
||||
{
|
||||
grub_pci_write (grub_pci_make_address (dev, GRUB_CS5536_MSR_MAILBOX_ADDR),
|
||||
addr);
|
||||
grub_pci_write (grub_pci_make_address (dev, GRUB_CS5536_MSR_MAILBOX_DATA0),
|
||||
val & 0xffffffff);
|
||||
grub_pci_write (grub_pci_make_address (dev, GRUB_CS5536_MSR_MAILBOX_DATA1),
|
||||
val >> 32);
|
||||
}
|
||||
|
||||
grub_err_t
|
||||
grub_cs5536_smbus_wait (grub_port_t smbbase)
|
||||
{
|
||||
grub_uint64_t start = grub_get_time_ms ();
|
||||
while (1)
|
||||
{
|
||||
grub_uint8_t status;
|
||||
status = grub_inb (smbbase + GRUB_CS5536_SMB_REG_STATUS);
|
||||
if (status & GRUB_CS5536_SMB_REG_STATUS_SDAST)
|
||||
return GRUB_ERR_NONE;
|
||||
if (status & GRUB_CS5536_SMB_REG_STATUS_BER)
|
||||
return grub_error (GRUB_ERR_IO, "SM bus error");
|
||||
if (status & GRUB_CS5536_SMB_REG_STATUS_NACK)
|
||||
return grub_error (GRUB_ERR_IO, "NACK received");
|
||||
if (grub_get_time_ms () > start + 40)
|
||||
return grub_error (GRUB_ERR_IO, "SM stalled");
|
||||
}
|
||||
|
||||
return GRUB_ERR_NONE;
|
||||
}
|
||||
|
||||
grub_err_t
|
||||
grub_cs5536_read_spd_byte (grub_port_t smbbase, grub_uint8_t dev,
|
||||
grub_uint8_t addr, grub_uint8_t *res)
|
||||
{
|
||||
grub_err_t err;
|
||||
|
||||
/* Send START. */
|
||||
grub_outb (grub_inb (smbbase + GRUB_CS5536_SMB_REG_CTRL1)
|
||||
| GRUB_CS5536_SMB_REG_CTRL1_START,
|
||||
smbbase + GRUB_CS5536_SMB_REG_CTRL1);
|
||||
|
||||
/* Send device address. */
|
||||
err = grub_cs5536_smbus_wait (smbbase);
|
||||
if (err)
|
||||
return err;
|
||||
grub_outb (dev << 1, smbbase + GRUB_CS5536_SMB_REG_DATA);
|
||||
|
||||
/* Send ACK. */
|
||||
err = grub_cs5536_smbus_wait (smbbase);
|
||||
if (err)
|
||||
return err;
|
||||
grub_outb (grub_inb (smbbase + GRUB_CS5536_SMB_REG_CTRL1)
|
||||
| GRUB_CS5536_SMB_REG_CTRL1_ACK,
|
||||
smbbase + GRUB_CS5536_SMB_REG_CTRL1);
|
||||
|
||||
/* Send byte address. */
|
||||
grub_outb (addr, smbbase + GRUB_CS5536_SMB_REG_DATA);
|
||||
|
||||
/* Send START. */
|
||||
err = grub_cs5536_smbus_wait (smbbase);
|
||||
if (err)
|
||||
return err;
|
||||
grub_outb (grub_inb (smbbase + GRUB_CS5536_SMB_REG_CTRL1)
|
||||
| GRUB_CS5536_SMB_REG_CTRL1_START,
|
||||
smbbase + GRUB_CS5536_SMB_REG_CTRL1);
|
||||
|
||||
/* Send device address. */
|
||||
err = grub_cs5536_smbus_wait (smbbase);
|
||||
if (err)
|
||||
return err;
|
||||
grub_outb ((dev << 1) | 1, smbbase + GRUB_CS5536_SMB_REG_DATA);
|
||||
|
||||
/* Send STOP. */
|
||||
err = grub_cs5536_smbus_wait (smbbase);
|
||||
if (err)
|
||||
return err;
|
||||
grub_outb (grub_inb (smbbase + GRUB_CS5536_SMB_REG_CTRL1)
|
||||
| GRUB_CS5536_SMB_REG_CTRL1_STOP,
|
||||
smbbase + GRUB_CS5536_SMB_REG_CTRL1);
|
||||
|
||||
err = grub_cs5536_smbus_wait (smbbase);
|
||||
if (err)
|
||||
return err;
|
||||
*res = grub_inb (smbbase + GRUB_CS5536_SMB_REG_DATA);
|
||||
|
||||
return GRUB_ERR_NONE;
|
||||
}
|
||||
|
||||
grub_err_t
|
||||
grub_cs5536_init_smbus (grub_pci_device_t dev, grub_uint16_t divisor,
|
||||
grub_port_t *smbbase)
|
||||
{
|
||||
grub_uint64_t smbbar;
|
||||
|
||||
smbbar = grub_cs5536_read_msr (dev, GRUB_CS5536_MSR_SMB_BAR);
|
||||
|
||||
/* FIXME */
|
||||
if (!(smbbar & GRUB_CS5536_LBAR_ENABLE))
|
||||
return grub_error(GRUB_ERR_IO, "SMB controller not enabled\n");
|
||||
*smbbase = (smbbar & GRUB_CS5536_LBAR_ADDR_MASK) + GRUB_MACHINE_PCI_IO_BASE;
|
||||
|
||||
if (divisor < 8)
|
||||
return grub_error (GRUB_ERR_BAD_ARGUMENT, "invalid divisor");
|
||||
|
||||
/* Disable SMB. */
|
||||
grub_outb (0, *smbbase + GRUB_CS5536_SMB_REG_CTRL2);
|
||||
|
||||
/* Disable interrupts. */
|
||||
grub_outb (0, *smbbase + GRUB_CS5536_SMB_REG_CTRL1);
|
||||
|
||||
/* Set as master. */
|
||||
grub_outb (GRUB_CS5536_SMB_REG_ADDR_MASTER,
|
||||
*smbbase + GRUB_CS5536_SMB_REG_ADDR);
|
||||
|
||||
/* Launch. */
|
||||
grub_outb (((divisor >> 7) & 0xff), *smbbase + GRUB_CS5536_SMB_REG_CTRL3);
|
||||
grub_outb (((divisor << 1) & 0xfe) | GRUB_CS5536_SMB_REG_CTRL2_ENABLE,
|
||||
*smbbase + GRUB_CS5536_SMB_REG_CTRL2);
|
||||
|
||||
return GRUB_ERR_NONE;
|
||||
}
|
||||
|
||||
grub_err_t
|
||||
grub_cs5536_read_spd (grub_port_t smbbase, grub_uint8_t dev,
|
||||
struct grub_smbus_spd *res)
|
||||
{
|
||||
grub_err_t err;
|
||||
grub_size_t size;
|
||||
grub_uint8_t b;
|
||||
grub_size_t ptr;
|
||||
|
||||
err = grub_cs5536_read_spd_byte (smbbase, dev, 0, &b);
|
||||
if (err)
|
||||
return err;
|
||||
if (b == 0)
|
||||
return grub_error (GRUB_ERR_IO, "no SPD found");
|
||||
size = b;
|
||||
|
||||
((grub_uint8_t *) res)[0] = b;
|
||||
for (ptr = 1; ptr < size; ptr++)
|
||||
{
|
||||
err = grub_cs5536_read_spd_byte (smbbase, dev, ptr,
|
||||
&((grub_uint8_t *) res)[ptr]);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
return GRUB_ERR_NONE;
|
||||
}
|
||||
|
54
bus/pci.c
54
bus/pci.c
|
@ -19,6 +19,50 @@
|
|||
|
||||
#include <grub/dl.h>
|
||||
#include <grub/pci.h>
|
||||
#include <grub/mm.h>
|
||||
|
||||
/* FIXME: correctly support 64-bit architectures. */
|
||||
/* #if GRUB_TARGET_SIZEOF_VOID_P == 4 */
|
||||
struct grub_pci_dma_chunk *
|
||||
grub_memalign_dma32 (grub_size_t align, grub_size_t size)
|
||||
{
|
||||
return grub_memalign (align, size);
|
||||
}
|
||||
|
||||
void
|
||||
grub_dma_free (struct grub_pci_dma_chunk *ch)
|
||||
{
|
||||
grub_free (ch);
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
#ifdef GRUB_MACHINE_MIPS_YEELOONG
|
||||
volatile void *
|
||||
grub_dma_get_virt (struct grub_pci_dma_chunk *ch)
|
||||
{
|
||||
return (void *) ((((grub_uint32_t) ch) & 0x1fffffff) | 0xa0000000);
|
||||
}
|
||||
|
||||
grub_uint32_t
|
||||
grub_dma_get_phys (struct grub_pci_dma_chunk *ch)
|
||||
{
|
||||
return (((grub_uint32_t) ch) & 0x1fffffff) | 0x80000000;
|
||||
}
|
||||
#else
|
||||
|
||||
volatile void *
|
||||
grub_dma_get_virt (struct grub_pci_dma_chunk *ch)
|
||||
{
|
||||
return (void *) ch;
|
||||
}
|
||||
|
||||
grub_uint32_t
|
||||
grub_dma_get_phys (struct grub_pci_dma_chunk *ch)
|
||||
{
|
||||
return (grub_uint32_t) (grub_addr_t) ch;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
grub_pci_address_t
|
||||
grub_pci_make_address (grub_pci_device_t dev, int reg)
|
||||
|
@ -48,6 +92,16 @@ grub_pci_iterate (grub_pci_iteratefunc_t hook)
|
|||
if (id >> 16 == 0xFFFF)
|
||||
continue;
|
||||
|
||||
#ifdef GRUB_MACHINE_MIPS_YEELOONG
|
||||
/* Skip ghosts. */
|
||||
if (id == GRUB_YEELOONG_OHCI_PCIID
|
||||
&& dev.function == GRUB_YEELOONG_OHCI_GHOST_FUNCTION)
|
||||
continue;
|
||||
if (id == GRUB_YEELOONG_EHCI_PCIID
|
||||
&& dev.function == GRUB_YEELOONG_EHCI_GHOST_FUNCTION)
|
||||
continue;
|
||||
#endif
|
||||
|
||||
if (hook (dev, id))
|
||||
return;
|
||||
|
||||
|
|
627
bus/usb/ohci.c
627
bus/usb/ohci.c
|
@ -24,8 +24,10 @@
|
|||
#include <grub/misc.h>
|
||||
#include <grub/pci.h>
|
||||
#include <grub/cpu/pci.h>
|
||||
#include <grub/i386/io.h>
|
||||
#include <grub/cpu/io.h>
|
||||
#include <grub/time.h>
|
||||
#include <grub/cs5536.h>
|
||||
#include <grub/loader.h>
|
||||
|
||||
struct grub_ohci_hcca
|
||||
{
|
||||
|
@ -63,13 +65,15 @@ struct grub_ohci_td
|
|||
grub_uint32_t buffer_end;
|
||||
} __attribute__((packed));
|
||||
|
||||
typedef struct grub_ohci_td *grub_ohci_td_t;
|
||||
typedef struct grub_ohci_ed *grub_ohci_ed_t;
|
||||
typedef volatile struct grub_ohci_td *grub_ohci_td_t;
|
||||
typedef volatile struct grub_ohci_ed *grub_ohci_ed_t;
|
||||
|
||||
struct grub_ohci
|
||||
{
|
||||
volatile grub_uint32_t *iobase;
|
||||
volatile struct grub_ohci_hcca *hcca;
|
||||
grub_uint32_t hcca_addr;
|
||||
struct grub_pci_dma_chunk *hcca_chunk;
|
||||
struct grub_ohci *next;
|
||||
};
|
||||
|
||||
|
@ -91,10 +95,32 @@ typedef enum
|
|||
GRUB_OHCI_REG_BULKCURR,
|
||||
GRUB_OHCI_REG_DONEHEAD,
|
||||
GRUB_OHCI_REG_FRAME_INTERVAL,
|
||||
GRUB_OHCI_REG_PERIODIC_START = 16,
|
||||
GRUB_OHCI_REG_RHUBA = 18,
|
||||
GRUB_OHCI_REG_RHUBPORT = 21
|
||||
GRUB_OHCI_REG_RHUBPORT = 21,
|
||||
GRUB_OHCI_REG_LEGACY_CONTROL = 0x100,
|
||||
GRUB_OHCI_REG_LEGACY_INPUT = 0x104,
|
||||
GRUB_OHCI_REG_LEGACY_OUTPUT = 0x108,
|
||||
GRUB_OHCI_REG_LEGACY_STATUS = 0x10c
|
||||
} grub_ohci_reg_t;
|
||||
|
||||
#define GRUB_OHCI_RHUB_PORT_POWER_MASK 0x300
|
||||
#define GRUB_OHCI_RHUB_PORT_ALL_POWERED 0x200
|
||||
|
||||
#define GRUB_OHCI_REG_FRAME_INTERVAL_FSMPS_MASK 0x8fff0000
|
||||
#define GRUB_OHCI_REG_FRAME_INTERVAL_FSMPS_SHIFT 16
|
||||
#define GRUB_OHCI_REG_FRAME_INTERVAL_FI_SHIFT 0
|
||||
|
||||
/* XXX: Is this choice of timings sane? */
|
||||
#define GRUB_OHCI_FSMPS 0x2778
|
||||
#define GRUB_OHCI_PERIODIC_START 0x257f
|
||||
#define GRUB_OHCI_FRAME_INTERVAL 0x2edf
|
||||
|
||||
#define GRUB_OHCI_SET_PORT_ENABLE (1 << 1)
|
||||
#define GRUB_OHCI_CLEAR_PORT_ENABLE (1 << 0)
|
||||
#define GRUB_OHCI_SET_PORT_RESET (1 << 4)
|
||||
#define GRUB_OHCI_SET_PORT_RESET_STATUS_CHANGE (1 << 20)
|
||||
|
||||
static grub_uint32_t
|
||||
grub_ohci_readreg32 (struct grub_ohci *o, grub_ohci_reg_t reg)
|
||||
{
|
||||
|
@ -114,51 +140,81 @@ grub_ohci_writereg32 (struct grub_ohci *o,
|
|||
controller. If this is the case, initialize it. */
|
||||
static int NESTED_FUNC_ATTR
|
||||
grub_ohci_pci_iter (grub_pci_device_t dev,
|
||||
grub_pci_id_t pciid __attribute__((unused)))
|
||||
grub_pci_id_t pciid)
|
||||
{
|
||||
grub_uint32_t class_code;
|
||||
grub_uint32_t class;
|
||||
grub_uint32_t subclass;
|
||||
grub_uint32_t interf;
|
||||
grub_uint32_t base;
|
||||
grub_pci_address_t addr;
|
||||
struct grub_ohci *o;
|
||||
grub_uint32_t revision;
|
||||
grub_uint32_t frame_interval;
|
||||
|
||||
addr = grub_pci_make_address (dev, GRUB_PCI_REG_CLASS);
|
||||
class_code = grub_pci_read (addr) >> 8;
|
||||
|
||||
interf = class_code & 0xFF;
|
||||
subclass = (class_code >> 8) & 0xFF;
|
||||
class = class_code >> 16;
|
||||
|
||||
/* If this is not an OHCI controller, just return. */
|
||||
if (class != 0x0c || subclass != 0x03 || interf != 0x10)
|
||||
return 0;
|
||||
int cs5536;
|
||||
|
||||
/* Determine IO base address. */
|
||||
addr = grub_pci_make_address (dev, GRUB_PCI_REG_ADDRESS_REG0);
|
||||
base = grub_pci_read (addr);
|
||||
grub_dprintf ("ohci", "pciid = %x\n", pciid);
|
||||
|
||||
if (pciid == GRUB_CS5536_PCIID)
|
||||
{
|
||||
grub_uint64_t basereg;
|
||||
|
||||
cs5536 = 1;
|
||||
basereg = grub_cs5536_read_msr (dev, GRUB_CS5536_MSR_USB_OHCI_BASE);
|
||||
if (!(basereg & GRUB_CS5536_MSR_USB_BASE_MEMORY_ENABLE))
|
||||
{
|
||||
/* Shouldn't happen. */
|
||||
grub_dprintf ("ohci", "No OHCI address is assigned\n");
|
||||
return 0;
|
||||
}
|
||||
base = (basereg & GRUB_CS5536_MSR_USB_BASE_ADDR_MASK);
|
||||
basereg |= GRUB_CS5536_MSR_USB_BASE_BUS_MASTER;
|
||||
basereg &= ~GRUB_CS5536_MSR_USB_BASE_PME_ENABLED;
|
||||
basereg &= ~GRUB_CS5536_MSR_USB_BASE_PME_STATUS;
|
||||
grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_USB_OHCI_BASE, basereg);
|
||||
}
|
||||
else
|
||||
{
|
||||
grub_uint32_t class_code;
|
||||
grub_uint32_t class;
|
||||
grub_uint32_t subclass;
|
||||
|
||||
addr = grub_pci_make_address (dev, GRUB_PCI_REG_CLASS);
|
||||
class_code = grub_pci_read (addr) >> 8;
|
||||
|
||||
interf = class_code & 0xFF;
|
||||
subclass = (class_code >> 8) & 0xFF;
|
||||
class = class_code >> 16;
|
||||
|
||||
/* If this is not an OHCI controller, just return. */
|
||||
if (class != 0x0c || subclass != 0x03 || interf != 0x10)
|
||||
return 0;
|
||||
|
||||
addr = grub_pci_make_address (dev, GRUB_PCI_REG_ADDRESS_REG0);
|
||||
base = grub_pci_read (addr);
|
||||
|
||||
#if 0
|
||||
/* Stop if there is no IO space base address defined. */
|
||||
if (! (base & 1))
|
||||
return 0;
|
||||
/* Stop if there is no IO space base address defined. */
|
||||
if (! (base & 1))
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
grub_dprintf ("ohci", "class=0x%02x 0x%02x interface 0x%02x\n",
|
||||
class, subclass, interf);
|
||||
}
|
||||
|
||||
/* Allocate memory for the controller and register it. */
|
||||
o = grub_malloc (sizeof (*o));
|
||||
if (! o)
|
||||
return 1;
|
||||
|
||||
o->iobase = (grub_uint32_t *) base;
|
||||
o->iobase = grub_pci_device_map_range (dev, base, 0x800);
|
||||
|
||||
grub_dprintf ("ohci", "base=%p\n", o->iobase);
|
||||
|
||||
/* Reserve memory for the HCCA. */
|
||||
o->hcca = (struct grub_ohci_hcca *) grub_memalign (256, 256);
|
||||
|
||||
grub_dprintf ("ohci", "class=0x%02x 0x%02x interface 0x%02x base=%p\n",
|
||||
class, subclass, interf, o->iobase);
|
||||
o->hcca_chunk = grub_memalign_dma32 (256, 256);
|
||||
if (! o->hcca_chunk)
|
||||
return 1;
|
||||
o->hcca = grub_dma_get_virt (o->hcca_chunk);
|
||||
o->hcca_addr = grub_dma_get_phys (o->hcca_chunk);
|
||||
|
||||
/* Check if the OHCI revision is actually 1.0 as supported. */
|
||||
revision = grub_ohci_readreg32 (o, GRUB_OHCI_REG_REVISION);
|
||||
|
@ -166,27 +222,116 @@ grub_ohci_pci_iter (grub_pci_device_t dev,
|
|||
if ((revision & 0xFF) != 0x10)
|
||||
goto fail;
|
||||
|
||||
/* Backup the frame interval register. */
|
||||
frame_interval = grub_ohci_readreg32 (o, GRUB_OHCI_REG_FRAME_INTERVAL);
|
||||
|
||||
{
|
||||
grub_uint32_t control;
|
||||
/* Check SMM/BIOS ownership of OHCI (SMM = USB Legacy Support driver for BIOS) */
|
||||
control = grub_ohci_readreg32 (o, GRUB_OHCI_REG_CONTROL);
|
||||
if ((control & 0x100) != 0)
|
||||
{
|
||||
unsigned i;
|
||||
grub_dprintf("ohci", "OHCI is owned by SMM\n");
|
||||
/* Do change of ownership */
|
||||
/* Ownership change request */
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, (1<<3)); /* XXX: Magic. */
|
||||
/* Waiting for SMM deactivation */
|
||||
for (i=0; i < 10; i++)
|
||||
{
|
||||
if ((grub_ohci_readreg32 (o, GRUB_OHCI_REG_CONTROL) & 0x100) == 0)
|
||||
{
|
||||
grub_dprintf("ohci", "Ownership changed normally.\n");
|
||||
break;
|
||||
}
|
||||
grub_millisleep (100);
|
||||
}
|
||||
if (i >= 10)
|
||||
{
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL,
|
||||
grub_ohci_readreg32 (o, GRUB_OHCI_REG_CONTROL) & ~0x100);
|
||||
grub_dprintf("ohci", "Ownership changing timeout, change forced !\n");
|
||||
}
|
||||
}
|
||||
else if (((control & 0x100) == 0) &&
|
||||
((control & 0xc0) != 0)) /* Not owned by SMM nor reset */
|
||||
{
|
||||
grub_dprintf("ohci", "OHCI is owned by BIOS\n");
|
||||
/* Do change of ownership - not implemented yet... */
|
||||
/* In fact we probably need to do nothing ...? */
|
||||
}
|
||||
else
|
||||
{
|
||||
grub_dprintf("ohci", "OHCI is not owned by SMM nor BIOS\n");
|
||||
/* We can setup OHCI. */
|
||||
}
|
||||
}
|
||||
|
||||
/* Suspend the OHCI by issuing a reset. */
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, 1); /* XXX: Magic. */
|
||||
grub_millisleep (1);
|
||||
grub_dprintf ("ohci", "OHCI reset\n");
|
||||
|
||||
/* Restore the frame interval register. */
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_FRAME_INTERVAL, frame_interval);
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_FRAME_INTERVAL,
|
||||
(GRUB_OHCI_FSMPS
|
||||
<< GRUB_OHCI_REG_FRAME_INTERVAL_FSMPS_SHIFT)
|
||||
| (GRUB_OHCI_FRAME_INTERVAL
|
||||
<< GRUB_OHCI_REG_FRAME_INTERVAL_FI_SHIFT));
|
||||
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_PERIODIC_START,
|
||||
GRUB_OHCI_PERIODIC_START);
|
||||
|
||||
/* Setup the HCCA. */
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_HCCA, (grub_uint32_t) o->hcca);
|
||||
o->hcca->donehead = 0;
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_HCCA, o->hcca_addr);
|
||||
grub_dprintf ("ohci", "OHCI HCCA\n");
|
||||
|
||||
/* Misc. pre-sets. */
|
||||
o->hcca->donehead = 0;
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, 0x7f); /* Clears everything */
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLHEAD, 0);
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLCURR, 0);
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKHEAD, 0);
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKCURR, 0);
|
||||
|
||||
/* Check OHCI Legacy Support */
|
||||
if ((revision & 0x100) != 0)
|
||||
{
|
||||
grub_dprintf ("ohci", "Legacy Support registers detected\n");
|
||||
grub_dprintf ("ohci", "Current state of legacy control reg.: 0x%04x\n",
|
||||
grub_ohci_readreg32 (o, GRUB_OHCI_REG_LEGACY_CONTROL));
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_LEGACY_CONTROL,
|
||||
(grub_ohci_readreg32 (o, GRUB_OHCI_REG_LEGACY_CONTROL)) & ~1);
|
||||
grub_dprintf ("ohci", "OHCI Legacy Support disabled.\n");
|
||||
}
|
||||
|
||||
/* Enable the OHCI. */
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL,
|
||||
(2 << 6));
|
||||
grub_dprintf ("ohci", "OHCI enable: 0x%02x\n",
|
||||
(grub_ohci_readreg32 (o, GRUB_OHCI_REG_CONTROL) >> 6) & 3);
|
||||
|
||||
/* Power on all ports */
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBA,
|
||||
(grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBA)
|
||||
& ~GRUB_OHCI_RHUB_PORT_POWER_MASK)
|
||||
| GRUB_OHCI_RHUB_PORT_ALL_POWERED);
|
||||
/* Wait for stable power (100ms) and stable attachment (100ms) */
|
||||
/* I.e. minimum wait time should be probably 200ms. */
|
||||
/* We assume that device is attached when ohci is loaded. */
|
||||
/* Some devices take long time to power-on or indicate attach. */
|
||||
/* Here is some experimental value which should probably mostly work. */
|
||||
/* Cameras with manual USB mode selection and maybe some other similar
|
||||
* devices will not work in some cases - they are repowered during
|
||||
* ownership change and then they are starting slowly and mostly they
|
||||
* are wanting select proper mode again...
|
||||
* The same situation can be on computers where BIOS not set-up OHCI
|
||||
* to be at least powered USB bus (maybe it is Yeelong case...?)
|
||||
* Possible workaround could be for example some prompt
|
||||
* for user with confirmation of proper USB device connection.
|
||||
* Another workaround - "rmmod usbms", "rmmod ohci", proper start
|
||||
* and configuration of USB device and then "insmod ohci"
|
||||
* and "insmod usbms". */
|
||||
grub_millisleep (500);
|
||||
|
||||
/* Link to ohci now that initialisation is successful. */
|
||||
o->next = ohci;
|
||||
ohci = o;
|
||||
|
@ -195,10 +340,10 @@ grub_ohci_pci_iter (grub_pci_device_t dev,
|
|||
|
||||
fail:
|
||||
if (o)
|
||||
grub_free ((void *) o->hcca);
|
||||
grub_dma_free (o->hcca_chunk);
|
||||
grub_free (o);
|
||||
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
@ -229,7 +374,7 @@ grub_ohci_iterate (int (*hook) (grub_usb_controller_t dev))
|
|||
static void
|
||||
grub_ohci_transaction (grub_ohci_td_t td,
|
||||
grub_transfer_type_t type, unsigned int toggle,
|
||||
grub_size_t size, char *data)
|
||||
grub_size_t size, grub_uint32_t data)
|
||||
{
|
||||
grub_uint32_t token;
|
||||
grub_uint32_t buffer;
|
||||
|
@ -254,20 +399,38 @@ grub_ohci_transaction (grub_ohci_td_t td,
|
|||
break;
|
||||
}
|
||||
|
||||
#if 0 /* Always generate interrupt */
|
||||
/* Generate no interrupts. */
|
||||
token |= 7 << 21;
|
||||
#endif
|
||||
|
||||
/* Set the token. */
|
||||
token |= toggle << 24;
|
||||
token |= 1 << 25;
|
||||
|
||||
buffer = (grub_uint32_t) data;
|
||||
/* Set "Not accessed" error code */
|
||||
token |= 15 << 28;
|
||||
|
||||
buffer = data;
|
||||
buffer_end = buffer + size - 1;
|
||||
|
||||
/* Set correct buffer values in TD if zero transfer occurs */
|
||||
if (size)
|
||||
{
|
||||
buffer = (grub_uint32_t) data;
|
||||
buffer_end = buffer + size - 1;
|
||||
td->buffer = grub_cpu_to_le32 (buffer);
|
||||
td->buffer_end = grub_cpu_to_le32 (buffer_end);
|
||||
}
|
||||
else
|
||||
{
|
||||
td->buffer = 0;
|
||||
td->buffer_end = 0;
|
||||
}
|
||||
|
||||
/* Set the rest of TD */
|
||||
td->token = grub_cpu_to_le32 (token);
|
||||
td->buffer = grub_cpu_to_le32 (buffer);
|
||||
td->next_td = 0;
|
||||
td->buffer_end = grub_cpu_to_le32 (buffer_end);
|
||||
}
|
||||
|
||||
static grub_usb_err_t
|
||||
|
@ -276,7 +439,10 @@ grub_ohci_transfer (grub_usb_controller_t dev,
|
|||
{
|
||||
struct grub_ohci *o = (struct grub_ohci *) dev->data;
|
||||
grub_ohci_ed_t ed;
|
||||
grub_uint32_t ed_addr;
|
||||
struct grub_pci_dma_chunk *ed_chunk, *td_list_chunk;
|
||||
grub_ohci_td_t td_list;
|
||||
grub_uint32_t td_list_addr;
|
||||
grub_uint32_t target;
|
||||
grub_uint32_t td_tail;
|
||||
grub_uint32_t td_head;
|
||||
|
@ -284,20 +450,30 @@ grub_ohci_transfer (grub_usb_controller_t dev,
|
|||
grub_uint32_t control;
|
||||
grub_usb_err_t err;
|
||||
int i;
|
||||
grub_uint64_t maxtime;
|
||||
int err_timeout = 0;
|
||||
int err_unrec = 0;
|
||||
grub_uint32_t intstatus;
|
||||
grub_uint32_t tderr_addr = 0;
|
||||
|
||||
/* Allocate an Endpoint Descriptor. */
|
||||
ed = grub_memalign (16, sizeof (*ed));
|
||||
if (! ed)
|
||||
ed_chunk = grub_memalign_dma32 (256, sizeof (*ed));
|
||||
if (! ed_chunk)
|
||||
return GRUB_USB_ERR_INTERNAL;
|
||||
ed = grub_dma_get_virt (ed_chunk);
|
||||
ed_addr = grub_dma_get_phys (ed_chunk);
|
||||
|
||||
td_list = grub_memalign (16, sizeof (*td_list) * (transfer->transcnt + 1));
|
||||
if (! td_list)
|
||||
td_list_chunk = grub_memalign_dma32 (256, sizeof (*td_list)
|
||||
* (transfer->transcnt + 1));
|
||||
if (! td_list_chunk)
|
||||
{
|
||||
grub_free ((void *) ed);
|
||||
grub_dma_free (ed_chunk);
|
||||
return GRUB_USB_ERR_INTERNAL;
|
||||
}
|
||||
td_list = grub_dma_get_virt (td_list_chunk);
|
||||
td_list_addr = grub_dma_get_phys (td_list_chunk);
|
||||
|
||||
grub_dprintf ("ohci", "alloc=%p\n", td_list);
|
||||
grub_dprintf ("ohci", "alloc=%p/0x%x\n", td_list, td_list_addr);
|
||||
|
||||
/* Setup all Transfer Descriptors. */
|
||||
for (i = 0; i < transfer->transcnt; i++)
|
||||
|
@ -307,16 +483,31 @@ grub_ohci_transfer (grub_usb_controller_t dev,
|
|||
grub_ohci_transaction (&td_list[i], tr->pid, tr->toggle,
|
||||
tr->size, tr->data);
|
||||
|
||||
td_list[i].next_td = grub_cpu_to_le32 (&td_list[i + 1]);
|
||||
td_list[i].next_td = grub_cpu_to_le32 (td_list_addr
|
||||
+ (i + 1) * sizeof (td_list[0]));
|
||||
}
|
||||
|
||||
#if 0 /* Better will be enable interrupt on all TDs. */
|
||||
/* The last-1 TD token we should change to enable interrupt when TD finishes.
|
||||
* As OHCI interrupts are disabled, it does only setting of WDH bit in
|
||||
* HcInterruptStatus register - and that is what we want to safely detect
|
||||
* normal end of all transactions. */
|
||||
td_list[transfer->transcnt - 1].token &= ~(7 << 21);
|
||||
#endif
|
||||
|
||||
td_list[transfer->transcnt].token = 0;
|
||||
td_list[transfer->transcnt].buffer = 0;
|
||||
td_list[transfer->transcnt].buffer_end = 0;
|
||||
td_list[transfer->transcnt].next_td =
|
||||
(grub_uint32_t) &td_list[transfer->transcnt];
|
||||
|
||||
/* Setup the Endpoint Descriptor. */
|
||||
|
||||
/* Set the device address. */
|
||||
target = transfer->devaddr;
|
||||
|
||||
/* Set the endpoint. */
|
||||
target |= transfer->endpoint << 7;
|
||||
/* Set the endpoint. It should be masked, we need 4 bits only. */
|
||||
target |= (transfer->endpoint & 15) << 7;
|
||||
|
||||
/* Set the device speed. */
|
||||
target |= (transfer->dev->speed == GRUB_USB_SPEED_LOW) << 13;
|
||||
|
@ -324,9 +515,9 @@ grub_ohci_transfer (grub_usb_controller_t dev,
|
|||
/* Set the maximum packet size. */
|
||||
target |= transfer->max << 16;
|
||||
|
||||
td_head = (grub_uint32_t) td_list;
|
||||
td_head = td_list_addr;
|
||||
|
||||
td_tail = (grub_uint32_t) &td_list[transfer->transcnt];
|
||||
td_tail = td_list_addr + transfer->transcnt * sizeof (*td_list);
|
||||
|
||||
ed->target = grub_cpu_to_le32 (target);
|
||||
ed->td_head = grub_cpu_to_le32 (td_head);
|
||||
|
@ -335,6 +526,30 @@ grub_ohci_transfer (grub_usb_controller_t dev,
|
|||
|
||||
grub_dprintf ("ohci", "program OHCI\n");
|
||||
|
||||
/* Disable the Control and Bulk lists. */
|
||||
control = grub_ohci_readreg32 (o, GRUB_OHCI_REG_CONTROL);
|
||||
control &= ~(3 << 4);
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL, control);
|
||||
|
||||
/* Clear BulkListFilled and ControlListFilled. */
|
||||
status = grub_ohci_readreg32 (o, GRUB_OHCI_REG_CMDSTATUS);
|
||||
status &= ~(3 << 1);
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, status);
|
||||
|
||||
/* Now we should wait for start of next frame. Because we are not using
|
||||
* interrupt, we reset SF bit and wait when it goes to 1. */
|
||||
/* SF bit reset. (SF bit indicates Start Of Frame (SOF) packet) */
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, (1<<2));
|
||||
/* Wait for new SOF */
|
||||
while ((grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS) & 0x4) == 0);
|
||||
/* Now it should be safe to change CONTROL and BULK lists. */
|
||||
|
||||
/* This we do for safety's sake - it should be done in previous call
|
||||
* of grub_ohci_transfer and nobody should change it in meantime...
|
||||
* It should be done before start of control or bulk OHCI list. */
|
||||
o->hcca->donehead = 0;
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, (1 << 1)); /* Clears WDH */
|
||||
|
||||
/* Program the OHCI to actually transfer. */
|
||||
switch (transfer->type)
|
||||
{
|
||||
|
@ -342,24 +557,22 @@ grub_ohci_transfer (grub_usb_controller_t dev,
|
|||
{
|
||||
grub_dprintf ("ohci", "add to bulk list\n");
|
||||
|
||||
status = grub_ohci_readreg32 (o, GRUB_OHCI_REG_CMDSTATUS);
|
||||
control = grub_ohci_readreg32 (o, GRUB_OHCI_REG_CONTROL);
|
||||
/* Set BulkList Head and Current */
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKHEAD, ed_addr);
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKCURR, 0);
|
||||
|
||||
/* Disable the Control and Bulk lists. */
|
||||
control &= ~(3 << 4);
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL, control);
|
||||
|
||||
/* Clear BulkListFilled. */
|
||||
status &= ~(1 << 2);
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, status);
|
||||
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKHEAD, (grub_uint32_t) ed);
|
||||
#define GRUB_OHCI_REG_CONTROL_BULK_ENABLE (1 << 5)
|
||||
#define GRUB_OHCI_REG_CONTROL_CONTROL_ENABLE (1 << 4)
|
||||
|
||||
/* Enable the Bulk list. */
|
||||
control |= 1 << 5;
|
||||
control = grub_ohci_readreg32 (o, GRUB_OHCI_REG_CONTROL);
|
||||
control |= GRUB_OHCI_REG_CONTROL_BULK_ENABLE;
|
||||
control &= ~GRUB_OHCI_REG_CONTROL_CONTROL_ENABLE;
|
||||
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL, control);
|
||||
|
||||
/* Set BulkListFilled. */
|
||||
status = grub_ohci_readreg32 (o, GRUB_OHCI_REG_CMDSTATUS);
|
||||
status |= 1 << 2;
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, status);
|
||||
|
||||
|
@ -369,24 +582,14 @@ grub_ohci_transfer (grub_usb_controller_t dev,
|
|||
case GRUB_USB_TRANSACTION_TYPE_CONTROL:
|
||||
{
|
||||
grub_dprintf ("ohci", "add to control list\n");
|
||||
status = grub_ohci_readreg32 (o, GRUB_OHCI_REG_CMDSTATUS);
|
||||
control = grub_ohci_readreg32 (o, GRUB_OHCI_REG_CONTROL);
|
||||
|
||||
/* Disable the Control and Bulk lists. */
|
||||
control &= ~(3 << 4);
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL, control);
|
||||
|
||||
/* Clear ControlListFilled. */
|
||||
status &= ~(1 << 1);
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, status);
|
||||
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLHEAD,
|
||||
(grub_uint32_t) ed);
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLHEAD+1,
|
||||
(grub_uint32_t) ed);
|
||||
/* Set ControlList Head and Current */
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLHEAD, ed_addr);
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLCURR, 0);
|
||||
|
||||
/* Enable the Control list. */
|
||||
control |= 1 << 4;
|
||||
control |= GRUB_OHCI_REG_CONTROL_CONTROL_ENABLE;
|
||||
control &= ~GRUB_OHCI_REG_CONTROL_BULK_ENABLE;
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL, control);
|
||||
|
||||
/* Set ControlListFilled. */
|
||||
|
@ -397,37 +600,138 @@ grub_ohci_transfer (grub_usb_controller_t dev,
|
|||
}
|
||||
|
||||
grub_dprintf ("ohci", "wait for completion\n");
|
||||
grub_dprintf ("ohci", "control=0x%02x status=0x%02x\n",
|
||||
grub_dprintf ("ohci", "begin: control=0x%02x status=0x%02x\n",
|
||||
grub_ohci_readreg32 (o, GRUB_OHCI_REG_CONTROL),
|
||||
grub_ohci_readreg32 (o, GRUB_OHCI_REG_CMDSTATUS));
|
||||
grub_dprintf ("ohci","intstatus=0x%02x\n",
|
||||
grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS));
|
||||
|
||||
/* Safety measure to avoid a hang. */
|
||||
maxtime = grub_get_time_ms () + 1000;
|
||||
|
||||
/* Wait until the transfer is completed or STALLs. */
|
||||
while ((ed->td_head & ~0xf) != (ed->td_tail & ~0xf))
|
||||
do
|
||||
{
|
||||
grub_cpu_idle ();
|
||||
/* Check transfer status */
|
||||
intstatus = grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS);
|
||||
if ((intstatus & 0x2) != 0)
|
||||
{
|
||||
grub_dprintf ("ohci", "Current HccaDoneHead=0x%08x\n",
|
||||
o->hcca->donehead);
|
||||
/* Remember last successful TD */
|
||||
tderr_addr = grub_le_to_cpu32 (o->hcca->donehead) & ~0xf;
|
||||
/* Reset DoneHead */
|
||||
o->hcca->donehead = 0;
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, (1 << 1));
|
||||
/* if TD is last, finish */
|
||||
if (tderr_addr == td_list_addr
|
||||
+ sizeof (td_list[0]) * (transfer->transcnt - 1))
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
grub_dprintf ("ohci", "head=0x%02x tail=0x%02x\n", ed->td_head, ed->td_tail);
|
||||
if ((intstatus & 0x10) != 0)
|
||||
{ /* Unrecoverable error - only reset can help...! */
|
||||
err_unrec = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Detected a STALL. */
|
||||
if (ed->td_head & 1)
|
||||
/* Detected a HALT. */
|
||||
if (grub_le_to_cpu32 (ed->td_head) & 1)
|
||||
break;
|
||||
|
||||
/* Timeout ? */
|
||||
if (grub_get_time_ms () > maxtime)
|
||||
{
|
||||
/* Disable the Control and Bulk lists. */
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL,
|
||||
grub_ohci_readreg32 (o, GRUB_OHCI_REG_CONTROL) & ~(3 << 4));
|
||||
err_timeout = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if ((ed->td_head & ~0xf) == (ed->td_tail & ~0xf))
|
||||
break;
|
||||
|
||||
grub_cpu_idle ();
|
||||
}
|
||||
while (1);
|
||||
|
||||
grub_dprintf ("ohci", "complete\n");
|
||||
grub_dprintf ("ohci", "end: control=0x%02x status=0x%02x\n",
|
||||
grub_ohci_readreg32 (o, GRUB_OHCI_REG_CONTROL),
|
||||
grub_ohci_readreg32 (o, GRUB_OHCI_REG_CMDSTATUS));
|
||||
grub_dprintf ("ohci", "intstatus=0x%02x\n",
|
||||
grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS));
|
||||
|
||||
/* if (ed->td_head & 1) */
|
||||
/* err = GRUB_USB_ERR_STALL; */
|
||||
/* else if (ed->td */
|
||||
if (!tderr_addr)
|
||||
{
|
||||
/* It means that something wrong happened,
|
||||
* it could be:
|
||||
* - timeout and no TD processed
|
||||
* - some or unrecoverable error and no TD processed
|
||||
* - something unexpected... :-( */
|
||||
/* Try look into DONEHEAD reg., but there should be also zero */
|
||||
grub_dprintf("ohci", "HCCA DoneHead is zero, something is bad!\n");
|
||||
tderr_addr = grub_ohci_readreg32 (o, GRUB_OHCI_REG_DONEHEAD) & ~0xf;
|
||||
}
|
||||
|
||||
/* Remember last processed transaction (TD) - it is necessary for
|
||||
* proper setting of toggle bit in next transaction. */
|
||||
transfer->last_trans = ((tderr_addr - td_list_addr) / sizeof (*td_list));
|
||||
grub_dprintf("ohci", "tderr_addr=0x%x, td_list_addr=0x%x,\n",
|
||||
tderr_addr, td_list_addr);
|
||||
|
||||
if ((ed->td_head & ~0xf) == (ed->td_tail & ~0xf))
|
||||
transfer->last_trans = transfer->transcnt - 1;
|
||||
|
||||
if (ed->td_head & 1)
|
||||
/* Check correct value in last_trans */
|
||||
/* It could happen if timeout happens and no TD was retired */
|
||||
if (transfer->last_trans >= transfer->transcnt || !tderr_addr)
|
||||
{
|
||||
grub_dprintf("ohci", "tderr==0 or out of TDs range!\n");
|
||||
grub_dprintf("ohci", "last_trans=%d, transcnt=%d\n",
|
||||
transfer->last_trans, transfer->transcnt);
|
||||
|
||||
/* We should set something valid... */
|
||||
transfer->last_trans = -1; /* Probably no TD done */
|
||||
tderr_addr = td_list_addr;
|
||||
}
|
||||
|
||||
/* In case of timeout do not detect error from TD */
|
||||
if (err_timeout)
|
||||
{
|
||||
err = GRUB_ERR_TIMEOUT;
|
||||
grub_dprintf("ohci", "Timeout, target=%08x, head=%08x\n",
|
||||
grub_le_to_cpu32(ed->target),
|
||||
grub_le_to_cpu32(ed->td_head));
|
||||
grub_dprintf("ohci", "tail=%08x, next=%08x\n",
|
||||
grub_le_to_cpu32(ed->td_tail),
|
||||
grub_le_to_cpu32(ed->next_ed));
|
||||
}
|
||||
/* In case of unrecoverable error do not detect error from TD */
|
||||
else if (err_unrec)
|
||||
{
|
||||
err = GRUB_USB_ERR_UNRECOVERABLE;
|
||||
grub_dprintf("ohci",
|
||||
"Unrecoverable error, target=%08x, head=%08x\n",
|
||||
grub_le_to_cpu32(ed->target),
|
||||
grub_le_to_cpu32(ed->td_head));
|
||||
grub_dprintf("ohci", "tail=%08x, next=%08x\n",
|
||||
grub_le_to_cpu32(ed->td_tail),
|
||||
grub_le_to_cpu32(ed->next_ed));
|
||||
}
|
||||
else if (grub_le_to_cpu32 (ed->td_head) & 1)
|
||||
{
|
||||
grub_uint8_t errcode;
|
||||
grub_ohci_td_t tderr;
|
||||
grub_ohci_td_t tderr = NULL;
|
||||
|
||||
tderr = (grub_ohci_td_t) grub_ohci_readreg32 (o,
|
||||
GRUB_OHCI_REG_DONEHEAD);
|
||||
errcode = tderr->token >> 28;
|
||||
transfer->last_trans--;
|
||||
|
||||
tderr = (grub_ohci_td_t) ((char *) td_list
|
||||
+ (tderr_addr - td_list_addr));
|
||||
|
||||
errcode = grub_le_to_cpu32 (tderr->token) >> 28;
|
||||
grub_dprintf ("ohci", "OHCI errcode=0x%02x\n", errcode);
|
||||
|
||||
switch (errcode)
|
||||
{
|
||||
|
@ -473,11 +777,17 @@ grub_ohci_transfer (grub_usb_controller_t dev,
|
|||
case 8:
|
||||
/* XXX: Data overrun error. */
|
||||
err = GRUB_USB_ERR_DATA;
|
||||
grub_dprintf ("ohci", "Overrun, failed TD address: %p, index: %d\n",
|
||||
tderr, transfer->last_trans);
|
||||
break;
|
||||
|
||||
case 9:
|
||||
/* XXX: Data underrun error. */
|
||||
err = GRUB_USB_ERR_DATA;
|
||||
grub_dprintf ("ohci", "Underrun, failed TD address: %p, index: %d\n",
|
||||
tderr, transfer->last_trans);
|
||||
grub_dprintf ("ohci", "Underrun, number of not transferred bytes: %d\n",
|
||||
1 + grub_le_to_cpu32 (tderr->buffer_end) - grub_le_to_cpu32 (tderr->buffer));
|
||||
break;
|
||||
|
||||
case 10:
|
||||
|
@ -515,12 +825,58 @@ grub_ohci_transfer (grub_usb_controller_t dev,
|
|||
|
||||
/* Clear BulkListFilled and ControlListFilled. */
|
||||
status = grub_ohci_readreg32 (o, GRUB_OHCI_REG_CMDSTATUS);
|
||||
status &= ~((1 << 2) | (1 << 3));
|
||||
status &= ~(3 << 1);
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, status);
|
||||
|
||||
/* Set ED to be skipped - for safety */
|
||||
ed->target |= grub_cpu_to_le32 (1 << 14);
|
||||
|
||||
/* Now we should wait for start of next frame.
|
||||
* It is necessary because we will invalidate pointer to ED and it
|
||||
* can be on OHCI active till SOF!
|
||||
* Because we are not using interrupt, we reset SF bit and wait when
|
||||
* it goes to 1. */
|
||||
/* SF bit reset. (SF bit indicates Start Of Frame (SOF) packet) */
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, (1<<2));
|
||||
/* Wait for new SOF */
|
||||
while (((grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS) & 0x4) == 0)
|
||||
&& !err_unrec);
|
||||
/* Now it should be safe to change CONTROL and BULK lists. */
|
||||
|
||||
/* Important cleaning. */
|
||||
o->hcca->donehead = 0;
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, (1 << 1)); /* Clears WDH */
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLHEAD, 0);
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLCURR, 0);
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKHEAD, 0);
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKCURR, 0);
|
||||
|
||||
/* XXX */
|
||||
grub_free (td_list);
|
||||
grub_free (ed);
|
||||
if (err_unrec)
|
||||
{
|
||||
/* Do OHCI reset in case of unrecoverable error - maybe we will need
|
||||
* do more - re-enumerate bus etc. (?) */
|
||||
|
||||
/* Suspend the OHCI by issuing a reset. */
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, 1); /* XXX: Magic. */
|
||||
grub_millisleep (1);
|
||||
grub_dprintf ("ohci", "Unrecoverable error - OHCI reset\n");
|
||||
|
||||
/* Misc. resets. */
|
||||
o->hcca->donehead = 0;
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, 0x7f); /* Clears everything */
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLHEAD, 0);
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLCURR, 0);
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKHEAD, 0);
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKCURR, 0);
|
||||
|
||||
/* Enable the OHCI. */
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL, (2 << 6));
|
||||
}
|
||||
|
||||
grub_dprintf ("ohci", "OHCI finished, freeing, err=0x%02x\n", err);
|
||||
|
||||
grub_dma_free (td_list_chunk);
|
||||
grub_dma_free (ed_chunk);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
@ -530,28 +886,30 @@ grub_ohci_portstatus (grub_usb_controller_t dev,
|
|||
unsigned int port, unsigned int enable)
|
||||
{
|
||||
struct grub_ohci *o = (struct grub_ohci *) dev->data;
|
||||
grub_uint32_t status;
|
||||
|
||||
/* Reset the port. */
|
||||
status = grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port);
|
||||
status |= (1 << 4); /* XXX: Magic. */
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port, status);
|
||||
grub_millisleep (100);
|
||||
grub_dprintf ("ohci", "begin of portstatus=0x%02x\n",
|
||||
grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port));
|
||||
|
||||
/* End the reset signaling. */
|
||||
status = grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port);
|
||||
status |= (1 << 20); /* XXX: Magic. */
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port, status);
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
|
||||
GRUB_OHCI_SET_PORT_RESET);
|
||||
grub_millisleep (50); /* For root hub should be nominaly 50ms */
|
||||
|
||||
/* End the reset signaling. */
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
|
||||
GRUB_OHCI_SET_PORT_RESET_STATUS_CHANGE);
|
||||
grub_millisleep (10);
|
||||
|
||||
/* Enable the port. */
|
||||
status = grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port);
|
||||
status |= (enable << 1); /* XXX: Magic. */
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port, status);
|
||||
|
||||
status = grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port);
|
||||
grub_dprintf ("ohci", "portstatus=0x%02x\n", status);
|
||||
if (enable)
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
|
||||
GRUB_OHCI_SET_PORT_ENABLE);
|
||||
else
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port,
|
||||
GRUB_OHCI_CLEAR_PORT_ENABLE);
|
||||
grub_millisleep (10);
|
||||
|
||||
grub_dprintf ("ohci", "end of portstatus=0x%02x\n",
|
||||
grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port));
|
||||
|
||||
return GRUB_ERR_NONE;
|
||||
}
|
||||
|
||||
|
@ -587,6 +945,42 @@ grub_ohci_hubports (grub_usb_controller_t dev)
|
|||
return portinfo & 0xFF;
|
||||
}
|
||||
|
||||
static grub_err_t
|
||||
grub_ohci_fini_hw (int noreturn __attribute__ ((unused)))
|
||||
{
|
||||
struct grub_ohci *o;
|
||||
|
||||
for (o = ohci; o; o = o->next)
|
||||
{
|
||||
int i, nports = grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBA) & 0xff;
|
||||
for (i = 0; i < nports; i++)
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + i,
|
||||
GRUB_OHCI_CLEAR_PORT_ENABLE);
|
||||
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_HCCA, 0);
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLHEAD, 0);
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLCURR, 0);
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKHEAD, 0);
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKCURR, 0);
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_DONEHEAD, 0);
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL, 0);
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, 1);
|
||||
}
|
||||
grub_millisleep (10);
|
||||
|
||||
return GRUB_ERR_NONE;
|
||||
}
|
||||
|
||||
static grub_err_t
|
||||
grub_ohci_restore_hw (void)
|
||||
{
|
||||
struct grub_ohci *o;
|
||||
|
||||
for (o = ohci; o; o = o->next)
|
||||
grub_ohci_writereg32 (o, GRUB_OHCI_REG_HCCA, o->hcca_addr);
|
||||
|
||||
return GRUB_ERR_NONE;
|
||||
}
|
||||
|
||||
|
||||
static struct grub_usb_controller_dev usb_controller =
|
||||
|
@ -603,9 +997,12 @@ GRUB_MOD_INIT(ohci)
|
|||
{
|
||||
grub_ohci_inithw ();
|
||||
grub_usb_controller_dev_register (&usb_controller);
|
||||
grub_loader_register_preboot_hook (grub_ohci_fini_hw, grub_ohci_restore_hw,
|
||||
GRUB_LOADER_PREBOOT_HOOK_PRIO_DISK);
|
||||
}
|
||||
|
||||
GRUB_MOD_FINI(ohci)
|
||||
{
|
||||
grub_ohci_fini_hw (0);
|
||||
grub_usb_controller_dev_unregister (&usb_controller);
|
||||
}
|
||||
|
|
|
@ -174,14 +174,15 @@ grub_uhci_pci_iter (grub_pci_device_t dev,
|
|||
return 1;
|
||||
|
||||
u->iobase = base & GRUB_UHCI_IOMASK;
|
||||
grub_dprintf ("uhci", "class=0x%02x 0x%02x interface 0x%02x base=0x%x\n",
|
||||
class, subclass, interf, u->iobase);
|
||||
|
||||
/* Reserve a page for the frame list. */
|
||||
u->framelist = grub_memalign (4096, 4096);
|
||||
if (! u->framelist)
|
||||
goto fail;
|
||||
|
||||
grub_dprintf ("uhci", "class=0x%02x 0x%02x interface 0x%02x base=0x%x framelist=%p\n",
|
||||
class, subclass, interf, u->iobase, u->framelist);
|
||||
|
||||
/* The framelist pointer of UHCI is only 32 bits, make sure this
|
||||
code works on on 64 bits architectures. */
|
||||
#if GRUB_CPU_SIZEOF_VOID_P == 8
|
||||
|
@ -221,6 +222,9 @@ grub_uhci_pci_iter (grub_pci_device_t dev,
|
|||
}
|
||||
#endif
|
||||
|
||||
grub_dprintf ("uhci", "QH=%p, TD=%p\n",
|
||||
u->qh, u->td);
|
||||
|
||||
/* Link all Transfer Descriptors in a list of available Transfer
|
||||
Descriptors. */
|
||||
for (i = 0; i < 256; i++)
|
||||
|
@ -328,13 +332,20 @@ grub_free_td (struct grub_uhci *u, grub_uhci_td_t td)
|
|||
}
|
||||
|
||||
static void
|
||||
grub_free_queue (struct grub_uhci *u, grub_uhci_td_t td)
|
||||
grub_free_queue (struct grub_uhci *u, grub_uhci_td_t td,
|
||||
grub_usb_transfer_t transfer)
|
||||
{
|
||||
/* Free the TDs in this queue. */
|
||||
while (td)
|
||||
int i; /* Index of TD in transfer */
|
||||
|
||||
/* Free the TDs in this queue and set last_trans. */
|
||||
for (i=0; td; i++)
|
||||
{
|
||||
grub_uhci_td_t tdprev;
|
||||
|
||||
/* Check state of TD and possibly set last_trans */
|
||||
if (transfer && (td->linkptr & 1))
|
||||
transfer->last_trans = i;
|
||||
|
||||
/* Unlink the queue. */
|
||||
tdprev = td;
|
||||
td = (grub_uhci_td_t) td->linkptr2;
|
||||
|
@ -380,7 +391,7 @@ static grub_uhci_td_t
|
|||
grub_uhci_transaction (struct grub_uhci *u, unsigned int endp,
|
||||
grub_transfer_type_t type, unsigned int addr,
|
||||
unsigned int toggle, grub_size_t size,
|
||||
char *data)
|
||||
grub_uint32_t data)
|
||||
{
|
||||
grub_uhci_td_t td;
|
||||
static const unsigned int tf[] = { 0x69, 0xE1, 0x2D };
|
||||
|
@ -398,7 +409,7 @@ grub_uhci_transaction (struct grub_uhci *u, unsigned int endp,
|
|||
}
|
||||
|
||||
grub_dprintf ("uhci",
|
||||
"transaction: endp=%d, type=%d, addr=%d, toggle=%d, size=%d data=%p td=%p\n",
|
||||
"transaction: endp=%d, type=%d, addr=%d, toggle=%d, size=%d data=0x%x td=%p\n",
|
||||
endp, type, addr, toggle, size, data, td);
|
||||
|
||||
/* Don't point to any TD, just terminate. */
|
||||
|
@ -418,7 +429,7 @@ grub_uhci_transaction (struct grub_uhci *u, unsigned int endp,
|
|||
td->token = ((size << 21) | (toggle << 19) | (endp << 15)
|
||||
| (addr << 8) | tf[type]);
|
||||
|
||||
td->buffer = (grub_uint32_t) data;
|
||||
td->buffer = data;
|
||||
|
||||
return td;
|
||||
}
|
||||
|
@ -441,6 +452,8 @@ grub_uhci_transfer (grub_usb_controller_t dev,
|
|||
if (! qh)
|
||||
return grub_errno;
|
||||
|
||||
grub_dprintf ("uhci", "transfer, iobase:%08x\n", u->iobase);
|
||||
|
||||
for (i = 0; i < transfer->transcnt; i++)
|
||||
{
|
||||
grub_usb_transaction_t tr = &transfer->transactions[i];
|
||||
|
@ -455,7 +468,7 @@ grub_uhci_transfer (grub_usb_controller_t dev,
|
|||
td_prev->linkptr = 1;
|
||||
|
||||
if (td_first)
|
||||
grub_free_queue (u, td_first);
|
||||
grub_free_queue (u, td_first, NULL);
|
||||
|
||||
return GRUB_USB_ERR_INTERNAL;
|
||||
}
|
||||
|
@ -548,12 +561,13 @@ grub_uhci_transfer (grub_usb_controller_t dev,
|
|||
|
||||
fail:
|
||||
|
||||
grub_dprintf ("uhci", "transaction failed\n");
|
||||
if (err != GRUB_USB_ERR_NONE)
|
||||
grub_dprintf ("uhci", "transaction failed\n");
|
||||
|
||||
/* Place the QH back in the free list and deallocate the associated
|
||||
TDs. */
|
||||
qh->elinkptr = 1;
|
||||
grub_free_queue (u, td_first);
|
||||
grub_free_queue (u, td_first, transfer);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
@ -583,6 +597,8 @@ grub_uhci_portstatus (grub_usb_controller_t dev,
|
|||
unsigned int status;
|
||||
grub_uint64_t endtime;
|
||||
|
||||
grub_dprintf ("uhci", "portstatus, iobase:%08x\n", u->iobase);
|
||||
|
||||
grub_dprintf ("uhci", "enable=%d port=%d\n", enable, port);
|
||||
|
||||
if (port == 0)
|
||||
|
@ -600,7 +616,7 @@ grub_uhci_portstatus (grub_usb_controller_t dev,
|
|||
grub_uhci_writereg16 (u, reg, enable << 9);
|
||||
|
||||
/* Wait for the reset to complete. XXX: How long exactly? */
|
||||
grub_millisleep (10);
|
||||
grub_millisleep (50); /* For root hub should be nominaly 50ms */
|
||||
status = grub_uhci_readreg16 (u, reg);
|
||||
grub_uhci_writereg16 (u, reg, status & ~(1 << 9));
|
||||
grub_dprintf ("uhci", "reset completed\n");
|
||||
|
@ -631,6 +647,8 @@ grub_uhci_detect_dev (grub_usb_controller_t dev, int port)
|
|||
int reg;
|
||||
unsigned int status;
|
||||
|
||||
grub_dprintf ("uhci", "detect_dev, iobase:%08x\n", u->iobase);
|
||||
|
||||
if (port == 0)
|
||||
reg = GRUB_UHCI_REG_PORTSC1;
|
||||
else if (port == 1)
|
||||
|
|
|
@ -105,10 +105,7 @@ grub_usb_clear_halt (grub_usb_device_t dev, int endpoint)
|
|||
grub_usb_err_t
|
||||
grub_usb_set_configuration (grub_usb_device_t dev, int configuration)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 16; i++)
|
||||
dev->toggle[i] = 0;
|
||||
grub_memset (dev->toggle, 0, sizeof (dev->toggle));
|
||||
|
||||
return grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT
|
||||
| GRUB_USB_REQTYPE_STANDARD
|
||||
|
@ -163,6 +160,16 @@ grub_usb_device_initialize (grub_usb_device_t dev)
|
|||
grub_usb_err_t err;
|
||||
int i;
|
||||
|
||||
/* First we have to read first 8 bytes only and determine
|
||||
* max. size of packet */
|
||||
dev->descdev.maxsize0 = 0; /* invalidating, for safety only, can be removed if it is sure it is zero here */
|
||||
err = grub_usb_get_descriptor (dev, GRUB_USB_DESCRIPTOR_DEVICE,
|
||||
0, 8, (char *) &dev->descdev);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Now we have valid value in dev->descdev.maxsize0,
|
||||
* so we can read whole device descriptor */
|
||||
err = grub_usb_get_descriptor (dev, GRUB_USB_DESCRIPTOR_DEVICE,
|
||||
0, sizeof (struct grub_usb_desc_device),
|
||||
(char *) &dev->descdev);
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
#include <grub/dl.h>
|
||||
#include <grub/pci.h>
|
||||
#include <grub/mm.h>
|
||||
#include <grub/misc.h>
|
||||
#include <grub/usb.h>
|
||||
|
@ -29,30 +30,59 @@ grub_usb_control_msg (grub_usb_device_t dev,
|
|||
grub_uint8_t request,
|
||||
grub_uint16_t value,
|
||||
grub_uint16_t index,
|
||||
grub_size_t size, char *data)
|
||||
grub_size_t size0, char *data_in)
|
||||
{
|
||||
int i;
|
||||
grub_usb_transfer_t transfer;
|
||||
int datablocks;
|
||||
struct grub_usb_packet_setup setupdata;
|
||||
volatile struct grub_usb_packet_setup *setupdata;
|
||||
grub_uint32_t setupdata_addr;
|
||||
grub_usb_err_t err;
|
||||
unsigned int max;
|
||||
struct grub_pci_dma_chunk *data_chunk, *setupdata_chunk;
|
||||
volatile char *data;
|
||||
grub_uint32_t data_addr;
|
||||
grub_size_t size = size0;
|
||||
|
||||
/* FIXME: avoid allocation any kind of buffer in a first place. */
|
||||
data_chunk = grub_memalign_dma32 (128, size ? : 16);
|
||||
if (!data_chunk)
|
||||
return GRUB_USB_ERR_INTERNAL;
|
||||
data = grub_dma_get_virt (data_chunk);
|
||||
data_addr = grub_dma_get_phys (data_chunk);
|
||||
grub_memcpy ((char *) data, data_in, size);
|
||||
|
||||
grub_dprintf ("usb",
|
||||
"control: reqtype=0x%02x req=0x%02x val=0x%02x idx=0x%02x size=%d\n",
|
||||
reqtype, request, value, index, size);
|
||||
|
||||
/* Create a transfer. */
|
||||
transfer = grub_malloc (sizeof (struct grub_usb_transfer));
|
||||
transfer = grub_malloc (sizeof (*transfer));
|
||||
if (! transfer)
|
||||
return grub_errno;
|
||||
{
|
||||
grub_dma_free (data_chunk);
|
||||
return grub_errno;
|
||||
}
|
||||
|
||||
setupdata_chunk = grub_memalign_dma32 (32, sizeof (*setupdata));
|
||||
if (! setupdata_chunk)
|
||||
{
|
||||
grub_free (transfer);
|
||||
grub_dma_free (data_chunk);
|
||||
return grub_errno;
|
||||
}
|
||||
|
||||
setupdata = grub_dma_get_virt (setupdata_chunk);
|
||||
setupdata_addr = grub_dma_get_phys (setupdata_chunk);
|
||||
|
||||
/* Determine the maximum packet size. */
|
||||
if (dev->initialized)
|
||||
if (dev->descdev.maxsize0)
|
||||
max = dev->descdev.maxsize0;
|
||||
else
|
||||
max = 64;
|
||||
|
||||
grub_dprintf ("usb", "transfer = %p, dev = %p\n", transfer, dev);
|
||||
|
||||
datablocks = (size + max - 1) / max;
|
||||
|
||||
/* XXX: Discriminate between different types of control
|
||||
|
@ -71,18 +101,20 @@ grub_usb_control_msg (grub_usb_device_t dev,
|
|||
if (! transfer->transactions)
|
||||
{
|
||||
grub_free (transfer);
|
||||
grub_dma_free (setupdata_chunk);
|
||||
grub_dma_free (data_chunk);
|
||||
return grub_errno;
|
||||
}
|
||||
|
||||
/* Build a Setup packet. XXX: Endianness. */
|
||||
setupdata.reqtype = reqtype;
|
||||
setupdata.request = request;
|
||||
setupdata.value = value;
|
||||
setupdata.index = index;
|
||||
setupdata.length = size;
|
||||
transfer->transactions[0].size = sizeof (setupdata);
|
||||
setupdata->reqtype = reqtype;
|
||||
setupdata->request = request;
|
||||
setupdata->value = value;
|
||||
setupdata->index = index;
|
||||
setupdata->length = size;
|
||||
transfer->transactions[0].size = sizeof (*setupdata);
|
||||
transfer->transactions[0].pid = GRUB_USB_TRANSFER_TYPE_SETUP;
|
||||
transfer->transactions[0].data = (char *) &setupdata;
|
||||
transfer->transactions[0].data = setupdata_addr;
|
||||
transfer->transactions[0].toggle = 0;
|
||||
|
||||
/* Now the data... XXX: Is this the right way to transfer control
|
||||
|
@ -99,14 +131,14 @@ grub_usb_control_msg (grub_usb_device_t dev,
|
|||
tr->pid = GRUB_USB_TRANSFER_TYPE_IN;
|
||||
else
|
||||
tr->pid = GRUB_USB_TRANSFER_TYPE_OUT;
|
||||
tr->data = &data[i * max];
|
||||
tr->data = data_addr + i * max;
|
||||
size -= max;
|
||||
}
|
||||
|
||||
/* End with an empty OUT transaction. */
|
||||
transfer->transactions[datablocks + 1].size = 0;
|
||||
transfer->transactions[datablocks + 1].data = NULL;
|
||||
if (reqtype & 128)
|
||||
transfer->transactions[datablocks + 1].data = 0;
|
||||
if ((reqtype & 128) && datablocks)
|
||||
transfer->transactions[datablocks + 1].pid = GRUB_USB_TRANSFER_TYPE_OUT;
|
||||
else
|
||||
transfer->transactions[datablocks + 1].pid = GRUB_USB_TRANSFER_TYPE_IN;
|
||||
|
@ -116,14 +148,19 @@ grub_usb_control_msg (grub_usb_device_t dev,
|
|||
err = dev->controller.dev->transfer (&dev->controller, transfer);
|
||||
|
||||
grub_free (transfer->transactions);
|
||||
|
||||
grub_free (transfer);
|
||||
grub_dma_free (data_chunk);
|
||||
grub_dma_free (setupdata_chunk);
|
||||
|
||||
grub_memcpy (data_in, (char *) data, size0);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static grub_usb_err_t
|
||||
grub_usb_bulk_readwrite (grub_usb_device_t dev,
|
||||
int endpoint, grub_size_t size, char *data,
|
||||
int endpoint, grub_size_t size0, char *data_in,
|
||||
grub_transfer_type_t type)
|
||||
{
|
||||
int i;
|
||||
|
@ -132,6 +169,19 @@ grub_usb_bulk_readwrite (grub_usb_device_t dev,
|
|||
unsigned int max;
|
||||
grub_usb_err_t err;
|
||||
int toggle = dev->toggle[endpoint];
|
||||
volatile char *data;
|
||||
grub_uint32_t data_addr;
|
||||
struct grub_pci_dma_chunk *data_chunk;
|
||||
grub_size_t size = size0;
|
||||
|
||||
/* FIXME: avoid allocation any kind of buffer in a first place. */
|
||||
data_chunk = grub_memalign_dma32 (128, size);
|
||||
if (!data_chunk)
|
||||
return GRUB_USB_ERR_INTERNAL;
|
||||
data = grub_dma_get_virt (data_chunk);
|
||||
data_addr = grub_dma_get_phys (data_chunk);
|
||||
if (type == GRUB_USB_TRANSFER_TYPE_OUT)
|
||||
grub_memcpy ((char *) data, data_in, size);
|
||||
|
||||
/* Use the maximum packet size given in the endpoint descriptor. */
|
||||
if (dev->initialized)
|
||||
|
@ -150,16 +200,20 @@ grub_usb_bulk_readwrite (grub_usb_device_t dev,
|
|||
/* Create a transfer. */
|
||||
transfer = grub_malloc (sizeof (struct grub_usb_transfer));
|
||||
if (! transfer)
|
||||
return grub_errno;
|
||||
{
|
||||
grub_dma_free (data_chunk);
|
||||
return grub_errno;
|
||||
}
|
||||
|
||||
datablocks = ((size + max - 1) / max);
|
||||
transfer->transcnt = datablocks;
|
||||
transfer->size = size - 1;
|
||||
transfer->endpoint = endpoint;
|
||||
transfer->endpoint = endpoint & 15;
|
||||
transfer->devaddr = dev->addr;
|
||||
transfer->type = GRUB_USB_TRANSACTION_TYPE_BULK;
|
||||
transfer->max = max;
|
||||
transfer->dev = dev;
|
||||
transfer->last_trans = -1; /* Reset index of last processed transaction (TD) */
|
||||
|
||||
/* Allocate an array of transfer data structures. */
|
||||
transfer->transactions = grub_malloc (transfer->transcnt
|
||||
|
@ -167,6 +221,7 @@ grub_usb_bulk_readwrite (grub_usb_device_t dev,
|
|||
if (! transfer->transactions)
|
||||
{
|
||||
grub_free (transfer);
|
||||
grub_dma_free (data_chunk);
|
||||
return grub_errno;
|
||||
}
|
||||
|
||||
|
@ -181,16 +236,27 @@ grub_usb_bulk_readwrite (grub_usb_device_t dev,
|
|||
tr->toggle = toggle;
|
||||
toggle = toggle ? 0 : 1;
|
||||
tr->pid = type;
|
||||
tr->data = &data[i * max];
|
||||
tr->data = data_addr + i * max;
|
||||
size -= tr->size;
|
||||
}
|
||||
|
||||
err = dev->controller.dev->transfer (&dev->controller, transfer);
|
||||
/* We must remember proper toggle value even if some transactions
|
||||
* were not processed - correct value should be inversion of last
|
||||
* processed transaction (TD). */
|
||||
if (transfer->last_trans >= 0)
|
||||
toggle = transfer->transactions[transfer->last_trans].toggle ? 0 : 1;
|
||||
else
|
||||
toggle = dev->toggle[endpoint]; /* Nothing done, take original */
|
||||
grub_dprintf ("usb", "toggle=%d\n", toggle);
|
||||
dev->toggle[endpoint] = toggle;
|
||||
|
||||
grub_free (transfer->transactions);
|
||||
grub_free (transfer);
|
||||
grub_dma_free (data_chunk);
|
||||
|
||||
if (type == GRUB_USB_TRANSFER_TYPE_IN)
|
||||
grub_memcpy (data_in, (char *) data, size0);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue