From 041d1ea37802bf7178a31a53f96c26efa6b8fb7b Mon Sep 17 00:00:00 2001 From: James Date: Fri, 16 Nov 2012 10:41:01 +0000 Subject: fish --- grub-core/bus/bonito.c | 90 +++ grub-core/bus/cs5536.c | 384 ++++++++++ grub-core/bus/emu/pci.c | 77 ++ grub-core/bus/pci.c | 137 ++++ grub-core/bus/usb/emu/usb.c | 203 ++++++ grub-core/bus/usb/ohci.c | 1441 +++++++++++++++++++++++++++++++++++++ grub-core/bus/usb/serial/common.c | 134 ++++ grub-core/bus/usb/serial/ftdi.c | 207 ++++++ grub-core/bus/usb/serial/pl2303.c | 220 ++++++ grub-core/bus/usb/uhci.c | 816 +++++++++++++++++++++ grub-core/bus/usb/usb.c | 349 +++++++++ grub-core/bus/usb/usbhub.c | 559 ++++++++++++++ grub-core/bus/usb/usbtrans.c | 413 +++++++++++ 13 files changed, 5030 insertions(+) create mode 100644 grub-core/bus/bonito.c create mode 100644 grub-core/bus/cs5536.c create mode 100644 grub-core/bus/emu/pci.c create mode 100644 grub-core/bus/pci.c create mode 100644 grub-core/bus/usb/emu/usb.c create mode 100644 grub-core/bus/usb/ohci.c create mode 100644 grub-core/bus/usb/serial/common.c create mode 100644 grub-core/bus/usb/serial/ftdi.c create mode 100644 grub-core/bus/usb/serial/pl2303.c create mode 100644 grub-core/bus/usb/uhci.c create mode 100644 grub-core/bus/usb/usb.c create mode 100644 grub-core/bus/usb/usbhub.c create mode 100644 grub-core/bus/usb/usbtrans.c (limited to 'grub-core/bus') diff --git a/grub-core/bus/bonito.c b/grub-core/bus/bonito.c new file mode 100644 index 0000000..c2e0cd6 --- /dev/null +++ b/grub-core/bus/bonito.c @@ -0,0 +1,90 @@ +/* bonito.c - PCI bonito interface. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2009 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 . + */ + +#include +#include + +static grub_uint32_t base_win[GRUB_MACHINE_PCI_NUM_WIN]; +static const grub_size_t sizes_win[GRUB_MACHINE_PCI_NUM_WIN] = + {GRUB_MACHINE_PCI_WIN1_SIZE, GRUB_MACHINE_PCI_WIN_SIZE, + GRUB_MACHINE_PCI_WIN_SIZE}; +/* Usage counters. */ +static int usage_win[GRUB_MACHINE_PCI_NUM_WIN]; +static grub_addr_t addr_win[GRUB_MACHINE_PCI_NUM_WIN] = + {GRUB_MACHINE_PCI_WIN1_ADDR, GRUB_MACHINE_PCI_WIN2_ADDR, + GRUB_MACHINE_PCI_WIN3_ADDR}; + +static inline void +write_bases (void) +{ + int i; + grub_uint32_t reg = 0; + for (i = 0; i < GRUB_MACHINE_PCI_NUM_WIN; i++) + reg |= (((base_win[i] >> GRUB_MACHINE_PCI_WIN_SHIFT) + & GRUB_MACHINE_PCI_WIN_MASK) + << (i * GRUB_MACHINE_PCI_WIN_MASK_SIZE)); + GRUB_MACHINE_PCI_IO_CTRL_REG = reg; +} + +volatile void * +grub_pci_device_map_range (grub_pci_device_t dev __attribute__ ((unused)), + grub_addr_t base, grub_size_t size) +{ + int i; + grub_addr_t newbase; + + /* First try already used registers. */ + for (i = 0; i < GRUB_MACHINE_PCI_NUM_WIN; i++) + if (usage_win[i] && base_win[i] <= base + && base_win[i] + sizes_win[i] > base + size) + { + usage_win[i]++; + return (void *) + (addr_win[i] | (base & GRUB_MACHINE_PCI_WIN_OFFSET_MASK)); + } + /* Map new register. */ + newbase = base & ~GRUB_MACHINE_PCI_WIN_OFFSET_MASK; + for (i = 0; i < GRUB_MACHINE_PCI_NUM_WIN; i++) + if (!usage_win[i] && newbase <= base + && newbase + sizes_win[i] > base + size) + { + usage_win[i]++; + base_win[i] = newbase; + write_bases (); + return (void *) + (addr_win[i] | (base & GRUB_MACHINE_PCI_WIN_OFFSET_MASK)); + } + grub_fatal ("Out of PCI windows."); +} + +void +grub_pci_device_unmap_range (grub_pci_device_t dev __attribute__ ((unused)), + volatile void *mem __attribute__ ((unused)), + grub_size_t size __attribute__ ((unused))) +{ + int i; + for (i = 0; i < GRUB_MACHINE_PCI_NUM_WIN; i++) + if (usage_win[i] && addr_win[i] + == (((grub_addr_t) mem) & ~GRUB_MACHINE_PCI_WIN_OFFSET_MASK)) + { + usage_win[i]--; + return; + } + grub_fatal ("Tried to unmap not mapped region"); +} diff --git a/grub-core/bus/cs5536.c b/grub-core/bus/cs5536.c new file mode 100644 index 0000000..fbcb83c --- /dev/null +++ b/grub-core/bus/cs5536.c @@ -0,0 +1,384 @@ +/* + * 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 . + */ + +#include +#include +#include +#include +#include + +#include + +GRUB_MOD_LICENSE ("GPLv3+"); + +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; +} + +/* Dump of GPIO connections. FIXME: Remove useless and macroify. */ +static grub_uint32_t gpiodump[] = { + 0xffff0000, 0x2ffdd002, 0xffff0000, 0xffff0000, + 0x2fffd000, 0xffff0000, 0x1000efff, 0xefff1000, + 0x3ffbc004, 0xffff0000, 0xffff0000, 0xffff0000, + 0x3ffbc004, 0x3ffbc004, 0xffff0000, 0x00000000, + 0xffff0000, 0xffff0000, 0x3ffbc004, 0x3f9bc064, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffff0000, 0xffff0000, 0xffff0000, 0xffff0000, + 0xffff0000, 0xffff0000, 0x0000ffff, 0xffff0000, + 0xefff1000, 0xffff0000, 0xffff0000, 0xffff0000, + 0xefff1000, 0xefff1000, 0xffff0000, 0x00000000, + 0xffff0000, 0xffff0000, 0xefff1000, 0xffff0000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x50000000, 0x00000000, 0x00000000, +}; + +static inline void +set_io_space (grub_pci_device_t dev, int num, grub_uint16_t start, + grub_uint16_t len) +{ + grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_GL_REGIONS_START + num, + ((((grub_uint64_t) start + len - 4) + << GRUB_CS5536_MSR_GL_REGION_IO_TOP_SHIFT) + & GRUB_CS5536_MSR_GL_REGION_TOP_MASK) + | (((grub_uint64_t) start + << GRUB_CS5536_MSR_GL_REGION_IO_BASE_SHIFT) + & GRUB_CS5536_MSR_GL_REGION_BASE_MASK) + | GRUB_CS5536_MSR_GL_REGION_IO + | GRUB_CS5536_MSR_GL_REGION_ENABLE); +} + +static inline void +set_iod (grub_pci_device_t dev, int num, int dest, int start, int mask) +{ + grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_GL_IOD_START + num, + ((grub_uint64_t) dest << GRUB_CS5536_IOD_DEST_SHIFT) + | (((grub_uint64_t) start & GRUB_CS5536_IOD_ADDR_MASK) + << GRUB_CS5536_IOD_BASE_SHIFT) + | ((mask & GRUB_CS5536_IOD_ADDR_MASK) + << GRUB_CS5536_IOD_MASK_SHIFT)); +} + +static inline void +set_p2d (grub_pci_device_t dev, int num, int dest, grub_uint32_t start) +{ + grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_GL_P2D_START + num, + (((grub_uint64_t) dest) << GRUB_CS5536_P2D_DEST_SHIFT) + | ((grub_uint64_t) (start >> GRUB_CS5536_P2D_LOG_ALIGN) + << GRUB_CS5536_P2D_BASE_SHIFT) + | (((1 << (32 - GRUB_CS5536_P2D_LOG_ALIGN)) - 1) + << GRUB_CS5536_P2D_MASK_SHIFT)); +} + +void +grub_cs5536_init_geode (grub_pci_device_t dev) +{ + int i; + + /* Make sure GPIO is where we expect it to be. */ + grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_GPIO_BAR, + GRUB_CS5536_LBAR_TURN_ON | GRUB_CS5536_LBAR_GPIO); + + /* Setup GPIO. */ + for (i = 0; i < (int) ARRAY_SIZE (gpiodump); i++) + ((volatile grub_uint32_t *) (GRUB_MACHINE_PCI_IO_BASE + + GRUB_CS5536_LBAR_GPIO)) [i] = gpiodump[i]; + + /* Enable more BARs. */ + grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_IRQ_MAP_BAR, + GRUB_CS5536_LBAR_TURN_ON | GRUB_CS5536_LBAR_IRQ_MAP); + grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_MFGPT_BAR, + GRUB_CS5536_LBAR_TURN_ON | GRUB_CS5536_LBAR_MFGPT); + grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_ACPI_BAR, + GRUB_CS5536_LBAR_TURN_ON | GRUB_CS5536_LBAR_ACPI); + grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_PM_BAR, + GRUB_CS5536_LBAR_TURN_ON | GRUB_CS5536_LBAR_PM); + + /* Setup DIVIL. */ + grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_DIVIL_LEG_IO, + GRUB_CS5536_MSR_DIVIL_LEG_IO_MODE_X86 + | GRUB_CS5536_MSR_DIVIL_LEG_IO_F_REMAP + | GRUB_CS5536_MSR_DIVIL_LEG_IO_RTC_ENABLE0 + | GRUB_CS5536_MSR_DIVIL_LEG_IO_RTC_ENABLE1); + grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_DIVIL_IRQ_MAPPER_PRIMARY_MASK, + (~GRUB_CS5536_DIVIL_LPC_INTERRUPTS) & 0xffff); + grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_DIVIL_IRQ_MAPPER_LPC_MASK, + GRUB_CS5536_DIVIL_LPC_INTERRUPTS); + grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_DIVIL_LPC_SERIAL_IRQ_CONTROL, + GRUB_CS5536_MSR_DIVIL_LPC_SERIAL_IRQ_CONTROL_ENABLE); + + /* Initialise USB controller. */ + /* FIXME: assign adresses dynamically. */ + grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_USB_OHCI_BASE, + GRUB_CS5536_MSR_USB_BASE_BUS_MASTER + | GRUB_CS5536_MSR_USB_BASE_MEMORY_ENABLE + | 0x05024000); + grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_USB_EHCI_BASE, + GRUB_CS5536_MSR_USB_BASE_BUS_MASTER + | GRUB_CS5536_MSR_USB_BASE_MEMORY_ENABLE + | (0x20ULL << GRUB_CS5536_MSR_USB_EHCI_BASE_FLDJ_SHIFT) + | 0x05023000); + grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_USB_CONTROLLER_BASE, + GRUB_CS5536_MSR_USB_BASE_BUS_MASTER + | GRUB_CS5536_MSR_USB_BASE_MEMORY_ENABLE | 0x05020000); + grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_USB_OPTION_CONTROLLER_BASE, + GRUB_CS5536_MSR_USB_BASE_MEMORY_ENABLE | 0x05022000); + set_p2d (dev, 0, GRUB_CS5536_DESTINATION_USB, 0x05020000); + set_p2d (dev, 1, GRUB_CS5536_DESTINATION_USB, 0x05022000); + set_p2d (dev, 5, GRUB_CS5536_DESTINATION_USB, 0x05024000); + set_p2d (dev, 6, GRUB_CS5536_DESTINATION_USB, 0x05023000); + + { + volatile grub_uint32_t *oc; + oc = grub_pci_device_map_range (dev, 0x05022000, + GRUB_CS5536_USB_OPTION_REGS_SIZE); + + oc[GRUB_CS5536_USB_OPTION_REG_UOCMUX] = + (oc[GRUB_CS5536_USB_OPTION_REG_UOCMUX] + & ~GRUB_CS5536_USB_OPTION_REG_UOCMUX_PMUX_MASK) + | GRUB_CS5536_USB_OPTION_REG_UOCMUX_PMUX_HC; + grub_pci_device_unmap_range (dev, oc, GRUB_CS5536_USB_OPTION_REGS_SIZE); + } + + /* Setup IDE controller. */ + grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_IDE_IO_BAR, + GRUB_CS5536_LBAR_IDE + | GRUB_CS5536_MSR_IDE_IO_BAR_UNITS); + grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_IDE_CFG, + GRUB_CS5536_MSR_IDE_CFG_CHANNEL_ENABLE); + grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_IDE_TIMING, + (GRUB_CS5536_MSR_IDE_TIMING_PIO0 + << GRUB_CS5536_MSR_IDE_TIMING_DRIVE0_SHIFT) + | (GRUB_CS5536_MSR_IDE_TIMING_PIO0 + << GRUB_CS5536_MSR_IDE_TIMING_DRIVE1_SHIFT)); + grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_IDE_CAS_TIMING, + (GRUB_CS5536_MSR_IDE_CAS_TIMING_CMD_PIO0 + << GRUB_CS5536_MSR_IDE_CAS_TIMING_CMD_SHIFT) + | (GRUB_CS5536_MSR_IDE_CAS_TIMING_PIO0 + << GRUB_CS5536_MSR_IDE_CAS_TIMING_DRIVE0_SHIFT) + | (GRUB_CS5536_MSR_IDE_CAS_TIMING_PIO0 + << GRUB_CS5536_MSR_IDE_CAS_TIMING_DRIVE1_SHIFT)); + + /* Setup Geodelink PCI. */ + grub_cs5536_write_msr (dev, GRUB_CS5536_MSR_GL_PCI_CTRL, + (4ULL << GRUB_CS5536_MSR_GL_PCI_CTRL_OUT_THR_SHIFT) + | (4ULL << GRUB_CS5536_MSR_GL_PCI_CTRL_IN_THR_SHIFT) + | (8ULL << GRUB_CS5536_MSR_GL_PCI_CTRL_LATENCY_SHIFT) + | GRUB_CS5536_MSR_GL_PCI_CTRL_IO_ENABLE + | GRUB_CS5536_MSR_GL_PCI_CTRL_MEMORY_ENABLE); + + /* Setup windows. */ + set_io_space (dev, 0, GRUB_CS5536_LBAR_SMBUS, GRUB_CS5536_SMBUS_REGS_SIZE); + set_io_space (dev, 1, GRUB_CS5536_LBAR_GPIO, GRUB_CS5536_GPIO_REGS_SIZE); + set_io_space (dev, 2, GRUB_CS5536_LBAR_MFGPT, GRUB_CS5536_MFGPT_REGS_SIZE); + set_io_space (dev, 3, GRUB_CS5536_LBAR_IRQ_MAP, GRUB_CS5536_IRQ_MAP_REGS_SIZE); + set_io_space (dev, 4, GRUB_CS5536_LBAR_PM, GRUB_CS5536_PM_REGS_SIZE); + set_io_space (dev, 5, GRUB_CS5536_LBAR_ACPI, GRUB_CS5536_ACPI_REGS_SIZE); + set_iod (dev, 0, GRUB_CS5536_DESTINATION_IDE, GRUB_ATA_CH0_PORT1, 0xffff8); + set_iod (dev, 1, GRUB_CS5536_DESTINATION_ACC, GRUB_CS5536_LBAR_ACC, 0xfff80); + set_iod (dev, 2, GRUB_CS5536_DESTINATION_IDE, GRUB_CS5536_LBAR_IDE, 0xffff0); +} diff --git a/grub-core/bus/emu/pci.c b/grub-core/bus/emu/pci.c new file mode 100644 index 0000000..d1beb56 --- /dev/null +++ b/grub-core/bus/emu/pci.c @@ -0,0 +1,77 @@ +/* pci.c - Generic PCI interfaces. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2007,2009 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 . + */ + +#include +#include +#include +#include + +grub_pci_address_t +grub_pci_make_address (grub_pci_device_t dev, int reg) +{ + grub_pci_address_t ret; + ret.dev = dev; + ret.pos = reg; + return ret; +} + +void +grub_pci_iterate (grub_pci_iteratefunc_t hook) +{ + struct pci_device_iterator *iter; + struct pci_slot_match slot; + struct pci_device *dev; + slot.domain = PCI_MATCH_ANY; + slot.bus = PCI_MATCH_ANY; + slot.dev = PCI_MATCH_ANY; + slot.func = PCI_MATCH_ANY; + iter = pci_slot_match_iterator_create (&slot); + while ((dev = pci_device_next (iter))) + hook (dev, dev->vendor_id | (dev->device_id << 16)); + pci_iterator_destroy (iter); +} + +void * +grub_pci_device_map_range (grub_pci_device_t dev, grub_addr_t base, + grub_size_t size) +{ + void *addr; + int err; + err = pci_device_map_range (dev, base, size, PCI_DEV_MAP_FLAG_WRITABLE, &addr); + if (err) + grub_util_error ("mapping 0x%x failed (error %d)\n", base, err); + return addr; +} + +void +grub_pci_device_unmap_range (grub_pci_device_t dev, void *mem, + grub_size_t size) +{ + pci_device_unmap_range (dev, mem, size); +} + +GRUB_MOD_INIT (pci) +{ + pci_system_init (); +} + +GRUB_MOD_FINI (pci) +{ + pci_system_cleanup (); +} diff --git a/grub-core/bus/pci.c b/grub-core/bus/pci.c new file mode 100644 index 0000000..766b6d7 --- /dev/null +++ b/grub-core/bus/pci.c @@ -0,0 +1,137 @@ +/* pci.c - Generic PCI interfaces. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2007,2009 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 . + */ + +#include +#include +#include +#include +#include + +GRUB_MOD_LICENSE ("GPLv3+"); + +/* 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) +{ + void *ret = grub_memalign (align, size); + if (!ret) + return 0; + grub_arch_sync_dma_caches (ret, size); + return ret; +} + +/* FIXME: evil. */ +void +grub_dma_free (struct grub_pci_dma_chunk *ch) +{ + grub_size_t size = (((struct grub_mm_header *) ch) - 1)->size * GRUB_MM_ALIGN; + grub_arch_sync_dma_caches (ch, size); + 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) +{ + return (1 << 31) | (dev.bus << 16) | (dev.device << 11) + | (dev.function << 8) | reg; +} + +void +grub_pci_iterate (grub_pci_iteratefunc_t hook) +{ + grub_pci_device_t dev; + grub_pci_address_t addr; + grub_pci_id_t id; + grub_uint32_t hdr; + + for (dev.bus = 0; dev.bus < GRUB_PCI_NUM_BUS; dev.bus++) + { + for (dev.device = 0; dev.device < GRUB_PCI_NUM_DEVICES; dev.device++) + { + for (dev.function = 0; dev.function < 8; dev.function++) + { + addr = grub_pci_make_address (dev, GRUB_PCI_REG_PCI_ID); + id = grub_pci_read (addr); + + /* Check if there is a device present. */ + if (id >> 16 == 0xFFFF) + { + if (dev.function == 0) + /* Devices are required to implement function 0, so if + it's missing then there is no device here. */ + break; + else + 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; + + /* Probe only func = 0 if the device if not multifunction */ + if (dev.function == 0) + { + addr = grub_pci_make_address (dev, GRUB_PCI_REG_CACHELINE); + hdr = grub_pci_read (addr); + if (!(hdr & 0x800000)) + break; + } + } + } + } +} diff --git a/grub-core/bus/usb/emu/usb.c b/grub-core/bus/usb/emu/usb.c new file mode 100644 index 0000000..38c5f01 --- /dev/null +++ b/grub-core/bus/usb/emu/usb.c @@ -0,0 +1,203 @@ +/* usb.c -- libusb USB support for GRUB. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2008 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 . + */ + +#include +#include +#include +#include +#include +#include + +GRUB_MOD_LICENSE ("GPLv3+"); + + +static struct grub_usb_controller_dev usb_controller = +{ + .name = "libusb" +}; + +static struct grub_usb_device *grub_usb_devs[128]; + +struct usb_bus *busses; + +static grub_err_t +grub_libusb_devices (void) + +{ + struct usb_bus *bus; + int last = 0; + + busses = usb_get_busses(); + + for (bus = busses; bus; bus = bus->next) + { + struct usb_device *usbdev; + struct grub_usb_device *dev; + + for (usbdev = bus->devices; usbdev; usbdev = usbdev->next) + { + struct usb_device_descriptor *desc = &usbdev->descriptor; + grub_err_t err; + + if (! desc->bcdUSB) + continue; + + dev = grub_malloc (sizeof (*dev)); + if (! dev) + return grub_errno; + + dev->data = usbdev; + + /* Fill in all descriptors. */ + err = grub_usb_device_initialize (dev); + if (err) + { + grub_errno = GRUB_ERR_NONE; + continue; + } + + /* Register the device. */ + grub_usb_devs[last++] = dev; + } + } + + return GRUB_USB_ERR_NONE; +} + +void +grub_usb_poll_devices (void) +{ + /* TODO: recheck grub_usb_devs */ +} + + +int +grub_usb_iterate (int (*hook) (grub_usb_device_t dev)) +{ + int i; + + for (i = 0; i < 128; i++) + { + if (grub_usb_devs[i]) + { + if (hook (grub_usb_devs[i])) + return 1; + } + } + + return 0; +} + +grub_usb_err_t +grub_usb_root_hub (grub_usb_controller_t controller __attribute__((unused))) +{ + return GRUB_USB_ERR_NONE; +} + +grub_usb_err_t +grub_usb_control_msg (grub_usb_device_t dev, grub_uint8_t reqtype, + grub_uint8_t request, grub_uint16_t value, + grub_uint16_t idx, grub_size_t size, char *data) +{ + usb_dev_handle *devh; + struct usb_device *d = dev->data; + + devh = usb_open (d); + if (usb_control_msg (devh, reqtype, request, + value, idx, data, size, 20) < 0) + { + usb_close (devh); + return GRUB_USB_ERR_STALL; + } + + usb_close (devh); + + return GRUB_USB_ERR_NONE; +} + +grub_usb_err_t +grub_usb_bulk_read (grub_usb_device_t dev, + int endpoint, grub_size_t size, char *data) +{ + usb_dev_handle *devh; + struct usb_device *d = dev->data; + + devh = usb_open (d); + if (usb_claim_interface (devh, 0) < 1) + { + usb_close (devh); + return GRUB_USB_ERR_STALL; + } + + if (usb_bulk_read (devh, endpoint, data, size, 20) < 1) + { + usb_close (devh); + return GRUB_USB_ERR_STALL; + } + + usb_release_interface (devh, 0); + usb_close (devh); + + return GRUB_USB_ERR_NONE; +} + +grub_usb_err_t +grub_usb_bulk_write (grub_usb_device_t dev, + int endpoint, grub_size_t size, char *data) +{ + usb_dev_handle *devh; + struct usb_device *d = dev->data; + + devh = usb_open (d); + if (usb_claim_interface (devh, 0) < 0) + goto fail; + + if (usb_bulk_write (devh, endpoint, data, size, 20) < 0) + goto fail; + + if (usb_release_interface (devh, 0) < 0) + goto fail; + + usb_close (devh); + + return GRUB_USB_ERR_NONE; + + fail: + usb_close (devh); + return GRUB_USB_ERR_STALL; +} + +GRUB_MOD_INIT (libusb) +{ + usb_init(); + usb_find_busses(); + usb_find_devices(); + + if (grub_libusb_devices ()) + return; + + grub_usb_controller_dev_register (&usb_controller); + + return; +} + +GRUB_MOD_FINI (libusb) +{ + return; +} diff --git a/grub-core/bus/usb/ohci.c b/grub-core/bus/usb/ohci.c new file mode 100644 index 0000000..df0d0f4 --- /dev/null +++ b/grub-core/bus/usb/ohci.c @@ -0,0 +1,1441 @@ +/* ohci.c - OHCI Support. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2008 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +GRUB_MOD_LICENSE ("GPLv3+"); + +struct grub_ohci_hcca +{ + /* Pointers to Interrupt Endpoint Descriptors. Not used by + GRUB. */ + grub_uint32_t inttable[32]; + + /* Current frame number. */ + grub_uint16_t framenumber; + + grub_uint16_t pad; + + /* List of completed TDs. */ + grub_uint32_t donehead; + + grub_uint8_t reserved[116]; +} __attribute__((packed)); + +/* OHCI General Transfer Descriptor */ +struct grub_ohci_td +{ + /* Information used to construct the TOKEN packet. */ + grub_uint32_t token; + grub_uint32_t buffer; /* LittleEndian physical address */ + grub_uint32_t next_td; /* LittleEndian physical address */ + grub_uint32_t buffer_end; /* LittleEndian physical address */ + /* next values are not for OHCI HW */ + volatile struct grub_ohci_td *link_td; /* pointer to next free/chained TD + * pointer as uint32 */ + grub_uint32_t prev_td_phys; /* we need it to find previous TD + * physical address in CPU endian */ + grub_uint32_t tr_index; /* index of TD in transfer */ + grub_uint8_t pad[8 - sizeof (volatile struct grub_ohci_td *)]; /* padding to 32 bytes */ +} __attribute__((packed)); + +/* OHCI Endpoint Descriptor. */ +struct grub_ohci_ed +{ + grub_uint32_t target; + grub_uint32_t td_tail; + grub_uint32_t td_head; + grub_uint32_t next_ed; +} __attribute__((packed)); + +typedef volatile struct grub_ohci_td *grub_ohci_td_t; +typedef volatile struct grub_ohci_ed *grub_ohci_ed_t; + +/* Experimental change of ED/TD allocation */ +/* Little bit similar as in UHCI */ +/* Implementation assumes: + * 32-bits architecture - XXX: fix for 64-bits + * memory allocated by grub_memalign_dma32 must be continuous + * in virtual and also in physical memory */ +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; + grub_ohci_ed_t ed_ctrl; /* EDs for CONTROL */ + grub_uint32_t ed_ctrl_addr; + struct grub_pci_dma_chunk *ed_ctrl_chunk; + grub_ohci_ed_t ed_bulk; /* EDs for BULK */ + grub_uint32_t ed_bulk_addr; + struct grub_pci_dma_chunk *ed_bulk_chunk; + grub_ohci_td_t td; /* TDs */ + grub_uint32_t td_addr; + struct grub_pci_dma_chunk *td_chunk; + struct grub_ohci *next; + grub_ohci_td_t td_free; /* Pointer to first free TD */ +}; + +static struct grub_ohci *ohci; + +typedef enum +{ + GRUB_OHCI_REG_REVISION = 0x00, + GRUB_OHCI_REG_CONTROL, + GRUB_OHCI_REG_CMDSTATUS, + GRUB_OHCI_REG_INTSTATUS, + GRUB_OHCI_REG_INTENA, + GRUB_OHCI_REG_INTDIS, + GRUB_OHCI_REG_HCCA, + GRUB_OHCI_REG_PERIODIC, + GRUB_OHCI_REG_CONTROLHEAD, + GRUB_OHCI_REG_CONTROLCURR, + GRUB_OHCI_REG_BULKHEAD, + 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_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) + +#define GRUB_OHCI_REG_CONTROL_BULK_ENABLE (1 << 5) +#define GRUB_OHCI_REG_CONTROL_CONTROL_ENABLE (1 << 4) + +#define GRUB_OHCI_RESET_CONNECT_CHANGE (1 << 16) +#define GRUB_OHCI_CTRL_EDS 256 +#define GRUB_OHCI_BULK_EDS 510 +#define GRUB_OHCI_TDS 640 + +#define GRUB_OHCI_ED_ADDR_MASK 0x7ff + +static inline grub_ohci_ed_t +grub_ohci_ed_phys2virt (struct grub_ohci *o, int bulk, grub_uint32_t x) +{ + if (!x) + return NULL; + if (bulk) + return (grub_ohci_ed_t) (x - o->ed_bulk_addr + + (grub_uint8_t *) o->ed_bulk); + return (grub_ohci_ed_t) (x - o->ed_ctrl_addr + + (grub_uint8_t *) o->ed_ctrl); +} + +static grub_uint32_t +grub_ohci_virt_to_phys (struct grub_ohci *o, int bulk, grub_ohci_ed_t x) +{ + if (!x) + return 0; + + if (bulk) + return (grub_uint8_t *) x - (grub_uint8_t *) o->ed_bulk + o->ed_bulk_addr; + return (grub_uint8_t *) x - (grub_uint8_t *) o->ed_ctrl + o->ed_ctrl_addr; +} + +static inline grub_ohci_td_t +grub_ohci_td_phys2virt (struct grub_ohci *o, grub_uint32_t x) +{ + if (!x) + return NULL; + return (grub_ohci_td_t) (x - o->td_addr + (grub_uint8_t *) o->td); +} + +static grub_uint32_t +grub_ohci_td_virt2phys (struct grub_ohci *o, grub_ohci_td_t x) +{ + if (!x) + return 0; + return (grub_uint8_t *)x - (grub_uint8_t *)o->td + o->td_addr; +} + + +static grub_uint32_t +grub_ohci_readreg32 (struct grub_ohci *o, grub_ohci_reg_t reg) +{ + return grub_le_to_cpu32 (*(o->iobase + reg)); +} + +static void +grub_ohci_writereg32 (struct grub_ohci *o, + grub_ohci_reg_t reg, grub_uint32_t val) +{ + *(o->iobase + reg) = grub_cpu_to_le32 (val); +} + + + +/* Iterate over all PCI devices. Determine if a device is an OHCI + 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) +{ + grub_uint32_t interf; + grub_uint32_t base; + grub_pci_address_t addr; + struct grub_ohci *o; + grub_uint32_t revision; + int j; + + /* Determine IO base address. */ + grub_dprintf ("ohci", "pciid = %x\n", pciid); + + if (pciid == GRUB_CS5536_PCIID) + { + grub_uint64_t basereg; + + 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; +#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; + grub_memset ((void*)o, 0, sizeof (*o)); + 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_chunk = grub_memalign_dma32 (256, 256); + if (! o->hcca_chunk) + goto fail; + o->hcca = grub_dma_get_virt (o->hcca_chunk); + o->hcca_addr = grub_dma_get_phys (o->hcca_chunk); + grub_memset ((void*)o->hcca, 0, sizeof(*o->hcca)); + grub_dprintf ("ohci", "hcca: chunk=%p, virt=%p, phys=0x%02x\n", + o->hcca_chunk, o->hcca, o->hcca_addr); + + /* Reserve memory for ctrl EDs. */ + o->ed_ctrl_chunk = grub_memalign_dma32 (16, sizeof(struct grub_ohci_ed)*GRUB_OHCI_CTRL_EDS); + if (! o->ed_ctrl_chunk) + goto fail; + o->ed_ctrl = grub_dma_get_virt (o->ed_ctrl_chunk); + o->ed_ctrl_addr = grub_dma_get_phys (o->ed_ctrl_chunk); + /* Preset EDs */ + grub_memset ((void*)o->ed_ctrl, 0, sizeof(struct grub_ohci_ed) * GRUB_OHCI_CTRL_EDS); + for (j=0; j < GRUB_OHCI_CTRL_EDS; j++) + o->ed_ctrl[j].target = grub_cpu_to_le32 (1 << 14); /* skip */ + + grub_dprintf ("ohci", "EDs-C: chunk=%p, virt=%p, phys=0x%02x\n", + o->ed_ctrl_chunk, o->ed_ctrl, o->ed_ctrl_addr); + + /* Reserve memory for bulk EDs. */ + o->ed_bulk_chunk = grub_memalign_dma32 (16, sizeof(struct grub_ohci_ed)*GRUB_OHCI_BULK_EDS); + if (! o->ed_bulk_chunk) + goto fail; + o->ed_bulk = grub_dma_get_virt (o->ed_bulk_chunk); + o->ed_bulk_addr = grub_dma_get_phys (o->ed_bulk_chunk); + /* Preset EDs */ + grub_memset ((void*)o->ed_bulk, 0, sizeof(struct grub_ohci_ed) * GRUB_OHCI_BULK_EDS); + for (j=0; j < GRUB_OHCI_BULK_EDS; j++) + o->ed_bulk[j].target = grub_cpu_to_le32 (1 << 14); /* skip */ + + grub_dprintf ("ohci", "EDs-B: chunk=%p, virt=%p, phys=0x%02x\n", + o->ed_bulk_chunk, o->ed_bulk, o->ed_bulk_addr); + + /* Reserve memory for TDs. */ + o->td_chunk = grub_memalign_dma32 (32, sizeof(struct grub_ohci_td)*GRUB_OHCI_TDS); + /* Why is it aligned on 32 boundary if spec. says 16 ? + * We have structure 32 bytes long and we don't want cross + * 4K boundary inside structure. */ + if (! o->td_chunk) + goto fail; + o->td_free = o->td = grub_dma_get_virt (o->td_chunk); + o->td_addr = grub_dma_get_phys (o->td_chunk); + /* Preset free TDs chain in TDs */ + grub_memset ((void*)o->td, 0, sizeof(struct grub_ohci_td) * GRUB_OHCI_TDS); + for (j=0; j < (GRUB_OHCI_TDS-1); j++) + o->td[j].link_td = &o->td[j+1]; + + grub_dprintf ("ohci", "TDs: chunk=%p, virt=%p, phys=0x%02x\n", + o->td_chunk, o->td, o->td_addr); + + /* Check if the OHCI revision is actually 1.0 as supported. */ + revision = grub_ohci_readreg32 (o, GRUB_OHCI_REG_REVISION); + grub_dprintf ("ohci", "OHCI revision=0x%02x\n", revision & 0xFF); + if ((revision & 0xFF) != 0x10) + goto fail; + + { + 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"); + + 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. */ + 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 */ + /* We don't want modify CONTROL/BULK HEAD registers. + * So we assign to HEAD registers zero ED from related array + * and we will not use this ED, it will be always skipped. + * It should not produce notable performance penalty (I hope). */ + grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLHEAD, o->ed_ctrl_addr); + grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLCURR, 0); + grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKHEAD, o->ed_bulk_addr); + 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 + enable CONTROL and BULK LIST. */ + grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL, + (2 << 6) + | GRUB_OHCI_REG_CONTROL_CONTROL_ENABLE + | GRUB_OHCI_REG_CONTROL_BULK_ENABLE ); + 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); +#if 0 /* We don't need it at all, handled via hotplugging */ + /* Now we have hot-plugging, we need to wait for stable power only */ + grub_millisleep (100); +#endif + + /* Link to ohci now that initialisation is successful. */ + o->next = ohci; + ohci = o; + + return 0; + + fail: + if (o) + grub_dma_free (o->td_chunk); + grub_dma_free (o->ed_bulk_chunk); + grub_dma_free (o->ed_ctrl_chunk); + grub_dma_free (o->hcca_chunk); + grub_free (o); + + return 0; +} + + +static void +grub_ohci_inithw (void) +{ + grub_pci_iterate (grub_ohci_pci_iter); +} + + + +static int +grub_ohci_iterate (int (*hook) (grub_usb_controller_t dev)) +{ + struct grub_ohci *o; + struct grub_usb_controller dev; + + for (o = ohci; o; o = o->next) + { + dev.data = o; + if (hook (&dev)) + return 1; + } + + return 0; +} + +static grub_ohci_ed_t +grub_ohci_find_ed (struct grub_ohci *o, int bulk, grub_uint32_t target) +{ + grub_ohci_ed_t ed, ed_next; + grub_uint32_t target_addr = target & GRUB_OHCI_ED_ADDR_MASK; + int count; + int i; + + /* Use proper values and structures. */ + if (bulk) + { + count = GRUB_OHCI_BULK_EDS; + ed = o->ed_bulk; + ed_next = grub_ohci_ed_phys2virt(o, bulk, + grub_le_to_cpu32 (ed->next_ed) ); + } + else + { + count = GRUB_OHCI_CTRL_EDS; + ed = o->ed_ctrl; + ed_next = grub_ohci_ed_phys2virt(o, bulk, + grub_le_to_cpu32 (ed->next_ed) ); + } + + /* First try to find existing ED with proper target address */ + for (i = 0; ; ) + { + if (i && /* We ignore zero ED */ + ((ed->target & GRUB_OHCI_ED_ADDR_MASK) == target_addr)) + return ed; /* Found proper existing ED */ + i++; + if (ed_next && (i < count)) + { + ed = ed_next; + ed_next = grub_ohci_ed_phys2virt(o, bulk, + grub_le_to_cpu32 (ed->next_ed) ); + continue; + } + break; + } + /* ED with target_addr does not exist, we have to add it */ + /* Have we any free ED in array ? */ + if (i >= count) /* No. */ + return NULL; + /* Currently we simply take next ED in array, no allocation + * function is used. It should be no problem until hot-plugging + * will be implemented, i.e. until we will need to de-allocate EDs + * of unplugged devices. */ + /* We can link new ED to previous ED safely as the new ED should + * still have set skip bit. */ + ed->next_ed = grub_cpu_to_le32 ( grub_ohci_virt_to_phys (o, + bulk, &ed[1])); + return &ed[1]; +} + +static grub_ohci_td_t +grub_ohci_alloc_td (struct grub_ohci *o) +{ + grub_ohci_td_t ret; + + /* Check if there is a Transfer Descriptor available. */ + if (! o->td_free) + return NULL; + + ret = o->td_free; /* Take current free TD */ + o->td_free = (grub_ohci_td_t)ret->link_td; /* Advance to next free TD in chain */ + ret->link_td = 0; /* Reset link_td in allocated TD */ + return ret; +} + +static void +grub_ohci_free_td (struct grub_ohci *o, grub_ohci_td_t td) +{ + grub_memset ( (void*)td, 0, sizeof(struct grub_ohci_td) ); + td->link_td = o->td_free; /* Cahin new free TD & rest */ + o->td_free = td; /* Change address of first free TD */ +} + +static void +grub_ohci_free_tds (struct grub_ohci *o, grub_ohci_td_t td) +{ + if (!td) + return; + + /* Unchain first TD from previous TD if it is chained */ + if (td->prev_td_phys) + { + grub_ohci_td_t td_prev_virt = grub_ohci_td_phys2virt(o, + td->prev_td_phys); + + if (td == (grub_ohci_td_t) td_prev_virt->link_td) + td_prev_virt->link_td = 0; + } + + /* Free all TDs from td (chained by link_td) */ + while (td) + { + grub_ohci_td_t tdprev; + + /* Unlink the queue. */ + tdprev = td; + td = (grub_ohci_td_t) td->link_td; + + /* Free the TD. */ + grub_ohci_free_td (o, tdprev); + } +} + +static void +grub_ohci_transaction (grub_ohci_td_t td, + grub_transfer_type_t type, unsigned int toggle, + grub_size_t size, grub_uint32_t data) +{ + grub_uint32_t token; + grub_uint32_t buffer; + grub_uint32_t buffer_end; + + grub_dprintf ("ohci", "OHCI transaction td=%p type=%d, toggle=%d, size=%lu\n", + td, type, toggle, (unsigned long) size); + + switch (type) + { + case GRUB_USB_TRANSFER_TYPE_SETUP: + token = 0 << 19; + break; + case GRUB_USB_TRANSFER_TYPE_IN: + token = 2 << 19; + break; + case GRUB_USB_TRANSFER_TYPE_OUT: + token = 1 << 19; + break; + default: + token = 0; + break; + } + + /* Set the token */ + token |= ( 7 << 21); /* Never generate interrupt */ + token |= toggle << 24; + token |= 1 << 25; + + /* 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->next_td = 0; +} + +struct grub_ohci_transfer_controller_data +{ + grub_uint32_t tderr_phys; + grub_uint32_t td_last_phys; + grub_ohci_ed_t ed_virt; + grub_ohci_td_t td_current_virt; + grub_ohci_td_t td_head_virt; +}; + +static grub_usb_err_t +grub_ohci_setup_transfer (grub_usb_controller_t dev, + grub_usb_transfer_t transfer) +{ + struct grub_ohci *o = (struct grub_ohci *) dev->data; + int bulk = 0; + grub_ohci_td_t td_next_virt; + grub_uint32_t target; + grub_uint32_t td_head_phys; + grub_uint32_t td_tail_phys; + int i; + struct grub_ohci_transfer_controller_data *cdata; + + cdata = grub_zalloc (sizeof (*cdata)); + if (!cdata) + return GRUB_USB_ERR_INTERNAL; + + /* Pre-set target for ED - we need it to find proper ED */ + /* Set the device address. */ + target = transfer->devaddr; + /* 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; + /* Set the maximum packet size. */ + target |= transfer->max << 16; + + /* Determine if transfer type is bulk - we need to select proper ED */ + switch (transfer->type) + { + case GRUB_USB_TRANSACTION_TYPE_BULK: + bulk = 1; + break; + + case GRUB_USB_TRANSACTION_TYPE_CONTROL: + break; + + default: + grub_free (cdata); + return GRUB_USB_ERR_INTERNAL; + } + + /* Find proper ED or add new ED */ + cdata->ed_virt = grub_ohci_find_ed (o, bulk, target); + if (!cdata->ed_virt) + { + grub_dprintf ("ohci","Fatal: No free ED !\n"); + grub_free (cdata); + return GRUB_USB_ERR_INTERNAL; + } + + /* Take pointer to first TD from ED */ + td_head_phys = grub_le_to_cpu32 (cdata->ed_virt->td_head) & ~0xf; + td_tail_phys = grub_le_to_cpu32 (cdata->ed_virt->td_tail) & ~0xf; + + /* Sanity check - td_head should be equal to td_tail */ + if (td_head_phys != td_tail_phys) /* Should never happen ! */ + { + grub_dprintf ("ohci", "Fatal: HEAD is not equal to TAIL !\n"); + grub_dprintf ("ohci", "HEAD = 0x%02x, TAIL = 0x%02x\n", + td_head_phys, td_tail_phys); + /* XXX: Fix: What to do ? */ + grub_free (cdata); + return GRUB_USB_ERR_INTERNAL; + } + + /* Now we should handle first TD. If ED is newly allocated, + * we must allocate the first TD. */ + if (!td_head_phys) + { + cdata->td_head_virt = grub_ohci_alloc_td (o); + if (!cdata->td_head_virt) + return GRUB_USB_ERR_INTERNAL; /* We don't need de-allocate ED */ + /* We can set td_head only when ED is not active, i.e. + * when it is newly allocated. */ + cdata->ed_virt->td_head + = grub_cpu_to_le32 (grub_ohci_td_virt2phys (o, cdata->td_head_virt)); + cdata->ed_virt->td_tail = cdata->ed_virt->td_head; + } + else + cdata->td_head_virt = grub_ohci_td_phys2virt ( o, td_head_phys ); + + /* Set TDs */ + cdata->td_last_phys = td_head_phys; /* initial value to make compiler happy... */ + for (i = 0, cdata->td_current_virt = cdata->td_head_virt; + i < transfer->transcnt; i++) + { + grub_usb_transaction_t tr = &transfer->transactions[i]; + + grub_ohci_transaction (cdata->td_current_virt, tr->pid, tr->toggle, + tr->size, tr->data); + + /* Set index of TD in transfer */ + cdata->td_current_virt->tr_index = (grub_uint32_t) i; + + /* Remember last used (processed) TD phys. addr. */ + cdata->td_last_phys = grub_ohci_td_virt2phys (o, cdata->td_current_virt); + + /* Allocate next TD */ + td_next_virt = grub_ohci_alloc_td (o); + if (!td_next_virt) /* No free TD, cancel transfer and free TDs except head TD */ + { + if (i) /* if i==0 we have nothing to free... */ + grub_ohci_free_tds (o, grub_ohci_td_phys2virt(o, + grub_le_to_cpu32 (cdata->td_head_virt->next_td))); + /* Reset head TD */ + grub_memset ( (void*)cdata->td_head_virt, 0, + sizeof(struct grub_ohci_td) ); + grub_dprintf ("ohci", "Fatal: No free TD !"); + grub_free (cdata); + return GRUB_USB_ERR_INTERNAL; + } + + /* Chain TDs */ + + cdata->td_current_virt->link_td = td_next_virt; + cdata->td_current_virt->next_td = grub_cpu_to_le32 ( + grub_ohci_td_virt2phys (o, + td_next_virt) ); + td_next_virt->prev_td_phys = grub_ohci_td_virt2phys (o, + cdata->td_current_virt); + cdata->td_current_virt = td_next_virt; + } + + grub_dprintf ("ohci", "Tail TD (not processed) = %p\n", + cdata->td_current_virt); + + /* Setup the Endpoint Descriptor for transfer. */ + /* First set necessary fields in TARGET but keep (or set) skip bit */ + /* Note: It could be simpler if speed, format and max. packet + * size never change after first allocation of ED. + * But unfortunately max. packet size may change during initial + * setup sequence and we must handle it. */ + cdata->ed_virt->target = grub_cpu_to_le32 (target | (1 << 14)); + /* Set td_tail */ + cdata->ed_virt->td_tail + = grub_cpu_to_le32 (grub_ohci_td_virt2phys (o, cdata->td_current_virt)); + /* Now reset skip bit */ + cdata->ed_virt->target = grub_cpu_to_le32 (target); + /* ed_virt->td_head = grub_cpu_to_le32 (td_head); Must not be changed, it is maintained by OHCI */ + /* ed_virt->next_ed = grub_cpu_to_le32 (0); Handled by grub_ohci_find_ed, do not change ! */ + + grub_dprintf ("ohci", "program OHCI\n"); + + /* Program the OHCI to actually transfer. */ + switch (transfer->type) + { + case GRUB_USB_TRANSACTION_TYPE_BULK: + { + grub_dprintf ("ohci", "BULK list filled\n"); + /* Set BulkListFilled. */ + grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, 1 << 2); + /* Read back of register should ensure it is really written */ + grub_ohci_readreg32 (o, GRUB_OHCI_REG_CMDSTATUS); + break; + } + + case GRUB_USB_TRANSACTION_TYPE_CONTROL: + { + grub_dprintf ("ohci", "CONTROL list filled\n"); + /* Set ControlListFilled. */ + grub_ohci_writereg32 (o, GRUB_OHCI_REG_CMDSTATUS, 1 << 1); + /* Read back of register should ensure it is really written */ + grub_ohci_readreg32 (o, GRUB_OHCI_REG_CMDSTATUS); + break; + } + } + + transfer->controller_data = cdata; + + return GRUB_USB_ERR_NONE; +} + +static void +pre_finish_transfer (grub_usb_controller_t dev, + grub_usb_transfer_t transfer) +{ + struct grub_ohci *o = dev->data; + struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data; + grub_uint32_t target; + grub_uint32_t status; + grub_uint32_t control; + grub_uint32_t intstatus; + + /* There are many ways how the loop above can finish: + * - normally without any error via INTSTATUS WDH bit + * : tderr_phys == td_last_phys, td_head == td_tail + * - normally with error via HALT bit in ED TD HEAD + * : td_head = next TD after TD with error + * : tderr_phys = last processed and retired TD with error, + * i.e. should be != 0 + * : if bad_OHCI == TRUE, tderr_phys will be probably invalid + * - unrecoverable error - I never seen it but it could be + * : err_unrec == TRUE, other values can contain anything... + * - timeout, it can be caused by: + * -- bad USB device - some devices have some bugs, see Linux source + * and related links + * -- bad OHCI controller - e.g. lost interrupts or does not set + * proper bits in INTSTATUS when real IRQ not enabled etc., + * see Linux source and related links + * One known bug is handled - if transfer finished + * successfully (i.e. HEAD==TAIL, last transfer TD is retired, + * HALT bit is not set) and WDH bit is not set in INTSTATUS - in + * this case we set o->bad_OHCI=TRUE and do alternate loop + * and error handling - but there is problem how to find retired + * TD with error code if HALT occurs and if DONEHEAD is not + * working - we need to find TD previous to current ED HEAD + * -- bad code of this driver or some unknown reasons - :-( + * it can be e.g. bad handling of EDs/TDs/toggle bit... + */ + + /* Remember target for debug and set skip flag in ED */ + /* It should be normaly not necessary but we need it at least + * in case of timeout */ + target = grub_le_to_cpu32 ( cdata->ed_virt->target ); + cdata->ed_virt->target = grub_cpu_to_le32 (target | (1 << 14)); + /* Read registers for debug - they should be read now because + * debug prints case unwanted delays, so something can happen + * in the meantime... */ + control = grub_ohci_readreg32 (o, GRUB_OHCI_REG_CONTROL); + status = grub_ohci_readreg32 (o, GRUB_OHCI_REG_CMDSTATUS); + intstatus = grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS); + /* Now print debug values - to have full info what happened */ + grub_dprintf ("ohci", "loop finished: control=0x%02x status=0x%02x\n", + control, status); + grub_dprintf ("ohci", "intstatus=0x%02x, td_last_phys=0x%02x\n", + intstatus, cdata->td_last_phys); + grub_dprintf ("ohci", "TARGET=0x%02x, HEAD=0x%02x, TAIL=0x%02x\n", + target, + grub_le_to_cpu32 (cdata->ed_virt->td_head), + grub_le_to_cpu32 (cdata->ed_virt->td_tail) ); + +} + +static void +finish_transfer (grub_usb_controller_t dev, + grub_usb_transfer_t transfer) +{ + struct grub_ohci *o = dev->data; + struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data; + + /* Set empty ED - set HEAD = TAIL = last (not processed) TD */ + cdata->ed_virt->td_head = grub_cpu_to_le32 (grub_le_to_cpu32 (cdata->ed_virt->td_tail) & ~0xf); + + /* At this point always should be: + * ED has skip bit set and halted or empty or after next SOF, + * i.e. it is safe to free all TDs except last not processed + * ED HEAD == TAIL == phys. addr. of td_current_virt */ + + /* Un-chainig of last TD */ + if (cdata->td_current_virt->prev_td_phys) + { + grub_ohci_td_t td_prev_virt + = grub_ohci_td_phys2virt (o, cdata->td_current_virt->prev_td_phys); + + if (cdata->td_current_virt == (grub_ohci_td_t) td_prev_virt->link_td) + td_prev_virt->link_td = 0; + + cdata->td_current_virt->prev_td_phys = 0; + } + + grub_dprintf ("ohci", "OHCI finished, freeing\n"); + grub_ohci_free_tds (o, cdata->td_head_virt); + grub_free (cdata); +} + +static grub_usb_err_t +parse_halt (grub_usb_controller_t dev, + grub_usb_transfer_t transfer, + grub_size_t *actual) +{ + struct grub_ohci *o = dev->data; + struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data; + grub_uint8_t errcode = 0; + grub_usb_err_t err = GRUB_USB_ERR_NAK; + grub_ohci_td_t tderr_virt = NULL; + + *actual = 0; + + pre_finish_transfer (dev, transfer); + + /* First we must get proper tderr_phys value */ + /* Retired TD with error should be previous TD to ED->td_head */ + cdata->tderr_phys = grub_ohci_td_phys2virt (o, + grub_le_to_cpu32 (cdata->ed_virt->td_head) & ~0xf ) + ->prev_td_phys; + + /* Prepare pointer to last processed TD and get error code */ + tderr_virt = grub_ohci_td_phys2virt (o, cdata->tderr_phys); + /* Set index of last processed TD */ + if (tderr_virt) + { + errcode = grub_le_to_cpu32 (tderr_virt->token) >> 28; + transfer->last_trans = tderr_virt->tr_index; + } + else + transfer->last_trans = -1; + + /* Evaluation of error code */ + grub_dprintf ("ohci", "OHCI tderr_phys=0x%02x, errcode=0x%02x\n", + cdata->tderr_phys, errcode); + switch (errcode) + { + case 0: + /* XXX: Should not happen! */ + grub_error (GRUB_ERR_IO, "OHCI failed without reporting the reason"); + err = GRUB_USB_ERR_INTERNAL; + break; + + case 1: + /* XXX: CRC error. */ + err = GRUB_USB_ERR_TIMEOUT; + break; + + case 2: + err = GRUB_USB_ERR_BITSTUFF; + break; + + case 3: + /* XXX: Data Toggle error. */ + err = GRUB_USB_ERR_DATA; + break; + + case 4: + err = GRUB_USB_ERR_STALL; + break; + + case 5: + /* XXX: Not responding. */ + err = GRUB_USB_ERR_TIMEOUT; + break; + + case 6: + /* XXX: PID Check bits failed. */ + err = GRUB_USB_ERR_BABBLE; + break; + + case 7: + /* XXX: PID unexpected failed. */ + err = GRUB_USB_ERR_BABBLE; + break; + + case 8: + /* XXX: Data overrun error. */ + err = GRUB_USB_ERR_DATA; + grub_dprintf ("ohci", "Overrun, failed TD address: %p, index: %d\n", + tderr_virt, tderr_virt->tr_index); + break; + + case 9: + /* XXX: Data underrun error. */ + grub_dprintf ("ohci", "Underrun, failed TD address: %p, index: %d\n", + tderr_virt, tderr_virt->tr_index); + if (transfer->last_trans == -1) + break; + *actual = transfer->transactions[transfer->last_trans].size + - (grub_le_to_cpu32 (tderr_virt->buffer_end) + - grub_le_to_cpu32 (tderr_virt->buffer)) + + transfer->transactions[transfer->last_trans].preceding; + err = GRUB_USB_ERR_NONE; + break; + + case 10: + /* XXX: Reserved. */ + err = GRUB_USB_ERR_NAK; + break; + + case 11: + /* XXX: Reserved. */ + err = GRUB_USB_ERR_NAK; + break; + + case 12: + /* XXX: Buffer overrun. */ + err = GRUB_USB_ERR_DATA; + break; + + case 13: + /* XXX: Buffer underrun. */ + err = GRUB_USB_ERR_DATA; + break; + + default: + err = GRUB_USB_ERR_NAK; + break; + } + + finish_transfer (dev, transfer); + + return err; +} + +static grub_usb_err_t +parse_success (grub_usb_controller_t dev, + grub_usb_transfer_t transfer, + grub_size_t *actual) +{ + struct grub_ohci *o = dev->data; + struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data; + grub_ohci_td_t tderr_virt = NULL; + + pre_finish_transfer (dev, transfer); + + /* I hope we can do it as transfer (most probably) finished OK */ + cdata->tderr_phys = cdata->td_last_phys; + + /* Prepare pointer to last processed TD */ + tderr_virt = grub_ohci_td_phys2virt (o, cdata->tderr_phys); + + /* Set index of last processed TD */ + if (tderr_virt) + transfer->last_trans = tderr_virt->tr_index; + else + transfer->last_trans = -1; + *actual = transfer->size + 1; + + finish_transfer (dev, transfer); + + return GRUB_USB_ERR_NONE; +} + +static grub_usb_err_t +parse_unrec (grub_usb_controller_t dev, + grub_usb_transfer_t transfer, + grub_size_t *actual) +{ + struct grub_ohci *o = dev->data; + + *actual = 0; + + pre_finish_transfer (dev, transfer); + + /* Don't try to get error code and last processed TD for proper + * toggle bit value - anything can be invalid */ + grub_dprintf("ohci", "Unrecoverable error!"); + + /* 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. */ + /* Read back of register should ensure it is really written */ + grub_ohci_readreg32 (o, GRUB_OHCI_REG_CMDSTATUS); + 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, o->ed_ctrl_addr); + grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLCURR, 0); + grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKHEAD, o->ed_bulk_addr); + grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKCURR, 0); + /* Read back of register should ensure it is really written */ + grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS); + + /* Enable the OHCI. */ + grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL, + (2 << 6) + | GRUB_OHCI_REG_CONTROL_CONTROL_ENABLE + | GRUB_OHCI_REG_CONTROL_BULK_ENABLE ); + finish_transfer (dev, transfer); + + return GRUB_USB_ERR_UNRECOVERABLE; +} + +static grub_usb_err_t +grub_ohci_check_transfer (grub_usb_controller_t dev, + grub_usb_transfer_t transfer, + grub_size_t *actual) +{ + struct grub_ohci *o = dev->data; + struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data; + grub_uint32_t intstatus; + + /* Check transfer status */ + intstatus = grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS); + + if ((intstatus & 0x10) != 0) + /* Unrecoverable error - only reset can help...! */ + return parse_unrec (dev, transfer, actual); + + /* Detected a HALT. */ + if ((grub_le_to_cpu32 (cdata->ed_virt->td_head) & 1)) + return parse_halt (dev, transfer, actual); + + /* Finished ED detection */ + if ( (grub_le_to_cpu32 (cdata->ed_virt->td_head) & ~0xf) == + (grub_le_to_cpu32 (cdata->ed_virt->td_tail) & ~0xf) ) /* Empty ED */ + { + /* Check the HALT bit */ + /* It looks like nonsense - it was tested previously... + * but it can change because OHCI is working + * simultaneously via DMA... */ + if (grub_le_to_cpu32 (cdata->ed_virt->td_head) & 1) + return parse_halt (dev, transfer, actual); + else + return parse_success (dev, transfer, actual); + } + + return GRUB_USB_ERR_WAIT; +} + +static grub_usb_err_t +grub_ohci_cancel_transfer (grub_usb_controller_t dev, + grub_usb_transfer_t transfer) +{ + struct grub_ohci *o = dev->data; + struct grub_ohci_transfer_controller_data *cdata = transfer->controller_data; + grub_ohci_td_t tderr_virt = NULL; + + pre_finish_transfer (dev, transfer); + + grub_dprintf("ohci", "Timeout !\n"); + + /* We should wait for next SOF to be sure that ED is unaccessed + * by OHCI */ + /* 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); + + /* Possible retired TD with error should be previous TD to ED->td_head */ + cdata->tderr_phys + = grub_ohci_td_phys2virt (o, grub_le_to_cpu32 (cdata->ed_virt->td_head) + & ~0xf)->prev_td_phys; + + tderr_virt = grub_ohci_td_phys2virt (o,cdata-> tderr_phys); + + grub_dprintf ("ohci", "Cancel: tderr_phys=0x%x, tderr_virt=%p\n", + cdata->tderr_phys, tderr_virt); + + if (tderr_virt) + transfer->last_trans = tderr_virt->tr_index; + else + transfer->last_trans = -1; + + finish_transfer (dev, transfer); + + return GRUB_USB_ERR_NONE; +} + +static grub_err_t +grub_ohci_portstatus (grub_usb_controller_t dev, + unsigned int port, unsigned int enable) +{ + struct grub_ohci *o = (struct grub_ohci *) dev->data; + grub_uint64_t endtime; + int i; + + grub_dprintf ("ohci", "begin of portstatus=0x%02x\n", + grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port)); + + if (!enable) /* We don't need reset port */ + { + /* Disable the port and wait for it. */ + grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port, + GRUB_OHCI_CLEAR_PORT_ENABLE); + endtime = grub_get_time_ms () + 1000; + while ((grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port) + & (1 << 1))) + if (grub_get_time_ms () > endtime) + return grub_error (GRUB_ERR_IO, "OHCI Timed out - disable"); + + grub_dprintf ("ohci", "end of portstatus=0x%02x\n", + grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port)); + return GRUB_ERR_NONE; + } + + /* OHCI does one reset signal 10ms long but USB spec. + * requests 50ms for root hub (no need to be continuous). + * So, we do reset 5 times... */ + for (i = 0; i < 5; i++) + { + /* Reset the port - timing of reset is done by OHCI */ + grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port, + GRUB_OHCI_SET_PORT_RESET); + + /* Wait for reset completion */ + endtime = grub_get_time_ms () + 1000; + while (! (grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port) + & GRUB_OHCI_SET_PORT_RESET_STATUS_CHANGE)) + if (grub_get_time_ms () > endtime) + return grub_error (GRUB_ERR_IO, "OHCI Timed out - reset"); + + /* End the reset signaling - reset the reset status change */ + grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port, + GRUB_OHCI_SET_PORT_RESET_STATUS_CHANGE); + grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port); + } + + /* Enable port */ + grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port, + GRUB_OHCI_SET_PORT_ENABLE); + grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port); + + /* Wait for signal enabled */ + endtime = grub_get_time_ms () + 1000; + while (! (grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port) + & (1 << 1))) + if (grub_get_time_ms () > endtime) + return grub_error (GRUB_ERR_IO, "OHCI Timed out - enable"); + + /* Reset bit Connect Status Change */ + grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port, + GRUB_OHCI_RESET_CONNECT_CHANGE); + + /* "Reset recovery time" (USB spec.) */ + 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; +} + +static grub_usb_speed_t +grub_ohci_detect_dev (grub_usb_controller_t dev, int port, int *changed) +{ + struct grub_ohci *o = (struct grub_ohci *) dev->data; + grub_uint32_t status; + + status = grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port); + + grub_dprintf ("ohci", "detect_dev status=0x%02x\n", status); + + /* Connect Status Change bit - it detects change of connection */ + if (status & GRUB_OHCI_RESET_CONNECT_CHANGE) + { + *changed = 1; + /* Reset bit Connect Status Change */ + grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port, + GRUB_OHCI_RESET_CONNECT_CHANGE); + } + else + *changed = 0; + + if (! (status & 1)) + return GRUB_USB_SPEED_NONE; + else if (status & (1 << 9)) + return GRUB_USB_SPEED_LOW; + else + return GRUB_USB_SPEED_FULL; +} + +static int +grub_ohci_hubports (grub_usb_controller_t dev) +{ + struct grub_ohci *o = (struct grub_ohci *) dev->data; + grub_uint32_t portinfo; + + portinfo = grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBA); + + grub_dprintf ("ohci", "root hub ports=%d\n", portinfo & 0xFF); + + 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; + grub_uint64_t maxtime; + + /* Set skip in all EDs */ + if (o->ed_bulk) + for (i=0; i < GRUB_OHCI_BULK_EDS; i++) + o->ed_bulk[i].target |= grub_cpu_to_le32 (1 << 14); /* skip */ + if (o->ed_ctrl) + for (i=0; i < GRUB_OHCI_CTRL_EDS; i++) + o->ed_ctrl[i].target |= grub_cpu_to_le32 (1 << 14); /* skip */ + + /* We should wait for next SOF to be sure that all EDs are + * unaccessed by OHCI. But OHCI can be non-functional, so + * more than 1ms timeout have to be applied. */ + /* SF bit reset. (SF bit indicates Start Of Frame (SOF) packet) */ + grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, (1<<2)); + maxtime = grub_get_time_ms () + 2; + /* Wait for new SOF or timeout */ + while ( ((grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS) & 0x4) + == 0) || (grub_get_time_ms () >= maxtime) ); + + 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_CMDSTATUS, 1); + grub_millisleep (1); + 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); + /* Read back of register should ensure it is really written */ + grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS); + +#if 0 /* Is this necessary before booting? Probably not .(?) + * But it must be done if module is removed ! (Or not ?) + * How to do it ? - Probably grub_ohci_restore_hw should be more + * complicated. (?) + * (If we do it, we need to reallocate EDs and TDs in function + * grub_ohci_restore_hw ! */ + + /* Free allocated EDs and TDs */ + grub_dma_free (o->td_chunk); + grub_dma_free (o->ed_bulk_chunk); + grub_dma_free (o->ed_ctrl_chunk); + grub_dma_free (o->hcca_chunk); +#endif + } + 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); + o->hcca->donehead = 0; + grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, 0x7f); /* Clears everything */ + grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLHEAD, o->ed_ctrl_addr); + grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLCURR, 0); + grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKHEAD, o->ed_bulk_addr); + grub_ohci_writereg32 (o, GRUB_OHCI_REG_BULKCURR, 0); + /* Read back of register should ensure it is really written */ + grub_ohci_readreg32 (o, GRUB_OHCI_REG_INTSTATUS); + + /* Enable the OHCI. */ + grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROL, + (2 << 6) + | GRUB_OHCI_REG_CONTROL_CONTROL_ENABLE + | GRUB_OHCI_REG_CONTROL_BULK_ENABLE ); + } + + return GRUB_ERR_NONE; +} + + +static struct grub_usb_controller_dev usb_controller = +{ + .name = "ohci", + .iterate = grub_ohci_iterate, + .setup_transfer = grub_ohci_setup_transfer, + .check_transfer = grub_ohci_check_transfer, + .cancel_transfer = grub_ohci_cancel_transfer, + .hubports = grub_ohci_hubports, + .portstatus = grub_ohci_portstatus, + .detect_dev = grub_ohci_detect_dev +}; + +GRUB_MOD_INIT(ohci) +{ + COMPILE_TIME_ASSERT (sizeof (struct grub_ohci_td) == 32); + COMPILE_TIME_ASSERT (sizeof (struct grub_ohci_ed) == 16); + 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); +} diff --git a/grub-core/bus/usb/serial/common.c b/grub-core/bus/usb/serial/common.c new file mode 100644 index 0000000..55d1884 --- /dev/null +++ b/grub-core/bus/usb/serial/common.c @@ -0,0 +1,134 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2000,2001,2002,2003,2004,2005,2007,2008,2009,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 . + */ + +#include +#include +#include + +GRUB_MOD_LICENSE ("GPLv3+"); + +void +grub_usbserial_fini (struct grub_serial_port *port) +{ + port->usbdev->config[port->configno].interf[port->interfno].detach_hook = 0; + port->usbdev->config[port->configno].interf[port->interfno].attached = 0; +} + +void +grub_usbserial_detach (grub_usb_device_t usbdev, int configno, int interfno) +{ + static struct grub_serial_port *port; + port = usbdev->config[configno].interf[interfno].detach_data; + + grub_serial_unregister (port); +} + +static int usbnum = 0; + +int +grub_usbserial_attach (grub_usb_device_t usbdev, int configno, int interfno, + struct grub_serial_driver *driver) +{ + struct grub_serial_port *port; + int j; + struct grub_usb_desc_if *interf; + grub_usb_err_t err = GRUB_USB_ERR_NONE; + + interf = usbdev->config[configno].interf[interfno].descif; + + port = grub_malloc (sizeof (*port)); + if (!port) + { + grub_print_error (); + return 0; + } + + port->name = grub_xasprintf ("usb%d", usbnum++); + if (!port->name) + { + grub_free (port); + grub_print_error (); + return 0; + } + + port->usbdev = usbdev; + port->driver = driver; + for (j = 0; j < interf->endpointcnt; j++) + { + struct grub_usb_desc_endp *endp; + endp = &usbdev->config[0].interf[interfno].descendp[j]; + + if ((endp->endp_addr & 128) && (endp->attrib & 3) == 2) + { + /* Bulk IN endpoint. */ + port->in_endp = endp; + } + else if (!(endp->endp_addr & 128) && (endp->attrib & 3) == 2) + { + /* Bulk OUT endpoint. */ + port->out_endp = endp; + } + } + + /* Configure device */ + if (port->out_endp && port->in_endp) + err = grub_usb_set_configuration (usbdev, configno + 1); + + if (!port->out_endp || !port->in_endp || err) + { + grub_free (port->name); + grub_free (port); + return 0; + } + + port->configno = configno; + port->interfno = interfno; + + grub_serial_config_defaults (port); + grub_serial_register (port); + + port->usbdev->config[port->configno].interf[port->interfno].detach_hook + = grub_usbserial_detach; + port->usbdev->config[port->configno].interf[port->interfno].detach_data + = port; + + return 1; +} + +int +grub_usbserial_fetch (struct grub_serial_port *port, grub_size_t header_size) +{ + grub_usb_err_t err; + grub_size_t actual; + + if (port->bufstart < port->bufend) + return port->buf[port->bufstart++]; + + err = grub_usb_bulk_read_extended (port->usbdev, port->in_endp->endp_addr, + sizeof (port->buf), port->buf, 10, + &actual); + if (err != GRUB_USB_ERR_NONE) + return -1; + + port->bufstart = header_size; + port->bufend = actual; + if (port->bufstart >= port->bufend) + return -1; + + return port->buf[port->bufstart++]; +} diff --git a/grub-core/bus/usb/serial/ftdi.c b/grub-core/bus/usb/serial/ftdi.c new file mode 100644 index 0000000..07ac7ac --- /dev/null +++ b/grub-core/bus/usb/serial/ftdi.c @@ -0,0 +1,207 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2000,2001,2002,2003,2004,2005,2007,2008,2009,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 . + */ + +#include +#include +#include +#include +#include +#include +#include + +GRUB_MOD_LICENSE ("GPLv3+"); + +enum + { + GRUB_FTDI_MODEM_CTRL = 0x01, + GRUB_FTDI_FLOW_CTRL = 0x02, + GRUB_FTDI_SPEED_CTRL = 0x03, + GRUB_FTDI_DATA_CTRL = 0x04 + }; + +#define GRUB_FTDI_MODEM_CTRL_DTRRTS 3 +#define GRUB_FTDI_FLOW_CTRL_DTRRTS 3 + +/* Convert speed to divisor. */ +static grub_uint32_t +get_divisor (unsigned int speed) +{ + unsigned int i; + + /* The structure for speed vs. divisor. */ + struct divisor + { + unsigned int speed; + grub_uint32_t div; + }; + + /* The table which lists common configurations. */ + /* Computed with a division formula with 3MHz as base frequency. */ + static struct divisor divisor_tab[] = + { + { 2400, 0x04e2 }, + { 4800, 0x0271 }, + { 9600, 0x4138 }, + { 19200, 0x809c }, + { 38400, 0xc04e }, + { 57600, 0xc034 }, + { 115200, 0x001a } + }; + + /* Set the baud rate. */ + for (i = 0; i < ARRAY_SIZE (divisor_tab); i++) + if (divisor_tab[i].speed == speed) + return divisor_tab[i].div; + return 0; +} + +static void +real_config (struct grub_serial_port *port) +{ + grub_uint32_t divisor; + const grub_uint16_t parities[] = { + [GRUB_SERIAL_PARITY_NONE] = 0x0000, + [GRUB_SERIAL_PARITY_ODD] = 0x0100, + [GRUB_SERIAL_PARITY_EVEN] = 0x0200 + }; + const grub_uint16_t stop_bits[] = { + [GRUB_SERIAL_STOP_BITS_1] = 0x0000, + [GRUB_SERIAL_STOP_BITS_2] = 0x1000, + }; + + if (port->configured) + return; + + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_OUT, + GRUB_FTDI_MODEM_CTRL, + GRUB_FTDI_MODEM_CTRL_DTRRTS, 0, 0, 0); + + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_OUT, + GRUB_FTDI_FLOW_CTRL, + GRUB_FTDI_FLOW_CTRL_DTRRTS, 0, 0, 0); + + divisor = get_divisor (port->config.speed); + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_OUT, + GRUB_FTDI_SPEED_CTRL, + divisor & 0xffff, divisor >> 16, 0, 0); + + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_OUT, + GRUB_FTDI_DATA_CTRL, + parities[port->config.parity] + | stop_bits[port->config.stop_bits] + | port->config.word_len, 0, 0, 0); + + port->configured = 1; +} + +/* Fetch a key. */ +static int +ftdi_hw_fetch (struct grub_serial_port *port) +{ + real_config (port); + + return grub_usbserial_fetch (port, 2); +} + +/* Put a character. */ +static void +ftdi_hw_put (struct grub_serial_port *port, const int c) +{ + char cc = c; + + real_config (port); + + grub_usb_bulk_write (port->usbdev, port->out_endp->endp_addr, 1, &cc); +} + +static grub_err_t +ftdi_hw_configure (struct grub_serial_port *port, + struct grub_serial_config *config) +{ + grub_uint16_t divisor; + + divisor = get_divisor (config->speed); + if (divisor == 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad speed"); + + if (config->parity != GRUB_SERIAL_PARITY_NONE + && config->parity != GRUB_SERIAL_PARITY_ODD + && config->parity != GRUB_SERIAL_PARITY_EVEN) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "unsupported parity"); + + if (config->stop_bits != GRUB_SERIAL_STOP_BITS_1 + && config->stop_bits != GRUB_SERIAL_STOP_BITS_2) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "unsupported stop bits"); + + if (config->word_len < 5 || config->word_len > 8) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "unsupported word length"); + + port->config = *config; + port->configured = 0; + + return GRUB_ERR_NONE; +} + +static struct grub_serial_driver grub_ftdi_driver = + { + .configure = ftdi_hw_configure, + .fetch = ftdi_hw_fetch, + .put = ftdi_hw_put, + .fini = grub_usbserial_fini + }; + +static const struct +{ + grub_uint16_t vendor, product; +} products[] = + { + {0x0403, 0x6001} /* QEMU virtual USBserial. */ + }; + +static int +grub_ftdi_attach (grub_usb_device_t usbdev, int configno, int interfno) +{ + unsigned j; + + for (j = 0; j < ARRAY_SIZE (products); j++) + if (usbdev->descdev.vendorid == products[j].vendor + && usbdev->descdev.prodid == products[j].product) + break; + if (j == ARRAY_SIZE (products)) + return 0; + + return grub_usbserial_attach (usbdev, configno, interfno, + &grub_ftdi_driver); +} + +static struct grub_usb_attach_desc attach_hook = +{ + .class = 0xff, + .hook = grub_ftdi_attach +}; + +GRUB_MOD_INIT(usbserial_ftdi) +{ + grub_usb_register_attach_hook_class (&attach_hook); +} + +GRUB_MOD_FINI(usbserial_ftdi) +{ + grub_serial_unregister_driver (&grub_ftdi_driver); + grub_usb_unregister_attach_hook_class (&attach_hook); +} diff --git a/grub-core/bus/usb/serial/pl2303.c b/grub-core/bus/usb/serial/pl2303.c new file mode 100644 index 0000000..b995411 --- /dev/null +++ b/grub-core/bus/usb/serial/pl2303.c @@ -0,0 +1,220 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2000,2001,2002,2003,2004,2005,2007,2008,2009,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 . + */ + +#include +#include +#include +#include +#include +#include +#include + +GRUB_MOD_LICENSE ("GPLv3+"); + +/* Convert speed to divisor. */ +static grub_uint32_t +is_speed_supported (unsigned int speed) +{ + unsigned int i; + unsigned int supported[] = { 2400, 4800, 9600, 19200, 38400, 57600, 115200}; + + for (i = 0; i < ARRAY_SIZE (supported); i++) + if (supported[i] == speed) + return 1; + return 0; +} + +#define GRUB_PL2303_REQUEST_SET_CONFIG 0x20 +#define GRUB_PL2303_STOP_BITS_1 0x0 +#define GRUB_PL2303_STOP_BITS_2 0x2 + +#define GRUB_PL2303_PARITY_NONE 0 +#define GRUB_PL2303_PARITY_ODD 1 +#define GRUB_PL2303_PARITY_EVEN 2 + +struct grub_pl2303_config +{ + grub_uint32_t speed; + grub_uint8_t stop_bits; + grub_uint8_t parity; + grub_uint8_t word_len; +} __attribute__ ((packed)); + +static void +real_config (struct grub_serial_port *port) +{ + struct grub_pl2303_config config_pl2303; + char xx; + + if (port->configured) + return; + + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_IN, + 1, 0x8484, 0, 1, &xx); + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_OUT, + 1, 0x0404, 0, 0, 0); + + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_IN, + 1, 0x8484, 0, 1, &xx); + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_IN, + 1, 0x8383, 0, 1, &xx); + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_IN, + 1, 0x8484, 0, 1, &xx); + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_OUT, + 1, 0x0404, 1, 0, 0); + + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_IN, + 1, 0x8484, 0, 1, &xx); + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_IN, + 1, 0x8383, 0, 1, &xx); + + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_OUT, + 1, 0, 1, 0, 0); + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_OUT, + 1, 1, 0, 0, 0); + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_OUT, + 1, 2, 0x44, 0, 0); + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_OUT, + 1, 8, 0, 0, 0); + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_OUT, + 1, 9, 0, 0, 0); + + if (port->config.stop_bits == GRUB_SERIAL_STOP_BITS_2) + config_pl2303.stop_bits = GRUB_PL2303_STOP_BITS_2; + else + config_pl2303.stop_bits = GRUB_PL2303_STOP_BITS_1; + + switch (port->config.parity) + { + case GRUB_SERIAL_PARITY_NONE: + config_pl2303.parity = GRUB_PL2303_PARITY_NONE; + break; + case GRUB_SERIAL_PARITY_ODD: + config_pl2303.parity = GRUB_PL2303_PARITY_ODD; + break; + case GRUB_SERIAL_PARITY_EVEN: + config_pl2303.parity = GRUB_PL2303_PARITY_EVEN; + break; + } + + config_pl2303.word_len = port->config.word_len; + config_pl2303.speed = port->config.speed; + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_CLASS_INTERFACE_OUT, + GRUB_PL2303_REQUEST_SET_CONFIG, 0, 0, + sizeof (config_pl2303), (char *) &config_pl2303); + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_CLASS_INTERFACE_OUT, + 0x22, 3, 0, 0, 0); + + grub_usb_control_msg (port->usbdev, GRUB_USB_REQTYPE_VENDOR_OUT, + 1, 0, 0x61, 0, 0); + port->configured = 1; +} + +/* Fetch a key. */ +static int +pl2303_hw_fetch (struct grub_serial_port *port) +{ + real_config (port); + + return grub_usbserial_fetch (port, 0); +} + +/* Put a character. */ +static void +pl2303_hw_put (struct grub_serial_port *port, const int c) +{ + char cc = c; + + real_config (port); + + grub_usb_bulk_write (port->usbdev, port->out_endp->endp_addr, 1, &cc); +} + +static grub_err_t +pl2303_hw_configure (struct grub_serial_port *port, + struct grub_serial_config *config) +{ + if (!is_speed_supported (config->speed)) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad speed"); + + if (config->parity != GRUB_SERIAL_PARITY_NONE + && config->parity != GRUB_SERIAL_PARITY_ODD + && config->parity != GRUB_SERIAL_PARITY_EVEN) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "unsupported parity"); + + if (config->stop_bits != GRUB_SERIAL_STOP_BITS_1 + && config->stop_bits != GRUB_SERIAL_STOP_BITS_2) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "unsupported stop bits"); + + if (config->word_len < 5 || config->word_len > 8) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "unsupported word length"); + + port->config = *config; + port->configured = 0; + + return GRUB_ERR_NONE; +} + +static struct grub_serial_driver grub_pl2303_driver = + { + .configure = pl2303_hw_configure, + .fetch = pl2303_hw_fetch, + .put = pl2303_hw_put, + .fini = grub_usbserial_fini + }; + +static const struct +{ + grub_uint16_t vendor, product; +} products[] = + { + {0x067b, 0x2303} + }; + +static int +grub_pl2303_attach (grub_usb_device_t usbdev, int configno, int interfno) +{ + unsigned j; + + for (j = 0; j < ARRAY_SIZE (products); j++) + if (usbdev->descdev.vendorid == products[j].vendor + && usbdev->descdev.prodid == products[j].product) + break; + if (j == ARRAY_SIZE (products)) + return 0; + + return grub_usbserial_attach (usbdev, configno, interfno, + &grub_pl2303_driver); +} + +static struct grub_usb_attach_desc attach_hook = +{ + .class = 0xff, + .hook = grub_pl2303_attach +}; + +GRUB_MOD_INIT(usbserial_pl2303) +{ + grub_usb_register_attach_hook_class (&attach_hook); +} + +GRUB_MOD_FINI(usbserial_pl2303) +{ + grub_serial_unregister_driver (&grub_pl2303_driver); + grub_usb_unregister_attach_hook_class (&attach_hook); +} diff --git a/grub-core/bus/usb/uhci.c b/grub-core/bus/usb/uhci.c new file mode 100644 index 0000000..99e597f --- /dev/null +++ b/grub-core/bus/usb/uhci.c @@ -0,0 +1,816 @@ +/* uhci.c - UHCI Support. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2008 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +GRUB_MOD_LICENSE ("GPLv3+"); + +#define GRUB_UHCI_IOMASK (0x7FF << 5) + +#define N_QH 256 +#define N_TD 640 + +typedef enum + { + GRUB_UHCI_REG_USBCMD = 0x00, + GRUB_UHCI_REG_FLBASEADD = 0x08, + GRUB_UHCI_REG_PORTSC1 = 0x10, + GRUB_UHCI_REG_PORTSC2 = 0x12 + } grub_uhci_reg_t; + +#define GRUB_UHCI_LINK_TERMINATE 1 +#define GRUB_UHCI_LINK_QUEUE_HEAD 2 + +enum + { + GRUB_UHCI_REG_PORTSC_CONNECT_CHANGED = 0x0002, + GRUB_UHCI_REG_PORTSC_PORT_ENABLED = 0x0004, + GRUB_UHCI_REG_PORTSC_RESUME = 0x0040, + GRUB_UHCI_REG_PORTSC_RESET = 0x0200, + GRUB_UHCI_REG_PORTSC_SUSPEND = 0x1000, + GRUB_UHCI_REG_PORTSC_RW = GRUB_UHCI_REG_PORTSC_PORT_ENABLED + | GRUB_UHCI_REG_PORTSC_RESUME | GRUB_UHCI_REG_PORTSC_RESET + | GRUB_UHCI_REG_PORTSC_SUSPEND, + /* These bits should not be written as 1 unless we really need it */ + GRUB_UHCI_PORTSC_RWC = ((1 << 1) | (1 << 3) | (1 << 11) | (3 << 13)) + }; + +/* UHCI Queue Head. */ +struct grub_uhci_qh +{ + /* Queue head link pointer which points to the next queue head. */ + grub_uint32_t linkptr; + + /* Queue element link pointer which points to the first data object + within the queue. */ + grub_uint32_t elinkptr; + + /* Queue heads are aligned on 16 bytes, pad so a queue head is 16 + bytes so we can store many in a 4K page. */ + grub_uint8_t pad[8]; +} __attribute__ ((packed)); + +/* UHCI Transfer Descriptor. */ +struct grub_uhci_td +{ + /* Pointer to the next TD in the list. */ + grub_uint32_t linkptr; + + /* Control and status bits. */ + grub_uint32_t ctrl_status; + + /* All information required to transfer the Token packet. */ + grub_uint32_t token; + + /* A pointer to the data buffer, UHCI requires this pointer to be 32 + bits. */ + grub_uint32_t buffer; + + /* Another linkptr that is not overwritten by the Host Controller. + This is GRUB specific. */ + grub_uint32_t linkptr2; + + /* 3 additional 32 bits words reserved for the Host Controller Driver. */ + grub_uint32_t data[3]; +} __attribute__ ((packed)); + +typedef volatile struct grub_uhci_td *grub_uhci_td_t; +typedef volatile struct grub_uhci_qh *grub_uhci_qh_t; + +struct grub_uhci +{ + int iobase; + grub_uint32_t *framelist; + + /* N_QH Queue Heads. */ + grub_uhci_qh_t qh; + + /* N_TD Transfer Descriptors. */ + grub_uhci_td_t td; + + /* Free Transfer Descriptors. */ + grub_uhci_td_t tdfree; + + int qh_busy[N_QH]; + + struct grub_uhci *next; +}; + +static struct grub_uhci *uhci; + +static grub_uint16_t +grub_uhci_readreg16 (struct grub_uhci *u, grub_uhci_reg_t reg) +{ + return grub_inw (u->iobase + reg); +} + +#if 0 +static grub_uint32_t +grub_uhci_readreg32 (struct grub_uhci *u, grub_uhci_reg_t reg) +{ + return grub_inl (u->iobase + reg); +} +#endif + +static void +grub_uhci_writereg16 (struct grub_uhci *u, + grub_uhci_reg_t reg, grub_uint16_t val) +{ + grub_outw (val, u->iobase + reg); +} + +static void +grub_uhci_writereg32 (struct grub_uhci *u, + grub_uhci_reg_t reg, grub_uint32_t val) +{ + grub_outl (val, u->iobase + reg); +} + +static grub_err_t +grub_uhci_portstatus (grub_usb_controller_t dev, + unsigned int port, unsigned int enable); + + +/* Iterate over all PCI devices. Determine if a device is an UHCI + controller. If this is the case, initialize it. */ +static int NESTED_FUNC_ATTR +grub_uhci_pci_iter (grub_pci_device_t dev, + grub_pci_id_t pciid __attribute__((unused))) +{ + grub_uint32_t class_code; + grub_uint32_t class; + grub_uint32_t subclass; + grub_uint32_t interf; + grub_uint32_t base; + grub_uint32_t fp; + grub_pci_address_t addr; + struct grub_uhci *u; + int i; + + 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 UHCI controller, just return. */ + if (class != 0x0c || subclass != 0x03 || interf != 0x00) + return 0; + + /* Determine IO base address. */ + addr = grub_pci_make_address (dev, GRUB_PCI_REG_ADDRESS_REG4); + base = grub_pci_read (addr); + /* Stop if there is no IO space base address defined. */ + if (! (base & 1)) + return 0; + + /* Allocate memory for the controller and register it. */ + u = grub_zalloc (sizeof (*u)); + if (! u) + return 1; + + u->iobase = base & GRUB_UHCI_IOMASK; + + /* 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 + if ((grub_uint64_t) u->framelist >> 32) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, + "allocated frame list memory not <4GB"); + goto fail; + } +#endif + + /* The QH pointer of UHCI is only 32 bits, make sure this + code works on on 64 bits architectures. */ + u->qh = (grub_uhci_qh_t) grub_memalign (4096, sizeof(struct grub_uhci_qh)*N_QH); + if (! u->qh) + goto fail; + +#if GRUB_CPU_SIZEOF_VOID_P == 8 + if ((grub_uint64_t) u->qh >> 32) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, "allocated QH memory not <4GB"); + goto fail; + } +#endif + + /* The TD pointer of UHCI is only 32 bits, make sure this + code works on on 64 bits architectures. */ + u->td = (grub_uhci_td_t) grub_memalign (4096, sizeof(struct grub_uhci_td)*N_TD); + if (! u->td) + goto fail; + +#if GRUB_CPU_SIZEOF_VOID_P == 8 + if ((grub_uint64_t) u->td >> 32) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, "allocated TD memory not <4GB"); + goto fail; + } +#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 < N_TD; i++) + u->td[i].linkptr = (grub_uint32_t) (grub_addr_t) &u->td[i + 1]; + u->td[N_TD - 2].linkptr = 0; + u->tdfree = u->td; + + /* Make sure UHCI is disabled! */ + grub_uhci_writereg16 (u, GRUB_UHCI_REG_USBCMD, 0); + + /* Setup the frame list pointers. Since no isochronous transfers + are and will be supported, they all point to the (same!) queue + head. */ + fp = (grub_uint32_t) (grub_addr_t) u->qh & (~15); + /* Mark this as a queue head. */ + fp |= 2; + for (i = 0; i < 1024; i++) + u->framelist[i] = fp; + /* Program the framelist address into the UHCI controller. */ + grub_uhci_writereg32 (u, GRUB_UHCI_REG_FLBASEADD, + (grub_uint32_t) (grub_addr_t) u->framelist); + + /* Make the Queue Heads point to each other. */ + for (i = 0; i < N_QH; i++) + { + /* Point to the next QH. */ + u->qh[i].linkptr = (grub_uint32_t) (grub_addr_t) (&u->qh[i + 1]) & (~15); + + /* This is a QH. */ + u->qh[i].linkptr |= GRUB_UHCI_LINK_QUEUE_HEAD; + + /* For the moment, do not point to a Transfer Descriptor. These + are set at transfer time, so just terminate it. */ + u->qh[i].elinkptr = 1; + } + + /* The last Queue Head should terminate. */ + u->qh[N_QH - 1].linkptr = 1; + + /* Enable UHCI again. */ + grub_uhci_writereg16 (u, GRUB_UHCI_REG_USBCMD, 1 | (1 << 7)); + + /* UHCI is initialized and ready for transfers. */ + grub_dprintf ("uhci", "UHCI initialized\n"); + + +#if 0 + { + int i; + for (i = 0; i < 10; i++) + { + grub_uint16_t frnum; + + frnum = grub_uhci_readreg16 (u, 6); + grub_dprintf ("uhci", "Framenum=%d\n", frnum); + grub_millisleep (100); + } + } +#endif + + /* Link to uhci now that initialisation is successful. */ + u->next = uhci; + uhci = u; + + return 0; + + fail: + if (u) + { + grub_free ((void *) u->qh); + grub_free (u->framelist); + } + grub_free (u); + + return 1; +} + +static void +grub_uhci_inithw (void) +{ + grub_pci_iterate (grub_uhci_pci_iter); +} + +static grub_uhci_td_t +grub_alloc_td (struct grub_uhci *u) +{ + grub_uhci_td_t ret; + + /* Check if there is a Transfer Descriptor available. */ + if (! u->tdfree) + return NULL; + + ret = u->tdfree; + u->tdfree = (grub_uhci_td_t) (grub_addr_t) u->tdfree->linkptr; + + return ret; +} + +static void +grub_free_td (struct grub_uhci *u, grub_uhci_td_t td) +{ + td->linkptr = (grub_uint32_t) (grub_addr_t) u->tdfree; + u->tdfree = td; +} + +static void +grub_free_queue (struct grub_uhci *u, grub_uhci_qh_t qh, grub_uhci_td_t td, + grub_usb_transfer_t transfer, grub_size_t *actual) +{ + int i; /* Index of TD in transfer */ + + u->qh_busy[qh - u->qh] = 0; + + *actual = 0; + + /* 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; + + *actual += (td->ctrl_status + 1) & 0x7ff; + + /* Unlink the queue. */ + tdprev = td; + td = (grub_uhci_td_t) (grub_addr_t) td->linkptr2; + + /* Free the TD. */ + grub_free_td (u, tdprev); + } +} + +static grub_uhci_qh_t +grub_alloc_qh (struct grub_uhci *u, + grub_transaction_type_t tr __attribute__((unused))) +{ + int i; + grub_uhci_qh_t qh; + + /* Look for a Queue Head for this transfer. Skip the first QH if + this is a Interrupt Transfer. */ +#if 0 + if (tr == GRUB_USB_TRANSACTION_TYPE_INTERRUPT) + i = 0; + else +#endif + i = 1; + + for (; i < N_QH; i++) + { + if (!u->qh_busy[i]) + break; + } + qh = &u->qh[i]; + if (i == N_QH) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, + "no free queue heads available"); + return NULL; + } + + u->qh_busy[qh - u->qh] = 1; + + return qh; +} + +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, + grub_uint32_t data, grub_usb_speed_t speed) +{ + grub_uhci_td_t td; + static const unsigned int tf[] = { 0x69, 0xE1, 0x2D }; + + /* XXX: Check if data is <4GB. If it isn't, just copy stuff around. + This is only relevant for 64 bits architectures. */ + + /* Grab a free Transfer Descriptor and initialize it. */ + td = grub_alloc_td (u); + if (! td) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, + "no transfer descriptors available for UHCI transfer"); + return 0; + } + + grub_dprintf ("uhci", + "transaction: endp=%d, type=%d, addr=%d, toggle=%d, size=%lu data=0x%x td=%p\n", + endp, type, addr, toggle, (unsigned long) size, data, td); + + /* Don't point to any TD, just terminate. */ + td->linkptr = 1; + + /* Active! Only retry a transfer 3 times. */ + td->ctrl_status = (1 << 23) | (3 << 27) | + ((speed == GRUB_USB_SPEED_LOW) ? (1 << 26) : 0); + + /* If zero bytes are transmitted, size is 0x7FF. Otherwise size is + size-1. */ + if (size == 0) + size = 0x7FF; + else + size = size - 1; + + /* Setup whatever is required for the token packet. */ + td->token = ((size << 21) | (toggle << 19) | (endp << 15) + | (addr << 8) | tf[type]); + + td->buffer = data; + + return td; +} + +struct grub_uhci_transfer_controller_data +{ + grub_uhci_qh_t qh; + grub_uhci_td_t td_first; +}; + +static grub_usb_err_t +grub_uhci_setup_transfer (grub_usb_controller_t dev, + grub_usb_transfer_t transfer) +{ + struct grub_uhci *u = (struct grub_uhci *) dev->data; + grub_uhci_td_t td; + grub_uhci_td_t td_prev = NULL; + int i; + struct grub_uhci_transfer_controller_data *cdata; + + cdata = grub_malloc (sizeof (*cdata)); + if (!cdata) + return GRUB_USB_ERR_INTERNAL; + + cdata->td_first = NULL; + + /* Allocate a queue head for the transfer queue. */ + cdata->qh = grub_alloc_qh (u, GRUB_USB_TRANSACTION_TYPE_CONTROL); + if (! cdata->qh) + { + grub_free (cdata); + return GRUB_USB_ERR_INTERNAL; + } + + grub_dprintf ("uhci", "transfer, iobase:%08x\n", u->iobase); + + for (i = 0; i < transfer->transcnt; i++) + { + grub_usb_transaction_t tr = &transfer->transactions[i]; + + td = grub_uhci_transaction (u, transfer->endpoint & 15, tr->pid, + transfer->devaddr, tr->toggle, + tr->size, tr->data, + transfer->dev->speed); + if (! td) + { + grub_size_t actual = 0; + /* Terminate and free. */ + td_prev->linkptr2 = 0; + td_prev->linkptr = 1; + + if (cdata->td_first) + grub_free_queue (u, cdata->qh, cdata->td_first, NULL, &actual); + + grub_free (cdata); + return GRUB_USB_ERR_INTERNAL; + } + + if (! cdata->td_first) + cdata->td_first = td; + else + { + td_prev->linkptr2 = (grub_uint32_t) (grub_addr_t) td; + td_prev->linkptr = (grub_uint32_t) (grub_addr_t) td; + td_prev->linkptr |= 4; + } + td_prev = td; + } + td_prev->linkptr2 = 0; + td_prev->linkptr = 1; + + grub_dprintf ("uhci", "setup transaction %d\n", transfer->type); + + /* Link it into the queue and terminate. Now the transaction can + take place. */ + cdata->qh->elinkptr = (grub_uint32_t) (grub_addr_t) cdata->td_first; + + grub_dprintf ("uhci", "initiate transaction\n"); + + transfer->controller_data = cdata; + + return GRUB_USB_ERR_NONE; +} + +static grub_usb_err_t +grub_uhci_check_transfer (grub_usb_controller_t dev, + grub_usb_transfer_t transfer, + grub_size_t *actual) +{ + struct grub_uhci *u = (struct grub_uhci *) dev->data; + grub_uhci_td_t errtd; + struct grub_uhci_transfer_controller_data *cdata = transfer->controller_data; + + *actual = 0; + + errtd = (grub_uhci_td_t) (grub_addr_t) (cdata->qh->elinkptr & ~0x0f); + + grub_dprintf ("uhci", ">t status=0x%02x data=0x%02x td=%p\n", + errtd->ctrl_status, errtd->buffer & (~15), errtd); + + /* Check if the transaction completed. */ + if (cdata->qh->elinkptr & 1) + { + grub_dprintf ("uhci", "transaction complete\n"); + + /* Place the QH back in the free list and deallocate the associated + TDs. */ + cdata->qh->elinkptr = 1; + grub_free_queue (u, cdata->qh, cdata->td_first, transfer, actual); + grub_free (cdata); + return GRUB_USB_ERR_NONE; + } + + grub_dprintf ("uhci", "t status=0x%02x\n", errtd->ctrl_status); + + if (!(errtd->ctrl_status & (1 << 23))) + { + grub_usb_err_t err = GRUB_USB_ERR_NONE; + + /* Check if the endpoint is stalled. */ + if (errtd->ctrl_status & (1 << 22)) + err = GRUB_USB_ERR_STALL; + + /* Check if an error related to the data buffer occurred. */ + else if (errtd->ctrl_status & (1 << 21)) + err = GRUB_USB_ERR_DATA; + + /* Check if a babble error occurred. */ + else if (errtd->ctrl_status & (1 << 20)) + err = GRUB_USB_ERR_BABBLE; + + /* Check if a NAK occurred. */ + else if (errtd->ctrl_status & (1 << 19)) + err = GRUB_USB_ERR_NAK; + + /* Check if a timeout occurred. */ + else if (errtd->ctrl_status & (1 << 18)) + err = GRUB_USB_ERR_TIMEOUT; + + /* Check if a bitstuff error occurred. */ + else if (errtd->ctrl_status & (1 << 17)) + err = GRUB_USB_ERR_BITSTUFF; + + if (err) + { + grub_dprintf ("uhci", "transaction failed\n"); + + /* Place the QH back in the free list and deallocate the associated + TDs. */ + cdata->qh->elinkptr = 1; + grub_free_queue (u, cdata->qh, cdata->td_first, transfer, actual); + grub_free (cdata); + + return err; + } + } + + /* Fall through, no errors occurred, so the QH might be + updated. */ + grub_dprintf ("uhci", "transaction fallthrough\n"); + + return GRUB_USB_ERR_WAIT; +} + +static grub_usb_err_t +grub_uhci_cancel_transfer (grub_usb_controller_t dev, + grub_usb_transfer_t transfer) +{ + struct grub_uhci *u = (struct grub_uhci *) dev->data; + grub_size_t actual; + struct grub_uhci_transfer_controller_data *cdata = transfer->controller_data; + + grub_dprintf ("uhci", "transaction cancel\n"); + + /* Place the QH back in the free list and deallocate the associated + TDs. */ + cdata->qh->elinkptr = 1; + grub_free_queue (u, cdata->qh, cdata->td_first, transfer, &actual); + grub_free (cdata); + + return GRUB_USB_ERR_NONE; +} + +static int +grub_uhci_iterate (int (*hook) (grub_usb_controller_t dev)) +{ + struct grub_uhci *u; + struct grub_usb_controller dev; + + for (u = uhci; u; u = u->next) + { + dev.data = u; + if (hook (&dev)) + return 1; + } + + return 0; +} + +static grub_err_t +grub_uhci_portstatus (grub_usb_controller_t dev, + unsigned int port, unsigned int enable) +{ + struct grub_uhci *u = (struct grub_uhci *) dev->data; + int reg; + 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) + reg = GRUB_UHCI_REG_PORTSC1; + else if (port == 1) + reg = GRUB_UHCI_REG_PORTSC2; + else + return grub_error (GRUB_ERR_OUT_OF_RANGE, + "UHCI Root Hub port does not exist"); + + status = grub_uhci_readreg16 (u, reg); + grub_dprintf ("uhci", "detect=0x%02x\n", status); + + if (!enable) /* We don't need reset port */ + { + /* Disable the port. */ + grub_uhci_writereg16 (u, reg, 0 << 2); + grub_dprintf ("uhci", "waiting for the port to be disabled\n"); + endtime = grub_get_time_ms () + 1000; + while ((grub_uhci_readreg16 (u, reg) & (1 << 2))) + if (grub_get_time_ms () > endtime) + return grub_error (GRUB_ERR_IO, "UHCI Timed out - disable"); + + status = grub_uhci_readreg16 (u, reg); + grub_dprintf ("uhci", ">3detect=0x%02x\n", status); + return GRUB_ERR_NONE; + } + + /* Reset the port. */ + status = grub_uhci_readreg16 (u, reg) & ~GRUB_UHCI_PORTSC_RWC; + grub_uhci_writereg16 (u, reg, status | (1 << 9)); + grub_uhci_readreg16 (u, reg); /* Ensure it is writen... */ + + /* Wait for the reset to complete. XXX: How long exactly? */ + grub_millisleep (50); /* For root hub should be nominaly 50ms */ + status = grub_uhci_readreg16 (u, reg) & ~GRUB_UHCI_PORTSC_RWC; + grub_uhci_writereg16 (u, reg, status & ~(1 << 9)); + grub_uhci_readreg16 (u, reg); /* Ensure it is writen... */ + + /* Note: some debug prints were removed because they affected reset/enable timing. */ + + grub_millisleep (1); /* Probably not needed at all or only few microsecs. */ + + /* Reset bits Connect & Enable Status Change */ + status = grub_uhci_readreg16 (u, reg) & ~GRUB_UHCI_PORTSC_RWC; + grub_uhci_writereg16 (u, reg, status | (1 << 3) | GRUB_UHCI_REG_PORTSC_CONNECT_CHANGED); + grub_uhci_readreg16 (u, reg); /* Ensure it is writen... */ + + /* Enable the port. */ + status = grub_uhci_readreg16 (u, reg) & ~GRUB_UHCI_PORTSC_RWC; + grub_uhci_writereg16 (u, reg, status | (1 << 2)); + grub_uhci_readreg16 (u, reg); /* Ensure it is writen... */ + + endtime = grub_get_time_ms () + 1000; + while (! ((status = grub_uhci_readreg16 (u, reg)) & (1 << 2))) + if (grub_get_time_ms () > endtime) + return grub_error (GRUB_ERR_IO, "UHCI Timed out - enable"); + + /* Reset recovery time */ + grub_millisleep (10); + + /* Read final port status */ + status = grub_uhci_readreg16 (u, reg); + grub_dprintf ("uhci", ">3detect=0x%02x\n", status); + + + return GRUB_ERR_NONE; +} + +static grub_usb_speed_t +grub_uhci_detect_dev (grub_usb_controller_t dev, int port, int *changed) +{ + struct grub_uhci *u = (struct grub_uhci *) dev->data; + 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) + reg = GRUB_UHCI_REG_PORTSC2; + else + return GRUB_USB_SPEED_NONE; + + status = grub_uhci_readreg16 (u, reg); + + grub_dprintf ("uhci", "detect=0x%02x port=%d\n", status, port); + + /* Connect Status Change bit - it detects change of connection */ + if (status & (1 << 1)) + { + *changed = 1; + /* Reset bit Connect Status Change */ + grub_uhci_writereg16 (u, reg, (status & GRUB_UHCI_REG_PORTSC_RW) + | GRUB_UHCI_REG_PORTSC_CONNECT_CHANGED); + } + else + *changed = 0; + + if (! (status & 1)) + return GRUB_USB_SPEED_NONE; + else if (status & (1 << 8)) + return GRUB_USB_SPEED_LOW; + else + return GRUB_USB_SPEED_FULL; +} + +static int +grub_uhci_hubports (grub_usb_controller_t dev __attribute__((unused))) +{ + /* The root hub has exactly two ports. */ + return 2; +} + + +static struct grub_usb_controller_dev usb_controller = +{ + .name = "uhci", + .iterate = grub_uhci_iterate, + .setup_transfer = grub_uhci_setup_transfer, + .check_transfer = grub_uhci_check_transfer, + .cancel_transfer = grub_uhci_cancel_transfer, + .hubports = grub_uhci_hubports, + .portstatus = grub_uhci_portstatus, + .detect_dev = grub_uhci_detect_dev +}; + +GRUB_MOD_INIT(uhci) +{ + grub_uhci_inithw (); + grub_usb_controller_dev_register (&usb_controller); + grub_dprintf ("uhci", "registered\n"); +} + +GRUB_MOD_FINI(uhci) +{ + struct grub_uhci *u; + + /* Disable all UHCI controllers. */ + for (u = uhci; u; u = u->next) + grub_uhci_writereg16 (u, GRUB_UHCI_REG_USBCMD, 0); + + /* Unregister the controller. */ + grub_usb_controller_dev_unregister (&usb_controller); +} diff --git a/grub-core/bus/usb/usb.c b/grub-core/bus/usb/usb.c new file mode 100644 index 0000000..005d3bc --- /dev/null +++ b/grub-core/bus/usb/usb.c @@ -0,0 +1,349 @@ +/* usb.c - Generic USB interfaces. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2008 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 . + */ + +#include +#include +#include +#include +#include +#include + +GRUB_MOD_LICENSE ("GPLv3+"); + +static grub_usb_controller_dev_t grub_usb_list; +static struct grub_usb_attach_desc *attach_hooks; + +void +grub_usb_controller_dev_register (grub_usb_controller_dev_t usb) +{ + auto int iterate_hook (grub_usb_controller_t dev); + + /* Iterate over all controllers found by the driver. */ + int iterate_hook (grub_usb_controller_t dev) + { + dev->dev = usb; + + /* Enable the ports of the USB Root Hub. */ + grub_usb_root_hub (dev); + + return 0; + } + + usb->next = grub_usb_list; + grub_usb_list = usb; + + if (usb->iterate) + usb->iterate (iterate_hook); +} + +void +grub_usb_controller_dev_unregister (grub_usb_controller_dev_t usb) +{ + grub_usb_controller_dev_t *p, q; + + for (p = &grub_usb_list, q = *p; q; p = &(q->next), q = q->next) + if (q == usb) + { + *p = q->next; + break; + } +} + +#if 0 +int +grub_usb_controller_iterate (int (*hook) (grub_usb_controller_t dev)) +{ + grub_usb_controller_dev_t p; + + auto int iterate_hook (grub_usb_controller_t dev); + + int iterate_hook (grub_usb_controller_t dev) + { + dev->dev = p; + if (hook (dev)) + return 1; + return 0; + } + + /* Iterate over all controller drivers. */ + for (p = grub_usb_list; p; p = p->next) + { + /* Iterate over the busses of the controllers. XXX: Actually, a + hub driver should do this. */ + if (p->iterate (iterate_hook)) + return 1; + } + + return 0; +} +#endif + + +grub_usb_err_t +grub_usb_clear_halt (grub_usb_device_t dev, int endpoint) +{ + dev->toggle[endpoint] = 0; + return grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT + | GRUB_USB_REQTYPE_STANDARD + | GRUB_USB_REQTYPE_TARGET_ENDP), + GRUB_USB_REQ_CLEAR_FEATURE, + GRUB_USB_FEATURE_ENDP_HALT, + endpoint, 0, 0); +} + +grub_usb_err_t +grub_usb_set_configuration (grub_usb_device_t dev, int configuration) +{ + grub_memset (dev->toggle, 0, sizeof (dev->toggle)); + + return grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT + | GRUB_USB_REQTYPE_STANDARD + | GRUB_USB_REQTYPE_TARGET_DEV), + GRUB_USB_REQ_SET_CONFIGURATION, configuration, + 0, 0, NULL); +} + +grub_usb_err_t +grub_usb_get_descriptor (grub_usb_device_t dev, + grub_uint8_t type, grub_uint8_t index, + grub_size_t size, char *data) +{ + return grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_IN + | GRUB_USB_REQTYPE_STANDARD + | GRUB_USB_REQTYPE_TARGET_DEV), + GRUB_USB_REQ_GET_DESCRIPTOR, + (type << 8) | index, + 0, size, data); +} + +struct grub_usb_desc_endp * +grub_usb_get_endpdescriptor (grub_usb_device_t usbdev, int addr) +{ + int i; + + for (i = 0; i < usbdev->config[0].descconf->numif; i++) + { + struct grub_usb_desc_if *interf; + int j; + + interf = usbdev->config[0].interf[i].descif; + + for (j = 0; j < interf->endpointcnt; j++) + { + struct grub_usb_desc_endp *endp; + endp = &usbdev->config[0].interf[i].descendp[j]; + + if (endp->endp_addr == addr) + return endp; + } + } + + return NULL; +} + +grub_usb_err_t +grub_usb_device_initialize (grub_usb_device_t dev) +{ + struct grub_usb_desc_device *descdev; + struct grub_usb_desc_config config; + 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); + if (err) + return err; + descdev = &dev->descdev; + + for (i = 0; i < 8; i++) + dev->config[i].descconf = NULL; + + for (i = 0; i < descdev->configcnt; i++) + { + int pos; + int currif; + char *data; + + /* First just read the first 4 bytes of the configuration + descriptor, after that it is known how many bytes really have + to be read. */ + err = grub_usb_get_descriptor (dev, GRUB_USB_DESCRIPTOR_CONFIG, i, 4, + (char *) &config); + + data = grub_malloc (config.totallen); + if (! data) + { + err = GRUB_USB_ERR_INTERNAL; + goto fail; + } + + dev->config[i].descconf = (struct grub_usb_desc_config *) data; + err = grub_usb_get_descriptor (dev, GRUB_USB_DESCRIPTOR_CONFIG, i, + config.totallen, data); + if (err) + goto fail; + + /* Skip the configuration descriptor. */ + pos = dev->config[i].descconf->length; + + /* Read all interfaces. */ + for (currif = 0; currif < dev->config[i].descconf->numif; currif++) + { + while (pos < config.totallen + && ((struct grub_usb_desc *)&data[pos])->type + != GRUB_USB_DESCRIPTOR_INTERFACE) + pos += ((struct grub_usb_desc *)&data[pos])->length; + dev->config[i].interf[currif].descif + = (struct grub_usb_desc_if *) &data[pos]; + pos += dev->config[i].interf[currif].descif->length; + + while (pos < config.totallen + && ((struct grub_usb_desc *)&data[pos])->type + != GRUB_USB_DESCRIPTOR_ENDPOINT) + pos += ((struct grub_usb_desc *)&data[pos])->length; + + /* Point to the first endpoint. */ + dev->config[i].interf[currif].descendp + = (struct grub_usb_desc_endp *) &data[pos]; + pos += (sizeof (struct grub_usb_desc_endp) + * dev->config[i].interf[currif].descif->endpointcnt); + } + } + + return GRUB_USB_ERR_NONE; + + fail: + + for (i = 0; i < 8; i++) + grub_free (dev->config[i].descconf); + + return err; +} + +void grub_usb_device_attach (grub_usb_device_t dev) +{ + int i; + + /* XXX: Just check configuration 0 for now. */ + for (i = 0; i < dev->config[0].descconf->numif; i++) + { + struct grub_usb_desc_if *interf; + struct grub_usb_attach_desc *desc; + + interf = dev->config[0].interf[i].descif; + + grub_dprintf ("usb", "iterate: interf=%d, class=%d, subclass=%d, protocol=%d\n", + i, interf->class, interf->subclass, interf->protocol); + + if (dev->config[0].interf[i].attached) + continue; + + for (desc = attach_hooks; desc; desc = desc->next) + if (interf->class == desc->class && desc->hook (dev, 0, i)) + dev->config[0].interf[i].attached = 1; + + if (dev->config[0].interf[i].attached) + continue; + + switch (interf->class) + { + case GRUB_USB_CLASS_MASS_STORAGE: + grub_dl_load ("usbms"); + break; + case GRUB_USB_CLASS_HID: + grub_dl_load ("usb_keyboard"); + break; + case 0xff: + /* FIXME: don't load useless modules. */ + grub_dl_load ("usbserial_ftdi"); + grub_dl_load ("usbserial_pl2303"); + break; + } + } +} + +void +grub_usb_register_attach_hook_class (struct grub_usb_attach_desc *desc) +{ + auto int usb_iterate (grub_usb_device_t dev); + + int usb_iterate (grub_usb_device_t usbdev) + { + struct grub_usb_desc_device *descdev = &usbdev->descdev; + int i; + + if (descdev->class != 0 || descdev->subclass || descdev->protocol != 0 + || descdev->configcnt == 0) + return 0; + + /* XXX: Just check configuration 0 for now. */ + for (i = 0; i < usbdev->config[0].descconf->numif; i++) + { + struct grub_usb_desc_if *interf; + + interf = usbdev->config[0].interf[i].descif; + + grub_dprintf ("usb", "iterate: interf=%d, class=%d, subclass=%d, protocol=%d\n", + i, interf->class, interf->subclass, interf->protocol); + + if (usbdev->config[0].interf[i].attached) + continue; + + if (interf->class != desc->class) + continue; + if (desc->hook (usbdev, 0, i)) + usbdev->config[0].interf[i].attached = 1; + } + + return 0; + } + + desc->next = attach_hooks; + attach_hooks = desc; + + grub_usb_iterate (usb_iterate); +} + +void +grub_usb_unregister_attach_hook_class (struct grub_usb_attach_desc *desc) +{ + grub_list_remove (GRUB_AS_LIST_P (&attach_hooks), GRUB_AS_LIST (desc)); +} + + +GRUB_MOD_INIT(usb) +{ + grub_term_poll_usb = grub_usb_poll_devices; +} + +GRUB_MOD_FINI(usb) +{ + grub_term_poll_usb = NULL; +} diff --git a/grub-core/bus/usb/usbhub.c b/grub-core/bus/usb/usbhub.c new file mode 100644 index 0000000..82bb2da --- /dev/null +++ b/grub-core/bus/usb/usbhub.c @@ -0,0 +1,559 @@ +/* usb.c - USB Hub Support. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2008 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 . + */ + +#include +#include +#include +#include +#include + +#define GRUB_USBHUB_MAX_DEVICES 128 + +/* USB Supports 127 devices, with device 0 as special case. */ +static struct grub_usb_device *grub_usb_devs[GRUB_USBHUB_MAX_DEVICES]; + +static int rescan = 0; + +struct grub_usb_hub +{ + struct grub_usb_hub *next; + grub_usb_controller_t controller; + int nports; + struct grub_usb_device **devices; + grub_usb_device_t dev; +}; + +static struct grub_usb_hub *hubs; + +/* Add a device that currently has device number 0 and resides on + CONTROLLER, the Hub reported that the device speed is SPEED. */ +static grub_usb_device_t +grub_usb_hub_add_dev (grub_usb_controller_t controller, grub_usb_speed_t speed) +{ + grub_usb_device_t dev; + int i; + grub_usb_err_t err; + + dev = grub_zalloc (sizeof (struct grub_usb_device)); + if (! dev) + return NULL; + + dev->controller = *controller; + dev->speed = speed; + + err = grub_usb_device_initialize (dev); + if (err) + { + grub_free (dev); + return NULL; + } + + /* Assign a new address to the device. */ + for (i = 1; i < GRUB_USBHUB_MAX_DEVICES; i++) + { + if (! grub_usb_devs[i]) + break; + } + if (i == GRUB_USBHUB_MAX_DEVICES) + { + grub_error (GRUB_ERR_IO, "can't assign address to USB device"); + for (i = 0; i < 8; i++) + grub_free (dev->config[i].descconf); + grub_free (dev); + return NULL; + } + + err = grub_usb_control_msg (dev, + (GRUB_USB_REQTYPE_OUT + | GRUB_USB_REQTYPE_STANDARD + | GRUB_USB_REQTYPE_TARGET_DEV), + GRUB_USB_REQ_SET_ADDRESS, + i, 0, 0, NULL); + if (err) + { + for (i = 0; i < 8; i++) + grub_free (dev->config[i].descconf); + grub_free (dev); + return NULL; + } + + dev->addr = i; + dev->initialized = 1; + grub_usb_devs[i] = dev; + + /* Wait "recovery interval", spec. says 2ms */ + grub_millisleep (2); + + grub_usb_device_attach (dev); + + return dev; +} + + +static grub_usb_err_t +grub_usb_add_hub (grub_usb_device_t dev) +{ + struct grub_usb_usb_hubdesc hubdesc; + grub_usb_err_t err; + int i; + + err = grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_IN + | GRUB_USB_REQTYPE_CLASS + | GRUB_USB_REQTYPE_TARGET_DEV), + GRUB_USB_REQ_GET_DESCRIPTOR, + (GRUB_USB_DESCRIPTOR_HUB << 8) | 0, + 0, sizeof (hubdesc), (char *) &hubdesc); + if (err) + return err; + grub_dprintf ("usb", "Hub descriptor:\n\t\t len:%d, typ:0x%02x, cnt:%d, char:0x%02x, pwg:%d, curr:%d\n", + hubdesc.length, hubdesc.type, hubdesc.portcnt, + hubdesc.characteristics, hubdesc.pwdgood, + hubdesc.current); + + /* Activate the first configuration. Hubs should have only one conf. */ + grub_dprintf ("usb", "Hub set configuration\n"); + grub_usb_set_configuration (dev, 1); + + dev->children = grub_zalloc (hubdesc.portcnt * sizeof (dev->children[0])); + if (!dev->children) + return GRUB_USB_ERR_INTERNAL; + dev->nports = hubdesc.portcnt; + + /* Power on all Hub ports. */ + for (i = 1; i <= hubdesc.portcnt; i++) + { + grub_dprintf ("usb", "Power on - port %d\n", i); + /* Power on the port and wait for possible device connect */ + grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT + | GRUB_USB_REQTYPE_CLASS + | GRUB_USB_REQTYPE_TARGET_OTHER), + GRUB_USB_REQ_SET_FEATURE, + GRUB_USB_HUB_FEATURE_PORT_POWER, + i, 0, NULL); + } + + /* Rest will be done on next usb poll. */ + for (i = 0; i < dev->config[0].interf[0].descif->endpointcnt; + i++) + { + struct grub_usb_desc_endp *endp = NULL; + endp = &dev->config[0].interf[0].descendp[i]; + + if ((endp->endp_addr & 128) && grub_usb_get_ep_type(endp) + == GRUB_USB_EP_INTERRUPT) + { + dev->hub_endpoint = endp; + dev->hub_transfer + = grub_usb_bulk_read_background (dev, endp->endp_addr, + grub_min (endp->maxpacket, + sizeof (dev->statuschange)), + (char *) &dev->statuschange); + break; + } + } + + rescan = 1; + + return GRUB_ERR_NONE; +} + +static void +attach_root_port (struct grub_usb_hub *hub, int portno, + grub_usb_speed_t speed) +{ + grub_usb_device_t dev; + grub_err_t err; + int total, i; + grub_usb_speed_t current_speed = GRUB_USB_SPEED_NONE; + int changed=0; + +#if 0 +/* Specification does not say about disabling of port when device + * connected. If disabling is really necessary for some devices, + * delete this #if 0 and related #endif */ + /* Disable the port. XXX: Why? */ + err = hub->controller->dev->portstatus (hub->controller, portno, 0); + if (err) + return; +#endif + /* Wait for completion of insertion and stable power (USB spec.) + * Should be at least 100ms, some devices requires more... + * There is also another thing - some devices have worse contacts + * and connected signal is unstable for some time - we should handle + * it - but prevent deadlock in case when device is too faulty... */ + for (total = i = 0; (i < 250) && (total < 2000); i++, total++) + { + grub_millisleep (1); + current_speed = hub->controller->dev->detect_dev + (hub->controller, portno, &changed); + if (current_speed == GRUB_USB_SPEED_NONE) + i = 0; + } + grub_dprintf ("usb", "total=%d\n", total); + if (total >= 2000) + return; + + /* Enable the port. */ + err = hub->controller->dev->portstatus (hub->controller, portno, 1); + if (err) + return; + hub->controller->dev->pending_reset = grub_get_time_ms () + 5000; + + /* Enable the port and create a device. */ + dev = grub_usb_hub_add_dev (hub->controller, speed); + hub->controller->dev->pending_reset = 0; + if (! dev) + return; + + hub->devices[portno] = dev; + + /* If the device is a Hub, scan it for more devices. */ + if (dev->descdev.class == 0x09) + grub_usb_add_hub (dev); +} + +grub_usb_err_t +grub_usb_root_hub (grub_usb_controller_t controller) +{ + int i; + struct grub_usb_hub *hub; + int changed=0; + + hub = grub_malloc (sizeof (*hub)); + if (!hub) + return GRUB_USB_ERR_INTERNAL; + + hub->next = hubs; + hubs = hub; + hub->controller = grub_malloc (sizeof (*controller)); + if (!hub->controller) + { + grub_free (hub); + return GRUB_USB_ERR_INTERNAL; + } + + grub_memcpy (hub->controller, controller, sizeof (*controller)); + hub->dev = 0; + + /* Query the number of ports the root Hub has. */ + hub->nports = controller->dev->hubports (controller); + hub->devices = grub_zalloc (sizeof (hub->devices[0]) * hub->nports); + if (!hub->devices) + { + grub_free (hub->controller); + grub_free (hub); + return GRUB_USB_ERR_INTERNAL; + } + + for (i = 0; i < hub->nports; i++) + { + grub_usb_speed_t speed; + if (!controller->dev->pending_reset) + { + speed = controller->dev->detect_dev (hub->controller, i, + &changed); + + if (speed != GRUB_USB_SPEED_NONE) + attach_root_port (hub, i, speed); + } + } + + return GRUB_USB_ERR_NONE; +} + +static void detach_device (grub_usb_device_t dev); + +static void +detach_device (grub_usb_device_t dev) +{ + unsigned i; + int k; + if (!dev) + return; + if (dev->descdev.class == GRUB_USB_CLASS_HUB) + { + if (dev->hub_transfer) + grub_usb_cancel_transfer (dev->hub_transfer); + + for (i = 0; i < dev->nports; i++) + detach_device (dev->children[i]); + grub_free (dev->children); + } + for (i = 0; i < ARRAY_SIZE (dev->config); i++) + if (dev->config[i].descconf) + for (k = 0; k < dev->config[i].descconf->numif; k++) + { + struct grub_usb_interface *inter = &dev->config[i].interf[k]; + if (inter && inter->detach_hook) + inter->detach_hook (dev, i, k); + } + grub_usb_devs[dev->addr] = 0; +} + +static void +poll_nonroot_hub (grub_usb_device_t dev) +{ + grub_usb_err_t err; + unsigned i; + grub_uint8_t changed; + grub_size_t actual; + int j, total; + + if (!dev->hub_transfer) + return; + + err = grub_usb_check_transfer (dev->hub_transfer, &actual); + + if (err == GRUB_USB_ERR_WAIT) + return; + + changed = dev->statuschange; + + dev->hub_transfer + = grub_usb_bulk_read_background (dev, dev->hub_endpoint->endp_addr, + grub_min (dev->hub_endpoint->maxpacket, + sizeof (dev->statuschange)), + (char *) &dev->statuschange); + + if (err || actual == 0 || changed == 0) + return; + + /* Iterate over the Hub ports. */ + for (i = 1; i <= dev->nports; i++) + { + grub_uint32_t status; + grub_uint32_t current_status = 0; + + if (!(changed & (1 << i))) + continue; + + /* Get the port status. */ + err = grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_IN + | GRUB_USB_REQTYPE_CLASS + | GRUB_USB_REQTYPE_TARGET_OTHER), + GRUB_USB_REQ_GET_STATUS, + 0, i, sizeof (status), (char *) &status); + + grub_printf ("dev = %p, i = %d, status = %08x\n", + dev, i, status); + + if (err) + continue; + + /* FIXME: properly handle these conditions. */ + if (status & GRUB_USB_HUB_STATUS_C_PORT_ENABLED) + grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT + | GRUB_USB_REQTYPE_CLASS + | GRUB_USB_REQTYPE_TARGET_OTHER), + GRUB_USB_REQ_CLEAR_FEATURE, + GRUB_USB_HUB_FEATURE_C_PORT_ENABLED, i, 0, 0); + + if (status & GRUB_USB_HUB_STATUS_C_PORT_SUSPEND) + grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT + | GRUB_USB_REQTYPE_CLASS + | GRUB_USB_REQTYPE_TARGET_OTHER), + GRUB_USB_REQ_CLEAR_FEATURE, + GRUB_USB_HUB_FEATURE_C_PORT_SUSPEND, i, 0, 0); + + if (status & GRUB_USB_HUB_STATUS_C_PORT_OVERCURRENT) + grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT + | GRUB_USB_REQTYPE_CLASS + | GRUB_USB_REQTYPE_TARGET_OTHER), + GRUB_USB_REQ_CLEAR_FEATURE, + GRUB_USB_HUB_FEATURE_C_PORT_OVERCURRENT, i, 0, 0); + + if (!dev->controller.dev->pending_reset && + (status & GRUB_USB_HUB_STATUS_C_PORT_CONNECTED)) + { + grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT + | GRUB_USB_REQTYPE_CLASS + | GRUB_USB_REQTYPE_TARGET_OTHER), + GRUB_USB_REQ_CLEAR_FEATURE, + GRUB_USB_HUB_FEATURE_C_PORT_CONNECTED, i, 0, 0); + + detach_device (dev->children[i - 1]); + dev->children[i - 1] = NULL; + + /* Connected and status of connection changed ? */ + if (status & GRUB_USB_HUB_STATUS_PORT_CONNECTED) + { + /* A device is actually connected to this port. */ + /* Wait for completion of insertion and stable power (USB spec.) + * Should be at least 100ms, some devices requires more... + * There is also another thing - some devices have worse contacts + * and connected signal is unstable for some time - we should handle + * it - but prevent deadlock in case when device is too faulty... */ + for (total = j = 0; (j < 250) && (total < 2000); j++, total++) + { + grub_millisleep (1); + /* Get the port status. */ + err = grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_IN + | GRUB_USB_REQTYPE_CLASS + | GRUB_USB_REQTYPE_TARGET_OTHER), + GRUB_USB_REQ_GET_STATUS, + 0, i, + sizeof (current_status), + (char *) ¤t_status); + if (err) + { + total = 2000; + break; + } + if (!(current_status & GRUB_USB_HUB_STATUS_PORT_CONNECTED)) + j = 0; + } + grub_dprintf ("usb", "(non-root) total=%d\n", total); + if (total >= 2000) + continue; + + /* Now do reset of port. */ + grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT + | GRUB_USB_REQTYPE_CLASS + | GRUB_USB_REQTYPE_TARGET_OTHER), + GRUB_USB_REQ_SET_FEATURE, + GRUB_USB_HUB_FEATURE_PORT_RESET, + i, 0, 0); + rescan = 1; + /* We cannot reset more than one device at the same time ! + * Resetting more devices together results in very bad + * situation: more than one device has default address 0 + * at the same time !!! + * Additionaly, we cannot perform another reset + * anywhere on the same OHCI controller until + * we will finish addressing of reseted device ! */ + dev->controller.dev->pending_reset = grub_get_time_ms () + 5000; + return; + } + } + + if (status & GRUB_USB_HUB_STATUS_C_PORT_RESET) + { + grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT + | GRUB_USB_REQTYPE_CLASS + | GRUB_USB_REQTYPE_TARGET_OTHER), + GRUB_USB_REQ_CLEAR_FEATURE, + GRUB_USB_HUB_FEATURE_C_PORT_RESET, i, 0, 0); + + if (status & GRUB_USB_HUB_STATUS_PORT_CONNECTED) + { + grub_usb_speed_t speed; + grub_usb_device_t next_dev; + + /* Determine the device speed. */ + if (status & GRUB_USB_HUB_STATUS_PORT_LOWSPEED) + speed = GRUB_USB_SPEED_LOW; + else + { + if (status & GRUB_USB_HUB_STATUS_PORT_HIGHSPEED) + speed = GRUB_USB_SPEED_HIGH; + else + speed = GRUB_USB_SPEED_FULL; + } + + /* Wait a recovery time after reset, spec. says 10ms */ + grub_millisleep (10); + + /* Add the device and assign a device address to it. */ + next_dev = grub_usb_hub_add_dev (&dev->controller, speed); + dev->controller.dev->pending_reset = 0; + if (! next_dev) + continue; + + dev->children[i - 1] = next_dev; + + /* If the device is a Hub, scan it for more devices. */ + if (next_dev->descdev.class == 0x09) + grub_usb_add_hub (next_dev); + } + } + } +} + +void +grub_usb_poll_devices (void) +{ + struct grub_usb_hub *hub; + int i; + + for (hub = hubs; hub; hub = hub->next) + { + /* Do we have to recheck number of ports? */ + /* No, it should be never changed, it should be constant. */ + for (i = 0; i < hub->nports; i++) + { + grub_usb_speed_t speed = GRUB_USB_SPEED_NONE; + int changed = 0; + + if (!hub->controller->dev->pending_reset) + { + /* Check for possible timeout */ + if (grub_get_time_ms () > hub->controller->dev->pending_reset) + { + /* Something went wrong, reset device was not + * addressed properly, timeout happened */ + hub->controller->dev->pending_reset = 0; + speed = hub->controller->dev->detect_dev (hub->controller, + i, &changed); + } + } + if (changed) + { + detach_device (hub->devices[i]); + hub->devices[i] = NULL; + if (speed != GRUB_USB_SPEED_NONE) + attach_root_port (hub, i, speed); + } + } + } + + while (1) + { + rescan = 0; + + /* We should check changes of non-root hubs too. */ + for (i = 0; i < GRUB_USBHUB_MAX_DEVICES; i++) + { + grub_usb_device_t dev = grub_usb_devs[i]; + + if (dev && dev->descdev.class == 0x09) + poll_nonroot_hub (dev); + } + if (!rescan) + break; + grub_millisleep (50); + } + +} + +int +grub_usb_iterate (int (*hook) (grub_usb_device_t dev)) +{ + int i; + + for (i = 0; i < GRUB_USBHUB_MAX_DEVICES; i++) + { + if (grub_usb_devs[i]) + { + if (hook (grub_usb_devs[i])) + return 1; + } + } + + return 0; +} diff --git a/grub-core/bus/usb/usbtrans.c b/grub-core/bus/usb/usbtrans.c new file mode 100644 index 0000000..167fae5 --- /dev/null +++ b/grub-core/bus/usb/usbtrans.c @@ -0,0 +1,413 @@ +/* usbtrans.c - USB Transfers and Transactions. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2008 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 . + */ + +#include +#include +#include +#include +#include +#include +#include + +static grub_usb_err_t +grub_usb_execute_and_wait_transfer (grub_usb_device_t dev, + grub_usb_transfer_t transfer, + int timeout, grub_size_t *actual) +{ + grub_usb_err_t err; + grub_uint64_t endtime; + + err = dev->controller.dev->setup_transfer (&dev->controller, transfer); + if (err) + return err; + /* endtime moved behind setup transfer to prevent false timeouts + * while debugging... */ + endtime = grub_get_time_ms () + timeout; + while (1) + { + err = dev->controller.dev->check_transfer (&dev->controller, transfer, + actual); + if (!err) + return GRUB_USB_ERR_NONE; + if (err != GRUB_USB_ERR_WAIT) + return err; + if (grub_get_time_ms () > endtime) + { + err = dev->controller.dev->cancel_transfer (&dev->controller, + transfer); + if (err) + return err; + return GRUB_USB_ERR_TIMEOUT; + } + grub_cpu_idle (); + } +} + +grub_usb_err_t +grub_usb_control_msg (grub_usb_device_t dev, + grub_uint8_t reqtype, + grub_uint8_t request, + grub_uint16_t value, + grub_uint16_t index, + grub_size_t size0, char *data_in) +{ + int i; + grub_usb_transfer_t transfer; + int datablocks; + 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; + grub_size_t actual; + + /* 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=%lu\n", + reqtype, request, value, index, (unsigned long)size); + + /* Create a transfer. */ + transfer = grub_malloc (sizeof (*transfer)); + if (! transfer) + { + grub_dma_free (data_chunk); + return GRUB_USB_ERR_INTERNAL; + } + + setupdata_chunk = grub_memalign_dma32 (32, sizeof (*setupdata)); + if (! setupdata_chunk) + { + grub_free (transfer); + grub_dma_free (data_chunk); + return GRUB_USB_ERR_INTERNAL; + } + + setupdata = grub_dma_get_virt (setupdata_chunk); + setupdata_addr = grub_dma_get_phys (setupdata_chunk); + + /* Determine the maximum packet size. */ + if (dev->descdev.maxsize0) + max = dev->descdev.maxsize0; + else + max = 64; + + grub_dprintf ("usb", "control: transfer = %p, dev = %p\n", transfer, dev); + + datablocks = (size + max - 1) / max; + + /* XXX: Discriminate between different types of control + messages. */ + transfer->transcnt = datablocks + 2; + transfer->size = size; /* XXX ? */ + transfer->endpoint = 0; + transfer->devaddr = dev->addr; + transfer->type = GRUB_USB_TRANSACTION_TYPE_CONTROL; + transfer->max = max; + transfer->dev = dev; + + /* Allocate an array of transfer data structures. */ + transfer->transactions = grub_malloc (transfer->transcnt + * sizeof (struct grub_usb_transfer)); + if (! transfer->transactions) + { + grub_free (transfer); + grub_dma_free (setupdata_chunk); + grub_dma_free (data_chunk); + return GRUB_USB_ERR_INTERNAL; + } + + /* 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); + transfer->transactions[0].pid = GRUB_USB_TRANSFER_TYPE_SETUP; + transfer->transactions[0].data = setupdata_addr; + transfer->transactions[0].toggle = 0; + + /* Now the data... XXX: Is this the right way to transfer control + transfers? */ + for (i = 0; i < datablocks; i++) + { + grub_usb_transaction_t tr = &transfer->transactions[i + 1]; + + tr->size = (size > max) ? max : size; + /* Use the right most bit as the data toggle. Simple and + effective. */ + tr->toggle = !(i & 1); + if (reqtype & 128) + tr->pid = GRUB_USB_TRANSFER_TYPE_IN; + else + tr->pid = GRUB_USB_TRANSFER_TYPE_OUT; + tr->data = data_addr + i * max; + tr->preceding = i * max; + size -= max; + } + + /* End with an empty OUT transaction. */ + transfer->transactions[datablocks + 1].size = 0; + 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; + + transfer->transactions[datablocks + 1].toggle = 1; + + err = grub_usb_execute_and_wait_transfer (dev, transfer, 1000, &actual); + + grub_dprintf ("usb", "control: err=%d\n", err); + + 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_transfer_t +grub_usb_bulk_setup_readwrite (grub_usb_device_t dev, + int endpoint, grub_size_t size0, char *data_in, + grub_transfer_type_t type) +{ + int i; + grub_usb_transfer_t transfer; + int datablocks; + unsigned int max; + volatile char *data; + grub_uint32_t data_addr; + struct grub_pci_dma_chunk *data_chunk; + grub_size_t size = size0; + int toggle = dev->toggle[endpoint]; + + grub_dprintf ("usb", "bulk: size=0x%02lx type=%d\n", (unsigned long) size, + type); + + /* FIXME: avoid allocation any kind of buffer in a first place. */ + data_chunk = grub_memalign_dma32 (128, size); + if (!data_chunk) + return NULL; + 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) + { + struct grub_usb_desc_endp *endpdesc; + endpdesc = grub_usb_get_endpdescriptor (dev, endpoint); + + if (endpdesc) + max = endpdesc->maxpacket; + else + max = 64; + } + else + max = 64; + + /* Create a transfer. */ + transfer = grub_malloc (sizeof (struct grub_usb_transfer)); + if (! transfer) + { + grub_dma_free (data_chunk); + return NULL; + } + + datablocks = ((size + max - 1) / max); + transfer->transcnt = datablocks; + transfer->size = size - 1; + transfer->endpoint = endpoint; + transfer->devaddr = dev->addr; + transfer->type = GRUB_USB_TRANSACTION_TYPE_BULK; + transfer->dir = type; + transfer->max = max; + transfer->dev = dev; + transfer->last_trans = -1; /* Reset index of last processed transaction (TD) */ + transfer->data_chunk = data_chunk; + transfer->data = data_in; + + /* Allocate an array of transfer data structures. */ + transfer->transactions = grub_malloc (transfer->transcnt + * sizeof (struct grub_usb_transfer)); + if (! transfer->transactions) + { + grub_free (transfer); + grub_dma_free (data_chunk); + return NULL; + } + + /* Set up all transfers. */ + for (i = 0; i < datablocks; i++) + { + grub_usb_transaction_t tr = &transfer->transactions[i]; + + tr->size = (size > max) ? max : size; + /* XXX: Use the right most bit as the data toggle. Simple and + effective. */ + tr->toggle = toggle; + toggle = toggle ? 0 : 1; + tr->pid = type; + tr->data = data_addr + i * max; + tr->preceding = i * max; + size -= tr->size; + } + return transfer; +} + +static void +grub_usb_bulk_finish_readwrite (grub_usb_transfer_t transfer) +{ + grub_usb_device_t dev = transfer->dev; + int toggle = dev->toggle[transfer->endpoint]; + + /* 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[transfer->endpoint]; /* Nothing done, take original */ + grub_dprintf ("usb", "bulk: toggle=%d\n", toggle); + dev->toggle[transfer->endpoint] = toggle; + + if (transfer->dir == GRUB_USB_TRANSFER_TYPE_IN) + grub_memcpy (transfer->data, (void *) + grub_dma_get_virt (transfer->data_chunk), + transfer->size + 1); + + grub_free (transfer->transactions); + grub_free (transfer); + grub_dma_free (transfer->data_chunk); +} + +static grub_usb_err_t +grub_usb_bulk_readwrite (grub_usb_device_t dev, + int endpoint, grub_size_t size0, char *data_in, + grub_transfer_type_t type, int timeout, + grub_size_t *actual) +{ + grub_usb_err_t err; + grub_usb_transfer_t transfer; + + transfer = grub_usb_bulk_setup_readwrite (dev, endpoint, size0, + data_in, type); + if (!transfer) + return GRUB_USB_ERR_INTERNAL; + err = grub_usb_execute_and_wait_transfer (dev, transfer, timeout, actual); + + grub_usb_bulk_finish_readwrite (transfer); + + return err; +} + +grub_usb_err_t +grub_usb_bulk_write (grub_usb_device_t dev, + int endpoint, grub_size_t size, char *data) +{ + grub_size_t actual; + grub_usb_err_t err; + + err = grub_usb_bulk_readwrite (dev, endpoint, size, data, + GRUB_USB_TRANSFER_TYPE_OUT, 1000, &actual); + if (!err && actual != size) + err = GRUB_USB_ERR_DATA; + return err; +} + +grub_usb_err_t +grub_usb_bulk_read (grub_usb_device_t dev, + int endpoint, grub_size_t size, char *data) +{ + grub_size_t actual; + grub_usb_err_t err; + err = grub_usb_bulk_readwrite (dev, endpoint, size, data, + GRUB_USB_TRANSFER_TYPE_IN, 1000, &actual); + if (!err && actual != size) + err = GRUB_USB_ERR_DATA; + return err; +} + +grub_usb_err_t +grub_usb_check_transfer (grub_usb_transfer_t transfer, grub_size_t *actual) +{ + grub_usb_err_t err; + grub_usb_device_t dev = transfer->dev; + + err = dev->controller.dev->check_transfer (&dev->controller, transfer, + actual); + if (err == GRUB_USB_ERR_WAIT) + return err; + + grub_usb_bulk_finish_readwrite (transfer); + + return err; +} + +grub_usb_transfer_t +grub_usb_bulk_read_background (grub_usb_device_t dev, + int endpoint, grub_size_t size, void *data) +{ + grub_usb_err_t err; + grub_usb_transfer_t transfer; + + transfer = grub_usb_bulk_setup_readwrite (dev, endpoint, size, + data, GRUB_USB_TRANSFER_TYPE_IN); + if (!transfer) + return NULL; + + err = dev->controller.dev->setup_transfer (&dev->controller, transfer); + if (err) + return NULL; + + return transfer; +} + +void +grub_usb_cancel_transfer (grub_usb_transfer_t transfer) +{ + grub_usb_device_t dev = transfer->dev; + dev->controller.dev->cancel_transfer (&dev->controller, transfer); + grub_errno = GRUB_ERR_NONE; +} + +grub_usb_err_t +grub_usb_bulk_read_extended (grub_usb_device_t dev, + int endpoint, grub_size_t size, char *data, + int timeout, grub_size_t *actual) +{ + return grub_usb_bulk_readwrite (dev, endpoint, size, data, + GRUB_USB_TRANSFER_TYPE_IN, timeout, actual); +} -- cgit v1.2.3