2006-05-21 20:30:15 +04:00
|
|
|
/*
|
|
|
|
* QEMU USB OHCI Emulation
|
|
|
|
* Copyright (c) 2004 Gianni Tedesco
|
|
|
|
* Copyright (c) 2006 CodeSourcery
|
2007-03-17 19:59:31 +03:00
|
|
|
* Copyright (c) 2006 Openedhand Ltd.
|
2006-05-21 20:30:15 +04:00
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
2020-10-23 15:23:32 +03:00
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
2006-05-21 20:30:15 +04:00
|
|
|
*
|
|
|
|
* This library 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
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
2009-07-17 00:47:01 +04:00
|
|
|
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
2006-05-21 20:30:15 +04:00
|
|
|
*
|
|
|
|
* TODO:
|
|
|
|
* o Isochronous transfers
|
|
|
|
* o Allocate bandwidth in frames properly
|
|
|
|
* o Disable timers when nothing needs to be done, or remove timer usage
|
|
|
|
* all together.
|
|
|
|
* o BIOS work to boot from USB storage
|
2023-02-20 21:15:04 +03:00
|
|
|
*/
|
2006-05-21 20:30:15 +04:00
|
|
|
|
2016-01-26 21:17:12 +03:00
|
|
|
#include "qemu/osdep.h"
|
2019-08-12 08:23:42 +03:00
|
|
|
#include "hw/irq.h"
|
include/qemu/osdep.h: Don't include qapi/error.h
Commit 57cb38b included qapi/error.h into qemu/osdep.h to get the
Error typedef. Since then, we've moved to include qemu/osdep.h
everywhere. Its file comment explains: "To avoid getting into
possible circular include dependencies, this file should not include
any other QEMU headers, with the exceptions of config-host.h,
compiler.h, os-posix.h and os-win32.h, all of which are doing a
similar job to this file and are under similar constraints."
qapi/error.h doesn't do a similar job, and it doesn't adhere to
similar constraints: it includes qapi-types.h. That's in excess of
100KiB of crap most .c files don't actually need.
Add the typedef to qemu/typedefs.h, and include that instead of
qapi/error.h. Include qapi/error.h in .c files that need it and don't
get it now. Include qapi-types.h in qom/object.h for uint16List.
Update scripts/clean-includes accordingly. Update it further to match
reality: replace config.h by config-target.h, add sysemu/os-posix.h,
sysemu/os-win32.h. Update the list of includes in the qemu/osdep.h
comment quoted above similarly.
This reduces the number of objects depending on qapi/error.h from "all
of them" to less than a third. Unfortunately, the number depending on
qapi-types.h shrinks only a little. More work is needed for that one.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
[Fix compilation without the spice devel packages. - Paolo]
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
2016-03-14 11:01:28 +03:00
|
|
|
#include "qapi/error.h"
|
2019-05-23 17:35:07 +03:00
|
|
|
#include "qemu/module.h"
|
2012-12-17 21:20:00 +04:00
|
|
|
#include "qemu/timer.h"
|
2012-03-07 17:55:18 +04:00
|
|
|
#include "hw/usb.h"
|
2019-08-12 08:23:45 +03:00
|
|
|
#include "migration/vmstate.h"
|
2012-03-07 17:55:18 +04:00
|
|
|
#include "hw/sysbus.h"
|
2012-06-27 08:50:39 +04:00
|
|
|
#include "hw/qdev-dma.h"
|
2019-08-12 08:23:51 +03:00
|
|
|
#include "hw/qdev-properties.h"
|
2014-09-12 12:55:26 +04:00
|
|
|
#include "trace.h"
|
2019-04-19 10:56:25 +03:00
|
|
|
#include "hcd-ohci.h"
|
2006-05-21 20:30:15 +04:00
|
|
|
|
|
|
|
/* This causes frames to occur 1000x slower */
|
2023-02-20 21:15:04 +03:00
|
|
|
/*#define OHCI_TIME_WARP 1*/
|
2006-05-21 20:30:15 +04:00
|
|
|
|
2017-03-07 11:40:18 +03:00
|
|
|
#define ED_LINK_LIMIT 32
|
2017-02-07 13:23:33 +03:00
|
|
|
|
2006-05-21 20:30:15 +04:00
|
|
|
static int64_t usb_frame_time;
|
|
|
|
static int64_t usb_bit_time;
|
|
|
|
|
|
|
|
/* Host Controller Communications Area */
|
|
|
|
struct ohci_hcca {
|
|
|
|
uint32_t intr[32];
|
|
|
|
uint16_t frame, pad;
|
|
|
|
uint32_t done;
|
|
|
|
};
|
2012-03-08 05:10:44 +04:00
|
|
|
#define HCCA_WRITEBACK_OFFSET offsetof(struct ohci_hcca, frame)
|
|
|
|
#define HCCA_WRITEBACK_SIZE 8 /* frame, pad, done */
|
|
|
|
|
|
|
|
#define ED_WBACK_OFFSET offsetof(struct ohci_ed, head)
|
|
|
|
#define ED_WBACK_SIZE 4
|
2006-05-21 20:30:15 +04:00
|
|
|
|
2023-02-20 21:15:10 +03:00
|
|
|
/* Bitfields for the first word of an Endpoint Descriptor. */
|
2006-05-21 20:30:15 +04:00
|
|
|
#define OHCI_ED_FA_SHIFT 0
|
2023-02-20 21:15:05 +03:00
|
|
|
#define OHCI_ED_FA_MASK (0x7f << OHCI_ED_FA_SHIFT)
|
2006-05-21 20:30:15 +04:00
|
|
|
#define OHCI_ED_EN_SHIFT 7
|
2023-02-20 21:15:05 +03:00
|
|
|
#define OHCI_ED_EN_MASK (0xf << OHCI_ED_EN_SHIFT)
|
2006-05-21 20:30:15 +04:00
|
|
|
#define OHCI_ED_D_SHIFT 11
|
2023-02-20 21:15:05 +03:00
|
|
|
#define OHCI_ED_D_MASK (3 << OHCI_ED_D_SHIFT)
|
|
|
|
#define OHCI_ED_S (1 << 13)
|
|
|
|
#define OHCI_ED_K (1 << 14)
|
|
|
|
#define OHCI_ED_F (1 << 15)
|
2007-10-31 03:34:21 +03:00
|
|
|
#define OHCI_ED_MPS_SHIFT 16
|
2023-02-20 21:15:05 +03:00
|
|
|
#define OHCI_ED_MPS_MASK (0x7ff << OHCI_ED_MPS_SHIFT)
|
2006-05-21 20:30:15 +04:00
|
|
|
|
2023-02-20 21:15:10 +03:00
|
|
|
/* Flags in the head field of an Endpoint Descriptor. */
|
2006-05-21 20:30:15 +04:00
|
|
|
#define OHCI_ED_H 1
|
|
|
|
#define OHCI_ED_C 2
|
|
|
|
|
2023-02-20 21:15:10 +03:00
|
|
|
/* Bitfields for the first word of a Transfer Descriptor. */
|
2023-02-20 21:15:05 +03:00
|
|
|
#define OHCI_TD_R (1 << 18)
|
2006-05-21 20:30:15 +04:00
|
|
|
#define OHCI_TD_DP_SHIFT 19
|
2023-02-20 21:15:05 +03:00
|
|
|
#define OHCI_TD_DP_MASK (3 << OHCI_TD_DP_SHIFT)
|
2006-05-21 20:30:15 +04:00
|
|
|
#define OHCI_TD_DI_SHIFT 21
|
2023-02-20 21:15:05 +03:00
|
|
|
#define OHCI_TD_DI_MASK (7 << OHCI_TD_DI_SHIFT)
|
|
|
|
#define OHCI_TD_T0 (1 << 24)
|
|
|
|
#define OHCI_TD_T1 (1 << 25)
|
2006-05-21 20:30:15 +04:00
|
|
|
#define OHCI_TD_EC_SHIFT 26
|
2023-02-20 21:15:05 +03:00
|
|
|
#define OHCI_TD_EC_MASK (3 << OHCI_TD_EC_SHIFT)
|
2006-05-21 20:30:15 +04:00
|
|
|
#define OHCI_TD_CC_SHIFT 28
|
2023-02-20 21:15:05 +03:00
|
|
|
#define OHCI_TD_CC_MASK (0xf << OHCI_TD_CC_SHIFT)
|
2006-05-21 20:30:15 +04:00
|
|
|
|
2023-02-20 21:15:10 +03:00
|
|
|
/* Bitfields for the first word of an Isochronous Transfer Descriptor. */
|
|
|
|
/* CC & DI - same as in the General Transfer Descriptor */
|
2007-10-31 03:34:21 +03:00
|
|
|
#define OHCI_TD_SF_SHIFT 0
|
2023-02-20 21:15:05 +03:00
|
|
|
#define OHCI_TD_SF_MASK (0xffff << OHCI_TD_SF_SHIFT)
|
2007-10-31 03:34:21 +03:00
|
|
|
#define OHCI_TD_FC_SHIFT 24
|
2023-02-20 21:15:05 +03:00
|
|
|
#define OHCI_TD_FC_MASK (7 << OHCI_TD_FC_SHIFT)
|
2007-10-31 03:34:21 +03:00
|
|
|
|
2023-02-20 21:15:10 +03:00
|
|
|
/* Isochronous Transfer Descriptor - Offset / PacketStatusWord */
|
2007-10-31 03:34:21 +03:00
|
|
|
#define OHCI_TD_PSW_CC_SHIFT 12
|
2023-02-20 21:15:05 +03:00
|
|
|
#define OHCI_TD_PSW_CC_MASK (0xf << OHCI_TD_PSW_CC_SHIFT)
|
2007-10-31 03:34:21 +03:00
|
|
|
#define OHCI_TD_PSW_SIZE_SHIFT 0
|
2023-02-20 21:15:05 +03:00
|
|
|
#define OHCI_TD_PSW_SIZE_MASK (0xfff << OHCI_TD_PSW_SIZE_SHIFT)
|
2007-10-31 03:34:21 +03:00
|
|
|
|
|
|
|
#define OHCI_PAGE_MASK 0xfffff000
|
|
|
|
#define OHCI_OFFSET_MASK 0xfff
|
|
|
|
|
2006-05-21 20:30:15 +04:00
|
|
|
#define OHCI_DPTR_MASK 0xfffffff0
|
|
|
|
|
|
|
|
#define OHCI_BM(val, field) \
|
|
|
|
(((val) & OHCI_##field##_MASK) >> OHCI_##field##_SHIFT)
|
|
|
|
|
|
|
|
#define OHCI_SET_BM(val, field, newval) do { \
|
|
|
|
val &= ~OHCI_##field##_MASK; \
|
|
|
|
val |= ((newval) << OHCI_##field##_SHIFT) & OHCI_##field##_MASK; \
|
2023-02-20 21:15:05 +03:00
|
|
|
} while (0)
|
2006-05-21 20:30:15 +04:00
|
|
|
|
|
|
|
/* endpoint descriptor */
|
|
|
|
struct ohci_ed {
|
|
|
|
uint32_t flags;
|
|
|
|
uint32_t tail;
|
|
|
|
uint32_t head;
|
|
|
|
uint32_t next;
|
|
|
|
};
|
|
|
|
|
|
|
|
/* General transfer descriptor */
|
|
|
|
struct ohci_td {
|
|
|
|
uint32_t flags;
|
|
|
|
uint32_t cbp;
|
|
|
|
uint32_t next;
|
|
|
|
uint32_t be;
|
|
|
|
};
|
|
|
|
|
2007-10-31 03:34:21 +03:00
|
|
|
/* Isochronous transfer descriptor */
|
|
|
|
struct ohci_iso_td {
|
|
|
|
uint32_t flags;
|
|
|
|
uint32_t bp;
|
|
|
|
uint32_t next;
|
|
|
|
uint32_t be;
|
|
|
|
uint16_t offset[8];
|
|
|
|
};
|
|
|
|
|
2006-05-21 20:30:15 +04:00
|
|
|
#define USB_HZ 12000000
|
|
|
|
|
|
|
|
/* OHCI Local stuff */
|
2023-02-20 21:15:05 +03:00
|
|
|
#define OHCI_CTL_CBSR ((1 << 0) | (1 << 1))
|
|
|
|
#define OHCI_CTL_PLE (1 << 2)
|
|
|
|
#define OHCI_CTL_IE (1 << 3)
|
|
|
|
#define OHCI_CTL_CLE (1 << 4)
|
|
|
|
#define OHCI_CTL_BLE (1 << 5)
|
|
|
|
#define OHCI_CTL_HCFS ((1 << 6) | (1 << 7))
|
2006-05-21 20:30:15 +04:00
|
|
|
#define OHCI_USB_RESET 0x00
|
|
|
|
#define OHCI_USB_RESUME 0x40
|
|
|
|
#define OHCI_USB_OPERATIONAL 0x80
|
|
|
|
#define OHCI_USB_SUSPEND 0xc0
|
2023-02-20 21:15:05 +03:00
|
|
|
#define OHCI_CTL_IR (1 << 8)
|
|
|
|
#define OHCI_CTL_RWC (1 << 9)
|
|
|
|
#define OHCI_CTL_RWE (1 << 10)
|
|
|
|
|
|
|
|
#define OHCI_STATUS_HCR (1 << 0)
|
|
|
|
#define OHCI_STATUS_CLF (1 << 1)
|
|
|
|
#define OHCI_STATUS_BLF (1 << 2)
|
|
|
|
#define OHCI_STATUS_OCR (1 << 3)
|
|
|
|
#define OHCI_STATUS_SOC ((1 << 6) | (1 << 7))
|
|
|
|
|
|
|
|
#define OHCI_INTR_SO (1U << 0) /* Scheduling overrun */
|
|
|
|
#define OHCI_INTR_WD (1U << 1) /* HcDoneHead writeback */
|
|
|
|
#define OHCI_INTR_SF (1U << 2) /* Start of frame */
|
|
|
|
#define OHCI_INTR_RD (1U << 3) /* Resume detect */
|
|
|
|
#define OHCI_INTR_UE (1U << 4) /* Unrecoverable error */
|
|
|
|
#define OHCI_INTR_FNO (1U << 5) /* Frame number overflow */
|
|
|
|
#define OHCI_INTR_RHSC (1U << 6) /* Root hub status change */
|
|
|
|
#define OHCI_INTR_OC (1U << 30) /* Ownership change */
|
|
|
|
#define OHCI_INTR_MIE (1U << 31) /* Master Interrupt Enable */
|
2006-05-21 20:30:15 +04:00
|
|
|
|
|
|
|
#define OHCI_HCCA_SIZE 0x100
|
|
|
|
#define OHCI_HCCA_MASK 0xffffff00
|
|
|
|
|
|
|
|
#define OHCI_EDPTR_MASK 0xfffffff0
|
|
|
|
|
|
|
|
#define OHCI_FMI_FI 0x00003fff
|
|
|
|
#define OHCI_FMI_FSMPS 0xffff0000
|
|
|
|
#define OHCI_FMI_FIT 0x80000000
|
|
|
|
|
2023-02-20 21:15:05 +03:00
|
|
|
#define OHCI_FR_RT (1U << 31)
|
2006-05-21 20:30:15 +04:00
|
|
|
|
|
|
|
#define OHCI_LS_THRESH 0x628
|
|
|
|
|
|
|
|
#define OHCI_RHA_RW_MASK 0x00000000 /* Mask of supported features. */
|
2023-02-20 21:15:05 +03:00
|
|
|
#define OHCI_RHA_PSM (1 << 8)
|
|
|
|
#define OHCI_RHA_NPS (1 << 9)
|
|
|
|
#define OHCI_RHA_DT (1 << 10)
|
|
|
|
#define OHCI_RHA_OCPM (1 << 11)
|
|
|
|
#define OHCI_RHA_NOCP (1 << 12)
|
2006-05-21 20:30:15 +04:00
|
|
|
#define OHCI_RHA_POTPGT_MASK 0xff000000
|
|
|
|
|
2023-02-20 21:15:05 +03:00
|
|
|
#define OHCI_RHS_LPS (1U << 0)
|
|
|
|
#define OHCI_RHS_OCI (1U << 1)
|
|
|
|
#define OHCI_RHS_DRWE (1U << 15)
|
|
|
|
#define OHCI_RHS_LPSC (1U << 16)
|
|
|
|
#define OHCI_RHS_OCIC (1U << 17)
|
|
|
|
#define OHCI_RHS_CRWE (1U << 31)
|
|
|
|
|
|
|
|
#define OHCI_PORT_CCS (1 << 0)
|
|
|
|
#define OHCI_PORT_PES (1 << 1)
|
|
|
|
#define OHCI_PORT_PSS (1 << 2)
|
|
|
|
#define OHCI_PORT_POCI (1 << 3)
|
|
|
|
#define OHCI_PORT_PRS (1 << 4)
|
|
|
|
#define OHCI_PORT_PPS (1 << 8)
|
|
|
|
#define OHCI_PORT_LSDA (1 << 9)
|
|
|
|
#define OHCI_PORT_CSC (1 << 16)
|
|
|
|
#define OHCI_PORT_PESC (1 << 17)
|
|
|
|
#define OHCI_PORT_PSSC (1 << 18)
|
|
|
|
#define OHCI_PORT_OCIC (1 << 19)
|
|
|
|
#define OHCI_PORT_PRSC (1 << 20)
|
|
|
|
#define OHCI_PORT_WTC (OHCI_PORT_CSC | OHCI_PORT_PESC | \
|
|
|
|
OHCI_PORT_PSSC | OHCI_PORT_OCIC | \
|
|
|
|
OHCI_PORT_PRSC)
|
2006-05-21 20:30:15 +04:00
|
|
|
#define OHCI_TD_DIR_SETUP 0x0
|
|
|
|
#define OHCI_TD_DIR_OUT 0x1
|
|
|
|
#define OHCI_TD_DIR_IN 0x2
|
|
|
|
#define OHCI_TD_DIR_RESERVED 0x3
|
|
|
|
|
|
|
|
#define OHCI_CC_NOERROR 0x0
|
|
|
|
#define OHCI_CC_CRC 0x1
|
|
|
|
#define OHCI_CC_BITSTUFFING 0x2
|
|
|
|
#define OHCI_CC_DATATOGGLEMISMATCH 0x3
|
|
|
|
#define OHCI_CC_STALL 0x4
|
|
|
|
#define OHCI_CC_DEVICENOTRESPONDING 0x5
|
|
|
|
#define OHCI_CC_PIDCHECKFAILURE 0x6
|
|
|
|
#define OHCI_CC_UNDEXPETEDPID 0x7
|
|
|
|
#define OHCI_CC_DATAOVERRUN 0x8
|
|
|
|
#define OHCI_CC_DATAUNDERRUN 0x9
|
|
|
|
#define OHCI_CC_BUFFEROVERRUN 0xc
|
|
|
|
#define OHCI_CC_BUFFERUNDERRUN 0xd
|
|
|
|
|
2007-03-17 19:59:31 +03:00
|
|
|
#define OHCI_HRESET_FSBIR (1 << 0)
|
|
|
|
|
2023-02-20 21:19:09 +03:00
|
|
|
static const char *ohci_reg_names[] = {
|
|
|
|
"HcRevision", "HcControl", "HcCommandStatus", "HcInterruptStatus",
|
|
|
|
"HcInterruptEnable", "HcInterruptDisable", "HcHCCA", "HcPeriodCurrentED",
|
|
|
|
"HcControlHeadED", "HcControlCurrentED", "HcBulkHeadED", "HcBulkCurrentED",
|
|
|
|
"HcDoneHead", "HcFmInterval", "HcFmRemaining", "HcFmNumber",
|
|
|
|
"HcPeriodicStart", "HcLSThreshold", "HcRhDescriptorA", "HcRhDescriptorB",
|
|
|
|
"HcRhStatus"
|
|
|
|
};
|
|
|
|
|
|
|
|
static const char *ohci_reg_name(hwaddr addr)
|
|
|
|
{
|
|
|
|
if (addr >> 2 < ARRAY_SIZE(ohci_reg_names)) {
|
|
|
|
return ohci_reg_names[addr >> 2];
|
|
|
|
} else {
|
|
|
|
return "<unknown>";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-19 10:56:24 +03:00
|
|
|
static void ohci_die(OHCIState *ohci)
|
|
|
|
{
|
|
|
|
ohci->ohci_die(ohci);
|
|
|
|
}
|
2013-07-26 14:52:05 +04:00
|
|
|
|
2006-05-22 21:17:06 +04:00
|
|
|
/* Update IRQ levels */
|
|
|
|
static inline void ohci_intr_update(OHCIState *ohci)
|
|
|
|
{
|
|
|
|
int level = 0;
|
|
|
|
|
|
|
|
if ((ohci->intr & OHCI_INTR_MIE) &&
|
|
|
|
(ohci->intr_status & ohci->intr))
|
|
|
|
level = 1;
|
|
|
|
|
2007-04-07 22:14:41 +04:00
|
|
|
qemu_set_irq(ohci->irq, level);
|
2006-05-22 21:17:06 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Set an interrupt */
|
|
|
|
static inline void ohci_set_interrupt(OHCIState *ohci, uint32_t intr)
|
|
|
|
{
|
|
|
|
ohci->intr_status |= intr;
|
|
|
|
ohci_intr_update(ohci);
|
|
|
|
}
|
|
|
|
|
2012-01-10 20:56:17 +04:00
|
|
|
static USBDevice *ohci_find_device(OHCIState *ohci, uint8_t addr)
|
|
|
|
{
|
|
|
|
USBDevice *dev;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < ohci->num_ports; i++) {
|
|
|
|
if ((ohci->rhport[i].ctrl & OHCI_PORT_PES) == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
dev = usb_find_device(&ohci->rhport[i].port, addr);
|
|
|
|
if (dev != NULL) {
|
|
|
|
return dev;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2019-04-19 10:56:25 +03:00
|
|
|
void ohci_stop_endpoints(OHCIState *ohci)
|
2012-12-14 17:35:40 +04:00
|
|
|
{
|
|
|
|
USBDevice *dev;
|
|
|
|
int i, j;
|
|
|
|
|
2022-01-25 16:33:20 +03:00
|
|
|
if (ohci->async_td) {
|
|
|
|
usb_cancel_packet(&ohci->usb_packet);
|
|
|
|
ohci->async_td = 0;
|
|
|
|
}
|
2012-12-14 17:35:40 +04:00
|
|
|
for (i = 0; i < ohci->num_ports; i++) {
|
|
|
|
dev = ohci->rhport[i].port.dev;
|
|
|
|
if (dev && dev->attached) {
|
|
|
|
usb_device_ep_stopped(dev, &dev->ep_ctl);
|
|
|
|
for (j = 0; j < USB_MAX_ENDPOINTS; j++) {
|
|
|
|
usb_device_ep_stopped(dev, &dev->ep_in[j]);
|
|
|
|
usb_device_ep_stopped(dev, &dev->ep_out[j]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-20 02:23:49 +03:00
|
|
|
static void ohci_roothub_reset(OHCIState *ohci)
|
2006-05-21 20:30:15 +04:00
|
|
|
{
|
|
|
|
OHCIPort *port;
|
|
|
|
int i;
|
|
|
|
|
2007-07-25 20:50:37 +04:00
|
|
|
ohci_bus_stop(ohci);
|
2015-12-20 02:23:49 +03:00
|
|
|
ohci->rhdesc_a = OHCI_RHA_NPS | ohci->num_ports;
|
|
|
|
ohci->rhdesc_b = 0x0; /* Impl. specific */
|
|
|
|
ohci->rhstatus = 0;
|
|
|
|
|
|
|
|
for (i = 0; i < ohci->num_ports; i++) {
|
|
|
|
port = &ohci->rhport[i];
|
|
|
|
port->ctrl = 0;
|
|
|
|
if (port->port.dev && port->port.dev->attached) {
|
|
|
|
usb_port_reset(&port->port);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ohci_stop_endpoints(ohci);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Reset the controller */
|
|
|
|
static void ohci_soft_reset(OHCIState *ohci)
|
|
|
|
{
|
|
|
|
trace_usb_ohci_reset(ohci->name);
|
|
|
|
|
|
|
|
ohci_bus_stop(ohci);
|
|
|
|
ohci->ctl = (ohci->ctl & OHCI_CTL_IR) | OHCI_USB_SUSPEND;
|
2006-08-12 05:04:27 +04:00
|
|
|
ohci->old_ctl = 0;
|
2006-05-21 20:30:15 +04:00
|
|
|
ohci->status = 0;
|
|
|
|
ohci->intr_status = 0;
|
|
|
|
ohci->intr = OHCI_INTR_MIE;
|
|
|
|
|
|
|
|
ohci->hcca = 0;
|
|
|
|
ohci->ctrl_head = ohci->ctrl_cur = 0;
|
|
|
|
ohci->bulk_head = ohci->bulk_cur = 0;
|
|
|
|
ohci->per_cur = 0;
|
|
|
|
ohci->done = 0;
|
|
|
|
ohci->done_count = 7;
|
2023-02-20 21:15:04 +03:00
|
|
|
/*
|
|
|
|
* FSMPS is marked TBD in OCHI 1.0, what gives ffs?
|
2006-05-21 20:30:15 +04:00
|
|
|
* I took the value linux sets ...
|
|
|
|
*/
|
|
|
|
ohci->fsmps = 0x2778;
|
|
|
|
ohci->fi = 0x2edf;
|
|
|
|
ohci->fit = 0;
|
|
|
|
ohci->frt = 0;
|
|
|
|
ohci->frame_number = 0;
|
|
|
|
ohci->pstart = 0;
|
|
|
|
ohci->lst = OHCI_LS_THRESH;
|
2015-12-20 02:23:49 +03:00
|
|
|
}
|
2006-05-21 20:30:15 +04:00
|
|
|
|
2019-04-19 10:56:25 +03:00
|
|
|
void ohci_hard_reset(OHCIState *ohci)
|
2015-12-20 02:23:49 +03:00
|
|
|
{
|
|
|
|
ohci_soft_reset(ohci);
|
|
|
|
ohci->ctl = 0;
|
|
|
|
ohci_roothub_reset(ohci);
|
2006-05-21 20:30:15 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Get an array of dwords from main memory */
|
2009-04-19 13:15:50 +04:00
|
|
|
static inline int get_dwords(OHCIState *ohci,
|
2012-06-27 08:50:39 +04:00
|
|
|
dma_addr_t addr, uint32_t *buf, int num)
|
2006-05-21 20:30:15 +04:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
2009-04-19 13:15:50 +04:00
|
|
|
addr += ohci->localmem_base;
|
|
|
|
|
2006-05-21 20:30:15 +04:00
|
|
|
for (i = 0; i < num; i++, buf++, addr += sizeof(*buf)) {
|
dma: Let dma_memory_read/write() take MemTxAttrs argument
Let devices specify transaction attributes when calling
dma_memory_read() or dma_memory_write().
Patch created mechanically using spatch with this script:
@@
expression E1, E2, E3, E4;
@@
(
- dma_memory_read(E1, E2, E3, E4)
+ dma_memory_read(E1, E2, E3, E4, MEMTXATTRS_UNSPECIFIED)
|
- dma_memory_write(E1, E2, E3, E4)
+ dma_memory_write(E1, E2, E3, E4, MEMTXATTRS_UNSPECIFIED)
)
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
Reviewed-by: Li Qiang <liq3ea@gmail.com>
Reviewed-by: Edgar E. Iglesias <edgar.iglesias@xilinx.com>
Signed-off-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
Message-Id: <20211223115554.3155328-6-philmd@redhat.com>
2020-09-03 11:08:29 +03:00
|
|
|
if (dma_memory_read(ohci->as, addr,
|
|
|
|
buf, sizeof(*buf), MEMTXATTRS_UNSPECIFIED)) {
|
2013-07-26 14:52:05 +04:00
|
|
|
return -1;
|
|
|
|
}
|
2006-05-21 20:30:15 +04:00
|
|
|
*buf = le32_to_cpu(*buf);
|
|
|
|
}
|
|
|
|
|
2013-07-26 14:52:05 +04:00
|
|
|
return 0;
|
2006-05-21 20:30:15 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Put an array of dwords in to main memory */
|
2009-04-19 13:15:50 +04:00
|
|
|
static inline int put_dwords(OHCIState *ohci,
|
2012-06-27 08:50:39 +04:00
|
|
|
dma_addr_t addr, uint32_t *buf, int num)
|
2006-05-21 20:30:15 +04:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
2009-04-19 13:15:50 +04:00
|
|
|
addr += ohci->localmem_base;
|
|
|
|
|
2006-05-21 20:30:15 +04:00
|
|
|
for (i = 0; i < num; i++, buf++, addr += sizeof(*buf)) {
|
|
|
|
uint32_t tmp = cpu_to_le32(*buf);
|
dma: Let dma_memory_read/write() take MemTxAttrs argument
Let devices specify transaction attributes when calling
dma_memory_read() or dma_memory_write().
Patch created mechanically using spatch with this script:
@@
expression E1, E2, E3, E4;
@@
(
- dma_memory_read(E1, E2, E3, E4)
+ dma_memory_read(E1, E2, E3, E4, MEMTXATTRS_UNSPECIFIED)
|
- dma_memory_write(E1, E2, E3, E4)
+ dma_memory_write(E1, E2, E3, E4, MEMTXATTRS_UNSPECIFIED)
)
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
Reviewed-by: Li Qiang <liq3ea@gmail.com>
Reviewed-by: Edgar E. Iglesias <edgar.iglesias@xilinx.com>
Signed-off-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
Message-Id: <20211223115554.3155328-6-philmd@redhat.com>
2020-09-03 11:08:29 +03:00
|
|
|
if (dma_memory_write(ohci->as, addr,
|
|
|
|
&tmp, sizeof(tmp), MEMTXATTRS_UNSPECIFIED)) {
|
2013-07-26 14:52:05 +04:00
|
|
|
return -1;
|
|
|
|
}
|
2006-05-21 20:30:15 +04:00
|
|
|
}
|
|
|
|
|
2013-07-26 14:52:05 +04:00
|
|
|
return 0;
|
2006-05-21 20:30:15 +04:00
|
|
|
}
|
|
|
|
|
2007-10-31 03:34:21 +03:00
|
|
|
/* Get an array of words from main memory */
|
2009-04-19 13:15:50 +04:00
|
|
|
static inline int get_words(OHCIState *ohci,
|
2012-06-27 08:50:39 +04:00
|
|
|
dma_addr_t addr, uint16_t *buf, int num)
|
2007-10-31 03:34:21 +03:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
2009-04-19 13:15:50 +04:00
|
|
|
addr += ohci->localmem_base;
|
|
|
|
|
2007-10-31 03:34:21 +03:00
|
|
|
for (i = 0; i < num; i++, buf++, addr += sizeof(*buf)) {
|
dma: Let dma_memory_read/write() take MemTxAttrs argument
Let devices specify transaction attributes when calling
dma_memory_read() or dma_memory_write().
Patch created mechanically using spatch with this script:
@@
expression E1, E2, E3, E4;
@@
(
- dma_memory_read(E1, E2, E3, E4)
+ dma_memory_read(E1, E2, E3, E4, MEMTXATTRS_UNSPECIFIED)
|
- dma_memory_write(E1, E2, E3, E4)
+ dma_memory_write(E1, E2, E3, E4, MEMTXATTRS_UNSPECIFIED)
)
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
Reviewed-by: Li Qiang <liq3ea@gmail.com>
Reviewed-by: Edgar E. Iglesias <edgar.iglesias@xilinx.com>
Signed-off-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
Message-Id: <20211223115554.3155328-6-philmd@redhat.com>
2020-09-03 11:08:29 +03:00
|
|
|
if (dma_memory_read(ohci->as, addr,
|
|
|
|
buf, sizeof(*buf), MEMTXATTRS_UNSPECIFIED)) {
|
2013-07-26 14:52:05 +04:00
|
|
|
return -1;
|
|
|
|
}
|
2007-10-31 03:34:21 +03:00
|
|
|
*buf = le16_to_cpu(*buf);
|
|
|
|
}
|
|
|
|
|
2013-07-26 14:52:05 +04:00
|
|
|
return 0;
|
2007-10-31 03:34:21 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Put an array of words in to main memory */
|
2009-04-19 13:15:50 +04:00
|
|
|
static inline int put_words(OHCIState *ohci,
|
2012-06-27 08:50:39 +04:00
|
|
|
dma_addr_t addr, uint16_t *buf, int num)
|
2007-10-31 03:34:21 +03:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
2009-04-19 13:15:50 +04:00
|
|
|
addr += ohci->localmem_base;
|
|
|
|
|
2007-10-31 03:34:21 +03:00
|
|
|
for (i = 0; i < num; i++, buf++, addr += sizeof(*buf)) {
|
|
|
|
uint16_t tmp = cpu_to_le16(*buf);
|
dma: Let dma_memory_read/write() take MemTxAttrs argument
Let devices specify transaction attributes when calling
dma_memory_read() or dma_memory_write().
Patch created mechanically using spatch with this script:
@@
expression E1, E2, E3, E4;
@@
(
- dma_memory_read(E1, E2, E3, E4)
+ dma_memory_read(E1, E2, E3, E4, MEMTXATTRS_UNSPECIFIED)
|
- dma_memory_write(E1, E2, E3, E4)
+ dma_memory_write(E1, E2, E3, E4, MEMTXATTRS_UNSPECIFIED)
)
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
Reviewed-by: Li Qiang <liq3ea@gmail.com>
Reviewed-by: Edgar E. Iglesias <edgar.iglesias@xilinx.com>
Signed-off-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
Message-Id: <20211223115554.3155328-6-philmd@redhat.com>
2020-09-03 11:08:29 +03:00
|
|
|
if (dma_memory_write(ohci->as, addr,
|
|
|
|
&tmp, sizeof(tmp), MEMTXATTRS_UNSPECIFIED)) {
|
2013-07-26 14:52:05 +04:00
|
|
|
return -1;
|
|
|
|
}
|
2007-10-31 03:34:21 +03:00
|
|
|
}
|
|
|
|
|
2013-07-26 14:52:05 +04:00
|
|
|
return 0;
|
2007-10-31 03:34:21 +03:00
|
|
|
}
|
|
|
|
|
2009-04-19 13:15:50 +04:00
|
|
|
static inline int ohci_read_ed(OHCIState *ohci,
|
2012-06-27 08:50:39 +04:00
|
|
|
dma_addr_t addr, struct ohci_ed *ed)
|
2006-05-21 20:30:15 +04:00
|
|
|
{
|
2009-04-19 13:15:50 +04:00
|
|
|
return get_dwords(ohci, addr, (uint32_t *)ed, sizeof(*ed) >> 2);
|
2006-05-21 20:30:15 +04:00
|
|
|
}
|
|
|
|
|
2009-04-19 13:15:50 +04:00
|
|
|
static inline int ohci_read_td(OHCIState *ohci,
|
2012-06-27 08:50:39 +04:00
|
|
|
dma_addr_t addr, struct ohci_td *td)
|
2006-05-21 20:30:15 +04:00
|
|
|
{
|
2009-04-19 13:15:50 +04:00
|
|
|
return get_dwords(ohci, addr, (uint32_t *)td, sizeof(*td) >> 2);
|
2006-05-21 20:30:15 +04:00
|
|
|
}
|
|
|
|
|
2009-04-19 13:15:50 +04:00
|
|
|
static inline int ohci_read_iso_td(OHCIState *ohci,
|
2012-06-27 08:50:39 +04:00
|
|
|
dma_addr_t addr, struct ohci_iso_td *td)
|
2007-10-31 03:34:21 +03:00
|
|
|
{
|
2013-07-26 14:52:05 +04:00
|
|
|
return get_dwords(ohci, addr, (uint32_t *)td, 4) ||
|
|
|
|
get_words(ohci, addr + 16, td->offset, 8);
|
2007-10-31 03:34:21 +03:00
|
|
|
}
|
|
|
|
|
2009-04-19 13:15:50 +04:00
|
|
|
static inline int ohci_read_hcca(OHCIState *ohci,
|
2012-06-27 08:50:39 +04:00
|
|
|
dma_addr_t addr, struct ohci_hcca *hcca)
|
2006-05-21 20:30:15 +04:00
|
|
|
{
|
dma: Let dma_memory_read/write() take MemTxAttrs argument
Let devices specify transaction attributes when calling
dma_memory_read() or dma_memory_write().
Patch created mechanically using spatch with this script:
@@
expression E1, E2, E3, E4;
@@
(
- dma_memory_read(E1, E2, E3, E4)
+ dma_memory_read(E1, E2, E3, E4, MEMTXATTRS_UNSPECIFIED)
|
- dma_memory_write(E1, E2, E3, E4)
+ dma_memory_write(E1, E2, E3, E4, MEMTXATTRS_UNSPECIFIED)
)
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
Reviewed-by: Li Qiang <liq3ea@gmail.com>
Reviewed-by: Edgar E. Iglesias <edgar.iglesias@xilinx.com>
Signed-off-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
Message-Id: <20211223115554.3155328-6-philmd@redhat.com>
2020-09-03 11:08:29 +03:00
|
|
|
return dma_memory_read(ohci->as, addr + ohci->localmem_base, hcca,
|
|
|
|
sizeof(*hcca), MEMTXATTRS_UNSPECIFIED);
|
2006-05-21 20:30:15 +04:00
|
|
|
}
|
|
|
|
|
2009-04-19 13:15:50 +04:00
|
|
|
static inline int ohci_put_ed(OHCIState *ohci,
|
2012-06-27 08:50:39 +04:00
|
|
|
dma_addr_t addr, struct ohci_ed *ed)
|
2006-05-21 20:30:15 +04:00
|
|
|
{
|
2023-02-20 21:15:04 +03:00
|
|
|
/*
|
|
|
|
* ed->tail is under control of the HCD.
|
2012-03-08 05:10:44 +04:00
|
|
|
* Since just ed->head is changed by HC, just write back this
|
|
|
|
*/
|
|
|
|
return put_dwords(ohci, addr + ED_WBACK_OFFSET,
|
|
|
|
(uint32_t *)((char *)ed + ED_WBACK_OFFSET),
|
|
|
|
ED_WBACK_SIZE >> 2);
|
2006-05-21 20:30:15 +04:00
|
|
|
}
|
|
|
|
|
2009-04-19 13:15:50 +04:00
|
|
|
static inline int ohci_put_td(OHCIState *ohci,
|
2012-06-27 08:50:39 +04:00
|
|
|
dma_addr_t addr, struct ohci_td *td)
|
2007-10-31 03:34:21 +03:00
|
|
|
{
|
2009-04-19 13:15:50 +04:00
|
|
|
return put_dwords(ohci, addr, (uint32_t *)td, sizeof(*td) >> 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int ohci_put_iso_td(OHCIState *ohci,
|
2012-06-27 08:50:39 +04:00
|
|
|
dma_addr_t addr, struct ohci_iso_td *td)
|
2009-04-19 13:15:50 +04:00
|
|
|
{
|
2014-08-10 00:34:36 +04:00
|
|
|
return put_dwords(ohci, addr, (uint32_t *)td, 4) ||
|
|
|
|
put_words(ohci, addr + 16, td->offset, 8);
|
2009-04-19 13:15:50 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline int ohci_put_hcca(OHCIState *ohci,
|
2012-06-27 08:50:39 +04:00
|
|
|
dma_addr_t addr, struct ohci_hcca *hcca)
|
2009-04-19 13:15:50 +04:00
|
|
|
{
|
2013-07-26 14:52:05 +04:00
|
|
|
return dma_memory_write(ohci->as,
|
|
|
|
addr + ohci->localmem_base + HCCA_WRITEBACK_OFFSET,
|
|
|
|
(char *)hcca + HCCA_WRITEBACK_OFFSET,
|
dma: Let dma_memory_read/write() take MemTxAttrs argument
Let devices specify transaction attributes when calling
dma_memory_read() or dma_memory_write().
Patch created mechanically using spatch with this script:
@@
expression E1, E2, E3, E4;
@@
(
- dma_memory_read(E1, E2, E3, E4)
+ dma_memory_read(E1, E2, E3, E4, MEMTXATTRS_UNSPECIFIED)
|
- dma_memory_write(E1, E2, E3, E4)
+ dma_memory_write(E1, E2, E3, E4, MEMTXATTRS_UNSPECIFIED)
)
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
Reviewed-by: Li Qiang <liq3ea@gmail.com>
Reviewed-by: Edgar E. Iglesias <edgar.iglesias@xilinx.com>
Signed-off-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
Message-Id: <20211223115554.3155328-6-philmd@redhat.com>
2020-09-03 11:08:29 +03:00
|
|
|
HCCA_WRITEBACK_SIZE, MEMTXATTRS_UNSPECIFIED);
|
2007-10-31 03:34:21 +03:00
|
|
|
}
|
|
|
|
|
2006-05-21 20:30:15 +04:00
|
|
|
/* Read/Write the contents of a TD from/to main memory. */
|
2013-07-26 14:52:05 +04:00
|
|
|
static int ohci_copy_td(OHCIState *ohci, struct ohci_td *td,
|
|
|
|
uint8_t *buf, int len, DMADirection dir)
|
2006-05-21 20:30:15 +04:00
|
|
|
{
|
2012-06-27 08:50:39 +04:00
|
|
|
dma_addr_t ptr, n;
|
2006-05-21 20:30:15 +04:00
|
|
|
|
|
|
|
ptr = td->cbp;
|
|
|
|
n = 0x1000 - (ptr & 0xfff);
|
2023-02-20 21:15:06 +03:00
|
|
|
if (n > len) {
|
2006-05-21 20:30:15 +04:00
|
|
|
n = len;
|
2023-02-20 21:15:06 +03:00
|
|
|
}
|
2020-09-03 10:37:43 +03:00
|
|
|
if (dma_memory_rw(ohci->as, ptr + ohci->localmem_base, buf,
|
|
|
|
n, dir, MEMTXATTRS_UNSPECIFIED)) {
|
2013-07-26 14:52:05 +04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (n == len) {
|
|
|
|
return 0;
|
|
|
|
}
|
2006-05-21 20:30:15 +04:00
|
|
|
ptr = td->be & ~0xfffu;
|
2006-05-26 03:37:07 +04:00
|
|
|
buf += n;
|
2013-07-26 14:52:05 +04:00
|
|
|
if (dma_memory_rw(ohci->as, ptr + ohci->localmem_base, buf,
|
2020-09-03 10:37:43 +03:00
|
|
|
len - n, dir, MEMTXATTRS_UNSPECIFIED)) {
|
2013-07-26 14:52:05 +04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
2006-05-21 20:30:15 +04:00
|
|
|
}
|
|
|
|
|
2007-10-31 03:34:21 +03:00
|
|
|
/* Read/Write the contents of an ISO TD from/to main memory. */
|
2013-07-26 14:52:05 +04:00
|
|
|
static int ohci_copy_iso_td(OHCIState *ohci,
|
|
|
|
uint32_t start_addr, uint32_t end_addr,
|
|
|
|
uint8_t *buf, int len, DMADirection dir)
|
2007-10-31 03:34:21 +03:00
|
|
|
{
|
2012-06-27 08:50:39 +04:00
|
|
|
dma_addr_t ptr, n;
|
2006-08-12 05:04:27 +04:00
|
|
|
|
2007-10-31 03:34:21 +03:00
|
|
|
ptr = start_addr;
|
|
|
|
n = 0x1000 - (ptr & 0xfff);
|
2023-02-20 21:15:06 +03:00
|
|
|
if (n > len) {
|
2007-10-31 03:34:21 +03:00
|
|
|
n = len;
|
2023-02-20 21:15:06 +03:00
|
|
|
}
|
2020-09-03 10:37:43 +03:00
|
|
|
if (dma_memory_rw(ohci->as, ptr + ohci->localmem_base, buf,
|
|
|
|
n, dir, MEMTXATTRS_UNSPECIFIED)) {
|
2013-07-26 14:52:05 +04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (n == len) {
|
|
|
|
return 0;
|
|
|
|
}
|
2007-10-31 03:34:21 +03:00
|
|
|
ptr = end_addr & ~0xfffu;
|
|
|
|
buf += n;
|
2013-07-26 14:52:05 +04:00
|
|
|
if (dma_memory_rw(ohci->as, ptr + ohci->localmem_base, buf,
|
2020-09-03 10:37:43 +03:00
|
|
|
len - n, dir, MEMTXATTRS_UNSPECIFIED)) {
|
2013-07-26 14:52:05 +04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
2007-10-31 03:34:21 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
#define USUB(a, b) ((int16_t)((uint16_t)(a) - (uint16_t)(b)))
|
|
|
|
|
2022-01-25 16:33:20 +03:00
|
|
|
static int ohci_service_iso_td(OHCIState *ohci, struct ohci_ed *ed)
|
2007-10-31 03:34:21 +03:00
|
|
|
{
|
|
|
|
int dir;
|
|
|
|
size_t len = 0;
|
2008-09-14 10:45:34 +04:00
|
|
|
const char *str = NULL;
|
2007-10-31 03:34:21 +03:00
|
|
|
int pid;
|
|
|
|
int ret;
|
|
|
|
int i;
|
|
|
|
USBDevice *dev;
|
2012-01-12 16:23:01 +04:00
|
|
|
USBEndpoint *ep;
|
2022-01-25 16:33:20 +03:00
|
|
|
USBPacket *pkt;
|
|
|
|
uint8_t buf[8192];
|
|
|
|
bool int_req;
|
2007-10-31 03:34:21 +03:00
|
|
|
struct ohci_iso_td iso_td;
|
|
|
|
uint32_t addr;
|
|
|
|
uint16_t starting_frame;
|
|
|
|
int16_t relative_frame_number;
|
|
|
|
int frame_count;
|
|
|
|
uint32_t start_offset, next_offset, end_offset = 0;
|
|
|
|
uint32_t start_addr, end_addr;
|
|
|
|
|
|
|
|
addr = ed->head & OHCI_DPTR_MASK;
|
|
|
|
|
hcd-ohci: Drop ohci_service_iso_td() if ed->head & OHCI_DPTR_MASK is zero
An abort happens in ohci_frame_boundary() when ohci->done is 0 [1].
``` c
static void ohci_frame_boundary(void *opaque)
{
// ...
if (ohci->done_count == 0 && !(ohci->intr_status & OHCI_INTR_WD)) {
if (!ohci->done)
abort(); <----------------------------------------- [1]
```
This was reported in https://bugs.launchpad.net/qemu/+bug/1911216/,
https://lists.gnu.org/archive/html/qemu-devel/2021-06/msg03613.html, and
https://gitlab.com/qemu-project/qemu/-/issues/545. I can still reproduce it with
the latest QEMU.
This happends due to crafted ED with putting ISO_TD at physical address 0.
Suppose ed->head & OHCI_DPTR_MASK is 0 [2], and we memset 0 to the phyiscal
memory from 0 to sizeof(ohci_iso_td). Then, starting_frame [3] and frame_count
[4] are both 0. As we can control the value of ohci->frame_number (0 to 0x1f,
suppose 1), we then control the value of relative_frame_number to be 1 [6]. The
control flow goes to [7] where ohci->done is 0. Have returned from
ohci_service_iso_td(), ohci_frame_boundary() will abort() [1].
``` c
static int ohci_service_iso_td(OHCIState *ohci, struct ohci_ed *ed)
{
// ...
addr = ed->head & OHCI_DPTR_MASK; // <--------------------- [2]
if (ohci_read_iso_td(ohci, addr, &iso_td)) { // <-------- [3]
// ...
starting_frame = OHCI_BM(iso_td.flags, TD_SF); // <-------- [4]
frame_count = OHCI_BM(iso_td.flags, TD_FC); // <-------- [5]
relative_frame_number = USUB(ohci->frame_number, starting_frame);
// <-------- [6]
if (relative_frame_number < 0) {
return 1;
} else if (relative_frame_number > frame_count) {
// ...
ohci->done = addr; // <-------- [7]
// ...
}
```
As only (afaik) a guest root user can manipulate ED, TD and the physical memory,
this assertion failure is not a security bug.
The idea to fix this issue is to drop ohci_service_iso_td() if ed->head &
OHCI_DPTR_MASK is 0, which is similar to the drop operation for
ohci_service_ed_list() when head is 0. Probably, a similar issue is in
ohci_service_td(). I drop ohci_service_td() if ed->head & OHCI_DPTR_MASK is 0.
Fixes: 7bfe577702 ("OHCI USB isochronous transfers support (Arnon Gilboa)")
Reported-by: Gaoning Pan <pgn@zju.edu.cn>
Reported-by: Alexander Bulekov <alxndr@bu.edu>
Reported-by: Qiang Liu <cyruscyliu@gmail.com>
Resolves: https://gitlab.com/qemu-project/qemu/-/issues/545
Buglink: https://lists.gnu.org/archive/html/qemu-devel/2021-06/msg03613.html
Buglink: https://bugs.launchpad.net/qemu/+bug/1911216
Signed-off-by: Qiang Liu <cyruscyliu@gmail.com>
Message-Id: <20220826051557.119570-1-cyruscyliu@gmail.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2022-08-26 08:15:56 +03:00
|
|
|
if (addr == 0) {
|
|
|
|
ohci_die(ohci);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2013-07-26 14:52:05 +04:00
|
|
|
if (ohci_read_iso_td(ohci, addr, &iso_td)) {
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_iso_td_read_failed(addr);
|
2013-07-26 14:52:05 +04:00
|
|
|
ohci_die(ohci);
|
2017-02-07 14:15:03 +03:00
|
|
|
return 1;
|
2007-10-31 03:34:21 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
starting_frame = OHCI_BM(iso_td.flags, TD_SF);
|
|
|
|
frame_count = OHCI_BM(iso_td.flags, TD_FC);
|
2023-02-20 21:15:05 +03:00
|
|
|
relative_frame_number = USUB(ohci->frame_number, starting_frame);
|
2007-10-31 03:34:21 +03:00
|
|
|
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_iso_td_head(
|
2007-10-31 03:34:21 +03:00
|
|
|
ed->head & OHCI_DPTR_MASK, ed->tail & OHCI_DPTR_MASK,
|
|
|
|
iso_td.flags, iso_td.bp, iso_td.next, iso_td.be,
|
2014-09-25 04:16:59 +04:00
|
|
|
ohci->frame_number, starting_frame,
|
2014-09-25 13:38:44 +04:00
|
|
|
frame_count, relative_frame_number);
|
2014-09-25 04:16:59 +04:00
|
|
|
trace_usb_ohci_iso_td_head_offset(
|
|
|
|
iso_td.offset[0], iso_td.offset[1],
|
|
|
|
iso_td.offset[2], iso_td.offset[3],
|
|
|
|
iso_td.offset[4], iso_td.offset[5],
|
|
|
|
iso_td.offset[6], iso_td.offset[7]);
|
2007-10-31 03:34:21 +03:00
|
|
|
|
|
|
|
if (relative_frame_number < 0) {
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_iso_td_relative_frame_number_neg(relative_frame_number);
|
2007-10-31 03:34:21 +03:00
|
|
|
return 1;
|
|
|
|
} else if (relative_frame_number > frame_count) {
|
2023-02-20 21:15:04 +03:00
|
|
|
/*
|
|
|
|
* ISO TD expired - retire the TD to the Done Queue and continue with
|
|
|
|
* the next ISO TD of the same ED
|
|
|
|
*/
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_iso_td_relative_frame_number_big(relative_frame_number,
|
|
|
|
frame_count);
|
2020-09-15 21:22:59 +03:00
|
|
|
if (OHCI_CC_DATAOVERRUN == OHCI_BM(iso_td.flags, TD_CC)) {
|
|
|
|
/* avoid infinite loop */
|
|
|
|
return 1;
|
|
|
|
}
|
2007-10-31 03:34:21 +03:00
|
|
|
OHCI_SET_BM(iso_td.flags, TD_CC, OHCI_CC_DATAOVERRUN);
|
|
|
|
ed->head &= ~OHCI_DPTR_MASK;
|
|
|
|
ed->head |= (iso_td.next & OHCI_DPTR_MASK);
|
|
|
|
iso_td.next = ohci->done;
|
|
|
|
ohci->done = addr;
|
|
|
|
i = OHCI_BM(iso_td.flags, TD_DI);
|
2023-02-20 21:15:06 +03:00
|
|
|
if (i < ohci->done_count) {
|
2007-10-31 03:34:21 +03:00
|
|
|
ohci->done_count = i;
|
2023-02-20 21:15:06 +03:00
|
|
|
}
|
2013-07-26 14:52:05 +04:00
|
|
|
if (ohci_put_iso_td(ohci, addr, &iso_td)) {
|
|
|
|
ohci_die(ohci);
|
|
|
|
return 1;
|
|
|
|
}
|
2007-10-31 03:34:21 +03:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
dir = OHCI_BM(ed->flags, ED_D);
|
|
|
|
switch (dir) {
|
|
|
|
case OHCI_TD_DIR_IN:
|
|
|
|
str = "in";
|
|
|
|
pid = USB_TOKEN_IN;
|
|
|
|
break;
|
|
|
|
case OHCI_TD_DIR_OUT:
|
|
|
|
str = "out";
|
|
|
|
pid = USB_TOKEN_OUT;
|
|
|
|
break;
|
|
|
|
case OHCI_TD_DIR_SETUP:
|
|
|
|
str = "setup";
|
|
|
|
pid = USB_TOKEN_SETUP;
|
|
|
|
break;
|
|
|
|
default:
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_iso_td_bad_direction(dir);
|
2007-10-31 03:34:21 +03:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!iso_td.bp || !iso_td.be) {
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_iso_td_bad_bp_be(iso_td.bp, iso_td.be);
|
2007-10-31 03:34:21 +03:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
start_offset = iso_td.offset[relative_frame_number];
|
2020-09-15 21:22:58 +03:00
|
|
|
if (relative_frame_number < frame_count) {
|
|
|
|
next_offset = iso_td.offset[relative_frame_number + 1];
|
|
|
|
} else {
|
|
|
|
next_offset = iso_td.be;
|
|
|
|
}
|
2007-10-31 03:34:21 +03:00
|
|
|
|
2023-02-20 21:15:05 +03:00
|
|
|
if (!(OHCI_BM(start_offset, TD_PSW_CC) & 0xe) ||
|
|
|
|
((relative_frame_number < frame_count) &&
|
2007-10-31 03:34:21 +03:00
|
|
|
!(OHCI_BM(next_offset, TD_PSW_CC) & 0xe))) {
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_iso_td_bad_cc_not_accessed(start_offset, next_offset);
|
2007-10-31 03:34:21 +03:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((relative_frame_number < frame_count) && (start_offset > next_offset)) {
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_iso_td_bad_cc_overrun(start_offset, next_offset);
|
2007-10-31 03:34:21 +03:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((start_offset & 0x1000) == 0) {
|
|
|
|
start_addr = (iso_td.bp & OHCI_PAGE_MASK) |
|
|
|
|
(start_offset & OHCI_OFFSET_MASK);
|
|
|
|
} else {
|
|
|
|
start_addr = (iso_td.be & OHCI_PAGE_MASK) |
|
|
|
|
(start_offset & OHCI_OFFSET_MASK);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (relative_frame_number < frame_count) {
|
|
|
|
end_offset = next_offset - 1;
|
|
|
|
if ((end_offset & 0x1000) == 0) {
|
|
|
|
end_addr = (iso_td.bp & OHCI_PAGE_MASK) |
|
|
|
|
(end_offset & OHCI_OFFSET_MASK);
|
|
|
|
} else {
|
|
|
|
end_addr = (iso_td.be & OHCI_PAGE_MASK) |
|
|
|
|
(end_offset & OHCI_OFFSET_MASK);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* Last packet in the ISO TD */
|
2020-09-15 21:22:58 +03:00
|
|
|
end_addr = next_offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (start_addr > end_addr) {
|
|
|
|
trace_usb_ohci_iso_td_bad_cc_overrun(start_addr, end_addr);
|
|
|
|
return 1;
|
2007-10-31 03:34:21 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if ((start_addr & OHCI_PAGE_MASK) != (end_addr & OHCI_PAGE_MASK)) {
|
|
|
|
len = (end_addr & OHCI_OFFSET_MASK) + 0x1001
|
|
|
|
- (start_addr & OHCI_OFFSET_MASK);
|
|
|
|
} else {
|
|
|
|
len = end_addr - start_addr + 1;
|
|
|
|
}
|
2022-01-25 16:33:20 +03:00
|
|
|
if (len > sizeof(buf)) {
|
|
|
|
len = sizeof(buf);
|
2020-09-15 21:22:58 +03:00
|
|
|
}
|
2007-10-31 03:34:21 +03:00
|
|
|
|
|
|
|
if (len && dir != OHCI_TD_DIR_IN) {
|
2022-01-25 16:33:20 +03:00
|
|
|
if (ohci_copy_iso_td(ohci, start_addr, end_addr, buf, len,
|
2013-07-26 14:52:05 +04:00
|
|
|
DMA_DIRECTION_TO_DEVICE)) {
|
|
|
|
ohci_die(ohci);
|
|
|
|
return 1;
|
|
|
|
}
|
2007-10-31 03:34:21 +03:00
|
|
|
}
|
|
|
|
|
2022-01-25 16:33:20 +03:00
|
|
|
dev = ohci_find_device(ohci, OHCI_BM(ed->flags, ED_FA));
|
|
|
|
if (dev == NULL) {
|
|
|
|
trace_usb_ohci_td_dev_error();
|
|
|
|
return 1;
|
2007-10-31 03:34:21 +03:00
|
|
|
}
|
2022-01-25 16:33:20 +03:00
|
|
|
ep = usb_ep_get(dev, pid, OHCI_BM(ed->flags, ED_EN));
|
|
|
|
pkt = g_new0(USBPacket, 1);
|
|
|
|
usb_packet_init(pkt);
|
|
|
|
int_req = relative_frame_number == frame_count &&
|
|
|
|
OHCI_BM(iso_td.flags, TD_DI) == 0;
|
|
|
|
usb_packet_setup(pkt, pid, ep, 0, addr, false, int_req);
|
|
|
|
usb_packet_addbuf(pkt, buf, len);
|
|
|
|
usb_handle_packet(dev, pkt);
|
|
|
|
if (pkt->status == USB_RET_ASYNC) {
|
|
|
|
usb_device_flush_ep_queue(dev, ep);
|
|
|
|
g_free(pkt);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (pkt->status == USB_RET_SUCCESS) {
|
|
|
|
ret = pkt->actual_length;
|
usb: split packet result into actual_length + status
Since with the ehci and xhci controllers a single packet can be larger
then maxpacketsize, it is possible for the result of a single packet
to be both having transferred some data as well as the transfer to have
an error.
An example would be an input transfer from a bulk endpoint successfully
receiving 1 or more maxpacketsize packets from the device, followed
by a packet signalling halt.
While already touching all the devices and controllers handle_packet /
handle_data / handle_control code, also change the return type of
these functions to void, solely storing the status in the packet. To
make the code paths for regular versus async packet handling more
uniform.
This patch unfortunately is somewhat invasive, since makeing the qemu
usb core deal with this requires changes everywhere. This patch only
prepares the usb core for this, all the hcd / device changes are done
in such a way that there are no functional changes.
This patch has been tested with uhci and ehci hcds, together with usb-audio,
usb-hid and usb-storage devices, as well as with usb-redir redirection
with a wide variety of real devices.
Note that there is usually no need to directly set packet->actual_length
form devices handle_data callback, as that is done by usb_packet_copy()
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2012-11-01 20:15:01 +04:00
|
|
|
} else {
|
2022-01-25 16:33:20 +03:00
|
|
|
ret = pkt->status;
|
usb: split packet result into actual_length + status
Since with the ehci and xhci controllers a single packet can be larger
then maxpacketsize, it is possible for the result of a single packet
to be both having transferred some data as well as the transfer to have
an error.
An example would be an input transfer from a bulk endpoint successfully
receiving 1 or more maxpacketsize packets from the device, followed
by a packet signalling halt.
While already touching all the devices and controllers handle_packet /
handle_data / handle_control code, also change the return type of
these functions to void, solely storing the status in the packet. To
make the code paths for regular versus async packet handling more
uniform.
This patch unfortunately is somewhat invasive, since makeing the qemu
usb core deal with this requires changes everywhere. This patch only
prepares the usb core for this, all the hcd / device changes are done
in such a way that there are no functional changes.
This patch has been tested with uhci and ehci hcds, together with usb-audio,
usb-hid and usb-storage devices, as well as with usb-redir redirection
with a wide variety of real devices.
Note that there is usually no need to directly set packet->actual_length
form devices handle_data callback, as that is done by usb_packet_copy()
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2012-11-01 20:15:01 +04:00
|
|
|
}
|
2022-01-25 16:33:20 +03:00
|
|
|
g_free(pkt);
|
2007-10-31 03:34:21 +03:00
|
|
|
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_iso_td_so(start_offset, end_offset, start_addr, end_addr,
|
|
|
|
str, len, ret);
|
2007-10-31 03:34:21 +03:00
|
|
|
|
|
|
|
/* Writeback */
|
|
|
|
if (dir == OHCI_TD_DIR_IN && ret >= 0 && ret <= len) {
|
|
|
|
/* IN transfer succeeded */
|
2022-01-25 16:33:20 +03:00
|
|
|
if (ohci_copy_iso_td(ohci, start_addr, end_addr, buf, ret,
|
2013-07-26 14:52:05 +04:00
|
|
|
DMA_DIRECTION_FROM_DEVICE)) {
|
|
|
|
ohci_die(ohci);
|
|
|
|
return 1;
|
|
|
|
}
|
2007-10-31 03:34:21 +03:00
|
|
|
OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_CC,
|
|
|
|
OHCI_CC_NOERROR);
|
|
|
|
OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_SIZE, ret);
|
|
|
|
} else if (dir == OHCI_TD_DIR_OUT && ret == len) {
|
|
|
|
/* OUT transfer succeeded */
|
|
|
|
OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_CC,
|
|
|
|
OHCI_CC_NOERROR);
|
|
|
|
OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_SIZE, 0);
|
|
|
|
} else {
|
2007-11-14 01:52:54 +03:00
|
|
|
if (ret > (ssize_t) len) {
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_iso_td_data_overrun(ret, len);
|
2007-10-31 03:34:21 +03:00
|
|
|
OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_CC,
|
|
|
|
OHCI_CC_DATAOVERRUN);
|
|
|
|
OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_SIZE,
|
|
|
|
len);
|
|
|
|
} else if (ret >= 0) {
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_iso_td_data_underrun(ret);
|
2007-10-31 03:34:21 +03:00
|
|
|
OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_CC,
|
|
|
|
OHCI_CC_DATAUNDERRUN);
|
|
|
|
} else {
|
|
|
|
switch (ret) {
|
2012-03-03 00:27:20 +04:00
|
|
|
case USB_RET_IOERROR:
|
2007-10-31 03:34:21 +03:00
|
|
|
case USB_RET_NODEV:
|
|
|
|
OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_CC,
|
|
|
|
OHCI_CC_DEVICENOTRESPONDING);
|
|
|
|
OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_SIZE,
|
|
|
|
0);
|
|
|
|
break;
|
|
|
|
case USB_RET_NAK:
|
|
|
|
case USB_RET_STALL:
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_iso_td_nak(ret);
|
2007-10-31 03:34:21 +03:00
|
|
|
OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_CC,
|
|
|
|
OHCI_CC_STALL);
|
|
|
|
OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_SIZE,
|
|
|
|
0);
|
|
|
|
break;
|
|
|
|
default:
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_iso_td_bad_response(ret);
|
2007-10-31 03:34:21 +03:00
|
|
|
OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_CC,
|
|
|
|
OHCI_CC_UNDEXPETEDPID);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (relative_frame_number == frame_count) {
|
|
|
|
/* Last data packet of ISO TD - retire the TD to the Done Queue */
|
|
|
|
OHCI_SET_BM(iso_td.flags, TD_CC, OHCI_CC_NOERROR);
|
|
|
|
ed->head &= ~OHCI_DPTR_MASK;
|
|
|
|
ed->head |= (iso_td.next & OHCI_DPTR_MASK);
|
|
|
|
iso_td.next = ohci->done;
|
|
|
|
ohci->done = addr;
|
|
|
|
i = OHCI_BM(iso_td.flags, TD_DI);
|
2023-02-20 21:15:06 +03:00
|
|
|
if (i < ohci->done_count) {
|
2007-10-31 03:34:21 +03:00
|
|
|
ohci->done_count = i;
|
2023-02-20 21:15:06 +03:00
|
|
|
}
|
2007-10-31 03:34:21 +03:00
|
|
|
}
|
2013-07-26 14:52:05 +04:00
|
|
|
if (ohci_put_iso_td(ohci, addr, &iso_td)) {
|
|
|
|
ohci_die(ohci);
|
|
|
|
}
|
2007-10-31 03:34:21 +03:00
|
|
|
return 1;
|
2006-08-12 05:04:27 +04:00
|
|
|
}
|
|
|
|
|
2022-08-19 18:39:29 +03:00
|
|
|
#define HEX_CHAR_PER_LINE 16
|
|
|
|
|
2014-09-12 12:55:26 +04:00
|
|
|
static void ohci_td_pkt(const char *msg, const uint8_t *buf, size_t len)
|
|
|
|
{
|
2017-07-31 17:07:18 +03:00
|
|
|
bool print16;
|
|
|
|
bool printall;
|
2014-09-12 12:55:26 +04:00
|
|
|
int i;
|
2022-08-19 18:39:29 +03:00
|
|
|
char tmp[3 * HEX_CHAR_PER_LINE + 1];
|
2014-09-12 12:55:26 +04:00
|
|
|
char *p = tmp;
|
|
|
|
|
2017-07-31 17:07:18 +03:00
|
|
|
print16 = !!trace_event_get_state_backends(TRACE_USB_OHCI_TD_PKT_SHORT);
|
|
|
|
printall = !!trace_event_get_state_backends(TRACE_USB_OHCI_TD_PKT_FULL);
|
|
|
|
|
2014-09-12 12:55:26 +04:00
|
|
|
if (!printall && !print16) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; ; i++) {
|
2022-08-19 18:39:29 +03:00
|
|
|
if (i && (!(i % HEX_CHAR_PER_LINE) || (i == len))) {
|
2014-09-12 12:55:26 +04:00
|
|
|
if (!printall) {
|
|
|
|
trace_usb_ohci_td_pkt_short(msg, tmp);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
trace_usb_ohci_td_pkt_full(msg, tmp);
|
|
|
|
p = tmp;
|
|
|
|
*p = 0;
|
|
|
|
}
|
|
|
|
if (i == len) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
p += sprintf(p, " %.2x", buf[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-20 21:15:04 +03:00
|
|
|
/*
|
|
|
|
* Service a transport descriptor.
|
|
|
|
* Returns nonzero to terminate processing of this endpoint.
|
|
|
|
*/
|
2006-05-21 20:30:15 +04:00
|
|
|
static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed)
|
|
|
|
{
|
|
|
|
int dir;
|
2011-09-14 21:48:59 +04:00
|
|
|
size_t len = 0, pktlen = 0;
|
2008-09-14 10:45:34 +04:00
|
|
|
const char *str = NULL;
|
2006-05-21 20:30:15 +04:00
|
|
|
int pid;
|
|
|
|
int ret;
|
|
|
|
int i;
|
|
|
|
USBDevice *dev;
|
2012-01-12 16:23:01 +04:00
|
|
|
USBEndpoint *ep;
|
2006-05-21 20:30:15 +04:00
|
|
|
struct ohci_td td;
|
|
|
|
uint32_t addr;
|
|
|
|
int flag_r;
|
2006-08-12 05:04:27 +04:00
|
|
|
int completion;
|
2006-05-21 20:30:15 +04:00
|
|
|
|
|
|
|
addr = ed->head & OHCI_DPTR_MASK;
|
hcd-ohci: Drop ohci_service_iso_td() if ed->head & OHCI_DPTR_MASK is zero
An abort happens in ohci_frame_boundary() when ohci->done is 0 [1].
``` c
static void ohci_frame_boundary(void *opaque)
{
// ...
if (ohci->done_count == 0 && !(ohci->intr_status & OHCI_INTR_WD)) {
if (!ohci->done)
abort(); <----------------------------------------- [1]
```
This was reported in https://bugs.launchpad.net/qemu/+bug/1911216/,
https://lists.gnu.org/archive/html/qemu-devel/2021-06/msg03613.html, and
https://gitlab.com/qemu-project/qemu/-/issues/545. I can still reproduce it with
the latest QEMU.
This happends due to crafted ED with putting ISO_TD at physical address 0.
Suppose ed->head & OHCI_DPTR_MASK is 0 [2], and we memset 0 to the phyiscal
memory from 0 to sizeof(ohci_iso_td). Then, starting_frame [3] and frame_count
[4] are both 0. As we can control the value of ohci->frame_number (0 to 0x1f,
suppose 1), we then control the value of relative_frame_number to be 1 [6]. The
control flow goes to [7] where ohci->done is 0. Have returned from
ohci_service_iso_td(), ohci_frame_boundary() will abort() [1].
``` c
static int ohci_service_iso_td(OHCIState *ohci, struct ohci_ed *ed)
{
// ...
addr = ed->head & OHCI_DPTR_MASK; // <--------------------- [2]
if (ohci_read_iso_td(ohci, addr, &iso_td)) { // <-------- [3]
// ...
starting_frame = OHCI_BM(iso_td.flags, TD_SF); // <-------- [4]
frame_count = OHCI_BM(iso_td.flags, TD_FC); // <-------- [5]
relative_frame_number = USUB(ohci->frame_number, starting_frame);
// <-------- [6]
if (relative_frame_number < 0) {
return 1;
} else if (relative_frame_number > frame_count) {
// ...
ohci->done = addr; // <-------- [7]
// ...
}
```
As only (afaik) a guest root user can manipulate ED, TD and the physical memory,
this assertion failure is not a security bug.
The idea to fix this issue is to drop ohci_service_iso_td() if ed->head &
OHCI_DPTR_MASK is 0, which is similar to the drop operation for
ohci_service_ed_list() when head is 0. Probably, a similar issue is in
ohci_service_td(). I drop ohci_service_td() if ed->head & OHCI_DPTR_MASK is 0.
Fixes: 7bfe577702 ("OHCI USB isochronous transfers support (Arnon Gilboa)")
Reported-by: Gaoning Pan <pgn@zju.edu.cn>
Reported-by: Alexander Bulekov <alxndr@bu.edu>
Reported-by: Qiang Liu <cyruscyliu@gmail.com>
Resolves: https://gitlab.com/qemu-project/qemu/-/issues/545
Buglink: https://lists.gnu.org/archive/html/qemu-devel/2021-06/msg03613.html
Buglink: https://bugs.launchpad.net/qemu/+bug/1911216
Signed-off-by: Qiang Liu <cyruscyliu@gmail.com>
Message-Id: <20220826051557.119570-1-cyruscyliu@gmail.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2022-08-26 08:15:56 +03:00
|
|
|
if (addr == 0) {
|
|
|
|
ohci_die(ohci);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2023-02-20 21:15:04 +03:00
|
|
|
/* See if this TD has already been submitted to the device. */
|
2006-08-12 05:04:27 +04:00
|
|
|
completion = (addr == ohci->async_td);
|
|
|
|
if (completion && !ohci->async_complete) {
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_td_skip_async();
|
2006-08-12 05:04:27 +04:00
|
|
|
return 1;
|
|
|
|
}
|
2013-07-26 14:52:05 +04:00
|
|
|
if (ohci_read_td(ohci, addr, &td)) {
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_td_read_error(addr);
|
2013-07-26 14:52:05 +04:00
|
|
|
ohci_die(ohci);
|
2017-02-22 13:56:30 +03:00
|
|
|
return 1;
|
2006-05-21 20:30:15 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
dir = OHCI_BM(ed->flags, ED_D);
|
|
|
|
switch (dir) {
|
|
|
|
case OHCI_TD_DIR_OUT:
|
|
|
|
case OHCI_TD_DIR_IN:
|
2023-02-20 21:15:04 +03:00
|
|
|
/* Same value. */
|
2006-05-21 20:30:15 +04:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
dir = OHCI_BM(td.flags, TD_DP);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (dir) {
|
|
|
|
case OHCI_TD_DIR_IN:
|
|
|
|
str = "in";
|
|
|
|
pid = USB_TOKEN_IN;
|
|
|
|
break;
|
|
|
|
case OHCI_TD_DIR_OUT:
|
|
|
|
str = "out";
|
|
|
|
pid = USB_TOKEN_OUT;
|
|
|
|
break;
|
|
|
|
case OHCI_TD_DIR_SETUP:
|
|
|
|
str = "setup";
|
|
|
|
pid = USB_TOKEN_SETUP;
|
2024-05-09 03:29:16 +03:00
|
|
|
if (OHCI_BM(ed->flags, ED_EN) > 0) { /* setup only allowed to ep 0 */
|
|
|
|
trace_usb_ohci_td_bad_pid(str, ed->flags, td.flags);
|
|
|
|
ohci_die(ohci);
|
|
|
|
return 1;
|
|
|
|
}
|
2006-05-21 20:30:15 +04:00
|
|
|
break;
|
|
|
|
default:
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_td_bad_direction(dir);
|
2006-05-21 20:30:15 +04:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (td.cbp && td.be) {
|
2006-05-26 03:37:07 +04:00
|
|
|
if ((td.cbp & 0xfffff000) != (td.be & 0xfffff000)) {
|
|
|
|
len = (td.be & 0xfff) + 0x1001 - (td.cbp & 0xfff);
|
|
|
|
} else {
|
hw/usb/hcd-ohci: Fix ohci_service_td: accept zero-length TDs where CBP=BE+1
This changes the way the ohci emulation handles a Transfer Descriptor
with "Buffer End" set to "Current Buffer Pointer" - 1, specifically
in the case of a zero-length packet.
The OHCI spec 4.3.1.2 Table 4-2 specifies td.cbp to be zero for a
zero-length packet. Peter Maydell tracked down commit 1328fe0c32
(hw: usb: hcd-ohci: check len and frame_number variables) where qemu
started checking this according to the spec.
What this patch does is loosen the qemu ohci implementation to allow a
zero-length packet if td.be (Buffer End) is set to td.cbp - 1, and with a
non-zero td.cbp value.
The spec is unclear whether this is valid or not -- it is not the
clearly documented way to send a zero length TD (which is CBP=BE=0),
but it isn't specifically forbidden. Actual hw seems to be ok with it.
Does any OS rely on this behavior? There have been no reports to
qemu-devel of this problem.
This is attempting to have qemu behave like actual hardware,
but this is just a minor change.
With a tiny OS[1] that boots and executes a test, the issue can be seen:
* OS that sends USB requests to a USB mass storage device
but sends td.cbp = td.be + 1
* qemu 4.2
* qemu HEAD (4e66a0854)
* Actual OHCI controller (hardware)
Command line:
qemu-system-x86_64 -m 20 \
-device pci-ohci,id=ohci \
-drive if=none,format=raw,id=d,file=testmbr.raw \
-device usb-storage,bus=ohci.0,drive=d \
--trace "usb_*" --trace "ohci_*" -D qemu.log
Results are:
qemu 4.2 | qemu HEAD | actual HW
-----------+------------+-----------
works fine | ohci_die() | works fine
Tip: if the flags "-serial pty -serial stdio" are added to the command line
the test will output USB requests like this:
Testing qemu HEAD:
> Free mem 2M ohci port2 conn FS
> setup { 80 6 0 1 0 0 8 0 }
> ED info=80000 { mps=8 en=0 d=0 } tail=c20920
> td0 c20880 nxt=c20960 f2000000 setup cbp=c20900 be=c20907
> td1 c20960 nxt=c20980 f3140000 in cbp=c20908 be=c2090f
> td2 c20980 nxt=c20920 f3080000 out cbp=c20910 be=c2090f ohci20 host err
> usb stopped
And in qemu.log:
usb_ohci_iso_td_bad_cc_overrun ISO_TD start_offset=0x00c20910 > next_offset=0x00c2090f
Testing qemu 4.2:
> Free mem 2M ohci port2 conn FS
> setup { 80 6 0 1 0 0 8 0 }
> ED info=80000 { mps=8 en=0 d=0 } tail=620920
> td0 620880 nxt=620960 f2000000 setup cbp=620900 be=620907 cbp=0 be=620907
> td1 620960 nxt=620980 f3140000 in cbp=620908 be=62090f cbp=0 be=62090f
> td2 620980 nxt=620920 f3080000 out cbp=620910 be=62090f cbp=0 be=62090f
> rx { 12 1 0 2 0 0 0 8 }
> setup { 0 5 1 0 0 0 0 0 } tx {}
> ED info=80000 { mps=8 en=0 d=0 } tail=620880
> td0 620920 nxt=620960 f2000000 setup cbp=620900 be=620907 cbp=0 be=620907
> td1 620960 nxt=620880 f3100000 in cbp=620908 be=620907 cbp=0 be=620907
> setup { 80 6 0 1 0 0 12 0 }
> ED info=80001 { mps=8 en=0 d=1 } tail=620960
> td0 620880 nxt=6209c0 f2000000 setup cbp=620920 be=620927 cbp=0 be=620927
> td1 6209c0 nxt=6209e0 f3140000 in cbp=620928 be=620939 cbp=0 be=620939
> td2 6209e0 nxt=620960 f3080000 out cbp=62093a be=620939 cbp=0 be=620939
> rx { 12 1 0 2 0 0 0 8 f4 46 1 0 0 0 1 2 3 1 }
> setup { 80 6 0 2 0 0 0 1 }
> ED info=80001 { mps=8 en=0 d=1 } tail=620880
> td0 620960 nxt=6209a0 f2000000 setup cbp=620a20 be=620a27 cbp=0 be=620a27
> td1 6209a0 nxt=6209c0 f3140004 in cbp=620a28 be=620b27 cbp=620a48 be=620b27
> td2 6209c0 nxt=620880 f3080000 out cbp=620b28 be=620b27 cbp=0 be=620b27
> rx { 9 2 20 0 1 1 4 c0 0 9 4 0 0 2 8 6 50 0 7 5 81 2 40 0 0 7 5 2 2 40 0 0 }
> setup { 0 9 1 0 0 0 0 0 } tx {}
> ED info=80001 { mps=8 en=0 d=1 } tail=620900
> td0 620880 nxt=620940 f2000000 setup cbp=620a00 be=620a07 cbp=0 be=620a07
> td1 620940 nxt=620900 f3100000 in cbp=620a08 be=620a07 cbp=0 be=620a07
[1] The OS disk image has been emailed to philmd@linaro.org, mjt@tls.msk.ru,
and kraxel@redhat.com:
* testCbpOffBy1.img.xz
* sha256: f87baddcb86de845de12f002c698670a426affb40946025cc32694f9daa3abed
Signed-off-by: David Hubbard <dmamfmgm@gmail.com>
Reviewed-by: Alex Bennée <alex.bennee@linaro.org>
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
2024-05-21 02:26:34 +03:00
|
|
|
if (td.cbp - 1 > td.be) { /* rely on td.cbp != 0 */
|
|
|
|
trace_usb_ohci_td_bad_buf(td.cbp, td.be);
|
2020-09-15 21:22:58 +03:00
|
|
|
ohci_die(ohci);
|
|
|
|
return 1;
|
|
|
|
}
|
2006-05-26 03:37:07 +04:00
|
|
|
len = (td.be - td.cbp) + 1;
|
|
|
|
}
|
2020-09-15 21:22:58 +03:00
|
|
|
if (len > sizeof(ohci->usb_buf)) {
|
|
|
|
len = sizeof(ohci->usb_buf);
|
|
|
|
}
|
2006-05-26 03:37:07 +04:00
|
|
|
|
2011-09-14 21:48:59 +04:00
|
|
|
pktlen = len;
|
|
|
|
if (len && dir != OHCI_TD_DIR_IN) {
|
|
|
|
/* The endpoint may not allow us to transfer it all now */
|
|
|
|
pktlen = (ed->flags & OHCI_ED_MPS_MASK) >> OHCI_ED_MPS_SHIFT;
|
|
|
|
if (pktlen > len) {
|
|
|
|
pktlen = len;
|
|
|
|
}
|
|
|
|
if (!completion) {
|
2013-07-26 14:52:05 +04:00
|
|
|
if (ohci_copy_td(ohci, &td, ohci->usb_buf, pktlen,
|
|
|
|
DMA_DIRECTION_TO_DEVICE)) {
|
|
|
|
ohci_die(ohci);
|
|
|
|
}
|
2011-09-14 21:48:59 +04:00
|
|
|
}
|
2006-05-21 20:30:15 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
flag_r = (td.flags & OHCI_TD_R) != 0;
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_td_pkt_hdr(addr, (int64_t)pktlen, (int64_t)len, str,
|
|
|
|
flag_r, td.cbp, td.be);
|
|
|
|
ohci_td_pkt("OUT", ohci->usb_buf, pktlen);
|
|
|
|
|
2006-08-12 05:04:27 +04:00
|
|
|
if (completion) {
|
|
|
|
ohci->async_td = 0;
|
2014-04-13 14:42:34 +04:00
|
|
|
ohci->async_complete = false;
|
2006-08-12 05:04:27 +04:00
|
|
|
} else {
|
2022-01-25 16:33:20 +03:00
|
|
|
dev = ohci_find_device(ohci, OHCI_BM(ed->flags, ED_FA));
|
|
|
|
if (dev == NULL) {
|
|
|
|
trace_usb_ohci_td_dev_error();
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
ep = usb_ep_get(dev, pid, OHCI_BM(ed->flags, ED_EN));
|
2012-01-10 20:56:17 +04:00
|
|
|
if (ohci->async_td) {
|
2023-02-20 21:15:04 +03:00
|
|
|
/*
|
|
|
|
* ??? The hardware should allow one active packet per
|
|
|
|
* endpoint. We only allow one active packet per controller.
|
|
|
|
* This should be sufficient as long as devices respond in a
|
|
|
|
* timely manner.
|
|
|
|
*/
|
2022-01-25 16:33:20 +03:00
|
|
|
trace_usb_ohci_td_too_many_pending(ep->nr);
|
2012-01-10 20:56:17 +04:00
|
|
|
return 1;
|
2006-08-12 05:04:27 +04:00
|
|
|
}
|
2013-01-29 15:44:35 +04:00
|
|
|
usb_packet_setup(&ohci->usb_packet, pid, ep, 0, addr, !flag_r,
|
2012-10-24 20:14:10 +04:00
|
|
|
OHCI_BM(td.flags, TD_DI) == 0);
|
2012-01-10 20:56:17 +04:00
|
|
|
usb_packet_addbuf(&ohci->usb_packet, ohci->usb_buf, pktlen);
|
usb: split packet result into actual_length + status
Since with the ehci and xhci controllers a single packet can be larger
then maxpacketsize, it is possible for the result of a single packet
to be both having transferred some data as well as the transfer to have
an error.
An example would be an input transfer from a bulk endpoint successfully
receiving 1 or more maxpacketsize packets from the device, followed
by a packet signalling halt.
While already touching all the devices and controllers handle_packet /
handle_data / handle_control code, also change the return type of
these functions to void, solely storing the status in the packet. To
make the code paths for regular versus async packet handling more
uniform.
This patch unfortunately is somewhat invasive, since makeing the qemu
usb core deal with this requires changes everywhere. This patch only
prepares the usb core for this, all the hcd / device changes are done
in such a way that there are no functional changes.
This patch has been tested with uhci and ehci hcds, together with usb-audio,
usb-hid and usb-storage devices, as well as with usb-redir redirection
with a wide variety of real devices.
Note that there is usually no need to directly set packet->actual_length
form devices handle_data callback, as that is done by usb_packet_copy()
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2012-11-01 20:15:01 +04:00
|
|
|
usb_handle_packet(dev, &ohci->usb_packet);
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_td_packet_status(ohci->usb_packet.status);
|
|
|
|
|
usb: split packet result into actual_length + status
Since with the ehci and xhci controllers a single packet can be larger
then maxpacketsize, it is possible for the result of a single packet
to be both having transferred some data as well as the transfer to have
an error.
An example would be an input transfer from a bulk endpoint successfully
receiving 1 or more maxpacketsize packets from the device, followed
by a packet signalling halt.
While already touching all the devices and controllers handle_packet /
handle_data / handle_control code, also change the return type of
these functions to void, solely storing the status in the packet. To
make the code paths for regular versus async packet handling more
uniform.
This patch unfortunately is somewhat invasive, since makeing the qemu
usb core deal with this requires changes everywhere. This patch only
prepares the usb core for this, all the hcd / device changes are done
in such a way that there are no functional changes.
This patch has been tested with uhci and ehci hcds, together with usb-audio,
usb-hid and usb-storage devices, as well as with usb-redir redirection
with a wide variety of real devices.
Note that there is usually no need to directly set packet->actual_length
form devices handle_data callback, as that is done by usb_packet_copy()
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2012-11-01 20:15:01 +04:00
|
|
|
if (ohci->usb_packet.status == USB_RET_ASYNC) {
|
2012-10-24 20:14:07 +04:00
|
|
|
usb_device_flush_ep_queue(dev, ep);
|
2006-08-12 05:04:27 +04:00
|
|
|
ohci->async_td = addr;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
usb: split packet result into actual_length + status
Since with the ehci and xhci controllers a single packet can be larger
then maxpacketsize, it is possible for the result of a single packet
to be both having transferred some data as well as the transfer to have
an error.
An example would be an input transfer from a bulk endpoint successfully
receiving 1 or more maxpacketsize packets from the device, followed
by a packet signalling halt.
While already touching all the devices and controllers handle_packet /
handle_data / handle_control code, also change the return type of
these functions to void, solely storing the status in the packet. To
make the code paths for regular versus async packet handling more
uniform.
This patch unfortunately is somewhat invasive, since makeing the qemu
usb core deal with this requires changes everywhere. This patch only
prepares the usb core for this, all the hcd / device changes are done
in such a way that there are no functional changes.
This patch has been tested with uhci and ehci hcds, together with usb-audio,
usb-hid and usb-storage devices, as well as with usb-redir redirection
with a wide variety of real devices.
Note that there is usually no need to directly set packet->actual_length
form devices handle_data callback, as that is done by usb_packet_copy()
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2012-11-01 20:15:01 +04:00
|
|
|
if (ohci->usb_packet.status == USB_RET_SUCCESS) {
|
|
|
|
ret = ohci->usb_packet.actual_length;
|
|
|
|
} else {
|
|
|
|
ret = ohci->usb_packet.status;
|
|
|
|
}
|
|
|
|
|
2006-05-21 20:30:15 +04:00
|
|
|
if (ret >= 0) {
|
|
|
|
if (dir == OHCI_TD_DIR_IN) {
|
2013-07-26 14:52:05 +04:00
|
|
|
if (ohci_copy_td(ohci, &td, ohci->usb_buf, ret,
|
|
|
|
DMA_DIRECTION_FROM_DEVICE)) {
|
|
|
|
ohci_die(ohci);
|
|
|
|
}
|
2014-09-12 12:55:26 +04:00
|
|
|
ohci_td_pkt("IN", ohci->usb_buf, pktlen);
|
2006-05-21 20:30:15 +04:00
|
|
|
} else {
|
2011-09-14 21:48:59 +04:00
|
|
|
ret = pktlen;
|
2006-05-21 20:30:15 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Writeback */
|
2011-09-14 21:48:59 +04:00
|
|
|
if (ret == pktlen || (dir == OHCI_TD_DIR_IN && ret >= 0 && flag_r)) {
|
2023-02-20 21:15:04 +03:00
|
|
|
/* Transmission succeeded. */
|
2006-05-21 20:30:15 +04:00
|
|
|
if (ret == len) {
|
|
|
|
td.cbp = 0;
|
|
|
|
} else {
|
|
|
|
if ((td.cbp & 0xfff) + ret > 0xfff) {
|
2011-12-22 13:34:30 +04:00
|
|
|
td.cbp = (td.be & ~0xfff) + ((td.cbp + ret) & 0xfff);
|
|
|
|
} else {
|
|
|
|
td.cbp += ret;
|
2006-05-21 20:30:15 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
td.flags |= OHCI_TD_T1;
|
|
|
|
td.flags ^= OHCI_TD_T0;
|
|
|
|
OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_NOERROR);
|
|
|
|
OHCI_SET_BM(td.flags, TD_EC, 0);
|
|
|
|
|
2011-09-14 21:48:59 +04:00
|
|
|
if ((dir != OHCI_TD_DIR_IN) && (ret != len)) {
|
|
|
|
/* Partial packet transfer: TD not ready to retire yet */
|
|
|
|
goto exit_no_retire;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Setting ED_C is part of the TD retirement process */
|
2006-05-21 20:30:15 +04:00
|
|
|
ed->head &= ~OHCI_ED_C;
|
2023-02-20 21:15:06 +03:00
|
|
|
if (td.flags & OHCI_TD_T0) {
|
2006-05-21 20:30:15 +04:00
|
|
|
ed->head |= OHCI_ED_C;
|
2023-02-20 21:15:06 +03:00
|
|
|
}
|
2006-05-21 20:30:15 +04:00
|
|
|
} else {
|
|
|
|
if (ret >= 0) {
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_td_underrun();
|
2006-05-21 20:30:15 +04:00
|
|
|
OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_DATAUNDERRUN);
|
|
|
|
} else {
|
|
|
|
switch (ret) {
|
2012-03-03 00:27:20 +04:00
|
|
|
case USB_RET_IOERROR:
|
2006-05-21 20:30:15 +04:00
|
|
|
case USB_RET_NODEV:
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_td_dev_error();
|
2006-05-21 20:30:15 +04:00
|
|
|
OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_DEVICENOTRESPONDING);
|
2013-09-22 00:26:41 +04:00
|
|
|
break;
|
2006-05-21 20:30:15 +04:00
|
|
|
case USB_RET_NAK:
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_td_nak();
|
2006-05-21 20:30:15 +04:00
|
|
|
return 1;
|
|
|
|
case USB_RET_STALL:
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_td_stall();
|
2006-05-21 20:30:15 +04:00
|
|
|
OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_STALL);
|
|
|
|
break;
|
|
|
|
case USB_RET_BABBLE:
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_td_babble();
|
2006-05-21 20:30:15 +04:00
|
|
|
OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_DATAOVERRUN);
|
|
|
|
break;
|
|
|
|
default:
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_td_bad_device_response(ret);
|
2006-05-21 20:30:15 +04:00
|
|
|
OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_UNDEXPETEDPID);
|
|
|
|
OHCI_SET_BM(td.flags, TD_EC, 3);
|
|
|
|
break;
|
|
|
|
}
|
2023-02-20 21:15:04 +03:00
|
|
|
/*
|
|
|
|
* An error occurred so we have to clear the interrupt counter.
|
|
|
|
* See spec at 6.4.4 on page 104
|
|
|
|
*/
|
2018-07-29 22:19:28 +03:00
|
|
|
ohci->done_count = 0;
|
2006-05-21 20:30:15 +04:00
|
|
|
}
|
|
|
|
ed->head |= OHCI_ED_H;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Retire this TD */
|
|
|
|
ed->head &= ~OHCI_DPTR_MASK;
|
|
|
|
ed->head |= td.next & OHCI_DPTR_MASK;
|
|
|
|
td.next = ohci->done;
|
|
|
|
ohci->done = addr;
|
|
|
|
i = OHCI_BM(td.flags, TD_DI);
|
2023-02-20 21:15:06 +03:00
|
|
|
if (i < ohci->done_count) {
|
2006-05-21 20:30:15 +04:00
|
|
|
ohci->done_count = i;
|
2023-02-20 21:15:06 +03:00
|
|
|
}
|
2011-09-14 21:48:59 +04:00
|
|
|
exit_no_retire:
|
2013-07-26 14:52:05 +04:00
|
|
|
if (ohci_put_td(ohci, addr, &td)) {
|
|
|
|
ohci_die(ohci);
|
|
|
|
return 1;
|
|
|
|
}
|
2006-05-21 20:30:15 +04:00
|
|
|
return OHCI_BM(td.flags, TD_CC) != OHCI_CC_NOERROR;
|
|
|
|
}
|
|
|
|
|
2023-02-20 21:15:04 +03:00
|
|
|
/* Service an endpoint list. Returns nonzero if active TD were found. */
|
2022-01-25 16:33:20 +03:00
|
|
|
static int ohci_service_ed_list(OHCIState *ohci, uint32_t head)
|
2006-05-21 20:30:15 +04:00
|
|
|
{
|
|
|
|
struct ohci_ed ed;
|
|
|
|
uint32_t next_ed;
|
|
|
|
uint32_t cur;
|
|
|
|
int active;
|
2017-02-07 13:23:33 +03:00
|
|
|
uint32_t link_cnt = 0;
|
2006-05-21 20:30:15 +04:00
|
|
|
active = 0;
|
|
|
|
|
2023-02-20 21:15:06 +03:00
|
|
|
if (head == 0) {
|
2006-05-21 20:30:15 +04:00
|
|
|
return 0;
|
2023-02-20 21:15:06 +03:00
|
|
|
}
|
2019-03-21 11:52:12 +03:00
|
|
|
for (cur = head; cur && link_cnt++ < ED_LINK_LIMIT; cur = next_ed) {
|
2013-07-26 14:52:05 +04:00
|
|
|
if (ohci_read_ed(ohci, cur, &ed)) {
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_ed_read_error(cur);
|
2013-07-26 14:52:05 +04:00
|
|
|
ohci_die(ohci);
|
2006-05-21 20:30:15 +04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
next_ed = ed.next & OHCI_DPTR_MASK;
|
|
|
|
|
2006-08-12 05:04:27 +04:00
|
|
|
if ((ed.head & OHCI_ED_H) || (ed.flags & OHCI_ED_K)) {
|
|
|
|
uint32_t addr;
|
2023-02-20 21:15:04 +03:00
|
|
|
/* Cancel pending packets for ED that have been paused. */
|
2006-08-12 05:04:27 +04:00
|
|
|
addr = ed.head & OHCI_DPTR_MASK;
|
|
|
|
if (ohci->async_td && addr == ohci->async_td) {
|
|
|
|
usb_cancel_packet(&ohci->usb_packet);
|
|
|
|
ohci->async_td = 0;
|
2012-12-14 17:35:40 +04:00
|
|
|
usb_device_ep_stopped(ohci->usb_packet.ep->dev,
|
|
|
|
ohci->usb_packet.ep);
|
2006-08-12 05:04:27 +04:00
|
|
|
}
|
2006-05-21 20:30:15 +04:00
|
|
|
continue;
|
2006-08-12 05:04:27 +04:00
|
|
|
}
|
2006-05-21 20:30:15 +04:00
|
|
|
|
|
|
|
while ((ed.head & OHCI_DPTR_MASK) != ed.tail) {
|
2014-09-25 04:16:59 +04:00
|
|
|
trace_usb_ohci_ed_pkt(cur, (ed.head & OHCI_ED_H) != 0,
|
|
|
|
(ed.head & OHCI_ED_C) != 0, ed.head & OHCI_DPTR_MASK,
|
|
|
|
ed.tail & OHCI_DPTR_MASK, ed.next & OHCI_DPTR_MASK);
|
|
|
|
trace_usb_ohci_ed_pkt_flags(
|
2006-05-21 20:30:15 +04:00
|
|
|
OHCI_BM(ed.flags, ED_FA), OHCI_BM(ed.flags, ED_EN),
|
2023-02-20 21:15:05 +03:00
|
|
|
OHCI_BM(ed.flags, ED_D), (ed.flags & OHCI_ED_S) != 0,
|
2006-05-21 20:30:15 +04:00
|
|
|
(ed.flags & OHCI_ED_K) != 0, (ed.flags & OHCI_ED_F) != 0,
|
2014-09-25 04:16:59 +04:00
|
|
|
OHCI_BM(ed.flags, ED_MPS));
|
2014-09-12 12:55:26 +04:00
|
|
|
|
2006-05-21 20:30:15 +04:00
|
|
|
active = 1;
|
|
|
|
|
2007-10-31 03:34:21 +03:00
|
|
|
if ((ed.flags & OHCI_ED_F) == 0) {
|
2023-02-20 21:15:06 +03:00
|
|
|
if (ohci_service_td(ohci, &ed)) {
|
2007-10-31 03:34:21 +03:00
|
|
|
break;
|
2023-02-20 21:15:06 +03:00
|
|
|
}
|
2007-10-31 03:34:21 +03:00
|
|
|
} else {
|
|
|
|
/* Handle isochronous endpoints */
|
2022-01-25 16:33:20 +03:00
|
|
|
if (ohci_service_iso_td(ohci, &ed)) {
|
2007-10-31 03:34:21 +03:00
|
|
|
break;
|
2022-01-25 16:33:20 +03:00
|
|
|
}
|
2007-10-31 03:34:21 +03:00
|
|
|
}
|
2006-05-21 20:30:15 +04:00
|
|
|
}
|
|
|
|
|
2013-07-26 14:52:05 +04:00
|
|
|
if (ohci_put_ed(ohci, cur, &ed)) {
|
|
|
|
ohci_die(ohci);
|
|
|
|
return 0;
|
|
|
|
}
|
2006-05-21 20:30:15 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
return active;
|
|
|
|
}
|
|
|
|
|
2016-01-06 22:45:24 +03:00
|
|
|
/* set a timer for EOF */
|
|
|
|
static void ohci_eof_timer(OHCIState *ohci)
|
2006-05-21 20:30:15 +04:00
|
|
|
{
|
2013-08-21 19:03:08 +04:00
|
|
|
timer_mod(ohci->eof_timer, ohci->sof_time + usb_frame_time);
|
2016-01-06 22:45:24 +03:00
|
|
|
}
|
|
|
|
/* Set a timer for EOF and generate a SOF event */
|
|
|
|
static void ohci_sof(OHCIState *ohci)
|
|
|
|
{
|
2018-09-27 18:19:36 +03:00
|
|
|
ohci->sof_time += usb_frame_time;
|
2016-01-06 22:45:24 +03:00
|
|
|
ohci_eof_timer(ohci);
|
2006-05-21 20:30:15 +04:00
|
|
|
ohci_set_interrupt(ohci, OHCI_INTR_SF);
|
|
|
|
}
|
|
|
|
|
2023-02-20 21:15:04 +03:00
|
|
|
/* Process Control and Bulk lists. */
|
2022-01-25 16:33:20 +03:00
|
|
|
static void ohci_process_lists(OHCIState *ohci)
|
2006-08-12 05:04:27 +04:00
|
|
|
{
|
|
|
|
if ((ohci->ctl & OHCI_CTL_CLE) && (ohci->status & OHCI_STATUS_CLF)) {
|
2010-04-18 18:22:14 +04:00
|
|
|
if (ohci->ctrl_cur && ohci->ctrl_cur != ohci->ctrl_head) {
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_process_lists(ohci->ctrl_head, ohci->ctrl_cur);
|
2010-04-18 18:22:14 +04:00
|
|
|
}
|
2022-01-25 16:33:20 +03:00
|
|
|
if (!ohci_service_ed_list(ohci, ohci->ctrl_head)) {
|
2006-08-12 05:04:27 +04:00
|
|
|
ohci->ctrl_cur = 0;
|
|
|
|
ohci->status &= ~OHCI_STATUS_CLF;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((ohci->ctl & OHCI_CTL_BLE) && (ohci->status & OHCI_STATUS_BLF)) {
|
2022-01-25 16:33:20 +03:00
|
|
|
if (!ohci_service_ed_list(ohci, ohci->bulk_head)) {
|
2006-08-12 05:04:27 +04:00
|
|
|
ohci->bulk_cur = 0;
|
|
|
|
ohci->status &= ~OHCI_STATUS_BLF;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-05-21 20:30:15 +04:00
|
|
|
/* Do frame processing on frame boundary */
|
|
|
|
static void ohci_frame_boundary(void *opaque)
|
|
|
|
{
|
|
|
|
OHCIState *ohci = opaque;
|
|
|
|
struct ohci_hcca hcca;
|
|
|
|
|
2013-07-26 14:52:05 +04:00
|
|
|
if (ohci_read_hcca(ohci, ohci->hcca, &hcca)) {
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_hcca_read_error(ohci->hcca);
|
2013-07-26 14:52:05 +04:00
|
|
|
ohci_die(ohci);
|
|
|
|
return;
|
|
|
|
}
|
2006-05-21 20:30:15 +04:00
|
|
|
|
|
|
|
/* Process all the lists at the end of the frame */
|
|
|
|
if (ohci->ctl & OHCI_CTL_PLE) {
|
|
|
|
int n;
|
|
|
|
|
|
|
|
n = ohci->frame_number & 0x1f;
|
2022-01-25 16:33:20 +03:00
|
|
|
ohci_service_ed_list(ohci, le32_to_cpu(hcca.intr[n]));
|
2006-05-21 20:30:15 +04:00
|
|
|
}
|
|
|
|
|
2023-02-20 21:15:04 +03:00
|
|
|
/* Cancel all pending packets if either of the lists has been disabled. */
|
2012-12-14 17:35:40 +04:00
|
|
|
if (ohci->old_ctl & (~ohci->ctl) & (OHCI_CTL_BLE | OHCI_CTL_CLE)) {
|
|
|
|
ohci_stop_endpoints(ohci);
|
2006-05-21 20:30:15 +04:00
|
|
|
}
|
2006-08-12 05:04:27 +04:00
|
|
|
ohci->old_ctl = ohci->ctl;
|
2022-01-25 16:33:20 +03:00
|
|
|
ohci_process_lists(ohci);
|
2006-05-21 20:30:15 +04:00
|
|
|
|
2013-07-26 14:52:05 +04:00
|
|
|
/* Stop if UnrecoverableError happened or ohci_sof will crash */
|
|
|
|
if (ohci->intr_status & OHCI_INTR_UE) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2006-05-21 20:30:15 +04:00
|
|
|
/* Frame boundary, so do EOF stuf here */
|
|
|
|
ohci->frt = ohci->fit;
|
|
|
|
|
2009-07-08 23:54:28 +04:00
|
|
|
/* Increment frame number and take care of endianness. */
|
2006-05-21 20:30:15 +04:00
|
|
|
ohci->frame_number = (ohci->frame_number + 1) & 0xffff;
|
2009-07-08 23:54:28 +04:00
|
|
|
hcca.frame = cpu_to_le16(ohci->frame_number);
|
2023-05-23 18:58:40 +03:00
|
|
|
/* When the HC updates frame number, set pad to 0. Ref OHCI Spec 4.4.1*/
|
|
|
|
hcca.pad = 0;
|
2006-05-21 20:30:15 +04:00
|
|
|
|
|
|
|
if (ohci->done_count == 0 && !(ohci->intr_status & OHCI_INTR_WD)) {
|
2023-02-20 21:15:06 +03:00
|
|
|
if (!ohci->done) {
|
2006-05-21 20:30:15 +04:00
|
|
|
abort();
|
2023-02-20 21:15:06 +03:00
|
|
|
}
|
|
|
|
if (ohci->intr & ohci->intr_status) {
|
2006-05-21 20:30:15 +04:00
|
|
|
ohci->done |= 1;
|
2023-02-20 21:15:06 +03:00
|
|
|
}
|
2006-05-21 20:30:15 +04:00
|
|
|
hcca.done = cpu_to_le32(ohci->done);
|
|
|
|
ohci->done = 0;
|
|
|
|
ohci->done_count = 7;
|
|
|
|
ohci_set_interrupt(ohci, OHCI_INTR_WD);
|
|
|
|
}
|
|
|
|
|
2023-02-20 21:15:06 +03:00
|
|
|
if (ohci->done_count != 7 && ohci->done_count != 0) {
|
2006-05-21 20:30:15 +04:00
|
|
|
ohci->done_count--;
|
2023-02-20 21:15:06 +03:00
|
|
|
}
|
2006-05-21 20:30:15 +04:00
|
|
|
/* Do SOF stuff here */
|
|
|
|
ohci_sof(ohci);
|
|
|
|
|
|
|
|
/* Writeback HCCA */
|
2013-07-26 14:52:05 +04:00
|
|
|
if (ohci_put_hcca(ohci, ohci->hcca, &hcca)) {
|
|
|
|
ohci_die(ohci);
|
|
|
|
}
|
2006-05-21 20:30:15 +04:00
|
|
|
}
|
|
|
|
|
2023-02-20 21:15:04 +03:00
|
|
|
/*
|
|
|
|
* Start sending SOF tokens across the USB bus, lists are processed in
|
2006-05-21 20:30:15 +04:00
|
|
|
* next frame
|
|
|
|
*/
|
|
|
|
static int ohci_bus_start(OHCIState *ohci)
|
|
|
|
{
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_start(ohci->name);
|
2023-02-20 21:15:04 +03:00
|
|
|
/*
|
|
|
|
* Delay the first SOF event by one frame time as linux driver is
|
|
|
|
* not ready to receive it and can meet some race conditions
|
2016-01-06 22:45:24 +03:00
|
|
|
*/
|
2018-09-27 18:19:36 +03:00
|
|
|
ohci->sof_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
2016-01-06 22:45:24 +03:00
|
|
|
ohci_eof_timer(ohci);
|
2006-05-21 20:30:15 +04:00
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Stop sending SOF tokens on the bus */
|
2019-04-19 10:56:25 +03:00
|
|
|
void ohci_bus_stop(OHCIState *ohci)
|
2006-05-21 20:30:15 +04:00
|
|
|
{
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_stop(ohci->name);
|
2016-02-22 11:50:11 +03:00
|
|
|
timer_del(ohci->eof_timer);
|
2006-05-21 20:30:15 +04:00
|
|
|
}
|
|
|
|
|
2023-02-20 21:15:04 +03:00
|
|
|
/* Frame interval toggle is manipulated by the hcd only */
|
2006-05-21 20:30:15 +04:00
|
|
|
static void ohci_set_frame_interval(OHCIState *ohci, uint16_t val)
|
|
|
|
{
|
|
|
|
val &= OHCI_FMI_FI;
|
|
|
|
|
|
|
|
if (val != ohci->fi) {
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_set_frame_interval(ohci->name, ohci->fi, ohci->fi);
|
2006-05-21 20:30:15 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
ohci->fi = val;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ohci_port_power(OHCIState *ohci, int i, int p)
|
|
|
|
{
|
|
|
|
if (p) {
|
|
|
|
ohci->rhport[i].ctrl |= OHCI_PORT_PPS;
|
|
|
|
} else {
|
2023-02-20 21:15:05 +03:00
|
|
|
ohci->rhport[i].ctrl &= ~(OHCI_PORT_PPS | OHCI_PORT_CCS |
|
|
|
|
OHCI_PORT_PSS | OHCI_PORT_PRS);
|
2006-05-21 20:30:15 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Set HcControlRegister */
|
|
|
|
static void ohci_set_ctl(OHCIState *ohci, uint32_t val)
|
|
|
|
{
|
|
|
|
uint32_t old_state;
|
|
|
|
uint32_t new_state;
|
|
|
|
|
|
|
|
old_state = ohci->ctl & OHCI_CTL_HCFS;
|
|
|
|
ohci->ctl = val;
|
|
|
|
new_state = ohci->ctl & OHCI_CTL_HCFS;
|
|
|
|
|
|
|
|
/* no state change */
|
2023-02-20 21:15:06 +03:00
|
|
|
if (old_state == new_state) {
|
2006-05-21 20:30:15 +04:00
|
|
|
return;
|
2023-02-20 21:15:06 +03:00
|
|
|
}
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_set_ctl(ohci->name, new_state);
|
2006-05-21 20:30:15 +04:00
|
|
|
switch (new_state) {
|
|
|
|
case OHCI_USB_OPERATIONAL:
|
|
|
|
ohci_bus_start(ohci);
|
|
|
|
break;
|
|
|
|
case OHCI_USB_SUSPEND:
|
|
|
|
ohci_bus_stop(ohci);
|
2016-01-06 22:45:25 +03:00
|
|
|
/* clear pending SF otherwise linux driver loops in ohci_irq() */
|
|
|
|
ohci->intr_status &= ~OHCI_INTR_SF;
|
|
|
|
ohci_intr_update(ohci);
|
2006-05-21 20:30:15 +04:00
|
|
|
break;
|
|
|
|
case OHCI_USB_RESUME:
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_resume(ohci->name);
|
2006-05-21 20:30:15 +04:00
|
|
|
break;
|
|
|
|
case OHCI_USB_RESET:
|
2015-12-20 02:23:50 +03:00
|
|
|
ohci_roothub_reset(ohci);
|
2006-05-21 20:30:15 +04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint32_t ohci_get_frame_remaining(OHCIState *ohci)
|
|
|
|
{
|
|
|
|
uint16_t fr;
|
|
|
|
int64_t tks;
|
|
|
|
|
2023-02-20 21:15:06 +03:00
|
|
|
if ((ohci->ctl & OHCI_CTL_HCFS) != OHCI_USB_OPERATIONAL) {
|
|
|
|
return ohci->frt << 31;
|
|
|
|
}
|
2023-08-23 09:53:26 +03:00
|
|
|
/* Being in USB operational state guarantees sof_time was set already. */
|
2013-08-21 19:03:08 +04:00
|
|
|
tks = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) - ohci->sof_time;
|
2018-09-27 18:19:36 +03:00
|
|
|
if (tks < 0) {
|
|
|
|
tks = 0;
|
|
|
|
}
|
2006-05-21 20:30:15 +04:00
|
|
|
|
|
|
|
/* avoid muldiv if possible */
|
2023-02-20 21:15:06 +03:00
|
|
|
if (tks >= usb_frame_time) {
|
|
|
|
return ohci->frt << 31;
|
|
|
|
}
|
2016-05-09 16:24:57 +03:00
|
|
|
tks = tks / usb_bit_time;
|
2006-05-21 20:30:15 +04:00
|
|
|
fr = (uint16_t)(ohci->fi - tks);
|
|
|
|
|
|
|
|
return (ohci->frt << 31) | fr;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Set root hub status */
|
|
|
|
static void ohci_set_hub_status(OHCIState *ohci, uint32_t val)
|
|
|
|
{
|
|
|
|
uint32_t old_state;
|
|
|
|
|
|
|
|
old_state = ohci->rhstatus;
|
|
|
|
|
|
|
|
/* write 1 to clear OCIC */
|
2023-02-20 21:15:06 +03:00
|
|
|
if (val & OHCI_RHS_OCIC) {
|
2006-05-21 20:30:15 +04:00
|
|
|
ohci->rhstatus &= ~OHCI_RHS_OCIC;
|
2023-02-20 21:15:06 +03:00
|
|
|
}
|
2006-05-21 20:30:15 +04:00
|
|
|
if (val & OHCI_RHS_LPS) {
|
|
|
|
int i;
|
|
|
|
|
2023-02-20 21:15:06 +03:00
|
|
|
for (i = 0; i < ohci->num_ports; i++) {
|
2006-05-21 20:30:15 +04:00
|
|
|
ohci_port_power(ohci, i, 0);
|
2023-02-20 21:15:06 +03:00
|
|
|
}
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_hub_power_down();
|
2006-05-21 20:30:15 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (val & OHCI_RHS_LPSC) {
|
|
|
|
int i;
|
|
|
|
|
2023-02-20 21:15:06 +03:00
|
|
|
for (i = 0; i < ohci->num_ports; i++) {
|
2006-05-21 20:30:15 +04:00
|
|
|
ohci_port_power(ohci, i, 1);
|
2023-02-20 21:15:06 +03:00
|
|
|
}
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_hub_power_up();
|
2006-05-21 20:30:15 +04:00
|
|
|
}
|
|
|
|
|
2023-02-20 21:15:06 +03:00
|
|
|
if (val & OHCI_RHS_DRWE) {
|
2006-05-21 20:30:15 +04:00
|
|
|
ohci->rhstatus |= OHCI_RHS_DRWE;
|
2023-02-20 21:15:06 +03:00
|
|
|
}
|
|
|
|
if (val & OHCI_RHS_CRWE) {
|
2006-05-21 20:30:15 +04:00
|
|
|
ohci->rhstatus &= ~OHCI_RHS_DRWE;
|
2023-02-20 21:15:06 +03:00
|
|
|
}
|
|
|
|
if (old_state != ohci->rhstatus) {
|
2006-05-21 20:30:15 +04:00
|
|
|
ohci_set_interrupt(ohci, OHCI_INTR_RHSC);
|
2023-02-20 21:15:06 +03:00
|
|
|
}
|
2006-05-21 20:30:15 +04:00
|
|
|
}
|
|
|
|
|
2023-02-20 21:15:09 +03:00
|
|
|
/* This is the one state transition the controller can do by itself */
|
|
|
|
static bool ohci_resume(OHCIState *s)
|
|
|
|
{
|
|
|
|
if ((s->ctl & OHCI_CTL_HCFS) == OHCI_USB_SUSPEND) {
|
|
|
|
trace_usb_ohci_remote_wakeup(s->name);
|
|
|
|
s->ctl &= ~OHCI_CTL_HCFS;
|
|
|
|
s->ctl |= OHCI_USB_RESUME;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-02-20 21:15:07 +03:00
|
|
|
/*
|
|
|
|
* Sets a flag in a port status reg but only set it if the port is connected.
|
|
|
|
* If not set ConnectStatusChange flag. If flag is enabled return 1.
|
|
|
|
*/
|
|
|
|
static int ohci_port_set_if_connected(OHCIState *ohci, int i, uint32_t val)
|
|
|
|
{
|
|
|
|
int ret = 1;
|
|
|
|
|
|
|
|
/* writing a 0 has no effect */
|
|
|
|
if (val == 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
/* If CurrentConnectStatus is cleared we set ConnectStatusChange */
|
|
|
|
if (!(ohci->rhport[i].ctrl & OHCI_PORT_CCS)) {
|
|
|
|
ohci->rhport[i].ctrl |= OHCI_PORT_CSC;
|
|
|
|
if (ohci->rhstatus & OHCI_RHS_DRWE) {
|
2023-02-20 21:15:09 +03:00
|
|
|
/* CSC is a wakeup event */
|
|
|
|
if (ohci_resume(ohci)) {
|
|
|
|
ohci_set_interrupt(ohci, OHCI_INTR_RD);
|
|
|
|
}
|
2023-02-20 21:15:07 +03:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ohci->rhport[i].ctrl & val) {
|
|
|
|
ret = 0;
|
|
|
|
}
|
|
|
|
/* set the bit */
|
|
|
|
ohci->rhport[i].ctrl |= val;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2006-05-21 20:30:15 +04:00
|
|
|
/* Set root hub port status */
|
|
|
|
static void ohci_port_set_status(OHCIState *ohci, int portnum, uint32_t val)
|
|
|
|
{
|
|
|
|
uint32_t old_state;
|
|
|
|
OHCIPort *port;
|
|
|
|
|
|
|
|
port = &ohci->rhport[portnum];
|
|
|
|
old_state = port->ctrl;
|
|
|
|
|
|
|
|
/* Write to clear CSC, PESC, PSSC, OCIC, PRSC */
|
2023-02-20 21:15:06 +03:00
|
|
|
if (val & OHCI_PORT_WTC) {
|
2006-05-21 20:30:15 +04:00
|
|
|
port->ctrl &= ~(val & OHCI_PORT_WTC);
|
2023-02-20 21:15:06 +03:00
|
|
|
}
|
|
|
|
if (val & OHCI_PORT_CCS) {
|
2006-05-21 20:30:15 +04:00
|
|
|
port->ctrl &= ~OHCI_PORT_PES;
|
2023-02-20 21:15:06 +03:00
|
|
|
}
|
2006-05-21 20:30:15 +04:00
|
|
|
ohci_port_set_if_connected(ohci, portnum, val & OHCI_PORT_PES);
|
|
|
|
|
2010-04-18 18:22:14 +04:00
|
|
|
if (ohci_port_set_if_connected(ohci, portnum, val & OHCI_PORT_PSS)) {
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_port_suspend(portnum);
|
2010-04-18 18:22:14 +04:00
|
|
|
}
|
2006-05-21 20:30:15 +04:00
|
|
|
|
|
|
|
if (ohci_port_set_if_connected(ohci, portnum, val & OHCI_PORT_PRS)) {
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_port_reset(portnum);
|
2012-01-06 18:23:10 +04:00
|
|
|
usb_device_reset(port->port.dev);
|
2006-05-21 20:30:15 +04:00
|
|
|
port->ctrl &= ~OHCI_PORT_PRS;
|
2023-02-20 21:15:04 +03:00
|
|
|
/* ??? Should this also set OHCI_PORT_PESC. */
|
2006-05-21 20:30:15 +04:00
|
|
|
port->ctrl |= OHCI_PORT_PES | OHCI_PORT_PRSC;
|
|
|
|
}
|
|
|
|
|
2023-02-20 21:15:04 +03:00
|
|
|
/* Invert order here to ensure in ambiguous case, device is powered up. */
|
2023-02-20 21:15:06 +03:00
|
|
|
if (val & OHCI_PORT_LSDA) {
|
2006-05-21 20:30:15 +04:00
|
|
|
ohci_port_power(ohci, portnum, 0);
|
2023-02-20 21:15:06 +03:00
|
|
|
}
|
|
|
|
if (val & OHCI_PORT_PPS) {
|
2006-05-21 20:30:15 +04:00
|
|
|
ohci_port_power(ohci, portnum, 1);
|
2023-02-20 21:15:06 +03:00
|
|
|
}
|
|
|
|
if (old_state != port->ctrl) {
|
2006-05-21 20:30:15 +04:00
|
|
|
ohci_set_interrupt(ohci, OHCI_INTR_RHSC);
|
2023-02-20 21:15:06 +03:00
|
|
|
}
|
2006-05-21 20:30:15 +04:00
|
|
|
}
|
|
|
|
|
2011-07-26 15:26:22 +04:00
|
|
|
static uint64_t ohci_mem_read(void *opaque,
|
2012-10-23 14:30:10 +04:00
|
|
|
hwaddr addr,
|
2011-07-26 15:26:22 +04:00
|
|
|
unsigned size)
|
2006-05-21 20:30:15 +04:00
|
|
|
{
|
2011-07-26 15:26:22 +04:00
|
|
|
OHCIState *ohci = opaque;
|
2009-01-18 23:56:30 +03:00
|
|
|
uint32_t retval;
|
2006-05-21 20:30:15 +04:00
|
|
|
|
|
|
|
/* Only aligned reads are allowed on OHCI */
|
|
|
|
if (addr & 3) {
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_mem_read_unaligned(addr);
|
2006-05-21 20:30:15 +04:00
|
|
|
return 0xffffffff;
|
2009-01-18 23:56:30 +03:00
|
|
|
} else if (addr >= 0x54 && addr < 0x54 + ohci->num_ports * 4) {
|
2006-05-21 20:30:15 +04:00
|
|
|
/* HcRhPortStatus */
|
2009-01-18 23:56:30 +03:00
|
|
|
retval = ohci->rhport[(addr - 0x54) >> 2].ctrl | OHCI_PORT_PPS;
|
2023-02-20 21:19:09 +03:00
|
|
|
trace_usb_ohci_mem_port_read(size, "HcRhPortStatus", (addr - 0x50) >> 2,
|
|
|
|
addr, addr >> 2, retval);
|
2009-01-18 23:56:30 +03:00
|
|
|
} else {
|
|
|
|
switch (addr >> 2) {
|
|
|
|
case 0: /* HcRevision */
|
|
|
|
retval = 0x10;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 1: /* HcControl */
|
|
|
|
retval = ohci->ctl;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 2: /* HcCommandStatus */
|
|
|
|
retval = ohci->status;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 3: /* HcInterruptStatus */
|
|
|
|
retval = ohci->intr_status;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 4: /* HcInterruptEnable */
|
|
|
|
case 5: /* HcInterruptDisable */
|
|
|
|
retval = ohci->intr;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 6: /* HcHCCA */
|
|
|
|
retval = ohci->hcca;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 7: /* HcPeriodCurrentED */
|
|
|
|
retval = ohci->per_cur;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 8: /* HcControlHeadED */
|
|
|
|
retval = ohci->ctrl_head;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 9: /* HcControlCurrentED */
|
|
|
|
retval = ohci->ctrl_cur;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 10: /* HcBulkHeadED */
|
|
|
|
retval = ohci->bulk_head;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 11: /* HcBulkCurrentED */
|
|
|
|
retval = ohci->bulk_cur;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 12: /* HcDoneHead */
|
|
|
|
retval = ohci->done;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 13: /* HcFmInterretval */
|
|
|
|
retval = (ohci->fit << 31) | (ohci->fsmps << 16) | (ohci->fi);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 14: /* HcFmRemaining */
|
|
|
|
retval = ohci_get_frame_remaining(ohci);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 15: /* HcFmNumber */
|
|
|
|
retval = ohci->frame_number;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 16: /* HcPeriodicStart */
|
|
|
|
retval = ohci->pstart;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 17: /* HcLSThreshold */
|
|
|
|
retval = ohci->lst;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 18: /* HcRhDescriptorA */
|
|
|
|
retval = ohci->rhdesc_a;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 19: /* HcRhDescriptorB */
|
|
|
|
retval = ohci->rhdesc_b;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 20: /* HcRhStatus */
|
|
|
|
retval = ohci->rhstatus;
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* PXA27x specific registers */
|
|
|
|
case 24: /* HcStatus */
|
|
|
|
retval = ohci->hstatus & ohci->hmask;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 25: /* HcHReset */
|
|
|
|
retval = ohci->hreset;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 26: /* HcHInterruptEnable */
|
|
|
|
retval = ohci->hmask;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 27: /* HcHInterruptTest */
|
|
|
|
retval = ohci->htest;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_mem_read_bad_offset(addr);
|
2009-01-18 23:56:30 +03:00
|
|
|
retval = 0xffffffff;
|
|
|
|
}
|
2023-02-20 21:19:09 +03:00
|
|
|
if (addr != 0xc || retval) {
|
|
|
|
trace_usb_ohci_mem_read(size, ohci_reg_name(addr), addr, addr >> 2,
|
|
|
|
retval);
|
|
|
|
}
|
2006-05-21 20:30:15 +04:00
|
|
|
}
|
|
|
|
|
2009-01-18 23:56:30 +03:00
|
|
|
return retval;
|
2006-05-21 20:30:15 +04:00
|
|
|
}
|
|
|
|
|
2011-07-26 15:26:22 +04:00
|
|
|
static void ohci_mem_write(void *opaque,
|
2012-10-23 14:30:10 +04:00
|
|
|
hwaddr addr,
|
2011-07-26 15:26:22 +04:00
|
|
|
uint64_t val,
|
|
|
|
unsigned size)
|
2006-05-21 20:30:15 +04:00
|
|
|
{
|
2011-07-26 15:26:22 +04:00
|
|
|
OHCIState *ohci = opaque;
|
2010-06-14 02:37:31 +04:00
|
|
|
|
2006-05-21 20:30:15 +04:00
|
|
|
/* Only aligned reads are allowed on OHCI */
|
|
|
|
if (addr & 3) {
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_mem_write_unaligned(addr);
|
2006-05-21 20:30:15 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (addr >= 0x54 && addr < 0x54 + ohci->num_ports * 4) {
|
|
|
|
/* HcRhPortStatus */
|
2023-02-20 21:19:09 +03:00
|
|
|
trace_usb_ohci_mem_port_write(size, "HcRhPortStatus",
|
|
|
|
(addr - 0x50) >> 2, addr, addr >> 2, val);
|
2006-05-21 20:30:15 +04:00
|
|
|
ohci_port_set_status(ohci, (addr - 0x54) >> 2, val);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-02-20 21:19:09 +03:00
|
|
|
trace_usb_ohci_mem_write(size, ohci_reg_name(addr), addr, addr >> 2, val);
|
2006-05-21 20:30:15 +04:00
|
|
|
switch (addr >> 2) {
|
|
|
|
case 1: /* HcControl */
|
|
|
|
ohci_set_ctl(ohci, val);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 2: /* HcCommandStatus */
|
|
|
|
/* SOC is read-only */
|
|
|
|
val = (val & ~OHCI_STATUS_SOC);
|
|
|
|
|
|
|
|
/* Bits written as '0' remain unchanged in the register */
|
|
|
|
ohci->status |= val;
|
|
|
|
|
2023-02-20 21:15:06 +03:00
|
|
|
if (ohci->status & OHCI_STATUS_HCR) {
|
2015-12-20 02:23:51 +03:00
|
|
|
ohci_soft_reset(ohci);
|
2023-02-20 21:15:06 +03:00
|
|
|
}
|
2006-05-21 20:30:15 +04:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 3: /* HcInterruptStatus */
|
|
|
|
ohci->intr_status &= ~val;
|
|
|
|
ohci_intr_update(ohci);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 4: /* HcInterruptEnable */
|
|
|
|
ohci->intr |= val;
|
|
|
|
ohci_intr_update(ohci);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 5: /* HcInterruptDisable */
|
|
|
|
ohci->intr &= ~val;
|
|
|
|
ohci_intr_update(ohci);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 6: /* HcHCCA */
|
|
|
|
ohci->hcca = val & OHCI_HCCA_MASK;
|
|
|
|
break;
|
|
|
|
|
2011-06-07 23:02:29 +04:00
|
|
|
case 7: /* HcPeriodCurrentED */
|
|
|
|
/* Ignore writes to this read-only register, Linux does them */
|
|
|
|
break;
|
|
|
|
|
2006-05-21 20:30:15 +04:00
|
|
|
case 8: /* HcControlHeadED */
|
|
|
|
ohci->ctrl_head = val & OHCI_EDPTR_MASK;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 9: /* HcControlCurrentED */
|
|
|
|
ohci->ctrl_cur = val & OHCI_EDPTR_MASK;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 10: /* HcBulkHeadED */
|
|
|
|
ohci->bulk_head = val & OHCI_EDPTR_MASK;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 11: /* HcBulkCurrentED */
|
|
|
|
ohci->bulk_cur = val & OHCI_EDPTR_MASK;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 13: /* HcFmInterval */
|
|
|
|
ohci->fsmps = (val & OHCI_FMI_FSMPS) >> 16;
|
|
|
|
ohci->fit = (val & OHCI_FMI_FIT) >> 31;
|
|
|
|
ohci_set_frame_interval(ohci, val);
|
|
|
|
break;
|
|
|
|
|
2007-10-31 03:34:21 +03:00
|
|
|
case 15: /* HcFmNumber */
|
|
|
|
break;
|
|
|
|
|
2006-05-21 20:30:15 +04:00
|
|
|
case 16: /* HcPeriodicStart */
|
|
|
|
ohci->pstart = val & 0xffff;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 17: /* HcLSThreshold */
|
|
|
|
ohci->lst = val & 0xffff;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 18: /* HcRhDescriptorA */
|
|
|
|
ohci->rhdesc_a &= ~OHCI_RHA_RW_MASK;
|
|
|
|
ohci->rhdesc_a |= val & OHCI_RHA_RW_MASK;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 19: /* HcRhDescriptorB */
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 20: /* HcRhStatus */
|
|
|
|
ohci_set_hub_status(ohci, val);
|
|
|
|
break;
|
|
|
|
|
2007-03-17 19:59:31 +03:00
|
|
|
/* PXA27x specific registers */
|
|
|
|
case 24: /* HcStatus */
|
|
|
|
ohci->hstatus &= ~(val & ohci->hmask);
|
2013-01-21 17:53:01 +04:00
|
|
|
break;
|
2007-03-17 19:59:31 +03:00
|
|
|
|
|
|
|
case 25: /* HcHReset */
|
|
|
|
ohci->hreset = val & ~OHCI_HRESET_FSBIR;
|
2023-02-20 21:15:06 +03:00
|
|
|
if (val & OHCI_HRESET_FSBIR) {
|
2015-12-20 02:23:49 +03:00
|
|
|
ohci_hard_reset(ohci);
|
2023-02-20 21:15:06 +03:00
|
|
|
}
|
2007-03-17 19:59:31 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 26: /* HcHInterruptEnable */
|
|
|
|
ohci->hmask = val;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 27: /* HcHInterruptTest */
|
|
|
|
ohci->htest = val;
|
|
|
|
break;
|
|
|
|
|
2006-05-21 20:30:15 +04:00
|
|
|
default:
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_mem_write_bad_offset(addr);
|
2006-05-21 20:30:15 +04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-25 16:33:20 +03:00
|
|
|
static const MemoryRegionOps ohci_mem_ops = {
|
|
|
|
.read = ohci_mem_read,
|
|
|
|
.write = ohci_mem_write,
|
|
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
|
|
};
|
|
|
|
|
|
|
|
/* USBPortOps */
|
|
|
|
static void ohci_attach(USBPort *port1)
|
|
|
|
{
|
|
|
|
OHCIState *s = port1->opaque;
|
|
|
|
OHCIPort *port = &s->rhport[port1->index];
|
|
|
|
uint32_t old_state = port->ctrl;
|
|
|
|
|
|
|
|
/* set connect status */
|
|
|
|
port->ctrl |= OHCI_PORT_CCS | OHCI_PORT_CSC;
|
|
|
|
|
|
|
|
/* update speed */
|
|
|
|
if (port->port.dev->speed == USB_SPEED_LOW) {
|
|
|
|
port->ctrl |= OHCI_PORT_LSDA;
|
|
|
|
} else {
|
|
|
|
port->ctrl &= ~OHCI_PORT_LSDA;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* notify of remote-wakeup */
|
|
|
|
if ((s->ctl & OHCI_CTL_HCFS) == OHCI_USB_SUSPEND) {
|
|
|
|
ohci_set_interrupt(s, OHCI_INTR_RD);
|
|
|
|
}
|
|
|
|
|
|
|
|
trace_usb_ohci_port_attach(port1->index);
|
|
|
|
|
|
|
|
if (old_state != port->ctrl) {
|
|
|
|
ohci_set_interrupt(s, OHCI_INTR_RHSC);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-25 16:33:20 +03:00
|
|
|
static void ohci_child_detach(USBPort *port1, USBDevice *dev)
|
2011-05-23 19:37:12 +04:00
|
|
|
{
|
2022-01-25 16:33:20 +03:00
|
|
|
OHCIState *ohci = port1->opaque;
|
|
|
|
|
2011-12-13 18:58:19 +04:00
|
|
|
if (ohci->async_td &&
|
2012-01-12 15:51:48 +04:00
|
|
|
usb_packet_is_inflight(&ohci->usb_packet) &&
|
|
|
|
ohci->usb_packet.ep->dev == dev) {
|
2011-05-23 19:37:12 +04:00
|
|
|
usb_cancel_packet(&ohci->usb_packet);
|
|
|
|
ohci->async_td = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-25 16:33:20 +03:00
|
|
|
static void ohci_detach(USBPort *port1)
|
|
|
|
{
|
|
|
|
OHCIState *s = port1->opaque;
|
|
|
|
OHCIPort *port = &s->rhport[port1->index];
|
|
|
|
uint32_t old_state = port->ctrl;
|
|
|
|
|
2022-01-25 16:33:20 +03:00
|
|
|
ohci_child_detach(port1, port1->dev);
|
2022-01-25 16:33:20 +03:00
|
|
|
|
|
|
|
/* set connect status */
|
|
|
|
if (port->ctrl & OHCI_PORT_CCS) {
|
|
|
|
port->ctrl &= ~OHCI_PORT_CCS;
|
|
|
|
port->ctrl |= OHCI_PORT_CSC;
|
|
|
|
}
|
|
|
|
/* disable port */
|
|
|
|
if (port->ctrl & OHCI_PORT_PES) {
|
|
|
|
port->ctrl &= ~OHCI_PORT_PES;
|
|
|
|
port->ctrl |= OHCI_PORT_PESC;
|
|
|
|
}
|
|
|
|
trace_usb_ohci_port_detach(port1->index);
|
|
|
|
|
|
|
|
if (old_state != port->ctrl) {
|
|
|
|
ohci_set_interrupt(s, OHCI_INTR_RHSC);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ohci_wakeup(USBPort *port1)
|
|
|
|
{
|
|
|
|
OHCIState *s = port1->opaque;
|
|
|
|
OHCIPort *port = &s->rhport[port1->index];
|
|
|
|
uint32_t intr = 0;
|
|
|
|
if (port->ctrl & OHCI_PORT_PSS) {
|
|
|
|
trace_usb_ohci_port_wakeup(port1->index);
|
|
|
|
port->ctrl |= OHCI_PORT_PSSC;
|
|
|
|
port->ctrl &= ~OHCI_PORT_PSS;
|
|
|
|
intr = OHCI_INTR_RHSC;
|
|
|
|
}
|
|
|
|
/* Note that the controller can be suspended even if this port is not */
|
2023-02-20 21:15:09 +03:00
|
|
|
if (ohci_resume(s)) {
|
2022-01-25 16:33:20 +03:00
|
|
|
/*
|
|
|
|
* In suspend mode only ResumeDetected is possible, not RHSC:
|
|
|
|
* see the OHCI spec 5.1.2.3.
|
|
|
|
*/
|
|
|
|
intr = OHCI_INTR_RD;
|
|
|
|
}
|
|
|
|
ohci_set_interrupt(s, intr);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ohci_async_complete_packet(USBPort *port, USBPacket *packet)
|
|
|
|
{
|
|
|
|
OHCIState *ohci = container_of(packet, OHCIState, usb_packet);
|
|
|
|
|
|
|
|
trace_usb_ohci_async_complete();
|
|
|
|
ohci->async_complete = true;
|
2022-01-25 16:33:20 +03:00
|
|
|
ohci_process_lists(ohci);
|
2022-01-25 16:33:20 +03:00
|
|
|
}
|
2006-05-21 20:30:15 +04:00
|
|
|
|
2010-12-01 13:08:44 +03:00
|
|
|
static USBPortOps ohci_port_ops = {
|
|
|
|
.attach = ohci_attach,
|
2010-12-01 13:27:05 +03:00
|
|
|
.detach = ohci_detach,
|
2011-06-24 14:31:11 +04:00
|
|
|
.child_detach = ohci_child_detach,
|
2011-06-07 22:50:12 +04:00
|
|
|
.wakeup = ohci_wakeup,
|
2010-12-16 19:03:44 +03:00
|
|
|
.complete = ohci_async_complete_packet,
|
2010-12-01 13:08:44 +03:00
|
|
|
};
|
|
|
|
|
2011-05-23 19:37:12 +04:00
|
|
|
static USBBusOps ohci_bus_ops = {
|
|
|
|
};
|
|
|
|
|
2019-04-19 10:56:25 +03:00
|
|
|
void usb_ohci_init(OHCIState *ohci, DeviceState *dev, uint32_t num_ports,
|
|
|
|
dma_addr_t localmem_base, char *masterbus,
|
|
|
|
uint32_t firstport, AddressSpace *as,
|
2023-02-20 13:44:15 +03:00
|
|
|
void (*ohci_die_fn)(OHCIState *), Error **errp)
|
2006-05-21 20:30:15 +04:00
|
|
|
{
|
2015-02-17 16:28:02 +03:00
|
|
|
Error *err = NULL;
|
2006-05-21 20:30:15 +04:00
|
|
|
int i;
|
|
|
|
|
2013-04-10 20:15:49 +04:00
|
|
|
ohci->as = as;
|
2019-04-19 10:56:24 +03:00
|
|
|
ohci->ohci_die = ohci_die_fn;
|
2012-06-27 08:50:39 +04:00
|
|
|
|
2016-05-23 12:23:07 +03:00
|
|
|
if (num_ports > OHCI_MAX_PORTS) {
|
2018-10-23 06:00:18 +03:00
|
|
|
error_setg(errp, "OHCI num-ports=%u is too big (limit is %u ports)",
|
2016-05-23 12:23:07 +03:00
|
|
|
num_ports, OHCI_MAX_PORTS);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2006-05-21 20:30:15 +04:00
|
|
|
if (usb_frame_time == 0) {
|
2008-09-06 21:47:39 +04:00
|
|
|
#ifdef OHCI_TIME_WARP
|
2016-03-21 19:02:30 +03:00
|
|
|
usb_frame_time = NANOSECONDS_PER_SECOND;
|
|
|
|
usb_bit_time = NANOSECONDS_PER_SECOND / (USB_HZ / 1000);
|
2006-05-21 20:30:15 +04:00
|
|
|
#else
|
2016-03-21 19:02:30 +03:00
|
|
|
usb_frame_time = NANOSECONDS_PER_SECOND / 1000;
|
|
|
|
if (NANOSECONDS_PER_SECOND >= USB_HZ) {
|
|
|
|
usb_bit_time = NANOSECONDS_PER_SECOND / USB_HZ;
|
2006-05-21 20:30:15 +04:00
|
|
|
} else {
|
|
|
|
usb_bit_time = 1;
|
|
|
|
}
|
|
|
|
#endif
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_init_time(usb_frame_time, usb_bit_time);
|
2006-05-21 20:30:15 +04:00
|
|
|
}
|
|
|
|
|
2011-06-24 22:29:05 +04:00
|
|
|
ohci->num_ports = num_ports;
|
|
|
|
if (masterbus) {
|
|
|
|
USBPort *ports[OHCI_MAX_PORTS];
|
2023-02-20 21:15:05 +03:00
|
|
|
for (i = 0; i < num_ports; i++) {
|
2011-06-24 22:29:05 +04:00
|
|
|
ports[i] = &ohci->rhport[i].port;
|
|
|
|
}
|
2015-02-17 16:28:02 +03:00
|
|
|
usb_register_companion(masterbus, ports, num_ports,
|
|
|
|
firstport, ohci, &ohci_port_ops,
|
|
|
|
USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL,
|
|
|
|
&err);
|
|
|
|
if (err) {
|
2015-02-17 16:28:04 +03:00
|
|
|
error_propagate(errp, err);
|
|
|
|
return;
|
2011-06-24 22:29:05 +04:00
|
|
|
}
|
|
|
|
} else {
|
2013-08-23 22:32:04 +04:00
|
|
|
usb_bus_new(&ohci->bus, sizeof(ohci->bus), &ohci_bus_ops, dev);
|
2011-06-24 22:29:05 +04:00
|
|
|
for (i = 0; i < num_ports; i++) {
|
|
|
|
usb_register_port(&ohci->bus, &ohci->rhport[i].port,
|
|
|
|
ohci, i, &ohci_port_ops,
|
|
|
|
USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-07 05:25:08 +04:00
|
|
|
memory_region_init_io(&ohci->mem, OBJECT(dev), &ohci_mem_ops,
|
|
|
|
ohci, "ohci", 256);
|
2009-04-19 13:15:50 +04:00
|
|
|
ohci->localmem_base = localmem_base;
|
2007-03-17 19:59:31 +03:00
|
|
|
|
2011-12-04 21:17:51 +04:00
|
|
|
ohci->name = object_get_typename(OBJECT(dev));
|
2011-07-12 17:22:25 +04:00
|
|
|
usb_packet_init(&ohci->usb_packet);
|
2007-03-17 19:59:31 +03:00
|
|
|
|
|
|
|
ohci->async_td = 0;
|
2016-02-22 11:50:11 +03:00
|
|
|
|
|
|
|
ohci->eof_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
|
|
|
|
ohci_frame_boundary, ohci);
|
2007-03-17 19:59:31 +03:00
|
|
|
}
|
|
|
|
|
2023-02-20 21:15:04 +03:00
|
|
|
/*
|
2019-04-19 10:56:24 +03:00
|
|
|
* A typical OHCI will stop operating and set itself into error state
|
|
|
|
* (which can be queried by MMIO) to signal that it got an error.
|
2013-07-26 14:52:05 +04:00
|
|
|
*/
|
2019-04-19 10:56:25 +03:00
|
|
|
void ohci_sysbus_die(struct OHCIState *ohci)
|
2013-07-26 14:52:05 +04:00
|
|
|
{
|
2014-09-12 12:55:26 +04:00
|
|
|
trace_usb_ohci_die();
|
2013-07-26 14:52:05 +04:00
|
|
|
|
|
|
|
ohci_set_interrupt(ohci, OHCI_INTR_UE);
|
|
|
|
ohci_bus_stop(ohci);
|
2019-04-19 10:56:24 +03:00
|
|
|
}
|
|
|
|
|
2014-04-13 14:42:34 +04:00
|
|
|
static const VMStateDescription vmstate_ohci_state_port = {
|
|
|
|
.name = "ohci-core/port",
|
|
|
|
.version_id = 1,
|
|
|
|
.minimum_version_id = 1,
|
2023-12-21 06:16:39 +03:00
|
|
|
.fields = (const VMStateField[]) {
|
2014-04-13 14:42:34 +04:00
|
|
|
VMSTATE_UINT32(ctrl, OHCIPort),
|
|
|
|
VMSTATE_END_OF_LIST()
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
static bool ohci_eof_timer_needed(void *opaque)
|
|
|
|
{
|
|
|
|
OHCIState *ohci = opaque;
|
|
|
|
|
2016-02-22 11:50:11 +03:00
|
|
|
return timer_pending(ohci->eof_timer);
|
2014-04-13 14:42:34 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static const VMStateDescription vmstate_ohci_eof_timer = {
|
|
|
|
.name = "ohci-core/eof-timer",
|
|
|
|
.version_id = 1,
|
|
|
|
.minimum_version_id = 1,
|
2014-09-23 16:09:54 +04:00
|
|
|
.needed = ohci_eof_timer_needed,
|
2023-12-21 06:16:39 +03:00
|
|
|
.fields = (const VMStateField[]) {
|
2015-01-08 12:18:59 +03:00
|
|
|
VMSTATE_TIMER_PTR(eof_timer, OHCIState),
|
2014-04-13 14:42:34 +04:00
|
|
|
VMSTATE_END_OF_LIST()
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2019-04-19 10:56:25 +03:00
|
|
|
const VMStateDescription vmstate_ohci_state = {
|
2014-04-13 14:42:34 +04:00
|
|
|
.name = "ohci-core",
|
|
|
|
.version_id = 1,
|
|
|
|
.minimum_version_id = 1,
|
2023-12-21 06:16:39 +03:00
|
|
|
.fields = (const VMStateField[]) {
|
2014-04-13 14:42:34 +04:00
|
|
|
VMSTATE_INT64(sof_time, OHCIState),
|
|
|
|
VMSTATE_UINT32(ctl, OHCIState),
|
|
|
|
VMSTATE_UINT32(status, OHCIState),
|
|
|
|
VMSTATE_UINT32(intr_status, OHCIState),
|
|
|
|
VMSTATE_UINT32(intr, OHCIState),
|
|
|
|
VMSTATE_UINT32(hcca, OHCIState),
|
|
|
|
VMSTATE_UINT32(ctrl_head, OHCIState),
|
|
|
|
VMSTATE_UINT32(ctrl_cur, OHCIState),
|
|
|
|
VMSTATE_UINT32(bulk_head, OHCIState),
|
|
|
|
VMSTATE_UINT32(bulk_cur, OHCIState),
|
|
|
|
VMSTATE_UINT32(per_cur, OHCIState),
|
|
|
|
VMSTATE_UINT32(done, OHCIState),
|
|
|
|
VMSTATE_INT32(done_count, OHCIState),
|
|
|
|
VMSTATE_UINT16(fsmps, OHCIState),
|
|
|
|
VMSTATE_UINT8(fit, OHCIState),
|
|
|
|
VMSTATE_UINT16(fi, OHCIState),
|
|
|
|
VMSTATE_UINT8(frt, OHCIState),
|
|
|
|
VMSTATE_UINT16(frame_number, OHCIState),
|
|
|
|
VMSTATE_UINT16(padding, OHCIState),
|
|
|
|
VMSTATE_UINT32(pstart, OHCIState),
|
|
|
|
VMSTATE_UINT32(lst, OHCIState),
|
|
|
|
VMSTATE_UINT32(rhdesc_a, OHCIState),
|
|
|
|
VMSTATE_UINT32(rhdesc_b, OHCIState),
|
|
|
|
VMSTATE_UINT32(rhstatus, OHCIState),
|
|
|
|
VMSTATE_STRUCT_ARRAY(rhport, OHCIState, OHCI_MAX_PORTS, 0,
|
|
|
|
vmstate_ohci_state_port, OHCIPort),
|
|
|
|
VMSTATE_UINT32(hstatus, OHCIState),
|
|
|
|
VMSTATE_UINT32(hmask, OHCIState),
|
|
|
|
VMSTATE_UINT32(hreset, OHCIState),
|
|
|
|
VMSTATE_UINT32(htest, OHCIState),
|
|
|
|
VMSTATE_UINT32(old_ctl, OHCIState),
|
|
|
|
VMSTATE_UINT8_ARRAY(usb_buf, OHCIState, 8192),
|
|
|
|
VMSTATE_UINT32(async_td, OHCIState),
|
|
|
|
VMSTATE_BOOL(async_complete, OHCIState),
|
|
|
|
VMSTATE_END_OF_LIST()
|
|
|
|
},
|
2023-12-21 06:16:39 +03:00
|
|
|
.subsections = (const VMStateDescription * const []) {
|
2014-09-23 16:09:54 +04:00
|
|
|
&vmstate_ohci_eof_timer,
|
|
|
|
NULL
|
2014-04-13 14:42:34 +04:00
|
|
|
}
|
|
|
|
};
|