diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:30:32 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:30:32 -0800 |
commit | 8b23a6c7e1aee255004dd19098d4c2462b61b849 (patch) | |
tree | 7a4d682ba51f0ff0364c5ca2509f515bdaf96de9 /hw | |
parent | f721e3ac031f892af46f255a47d7f54a91317b30 (diff) | |
download | external_qemu-8b23a6c7e1aee255004dd19098d4c2462b61b849.zip external_qemu-8b23a6c7e1aee255004dd19098d4c2462b61b849.tar.gz external_qemu-8b23a6c7e1aee255004dd19098d4c2462b61b849.tar.bz2 |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'hw')
53 files changed, 14976 insertions, 0 deletions
diff --git a/hw/android_arm.c b/hw/android_arm.c new file mode 100644 index 0000000..efc8ba1 --- /dev/null +++ b/hw/android_arm.c @@ -0,0 +1,175 @@ +/* Copyright (C) 2007-2008 The Android Open Source Project +** +** This software is licensed under the terms of the GNU General Public +** License version 2, as published by the Free Software Foundation, and +** may be copied, distributed, and modified under those terms. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +*/ +#include "hw.h" +#include "boards.h" +#include "devices.h" +#include "net.h" +#include "arm_pic.h" +#include "sysemu.h" +#include "goldfish_device.h" +#include "android/globals.h" +#include "audio/audio.h" +#include "arm-misc.h" + +#define ARM_CPU_SAVE_VERSION 1 + +int android_audio_enabled; +char* audio_input_source = NULL; + +void goldfish_memlog_init(uint32_t base); + +static struct goldfish_device event0_device = { + .name = "goldfish_events", + .id = 0, + .size = 0x1000, + .irq_count = 1 +}; + +static struct goldfish_device nand_device = { + .name = "goldfish_nand", + .id = 0, + .size = 0x1000 +}; + +static struct goldfish_device trace_device = { + .name = "qemu_trace", + .id = -1, + .size = 0x1000 +}; + +/* Board init. */ + +#define TEST_SWITCH 1 +#if TEST_SWITCH +uint32_t switch_test_write(void *opaque, uint32_t state) +{ + goldfish_switch_set_state(opaque, state); + return state; +} +#endif + +static void android_arm_init(ram_addr_t ram_size, int vga_ram_size, + const char *boot_device, DisplayState *ds, + const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename, + const char *cpu_model) +{ + CPUState *env; + qemu_irq *cpu_pic; + qemu_irq *goldfish_pic; + int i; + struct arm_boot_info info; + + if (!cpu_model) + cpu_model = "arm926"; + + env = cpu_init(cpu_model); + + register_savevm( "cpu", 0, ARM_CPU_SAVE_VERSION, cpu_save, cpu_load, env ); + + cpu_register_physical_memory(0, ram_size, IO_MEM_RAM); + + cpu_pic = arm_pic_init_cpu(env); + goldfish_pic = goldfish_interrupt_init(0xff000000, cpu_pic[ARM_PIC_CPU_IRQ], cpu_pic[ARM_PIC_CPU_FIQ]); + goldfish_device_init(goldfish_pic, 0xff010000, 0x7f0000, 10, 22); + + goldfish_device_bus_init(0xff001000, 1); + + goldfish_timer_and_rtc_init(0xff003000, 3); + + goldfish_tty_add(serial_hds[0], 0, 0xff002000, 4); + for(i = 1; i < MAX_SERIAL_PORTS; i++) { + //printf("android_arm_init serial %d %x\n", i, serial_hds[i]); + if(serial_hds[i]) { + goldfish_tty_add(serial_hds[i], i, 0, 0); + } + } + + for(i = 0; i < MAX_NICS; i++) { + if (nd_table[i].vlan) { + if (nd_table[i].model == NULL + || strcmp(nd_table[i].model, "smc91c111") == 0) { + struct goldfish_device *smc_device; + smc_device = qemu_mallocz(sizeof(*smc_device)); + smc_device->name = "smc91x"; + smc_device->id = i; + smc_device->size = 0x1000; + smc_device->irq_count = 1; + goldfish_add_device_no_io(smc_device); + smc91c111_init(&nd_table[i], smc_device->base, goldfish_pic[smc_device->irq]); + } else { + fprintf(stderr, "qemu: Unsupported NIC: %s\n", nd_table[0].model); + exit (1); + } + } + } + + goldfish_fb_init(ds, 0); +#ifdef HAS_AUDIO + if (android_audio_enabled) { + AUD_init(); + goldfish_audio_init(0xff004000, 0, audio_input_source); + } +#endif + { + int idx = drive_get_index( IF_IDE, 0, 0 ); + if (idx >= 0) + goldfish_mmc_init(0xff005000, 0, drives_table[idx].bdrv); + } + + goldfish_memlog_init(0xff006000); + + if (android_hw->hw_battery) + goldfish_battery_init(); + + goldfish_add_device_no_io(&event0_device); + events_dev_init(event0_device.base, goldfish_pic[event0_device.irq]); + +#ifdef CONFIG_NAND + goldfish_add_device_no_io(&nand_device); + nand_dev_init(nand_device.base); +#endif +#ifdef CONFIG_TRACE + extern const char *trace_filename; + if(trace_filename != NULL) { + goldfish_add_device_no_io(&trace_device); + trace_dev_init(trace_device.base); + } +#endif + +#if TEST_SWITCH + { + void *sw; + sw = goldfish_switch_add("test", NULL, NULL, 0); + goldfish_switch_set_state(sw, 1); + goldfish_switch_add("test2", switch_test_write, sw, 1); + } +#endif + + memset(&info, 0, sizeof info); + info.ram_size = ram_size; + info.kernel_filename = kernel_filename; + info.kernel_cmdline = kernel_cmdline; + info.initrd_filename = initrd_filename; + info.nb_cpus = 1; + info.board_id = 1441; + + arm_load_kernel(env, &info); +} + +QEMUMachine android_arm_machine = { + "android_arm", + "ARM Android Emulator", + android_arm_init, + NULL +}; diff --git a/hw/arm-misc.h b/hw/arm-misc.h new file mode 100644 index 0000000..707e699 --- /dev/null +++ b/hw/arm-misc.h @@ -0,0 +1,49 @@ +/* + * Misc ARM declarations + * + * Copyright (c) 2006 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the LGPL. + * + */ + +#ifndef ARM_MISC_H +#define ARM_MISC_H 1 + +#include "cpu.h" + +/* The CPU is also modeled as an interrupt controller. */ +#define ARM_PIC_CPU_IRQ 0 +#define ARM_PIC_CPU_FIQ 1 +qemu_irq *arm_pic_init_cpu(CPUState *env); + +/* armv7m.c */ +qemu_irq *armv7m_init(int flash_size, int sram_size, + const char *kernel_filename, const char *cpu_model); + +/* arm_boot.c */ +struct arm_boot_info { + int ram_size; + const char *kernel_filename; + const char *kernel_cmdline; + const char *initrd_filename; + target_phys_addr_t loader_start; + int nb_cpus; + int board_id; + int (*atag_board)(struct arm_boot_info *info, void *p); +}; +void arm_load_kernel(CPUState *env, struct arm_boot_info *info); + +/* armv7m_nvic.c */ + +/* Multiplication factor to convert from system clock ticks to qemu timer + ticks. */ +int system_clock_scale; +qemu_irq *armv7m_nvic_init(CPUState *env); + +/* stellaris_enent.c */ +void stellaris_enet_init(NICInfo *nd, uint32_t base, qemu_irq irq); + +#endif /* !ARM_MISC_H */ + diff --git a/hw/arm_boot.c b/hw/arm_boot.c new file mode 100644 index 0000000..5990961 --- /dev/null +++ b/hw/arm_boot.c @@ -0,0 +1,251 @@ +/* + * ARM kernel loader. + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ + +#include "hw.h" +#include "arm-misc.h" +#include "sysemu.h" + +#define KERNEL_ARGS_ADDR 0x100 +#define KERNEL_LOAD_ADDR 0x00010000 +#define INITRD_LOAD_ADDR 0x00800000 + +/* The worlds second smallest bootloader. Set r0-r2, then jump to kernel. */ +static uint32_t bootloader[] = { + 0xe3a00000, /* mov r0, #0 */ + 0xe3a01000, /* mov r1, #0x?? */ + 0xe3811c00, /* orr r1, r1, #0x??00 */ + 0xe59f2000, /* ldr r2, [pc, #0] */ + 0xe59ff000, /* ldr pc, [pc, #0] */ + 0, /* Address of kernel args. Set by integratorcp_init. */ + 0 /* Kernel entry point. Set by integratorcp_init. */ +}; + +/* Entry point for secondary CPUs. Enable interrupt controller and + Issue WFI until start address is written to system controller. */ +static uint32_t smpboot[] = { + 0xe3a00201, /* mov r0, #0x10000000 */ + 0xe3800601, /* orr r0, r0, #0x001000000 */ + 0xe3a01001, /* mov r1, #1 */ + 0xe5801100, /* str r1, [r0, #0x100] */ + 0xe3a00201, /* mov r0, #0x10000000 */ + 0xe3800030, /* orr r0, #0x30 */ + 0xe320f003, /* wfi */ + 0xe5901000, /* ldr r1, [r0] */ + 0xe3110003, /* tst r1, #3 */ + 0x1afffffb, /* bne <wfi> */ + 0xe12fff11 /* bx r1 */ +}; + +static void main_cpu_reset(void *opaque) +{ + CPUState *env = opaque; + + cpu_reset(env); + if (env->boot_info) + arm_load_kernel(env, env->boot_info); + + /* TODO: Reset secondary CPUs. */ +} + +static void set_kernel_args(struct arm_boot_info *info, + int initrd_size, void *base) +{ + uint32_t *p; + + p = (uint32_t *)(base + KERNEL_ARGS_ADDR); + /* ATAG_CORE */ + stl_raw(p++, 5); + stl_raw(p++, 0x54410001); + stl_raw(p++, 1); + stl_raw(p++, 0x1000); + stl_raw(p++, 0); + /* ATAG_MEM */ + /* TODO: handle multiple chips on one ATAG list */ + stl_raw(p++, 4); + stl_raw(p++, 0x54410002); + stl_raw(p++, info->ram_size); + stl_raw(p++, info->loader_start); + if (initrd_size) { + /* ATAG_INITRD2 */ + stl_raw(p++, 4); + stl_raw(p++, 0x54420005); + stl_raw(p++, info->loader_start + INITRD_LOAD_ADDR); + stl_raw(p++, initrd_size); + } + if (info->kernel_cmdline && *info->kernel_cmdline) { + /* ATAG_CMDLINE */ + int cmdline_size; + + cmdline_size = strlen(info->kernel_cmdline); + memcpy(p + 2, info->kernel_cmdline, cmdline_size + 1); + cmdline_size = (cmdline_size >> 2) + 1; + stl_raw(p++, cmdline_size + 2); + stl_raw(p++, 0x54410009); + p += cmdline_size; + } + if (info->atag_board) { + /* ATAG_BOARD */ + int atag_board_len; + + atag_board_len = (info->atag_board(info, p + 2) + 3) >> 2; + stl_raw(p++, 2 + atag_board_len); + stl_raw(p++, 0x414f4d50); + p += atag_board_len; + } + /* ATAG_END */ + stl_raw(p++, 0); + stl_raw(p++, 0); +} + +static void set_kernel_args_old(struct arm_boot_info *info, + int initrd_size, void *base) +{ + uint32_t *p; + unsigned char *s; + + /* see linux/include/asm-arm/setup.h */ + p = (uint32_t *)(base + KERNEL_ARGS_ADDR); + /* page_size */ + stl_raw(p++, 4096); + /* nr_pages */ + stl_raw(p++, info->ram_size / 4096); + /* ramdisk_size */ + stl_raw(p++, 0); +#define FLAG_READONLY 1 +#define FLAG_RDLOAD 4 +#define FLAG_RDPROMPT 8 + /* flags */ + stl_raw(p++, FLAG_READONLY | FLAG_RDLOAD | FLAG_RDPROMPT); + /* rootdev */ + stl_raw(p++, (31 << 8) | 0); /* /dev/mtdblock0 */ + /* video_num_cols */ + stl_raw(p++, 0); + /* video_num_rows */ + stl_raw(p++, 0); + /* video_x */ + stl_raw(p++, 0); + /* video_y */ + stl_raw(p++, 0); + /* memc_control_reg */ + stl_raw(p++, 0); + /* unsigned char sounddefault */ + /* unsigned char adfsdrives */ + /* unsigned char bytes_per_char_h */ + /* unsigned char bytes_per_char_v */ + stl_raw(p++, 0); + /* pages_in_bank[4] */ + stl_raw(p++, 0); + stl_raw(p++, 0); + stl_raw(p++, 0); + stl_raw(p++, 0); + /* pages_in_vram */ + stl_raw(p++, 0); + /* initrd_start */ + if (initrd_size) + stl_raw(p++, info->loader_start + INITRD_LOAD_ADDR); + else + stl_raw(p++, 0); + /* initrd_size */ + stl_raw(p++, initrd_size); + /* rd_start */ + stl_raw(p++, 0); + /* system_rev */ + stl_raw(p++, 0); + /* system_serial_low */ + stl_raw(p++, 0); + /* system_serial_high */ + stl_raw(p++, 0); + /* mem_fclk_21285 */ + stl_raw(p++, 0); + /* zero unused fields */ + memset(p, 0, 256 + 1024 - + (p - ((uint32_t *)(base + KERNEL_ARGS_ADDR)))); + s = base + KERNEL_ARGS_ADDR + 256 + 1024; + if (info->kernel_cmdline) + strcpy (s, info->kernel_cmdline); + else + stb_raw(s, 0); +} + +void arm_load_kernel(CPUState *env, struct arm_boot_info *info) +{ + int kernel_size; + int initrd_size; + int n; + int is_linux = 0; + uint64_t elf_entry; + target_ulong entry; + uint32_t pd; + void *loader_phys; + + /* Load the kernel. */ + if (!info->kernel_filename) { + fprintf(stderr, "Kernel image must be specified\n"); + exit(1); + } + + if (!env->boot_info) { + if (info->nb_cpus == 0) + info->nb_cpus = 1; + env->boot_info = info; + qemu_register_reset(main_cpu_reset, env); + } + + pd = cpu_get_physical_page_desc(info->loader_start); + loader_phys = phys_ram_base + (pd & TARGET_PAGE_MASK) + + (info->loader_start & ~TARGET_PAGE_MASK); + + /* Assume that raw images are linux kernels, and ELF images are not. */ + kernel_size = load_elf(info->kernel_filename, 0, &elf_entry, NULL, NULL); + entry = elf_entry; + if (kernel_size < 0) { + kernel_size = load_uboot(info->kernel_filename, &entry, &is_linux); + } + if (kernel_size < 0) { + kernel_size = load_image(info->kernel_filename, + loader_phys + KERNEL_LOAD_ADDR); + entry = info->loader_start + KERNEL_LOAD_ADDR; + is_linux = 1; + } + if (kernel_size < 0) { + fprintf(stderr, "qemu: could not load kernel '%s'\n", + info->kernel_filename); + exit(1); + } + if (!is_linux) { + /* Jump to the entry point. */ + env->regs[15] = entry & 0xfffffffe; + env->thumb = entry & 1; + } else { + if (info->initrd_filename) { + initrd_size = load_image(info->initrd_filename, + loader_phys + INITRD_LOAD_ADDR); + if (initrd_size < 0) { + fprintf(stderr, "qemu: could not load initrd '%s'\n", + info->initrd_filename); + exit(1); + } + } else { + initrd_size = 0; + } + bootloader[1] |= info->board_id & 0xff; + bootloader[2] |= (info->board_id >> 8) & 0xff; + bootloader[5] = info->loader_start + KERNEL_ARGS_ADDR; + bootloader[6] = entry; + for (n = 0; n < sizeof(bootloader) / 4; n++) + stl_raw(loader_phys + (n * 4), bootloader[n]); + if (info->nb_cpus > 1) + for (n = 0; n < sizeof(smpboot) / 4; n++) + stl_raw(loader_phys + info->ram_size + (n * 4), smpboot[n]); + if (old_param) + set_kernel_args_old(info, initrd_size, loader_phys); + else + set_kernel_args(info, initrd_size, loader_phys); + } +} diff --git a/hw/arm_gic.c b/hw/arm_gic.c new file mode 100644 index 0000000..54e99f4 --- /dev/null +++ b/hw/arm_gic.c @@ -0,0 +1,747 @@ +/* + * ARM Generic/Distributed Interrupt Controller + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ + +/* This file contains implementation code for the RealView EB interrupt + controller, MPCore distributed interrupt controller and ARMv7-M + Nested Vectored Interrupt Controller. */ + +//#define DEBUG_GIC + +#ifdef DEBUG_GIC +#define DPRINTF(fmt, args...) \ +do { printf("arm_gic: " fmt , ##args); } while (0) +#else +#define DPRINTF(fmt, args...) do {} while(0) +#endif + +#ifdef NVIC +static const uint8_t gic_id[] = +{ 0x00, 0xb0, 0x1b, 0x00, 0x0d, 0xe0, 0x05, 0xb1 }; +#define GIC_DIST_OFFSET 0 +/* The NVIC has 16 internal vectors. However these are not exposed + through the normal GIC interface. */ +#define GIC_BASE_IRQ 32 +#else +static const uint8_t gic_id[] = +{ 0x90, 0x13, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 }; +#define GIC_DIST_OFFSET 0x1000 +#define GIC_BASE_IRQ 0 +#endif + +typedef struct gic_irq_state +{ + /* ??? The documentation seems to imply the enable bits are global, even + for per-cpu interrupts. This seems strange. */ + unsigned enabled:1; + unsigned pending:NCPU; + unsigned active:NCPU; + unsigned level:1; + unsigned model:1; /* 0 = N:N, 1 = 1:N */ + unsigned trigger:1; /* nonzero = edge triggered. */ +} gic_irq_state; + +#define ALL_CPU_MASK ((1 << NCPU) - 1) + +#define GIC_SET_ENABLED(irq) s->irq_state[irq].enabled = 1 +#define GIC_CLEAR_ENABLED(irq) s->irq_state[irq].enabled = 0 +#define GIC_TEST_ENABLED(irq) s->irq_state[irq].enabled +#define GIC_SET_PENDING(irq, cm) s->irq_state[irq].pending |= (cm) +#define GIC_CLEAR_PENDING(irq, cm) s->irq_state[irq].pending &= ~(cm) +#define GIC_TEST_PENDING(irq, cm) ((s->irq_state[irq].pending & (cm)) != 0) +#define GIC_SET_ACTIVE(irq, cm) s->irq_state[irq].active |= (cm) +#define GIC_CLEAR_ACTIVE(irq, cm) s->irq_state[irq].active &= ~(cm) +#define GIC_TEST_ACTIVE(irq, cm) ((s->irq_state[irq].active & (cm)) != 0) +#define GIC_SET_MODEL(irq) s->irq_state[irq].model = 1 +#define GIC_CLEAR_MODEL(irq) s->irq_state[irq].model = 0 +#define GIC_TEST_MODEL(irq) s->irq_state[irq].model +#define GIC_SET_LEVEL(irq, cm) s->irq_state[irq].level = (cm) +#define GIC_CLEAR_LEVEL(irq, cm) s->irq_state[irq].level &= ~(cm) +#define GIC_TEST_LEVEL(irq, cm) ((s->irq_state[irq].level & (cm)) != 0) +#define GIC_SET_TRIGGER(irq) s->irq_state[irq].trigger = 1 +#define GIC_CLEAR_TRIGGER(irq) s->irq_state[irq].trigger = 0 +#define GIC_TEST_TRIGGER(irq) s->irq_state[irq].trigger +#define GIC_GET_PRIORITY(irq, cpu) \ + (((irq) < 32) ? s->priority1[irq][cpu] : s->priority2[(irq) - 32]) +#ifdef NVIC +#define GIC_TARGET(irq) 1 +#else +#define GIC_TARGET(irq) s->irq_target[irq] +#endif + +typedef struct gic_state +{ + uint32_t base; + qemu_irq parent_irq[NCPU]; + int enabled; + int cpu_enabled[NCPU]; + + gic_irq_state irq_state[GIC_NIRQ]; +#ifndef NVIC + int irq_target[GIC_NIRQ]; +#endif + int priority1[32][NCPU]; + int priority2[GIC_NIRQ - 32]; + int last_active[GIC_NIRQ][NCPU]; + + int priority_mask[NCPU]; + int running_irq[NCPU]; + int running_priority[NCPU]; + int current_pending[NCPU]; + + qemu_irq *in; +#ifdef NVIC + void *nvic; +#endif +} gic_state; + +/* TODO: Many places that call this routine could be optimized. */ +/* Update interrupt status after enabled or pending bits have been changed. */ +static void gic_update(gic_state *s) +{ + int best_irq; + int best_prio; + int irq; + int level; + int cpu; + int cm; + + for (cpu = 0; cpu < NCPU; cpu++) { + cm = 1 << cpu; + s->current_pending[cpu] = 1023; + if (!s->enabled || !s->cpu_enabled[cpu]) { + qemu_irq_lower(s->parent_irq[cpu]); + return; + } + best_prio = 0x100; + best_irq = 1023; + for (irq = 0; irq < GIC_NIRQ; irq++) { + if (GIC_TEST_ENABLED(irq) && GIC_TEST_PENDING(irq, cm)) { + if (GIC_GET_PRIORITY(irq, cpu) < best_prio) { + best_prio = GIC_GET_PRIORITY(irq, cpu); + best_irq = irq; + } + } + } + level = 0; + if (best_prio <= s->priority_mask[cpu]) { + s->current_pending[cpu] = best_irq; + if (best_prio < s->running_priority[cpu]) { + DPRINTF("Raised pending IRQ %d\n", best_irq); + level = 1; + } + } + qemu_set_irq(s->parent_irq[cpu], level); + } +} + +static void __attribute__((unused)) +gic_set_pending_private(gic_state *s, int cpu, int irq) +{ + int cm = 1 << cpu; + + if (GIC_TEST_PENDING(irq, cm)) + return; + + DPRINTF("Set %d pending cpu %d\n", irq, cpu); + GIC_SET_PENDING(irq, cm); + gic_update(s); +} + +/* Process a change in an external IRQ input. */ +static void gic_set_irq(void *opaque, int irq, int level) +{ + gic_state *s = (gic_state *)opaque; + /* The first external input line is internal interrupt 32. */ + irq += 32; + if (level == GIC_TEST_LEVEL(irq, ALL_CPU_MASK)) + return; + + if (level) { + GIC_SET_LEVEL(irq, ALL_CPU_MASK); + if (GIC_TEST_TRIGGER(irq) || GIC_TEST_ENABLED(irq)) { + DPRINTF("Set %d pending mask %x\n", irq, GIC_TARGET(irq)); + GIC_SET_PENDING(irq, GIC_TARGET(irq)); + } + } else { + GIC_CLEAR_LEVEL(irq, ALL_CPU_MASK); + } + gic_update(s); +} + +static void gic_set_running_irq(gic_state *s, int cpu, int irq) +{ + s->running_irq[cpu] = irq; + if (irq == 1023) { + s->running_priority[cpu] = 0x100; + } else { + s->running_priority[cpu] = GIC_GET_PRIORITY(irq, cpu); + } + gic_update(s); +} + +static uint32_t gic_acknowledge_irq(gic_state *s, int cpu) +{ + int new_irq; + int cm = 1 << cpu; + new_irq = s->current_pending[cpu]; + if (new_irq == 1023 + || GIC_GET_PRIORITY(new_irq, cpu) >= s->running_priority[cpu]) { + DPRINTF("ACK no pending IRQ\n"); + return 1023; + } + s->last_active[new_irq][cpu] = s->running_irq[cpu]; + /* Clear pending flags for both level and edge triggered interrupts. + Level triggered IRQs will be reasserted once they become inactive. */ + GIC_CLEAR_PENDING(new_irq, GIC_TEST_MODEL(new_irq) ? ALL_CPU_MASK : cm); + gic_set_running_irq(s, cpu, new_irq); + DPRINTF("ACK %d\n", new_irq); + return new_irq; +} + +static void gic_complete_irq(gic_state * s, int cpu, int irq) +{ + int update = 0; + int cm = 1 << cpu; + DPRINTF("EOI %d\n", irq); + if (s->running_irq[cpu] == 1023) + return; /* No active IRQ. */ + if (irq != 1023) { + /* Mark level triggered interrupts as pending if they are still + raised. */ + if (!GIC_TEST_TRIGGER(irq) && GIC_TEST_ENABLED(irq) + && GIC_TEST_LEVEL(irq, cm) && (GIC_TARGET(irq) & cm) != 0) { + DPRINTF("Set %d pending mask %x\n", irq, cm); + GIC_SET_PENDING(irq, cm); + update = 1; + } + } + if (irq != s->running_irq[cpu]) { + /* Complete an IRQ that is not currently running. */ + int tmp = s->running_irq[cpu]; + while (s->last_active[tmp][cpu] != 1023) { + if (s->last_active[tmp][cpu] == irq) { + s->last_active[tmp][cpu] = s->last_active[irq][cpu]; + break; + } + tmp = s->last_active[tmp][cpu]; + } + if (update) { + gic_update(s); + } + } else { + /* Complete the current running IRQ. */ + gic_set_running_irq(s, cpu, s->last_active[s->running_irq[cpu]][cpu]); + } +} + +static uint32_t gic_dist_readb(void *opaque, target_phys_addr_t offset) +{ + gic_state *s = (gic_state *)opaque; + uint32_t res; + int irq; + int i; + int cpu; + int cm; + int mask; + + cpu = gic_get_current_cpu(); + cm = 1 << cpu; + offset -= s->base + GIC_DIST_OFFSET; + if (offset < 0x100) { +#ifndef NVIC + if (offset == 0) + return s->enabled; + if (offset == 4) + return ((GIC_NIRQ / 32) - 1) | ((NCPU - 1) << 5); + if (offset < 0x08) + return 0; +#endif + goto bad_reg; + } else if (offset < 0x200) { + /* Interrupt Set/Clear Enable. */ + if (offset < 0x180) + irq = (offset - 0x100) * 8; + else + irq = (offset - 0x180) * 8; + irq += GIC_BASE_IRQ; + if (irq >= GIC_NIRQ) + goto bad_reg; + res = 0; + for (i = 0; i < 8; i++) { + if (GIC_TEST_ENABLED(irq + i)) { + res |= (1 << i); + } + } + } else if (offset < 0x300) { + /* Interrupt Set/Clear Pending. */ + if (offset < 0x280) + irq = (offset - 0x200) * 8; + else + irq = (offset - 0x280) * 8; + irq += GIC_BASE_IRQ; + if (irq >= GIC_NIRQ) + goto bad_reg; + res = 0; + mask = (irq < 32) ? cm : ALL_CPU_MASK; + for (i = 0; i < 8; i++) { + if (GIC_TEST_PENDING(irq + i, mask)) { + res |= (1 << i); + } + } + } else if (offset < 0x400) { + /* Interrupt Active. */ + irq = (offset - 0x300) * 8 + GIC_BASE_IRQ; + if (irq >= GIC_NIRQ) + goto bad_reg; + res = 0; + mask = (irq < 32) ? cm : ALL_CPU_MASK; + for (i = 0; i < 8; i++) { + if (GIC_TEST_ACTIVE(irq + i, mask)) { + res |= (1 << i); + } + } + } else if (offset < 0x800) { + /* Interrupt Priority. */ + irq = (offset - 0x400) + GIC_BASE_IRQ; + if (irq >= GIC_NIRQ) + goto bad_reg; + res = GIC_GET_PRIORITY(irq, cpu); +#ifndef NVIC + } else if (offset < 0xc00) { + /* Interrupt CPU Target. */ + irq = (offset - 0x800) + GIC_BASE_IRQ; + if (irq >= GIC_NIRQ) + goto bad_reg; + if (irq >= 29 && irq <= 31) { + res = cm; + } else { + res = GIC_TARGET(irq); + } + } else if (offset < 0xf00) { + /* Interrupt Configuration. */ + irq = (offset - 0xc00) * 2 + GIC_BASE_IRQ; + if (irq >= GIC_NIRQ) + goto bad_reg; + res = 0; + for (i = 0; i < 4; i++) { + if (GIC_TEST_MODEL(irq + i)) + res |= (1 << (i * 2)); + if (GIC_TEST_TRIGGER(irq + i)) + res |= (2 << (i * 2)); + } +#endif + } else if (offset < 0xfe0) { + goto bad_reg; + } else /* offset >= 0xfe0 */ { + if (offset & 3) { + res = 0; + } else { + res = gic_id[(offset - 0xfe0) >> 2]; + } + } + return res; +bad_reg: + cpu_abort(cpu_single_env, "gic_dist_readb: Bad offset %x\n", (int)offset); + return 0; +} + +static uint32_t gic_dist_readw(void *opaque, target_phys_addr_t offset) +{ + uint32_t val; + val = gic_dist_readb(opaque, offset); + val |= gic_dist_readb(opaque, offset + 1) << 8; + return val; +} + +static uint32_t gic_dist_readl(void *opaque, target_phys_addr_t offset) +{ + uint32_t val; +#ifdef NVIC + gic_state *s = (gic_state *)opaque; + uint32_t addr; + addr = offset - s->base; + if (addr < 0x100 || addr > 0xd00) + return nvic_readl(s->nvic, addr); +#endif + val = gic_dist_readw(opaque, offset); + val |= gic_dist_readw(opaque, offset + 2) << 16; + return val; +} + +static void gic_dist_writeb(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + gic_state *s = (gic_state *)opaque; + int irq; + int i; + int cpu; + + cpu = gic_get_current_cpu(); + offset -= s->base + GIC_DIST_OFFSET; + if (offset < 0x100) { +#ifdef NVIC + goto bad_reg; +#else + if (offset == 0) { + s->enabled = (value & 1); + DPRINTF("Distribution %sabled\n", s->enabled ? "En" : "Dis"); + } else if (offset < 4) { + /* ignored. */ + } else { + goto bad_reg; + } +#endif + } else if (offset < 0x180) { + /* Interrupt Set Enable. */ + irq = (offset - 0x100) * 8 + GIC_BASE_IRQ; + if (irq >= GIC_NIRQ) + goto bad_reg; + if (irq < 16) + value = 0xff; + for (i = 0; i < 8; i++) { + if (value & (1 << i)) { + int mask = (irq < 32) ? (1 << cpu) : GIC_TARGET(irq); + if (!GIC_TEST_ENABLED(irq + i)) + DPRINTF("Enabled IRQ %d\n", irq + i); + GIC_SET_ENABLED(irq + i); + /* If a raised level triggered IRQ enabled then mark + is as pending. */ + if (GIC_TEST_LEVEL(irq + i, mask) + && !GIC_TEST_TRIGGER(irq + i)) { + DPRINTF("Set %d pending mask %x\n", irq + i, mask); + GIC_SET_PENDING(irq + i, mask); + } + } + } + } else if (offset < 0x200) { + /* Interrupt Clear Enable. */ + irq = (offset - 0x180) * 8 + GIC_BASE_IRQ; + if (irq >= GIC_NIRQ) + goto bad_reg; + if (irq < 16) + value = 0; + for (i = 0; i < 8; i++) { + if (value & (1 << i)) { + if (GIC_TEST_ENABLED(irq + i)) + DPRINTF("Disabled IRQ %d\n", irq + i); + GIC_CLEAR_ENABLED(irq + i); + } + } + } else if (offset < 0x280) { + /* Interrupt Set Pending. */ + irq = (offset - 0x200) * 8 + GIC_BASE_IRQ; + if (irq >= GIC_NIRQ) + goto bad_reg; + if (irq < 16) + irq = 0; + + for (i = 0; i < 8; i++) { + if (value & (1 << i)) { + GIC_SET_PENDING(irq + i, GIC_TARGET(irq)); + } + } + } else if (offset < 0x300) { + /* Interrupt Clear Pending. */ + irq = (offset - 0x280) * 8 + GIC_BASE_IRQ; + if (irq >= GIC_NIRQ) + goto bad_reg; + for (i = 0; i < 8; i++) { + /* ??? This currently clears the pending bit for all CPUs, even + for per-CPU interrupts. It's unclear whether this is the + corect behavior. */ + if (value & (1 << i)) { + GIC_CLEAR_PENDING(irq + i, ALL_CPU_MASK); + } + } + } else if (offset < 0x400) { + /* Interrupt Active. */ + goto bad_reg; + } else if (offset < 0x800) { + /* Interrupt Priority. */ + irq = (offset - 0x400) + GIC_BASE_IRQ; + if (irq >= GIC_NIRQ) + goto bad_reg; + if (irq < 32) { + s->priority1[irq][cpu] = value; + } else { + s->priority2[irq - 32] = value; + } +#ifndef NVIC + } else if (offset < 0xc00) { + /* Interrupt CPU Target. */ + irq = (offset - 0x800) + GIC_BASE_IRQ; + if (irq >= GIC_NIRQ) + goto bad_reg; + if (irq < 29) + value = 0; + else if (irq < 32) + value = ALL_CPU_MASK; + s->irq_target[irq] = value & ALL_CPU_MASK; + } else if (offset < 0xf00) { + /* Interrupt Configuration. */ + irq = (offset - 0xc00) * 4 + GIC_BASE_IRQ; + if (irq >= GIC_NIRQ) + goto bad_reg; + if (irq < 32) + value |= 0xaa; + for (i = 0; i < 4; i++) { + if (value & (1 << (i * 2))) { + GIC_SET_MODEL(irq + i); + } else { + GIC_CLEAR_MODEL(irq + i); + } + if (value & (2 << (i * 2))) { + GIC_SET_TRIGGER(irq + i); + } else { + GIC_CLEAR_TRIGGER(irq + i); + } + } +#endif + } else { + /* 0xf00 is only handled for 32-bit writes. */ + goto bad_reg; + } + gic_update(s); + return; +bad_reg: + cpu_abort(cpu_single_env, "gic_dist_writeb: Bad offset %x\n", (int)offset); +} + +static void gic_dist_writew(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + gic_dist_writeb(opaque, offset, value & 0xff); + gic_dist_writeb(opaque, offset + 1, value >> 8); +} + +static void gic_dist_writel(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + gic_state *s = (gic_state *)opaque; +#ifdef NVIC + uint32_t addr; + addr = offset - s->base; + if (addr < 0x100 || (addr > 0xd00 && addr != 0xf00)) { + nvic_writel(s->nvic, addr, value); + return; + } +#endif + if (offset - s->base == GIC_DIST_OFFSET + 0xf00) { + int cpu; + int irq; + int mask; + + cpu = gic_get_current_cpu(); + irq = value & 0x3ff; + switch ((value >> 24) & 3) { + case 0: + mask = (value >> 16) & ALL_CPU_MASK; + break; + case 1: + mask = 1 << cpu; + break; + case 2: + mask = ALL_CPU_MASK ^ (1 << cpu); + break; + default: + DPRINTF("Bad Soft Int target filter\n"); + mask = ALL_CPU_MASK; + break; + } + GIC_SET_PENDING(irq, mask); + gic_update(s); + return; + } + gic_dist_writew(opaque, offset, value & 0xffff); + gic_dist_writew(opaque, offset + 2, value >> 16); +} + +static CPUReadMemoryFunc *gic_dist_readfn[] = { + gic_dist_readb, + gic_dist_readw, + gic_dist_readl +}; + +static CPUWriteMemoryFunc *gic_dist_writefn[] = { + gic_dist_writeb, + gic_dist_writew, + gic_dist_writel +}; + +#ifndef NVIC +static uint32_t gic_cpu_read(gic_state *s, int cpu, int offset) +{ + switch (offset) { + case 0x00: /* Control */ + return s->cpu_enabled[cpu]; + case 0x04: /* Priority mask */ + return s->priority_mask[cpu]; + case 0x08: /* Binary Point */ + /* ??? Not implemented. */ + return 0; + case 0x0c: /* Acknowledge */ + return gic_acknowledge_irq(s, cpu); + case 0x14: /* Runing Priority */ + return s->running_priority[cpu]; + case 0x18: /* Highest Pending Interrupt */ + return s->current_pending[cpu]; + default: + cpu_abort(cpu_single_env, "gic_cpu_read: Bad offset %x\n", + (int)offset); + return 0; + } +} + +static void gic_cpu_write(gic_state *s, int cpu, int offset, uint32_t value) +{ + switch (offset) { + case 0x00: /* Control */ + s->cpu_enabled[cpu] = (value & 1); + DPRINTF("CPU %sabled\n", s->cpu_enabled ? "En" : "Dis"); + break; + case 0x04: /* Priority mask */ + s->priority_mask[cpu] = (value & 0xff); + break; + case 0x08: /* Binary Point */ + /* ??? Not implemented. */ + break; + case 0x10: /* End Of Interrupt */ + return gic_complete_irq(s, cpu, value & 0x3ff); + default: + cpu_abort(cpu_single_env, "gic_cpu_write: Bad offset %x\n", + (int)offset); + return; + } + gic_update(s); +} +#endif + +static void gic_reset(gic_state *s) +{ + int i; + memset(s->irq_state, 0, GIC_NIRQ * sizeof(gic_irq_state)); + for (i = 0 ; i < NCPU; i++) { + s->priority_mask[i] = 0xf0; + s->current_pending[i] = 1023; + s->running_irq[i] = 1023; + s->running_priority[i] = 0x100; +#ifdef NVIC + /* The NVIC doesn't have per-cpu interfaces, so enable by default. */ + s->cpu_enabled[i] = 1; +#else + s->cpu_enabled[i] = 0; +#endif + } + for (i = 0; i < 16; i++) { + GIC_SET_ENABLED(i); + GIC_SET_TRIGGER(i); + } +#ifdef NVIC + /* The NVIC is always enabled. */ + s->enabled = 1; +#else + s->enabled = 0; +#endif +} + +static void gic_save(QEMUFile *f, void *opaque) +{ + gic_state *s = (gic_state *)opaque; + int i; + int j; + + qemu_put_be32(f, s->enabled); + for (i = 0; i < NCPU; i++) { + qemu_put_be32(f, s->cpu_enabled[i]); +#ifndef NVIC + qemu_put_be32(f, s->irq_target[i]); +#endif + for (j = 0; j < 32; j++) + qemu_put_be32(f, s->priority1[j][i]); + for (j = 0; j < GIC_NIRQ; j++) + qemu_put_be32(f, s->last_active[j][i]); + qemu_put_be32(f, s->priority_mask[i]); + qemu_put_be32(f, s->running_irq[i]); + qemu_put_be32(f, s->running_priority[i]); + qemu_put_be32(f, s->current_pending[i]); + } + for (i = 0; i < GIC_NIRQ - 32; i++) { + qemu_put_be32(f, s->priority2[i]); + } + for (i = 0; i < GIC_NIRQ; i++) { + qemu_put_byte(f, s->irq_state[i].enabled); + qemu_put_byte(f, s->irq_state[i].pending); + qemu_put_byte(f, s->irq_state[i].active); + qemu_put_byte(f, s->irq_state[i].level); + qemu_put_byte(f, s->irq_state[i].model); + qemu_put_byte(f, s->irq_state[i].trigger); + } +} + +static int gic_load(QEMUFile *f, void *opaque, int version_id) +{ + gic_state *s = (gic_state *)opaque; + int i; + int j; + + if (version_id != 1) + return -EINVAL; + + s->enabled = qemu_get_be32(f); + for (i = 0; i < NCPU; i++) { + s->cpu_enabled[i] = qemu_get_be32(f); +#ifndef NVIC + s->irq_target[i] = qemu_get_be32(f); +#endif + for (j = 0; j < 32; j++) + s->priority1[j][i] = qemu_get_be32(f); + for (j = 0; j < GIC_NIRQ; j++) + s->last_active[j][i] = qemu_get_be32(f); + s->priority_mask[i] = qemu_get_be32(f); + s->running_irq[i] = qemu_get_be32(f); + s->running_priority[i] = qemu_get_be32(f); + s->current_pending[i] = qemu_get_be32(f); + } + for (i = 0; i < GIC_NIRQ - 32; i++) { + s->priority2[i] = qemu_get_be32(f); + } + for (i = 0; i < GIC_NIRQ; i++) { + s->irq_state[i].enabled = qemu_get_byte(f); + s->irq_state[i].pending = qemu_get_byte(f); + s->irq_state[i].active = qemu_get_byte(f); + s->irq_state[i].level = qemu_get_byte(f); + s->irq_state[i].model = qemu_get_byte(f); + s->irq_state[i].trigger = qemu_get_byte(f); + } + + return 0; +} + +static gic_state *gic_init(uint32_t base, qemu_irq *parent_irq) +{ + gic_state *s; + int iomemtype; + int i; + + s = (gic_state *)qemu_mallocz(sizeof(gic_state)); + if (!s) + return NULL; + s->in = qemu_allocate_irqs(gic_set_irq, s, GIC_NIRQ); + for (i = 0; i < NCPU; i++) { + s->parent_irq[i] = parent_irq[i]; + } + iomemtype = cpu_register_io_memory(0, gic_dist_readfn, + gic_dist_writefn, s); + cpu_register_physical_memory(base + GIC_DIST_OFFSET, 0x00001000, + iomemtype); + s->base = base; + gic_reset(s); + register_savevm("arm_gic", -1, 1, gic_save, gic_load, s); + return s; +} diff --git a/hw/arm_pic.c b/hw/arm_pic.c new file mode 100644 index 0000000..1fe55b7 --- /dev/null +++ b/hw/arm_pic.c @@ -0,0 +1,48 @@ +/* + * Generic ARM Programmable Interrupt Controller support. + * + * Copyright (c) 2006 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the LGPL + */ + +#include "hw.h" +#include "arm-misc.h" + +/* Stub functions for hardware that doesn't exist. */ +void pic_info(void) +{ +} + +void irq_info(void) +{ +} + + +/* Input 0 is IRQ and input 1 is FIQ. */ +static void arm_pic_cpu_handler(void *opaque, int irq, int level) +{ + CPUState *env = (CPUState *)opaque; + switch (irq) { + case ARM_PIC_CPU_IRQ: + if (level) + cpu_interrupt(env, CPU_INTERRUPT_HARD); + else + cpu_reset_interrupt(env, CPU_INTERRUPT_HARD); + break; + case ARM_PIC_CPU_FIQ: + if (level) + cpu_interrupt(env, CPU_INTERRUPT_FIQ); + else + cpu_reset_interrupt(env, CPU_INTERRUPT_FIQ); + break; + default: + cpu_abort(env, "arm_pic_cpu_handler: Bad interrput line %d\n", irq); + } +} + +qemu_irq *arm_pic_init_cpu(CPUState *env) +{ + return qemu_allocate_irqs(arm_pic_cpu_handler, env, 2); +} diff --git a/hw/arm_pic.h b/hw/arm_pic.h new file mode 100644 index 0000000..7886bcf --- /dev/null +++ b/hw/arm_pic.h @@ -0,0 +1,25 @@ +/* + * Generic ARM Programmable Interrupt Controller support. + * + * Copyright (c) 2006 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the LGPL. + * + * Arm hardware uses a wide variety of interrupt handling hardware. + * This provides a generic framework for connecting interrupt sources and + * inputs. + */ + +#ifndef ARM_INTERRUPT_H +#define ARM_INTERRUPT_H 1 + +#include "irq.h" + +/* The CPU is also modeled as an interrupt controller. */ +#define ARM_PIC_CPU_IRQ 0 +#define ARM_PIC_CPU_FIQ 1 +qemu_irq *arm_pic_init_cpu(CPUState *env); + +#endif /* !ARM_INTERRUPT_H */ + diff --git a/hw/armv7m.c b/hw/armv7m.c new file mode 100644 index 0000000..b2bad3c --- /dev/null +++ b/hw/armv7m.c @@ -0,0 +1,206 @@ +/* + * ARMV7M System emulation. + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ + +#include "hw.h" +#include "arm-misc.h" +#include "sysemu.h" + +/* Bitbanded IO. Each word corresponds to a single bit. */ + +/* Get the byte address of the real memory for a bitband acess. */ +static inline uint32_t bitband_addr(uint32_t addr) +{ + uint32_t res; + + res = addr & 0xe0000000; + res |= (addr & 0x1ffffff) >> 5; + return res; + +} + +static uint32_t bitband_readb(void *opaque, target_phys_addr_t offset) +{ + uint8_t v; + cpu_physical_memory_read(bitband_addr(offset), &v, 1); + return (v & (1 << ((offset >> 2) & 7))) != 0; +} + +static void bitband_writeb(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + uint32_t addr; + uint8_t mask; + uint8_t v; + addr = bitband_addr(offset); + mask = (1 << ((offset >> 2) & 7)); + cpu_physical_memory_read(addr, &v, 1); + if (value & 1) + v |= mask; + else + v &= ~mask; + cpu_physical_memory_write(addr, &v, 1); +} + +static uint32_t bitband_readw(void *opaque, target_phys_addr_t offset) +{ + uint32_t addr; + uint16_t mask; + uint16_t v; + addr = bitband_addr(offset) & ~1; + mask = (1 << ((offset >> 2) & 15)); + mask = tswap16(mask); + cpu_physical_memory_read(addr, (uint8_t *)&v, 2); + return (v & mask) != 0; +} + +static void bitband_writew(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + uint32_t addr; + uint16_t mask; + uint16_t v; + addr = bitband_addr(offset) & ~1; + mask = (1 << ((offset >> 2) & 15)); + mask = tswap16(mask); + cpu_physical_memory_read(addr, (uint8_t *)&v, 2); + if (value & 1) + v |= mask; + else + v &= ~mask; + cpu_physical_memory_write(addr, (uint8_t *)&v, 2); +} + +static uint32_t bitband_readl(void *opaque, target_phys_addr_t offset) +{ + uint32_t addr; + uint32_t mask; + uint32_t v; + addr = bitband_addr(offset) & ~3; + mask = (1 << ((offset >> 2) & 31)); + mask = tswap32(mask); + cpu_physical_memory_read(addr, (uint8_t *)&v, 4); + return (v & mask) != 0; +} + +static void bitband_writel(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + uint32_t addr; + uint32_t mask; + uint32_t v; + addr = bitband_addr(offset) & ~3; + mask = (1 << ((offset >> 2) & 31)); + mask = tswap32(mask); + cpu_physical_memory_read(addr, (uint8_t *)&v, 4); + if (value & 1) + v |= mask; + else + v &= ~mask; + cpu_physical_memory_write(addr, (uint8_t *)&v, 4); +} + +static CPUReadMemoryFunc *bitband_readfn[] = { + bitband_readb, + bitband_readw, + bitband_readl +}; + +static CPUWriteMemoryFunc *bitband_writefn[] = { + bitband_writeb, + bitband_writew, + bitband_writel +}; + +static void armv7m_bitband_init(void) +{ + int iomemtype; + + iomemtype = cpu_register_io_memory(0, bitband_readfn, bitband_writefn, + NULL); + cpu_register_physical_memory(0x22000000, 0x02000000, iomemtype); + cpu_register_physical_memory(0x42000000, 0x02000000, iomemtype); +} + +/* Board init. */ +/* Init CPU and memory for a v7-M based board. + flash_size and sram_size are in kb. + Returns the NVIC array. */ + +qemu_irq *armv7m_init(int flash_size, int sram_size, + const char *kernel_filename, const char *cpu_model) +{ + CPUState *env; + qemu_irq *pic; + uint32_t pc; + int image_size; + uint64_t entry; + uint64_t lowaddr; + + flash_size *= 1024; + sram_size *= 1024; + + if (!cpu_model) + cpu_model = "cortex-m3"; + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + +#if 0 + /* > 32Mb SRAM gets complicated because it overlaps the bitband area. + We don't have proper commandline options, so allocate half of memory + as SRAM, up to a maximum of 32Mb, and the rest as code. */ + if (ram_size > (512 + 32) * 1024 * 1024) + ram_size = (512 + 32) * 1024 * 1024; + sram_size = (ram_size / 2) & TARGET_PAGE_MASK; + if (sram_size > 32 * 1024 * 1024) + sram_size = 32 * 1024 * 1024; + code_size = ram_size - sram_size; +#endif + + /* Flash programming is done via the SCU, so pretend it is ROM. */ + cpu_register_physical_memory(0, flash_size, IO_MEM_ROM); + cpu_register_physical_memory(0x20000000, sram_size, + flash_size + IO_MEM_RAM); + armv7m_bitband_init(); + + pic = armv7m_nvic_init(env); + + image_size = load_elf(kernel_filename, 0, &entry, &lowaddr, NULL); + if (image_size < 0) { + image_size = load_image(kernel_filename, phys_ram_base); + lowaddr = 0; + } + if (image_size < 0) { + fprintf(stderr, "qemu: could not load kernel '%s'\n", + kernel_filename); + exit(1); + } + + /* If the image was loaded at address zero then assume it is a + regular ROM image and perform the normal CPU reset sequence. + Otherwise jump directly to the entry point. */ + if (lowaddr == 0) { + env->regs[13] = tswap32(*(uint32_t *)phys_ram_base); + pc = tswap32(*(uint32_t *)(phys_ram_base + 4)); + } else { + pc = entry; + } + env->thumb = pc & 1; + env->regs[15] = pc & ~1; + + /* Hack to map an additional page of ram at the top of the address + space. This stops qemu complaining about executing code outside RAM + when returning from an exception. */ + cpu_register_physical_memory(0xfffff000, 0x1000, IO_MEM_RAM + ram_size); + + return pic; +} + diff --git a/hw/armv7m_nvic.c b/hw/armv7m_nvic.c new file mode 100644 index 0000000..c55c958 --- /dev/null +++ b/hw/armv7m_nvic.c @@ -0,0 +1,407 @@ +/* + * ARM Nested Vectored Interrupt Controller + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + * + * The ARMv7M System controller is fairly tightly tied in with the + * NVIC. Much of that is also implemented here. + */ + +#include "hw.h" +#include "qemu-timer.h" +#include "arm-misc.h" + +/* 32 internal lines (16 used for system exceptions) plus 64 external + interrupt lines. */ +#define GIC_NIRQ 96 +#define NCPU 1 +#define NVIC 1 + +/* Only a single "CPU" interface is present. */ +static inline int +gic_get_current_cpu(void) +{ + return 0; +} + +static uint32_t nvic_readl(void *opaque, uint32_t offset); +static void nvic_writel(void *opaque, uint32_t offset, uint32_t value); + +#include "arm_gic.c" + +typedef struct { + struct { + uint32_t control; + uint32_t reload; + int64_t tick; + QEMUTimer *timer; + } systick; + gic_state *gic; +} nvic_state; + +/* qemu timers run at 1GHz. We want something closer to 1MHz. */ +#define SYSTICK_SCALE 1000ULL + +#define SYSTICK_ENABLE (1 << 0) +#define SYSTICK_TICKINT (1 << 1) +#define SYSTICK_CLKSOURCE (1 << 2) +#define SYSTICK_COUNTFLAG (1 << 16) + +/* Conversion factor from qemu timer to SysTick frequencies. */ +static inline int64_t systick_scale(nvic_state *s) +{ + if (s->systick.control & SYSTICK_CLKSOURCE) + return system_clock_scale; + else + return 1000; +} + +static void systick_reload(nvic_state *s, int reset) +{ + if (reset) + s->systick.tick = qemu_get_clock(vm_clock); + s->systick.tick += (s->systick.reload + 1) * systick_scale(s); + qemu_mod_timer(s->systick.timer, s->systick.tick); +} + +static void systick_timer_tick(void * opaque) +{ + nvic_state *s = (nvic_state *)opaque; + s->systick.control |= SYSTICK_COUNTFLAG; + if (s->systick.control & SYSTICK_TICKINT) { + /* Trigger the interrupt. */ + armv7m_nvic_set_pending(s, ARMV7M_EXCP_SYSTICK); + } + if (s->systick.reload == 0) { + s->systick.control &= ~SYSTICK_ENABLE; + } else { + systick_reload(s, 0); + } +} + +/* The external routines use the hardware vector numbering, ie. the first + IRQ is #16. The internal GIC routines use #32 as the first IRQ. */ +void armv7m_nvic_set_pending(void *opaque, int irq) +{ + nvic_state *s = (nvic_state *)opaque; + if (irq >= 16) + irq += 16; + gic_set_pending_private(s->gic, 0, irq); +} + +/* Make pending IRQ active. */ +int armv7m_nvic_acknowledge_irq(void *opaque) +{ + nvic_state *s = (nvic_state *)opaque; + uint32_t irq; + + irq = gic_acknowledge_irq(s->gic, 0); + if (irq == 1023) + cpu_abort(cpu_single_env, "Interrupt but no vector\n"); + if (irq >= 32) + irq -= 16; + return irq; +} + +void armv7m_nvic_complete_irq(void *opaque, int irq) +{ + nvic_state *s = (nvic_state *)opaque; + if (irq >= 16) + irq += 16; + gic_complete_irq(s->gic, 0, irq); +} + +static uint32_t nvic_readl(void *opaque, uint32_t offset) +{ + nvic_state *s = (nvic_state *)opaque; + uint32_t val; + int irq; + + switch (offset) { + case 4: /* Interrupt Control Type. */ + return (GIC_NIRQ / 32) - 1; + case 0x10: /* SysTick Control and Status. */ + val = s->systick.control; + s->systick.control &= ~SYSTICK_COUNTFLAG; + return val; + case 0x14: /* SysTick Reload Value. */ + return s->systick.reload; + case 0x18: /* SysTick Current Value. */ + { + int64_t t; + if ((s->systick.control & SYSTICK_ENABLE) == 0) + return 0; + t = qemu_get_clock(vm_clock); + if (t >= s->systick.tick) + return 0; + val = ((s->systick.tick - (t + 1)) / systick_scale(s)) + 1; + /* The interrupt in triggered when the timer reaches zero. + However the counter is not reloaded until the next clock + tick. This is a hack to return zero during the first tick. */ + if (val > s->systick.reload) + val = 0; + return val; + } + case 0x1c: /* SysTick Calibration Value. */ + return 10000; + case 0xd00: /* CPUID Base. */ + return cpu_single_env->cp15.c0_cpuid; + case 0xd04: /* Interrypt Control State. */ + /* VECTACTIVE */ + val = s->gic->running_irq[0]; + if (val == 1023) { + val = 0; + } else if (val >= 32) { + val -= 16; + } + /* RETTOBASE */ + if (s->gic->running_irq[0] == 1023 + || s->gic->last_active[s->gic->running_irq[0]][0] == 1023) { + val |= (1 << 11); + } + /* VECTPENDING */ + if (s->gic->current_pending[0] != 1023) + val |= (s->gic->current_pending[0] << 12); + /* ISRPENDING */ + for (irq = 32; irq < GIC_NIRQ; irq++) { + if (s->gic->irq_state[irq].pending) { + val |= (1 << 22); + break; + } + } + /* PENDSTSET */ + if (s->gic->irq_state[ARMV7M_EXCP_SYSTICK].pending) + val |= (1 << 26); + /* PENDSVSET */ + if (s->gic->irq_state[ARMV7M_EXCP_PENDSV].pending) + val |= (1 << 28); + /* NMIPENDSET */ + if (s->gic->irq_state[ARMV7M_EXCP_NMI].pending) + val |= (1 << 31); + return val; + case 0xd08: /* Vector Table Offset. */ + return cpu_single_env->v7m.vecbase; + case 0xd0c: /* Application Interrupt/Reset Control. */ + return 0xfa05000; + case 0xd10: /* System Control. */ + /* TODO: Implement SLEEPONEXIT. */ + return 0; + case 0xd14: /* Configuration Control. */ + /* TODO: Implement Configuration Control bits. */ + return 0; + case 0xd18: case 0xd1c: case 0xd20: /* System Handler Priority. */ + irq = offset - 0xd14; + val = 0; + val = s->gic->priority1[irq++][0]; + val = s->gic->priority1[irq++][0] << 8; + val = s->gic->priority1[irq++][0] << 16; + val = s->gic->priority1[irq][0] << 24; + return val; + case 0xd24: /* System Handler Status. */ + val = 0; + if (s->gic->irq_state[ARMV7M_EXCP_MEM].active) val |= (1 << 0); + if (s->gic->irq_state[ARMV7M_EXCP_BUS].active) val |= (1 << 1); + if (s->gic->irq_state[ARMV7M_EXCP_USAGE].active) val |= (1 << 3); + if (s->gic->irq_state[ARMV7M_EXCP_SVC].active) val |= (1 << 7); + if (s->gic->irq_state[ARMV7M_EXCP_DEBUG].active) val |= (1 << 8); + if (s->gic->irq_state[ARMV7M_EXCP_PENDSV].active) val |= (1 << 10); + if (s->gic->irq_state[ARMV7M_EXCP_SYSTICK].active) val |= (1 << 11); + if (s->gic->irq_state[ARMV7M_EXCP_USAGE].pending) val |= (1 << 12); + if (s->gic->irq_state[ARMV7M_EXCP_MEM].pending) val |= (1 << 13); + if (s->gic->irq_state[ARMV7M_EXCP_BUS].pending) val |= (1 << 14); + if (s->gic->irq_state[ARMV7M_EXCP_SVC].pending) val |= (1 << 15); + if (s->gic->irq_state[ARMV7M_EXCP_MEM].enabled) val |= (1 << 16); + if (s->gic->irq_state[ARMV7M_EXCP_BUS].enabled) val |= (1 << 17); + if (s->gic->irq_state[ARMV7M_EXCP_USAGE].enabled) val |= (1 << 18); + return val; + case 0xd28: /* Configurable Fault Status. */ + /* TODO: Implement Fault Status. */ + cpu_abort(cpu_single_env, + "Not implemented: Configurable Fault Status."); + return 0; + case 0xd2c: /* Hard Fault Status. */ + case 0xd30: /* Debug Fault Status. */ + case 0xd34: /* Mem Manage Address. */ + case 0xd38: /* Bus Fault Address. */ + case 0xd3c: /* Aux Fault Status. */ + /* TODO: Implement fault status registers. */ + goto bad_reg; + case 0xd40: /* PFR0. */ + return 0x00000030; + case 0xd44: /* PRF1. */ + return 0x00000200; + case 0xd48: /* DFR0. */ + return 0x00100000; + case 0xd4c: /* AFR0. */ + return 0x00000000; + case 0xd50: /* MMFR0. */ + return 0x00000030; + case 0xd54: /* MMFR1. */ + return 0x00000000; + case 0xd58: /* MMFR2. */ + return 0x00000000; + case 0xd5c: /* MMFR3. */ + return 0x00000000; + case 0xd60: /* ISAR0. */ + return 0x01141110; + case 0xd64: /* ISAR1. */ + return 0x02111000; + case 0xd68: /* ISAR2. */ + return 0x21112231; + case 0xd6c: /* ISAR3. */ + return 0x01111110; + case 0xd70: /* ISAR4. */ + return 0x01310102; + /* TODO: Implement debug registers. */ + default: + bad_reg: + cpu_abort(cpu_single_env, "NVIC: Bad read offset 0x%x\n", offset); + } +} + +static void nvic_writel(void *opaque, uint32_t offset, uint32_t value) +{ + nvic_state *s = (nvic_state *)opaque; + uint32_t oldval; + switch (offset) { + case 0x10: /* SysTick Control and Status. */ + oldval = s->systick.control; + s->systick.control &= 0xfffffff8; + s->systick.control |= value & 7; + if ((oldval ^ value) & SYSTICK_ENABLE) { + int64_t now = qemu_get_clock(vm_clock); + if (value & SYSTICK_ENABLE) { + if (s->systick.tick) { + s->systick.tick += now; + qemu_mod_timer(s->systick.timer, s->systick.tick); + } else { + systick_reload(s, 1); + } + } else { + qemu_del_timer(s->systick.timer); + s->systick.tick -= now; + if (s->systick.tick < 0) + s->systick.tick = 0; + } + } else if ((oldval ^ value) & SYSTICK_CLKSOURCE) { + /* This is a hack. Force the timer to be reloaded + when the reference clock is changed. */ + systick_reload(s, 1); + } + break; + case 0x14: /* SysTick Reload Value. */ + s->systick.reload = value; + break; + case 0x18: /* SysTick Current Value. Writes reload the timer. */ + systick_reload(s, 1); + s->systick.control &= ~SYSTICK_COUNTFLAG; + break; + case 0xd04: /* Interrupt Control State. */ + if (value & (1 << 31)) { + armv7m_nvic_set_pending(s, ARMV7M_EXCP_NMI); + } + if (value & (1 << 28)) { + armv7m_nvic_set_pending(s, ARMV7M_EXCP_PENDSV); + } else if (value & (1 << 27)) { + s->gic->irq_state[ARMV7M_EXCP_PENDSV].pending = 0; + gic_update(s->gic); + } + if (value & (1 << 26)) { + armv7m_nvic_set_pending(s, ARMV7M_EXCP_SYSTICK); + } else if (value & (1 << 25)) { + s->gic->irq_state[ARMV7M_EXCP_SYSTICK].pending = 0; + gic_update(s->gic); + } + break; + case 0xd08: /* Vector Table Offset. */ + cpu_single_env->v7m.vecbase = value & 0xffffff80; + break; + case 0xd0c: /* Application Interrupt/Reset Control. */ + if ((value >> 16) == 0x05fa) { + if (value & 2) { + cpu_abort(cpu_single_env, "VECTCLRACTIVE not implemented"); + } + if (value & 5) { + cpu_abort(cpu_single_env, "System reset"); + } + } + break; + case 0xd10: /* System Control. */ + case 0xd14: /* Configuration Control. */ + /* TODO: Implement control registers. */ + goto bad_reg; + case 0xd18: case 0xd1c: case 0xd20: /* System Handler Priority. */ + { + int irq; + irq = offset - 0xd14; + s->gic->priority1[irq++][0] = value & 0xff; + s->gic->priority1[irq++][0] = (value >> 8) & 0xff; + s->gic->priority1[irq++][0] = (value >> 16) & 0xff; + s->gic->priority1[irq][0] = (value >> 24) & 0xff; + gic_update(s->gic); + } + break; + case 0xd24: /* System Handler Control. */ + /* TODO: Real hardware allows you to set/clear the active bits + under some circumstances. We don't implement this. */ + s->gic->irq_state[ARMV7M_EXCP_MEM].enabled = (value & (1 << 16)) != 0; + s->gic->irq_state[ARMV7M_EXCP_BUS].enabled = (value & (1 << 17)) != 0; + s->gic->irq_state[ARMV7M_EXCP_USAGE].enabled = (value & (1 << 18)) != 0; + break; + case 0xd28: /* Configurable Fault Status. */ + case 0xd2c: /* Hard Fault Status. */ + case 0xd30: /* Debug Fault Status. */ + case 0xd34: /* Mem Manage Address. */ + case 0xd38: /* Bus Fault Address. */ + case 0xd3c: /* Aux Fault Status. */ + goto bad_reg; + default: + bad_reg: + cpu_abort(cpu_single_env, "NVIC: Bad write offset 0x%x\n", offset); + } +} + +static void nvic_save(QEMUFile *f, void *opaque) +{ + nvic_state *s = (nvic_state *)opaque; + + qemu_put_be32(f, s->systick.control); + qemu_put_be32(f, s->systick.reload); + qemu_put_be64(f, s->systick.tick); + qemu_put_timer(f, s->systick.timer); +} + +static int nvic_load(QEMUFile *f, void *opaque, int version_id) +{ + nvic_state *s = (nvic_state *)opaque; + + if (version_id != 1) + return -EINVAL; + + s->systick.control = qemu_get_be32(f); + s->systick.reload = qemu_get_be32(f); + s->systick.tick = qemu_get_be64(f); + qemu_get_timer(f, s->systick.timer); + + return 0; +} + +qemu_irq *armv7m_nvic_init(CPUState *env) +{ + nvic_state *s; + qemu_irq *parent; + + parent = arm_pic_init_cpu(env); + s = (nvic_state *)qemu_mallocz(sizeof(nvic_state)); + s->gic = gic_init(0xe000e000, &parent[ARM_PIC_CPU_IRQ]); + s->gic->nvic = s; + s->systick.timer = qemu_new_timer(vm_clock, systick_timer_tick, s); + if (env->v7m.nvic) + cpu_abort(env, "CPU can only have one NVIC\n"); + env->v7m.nvic = s; + register_savevm("armv7m_nvic", -1, 1, nvic_save, nvic_load, s); + return s->gic->in; +} diff --git a/hw/audiodev.h b/hw/audiodev.h new file mode 100644 index 0000000..5f4a211 --- /dev/null +++ b/hw/audiodev.h @@ -0,0 +1,17 @@ +/* es1370.c */ +int es1370_init (PCIBus *bus, AudioState *s); + +/* sb16.c */ +int SB16_init (AudioState *s, qemu_irq *pic); + +/* adlib.c */ +int Adlib_init (AudioState *s, qemu_irq *pic); + +/* gus.c */ +int GUS_init (AudioState *s, qemu_irq *pic); + +/* ac97.c */ +int ac97_init (PCIBus *buf, AudioState *s); + +/* cs4231a.c */ +int cs4231a_init (AudioState *s, qemu_irq *pic); diff --git a/hw/baum.h b/hw/baum.h new file mode 100644 index 0000000..ac34b30 --- /dev/null +++ b/hw/baum.h @@ -0,0 +1,29 @@ +/* + * QEMU Baum + * + * Copyright (c) 2008 Samuel Thibault + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* usb device */ +USBDevice *usb_baum_init(void); + +/* char device */ +CharDriverState *chr_baum_init(void); diff --git a/hw/boards.h b/hw/boards.h new file mode 100644 index 0000000..cfb7c42 --- /dev/null +++ b/hw/boards.h @@ -0,0 +1,122 @@ +/* Declarations for use by board files for creating devices. */ + +#ifndef HW_BOARDS_H +#define HW_BOARDS_H + +typedef void QEMUMachineInitFunc(ram_addr_t ram_size, int vga_ram_size, + const char *boot_device, DisplayState *ds, + const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename, + const char *cpu_model); + +typedef struct QEMUMachine { + const char *name; + const char *desc; + QEMUMachineInitFunc *init; +#define RAMSIZE_FIXED (1 << 0) + ram_addr_t ram_require; + int nodisk_ok; + struct QEMUMachine *next; +} QEMUMachine; + +int qemu_register_machine(QEMUMachine *m); +void register_machines(void); + +/* Axis ETRAX. */ +extern QEMUMachine bareetraxfs_machine; + +/* pc.c */ +extern QEMUMachine pc_machine; +extern QEMUMachine isapc_machine; + +/* ppc.c */ +extern QEMUMachine prep_machine; +extern QEMUMachine core99_machine; +extern QEMUMachine heathrow_machine; +extern QEMUMachine ref405ep_machine; +extern QEMUMachine taihu_machine; + +/* mips_r4k.c */ +extern QEMUMachine mips_machine; + +/* mips_jazz.c */ +extern QEMUMachine mips_magnum_machine; +extern QEMUMachine mips_pica61_machine; + +/* mips_malta.c */ +extern QEMUMachine mips_malta_machine; + +/* mips_mipssim.c */ +extern QEMUMachine mips_mipssim_machine; + +/* shix.c */ +extern QEMUMachine shix_machine; + +/* r2d.c */ +extern QEMUMachine r2d_machine; + +/* sun4m.c */ +extern QEMUMachine ss5_machine, ss10_machine, ss600mp_machine, ss20_machine; +extern QEMUMachine voyager_machine, ss_lx_machine, ss4_machine, scls_machine; +extern QEMUMachine sbook_machine; +extern QEMUMachine ss2_machine; +extern QEMUMachine ss1000_machine, ss2000_machine; + +/* sun4u.c */ +extern QEMUMachine sun4u_machine; +extern QEMUMachine sun4v_machine; + +/* integratorcp.c */ +extern QEMUMachine integratorcp_machine; + +/* versatilepb.c */ +extern QEMUMachine versatilepb_machine; +extern QEMUMachine versatileab_machine; + +/* realview.c */ +extern QEMUMachine realview_machine; + +/* spitz.c */ +extern QEMUMachine akitapda_machine; +extern QEMUMachine spitzpda_machine; +extern QEMUMachine borzoipda_machine; +extern QEMUMachine terrierpda_machine; + +/* palm.c */ +extern QEMUMachine palmte_machine; + +/* nseries.c */ +extern QEMUMachine n800_machine; +extern QEMUMachine n810_machine; + +/* gumstix.c */ +extern QEMUMachine connex_machine; +extern QEMUMachine verdex_machine; + +/* stellaris.c */ +extern QEMUMachine lm3s811evb_machine; +extern QEMUMachine lm3s6965evb_machine; + +/* an5206.c */ +extern QEMUMachine an5206_machine; + +/* mcf5208.c */ +extern QEMUMachine mcf5208evb_machine; + +/* dummy_m68k.c */ +extern QEMUMachine dummy_m68k_machine; + +/* mainstone.c */ +extern QEMUMachine mainstone2_machine; + +/* musicpal.c */ +extern QEMUMachine musicpal_machine; + +/* tosa.c */ +extern QEMUMachine tosapda_machine; + +/* android_arm.c */ +extern QEMUMachine android_arm_machine; + +#endif diff --git a/hw/cdrom.c b/hw/cdrom.c new file mode 100644 index 0000000..2aa4d3b --- /dev/null +++ b/hw/cdrom.c @@ -0,0 +1,157 @@ +/* + * QEMU ATAPI CD-ROM Emulator + * + * Copyright (c) 2006 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* ??? Most of the ATAPI emulation is still in ide.c. It should be moved + here. */ + +#include "qemu-common.h" +#include "scsi-disk.h" + +static void lba_to_msf(uint8_t *buf, int lba) +{ + lba += 150; + buf[0] = (lba / 75) / 60; + buf[1] = (lba / 75) % 60; + buf[2] = lba % 75; +} + +/* same toc as bochs. Return -1 if error or the toc length */ +/* XXX: check this */ +int cdrom_read_toc(int nb_sectors, uint8_t *buf, int msf, int start_track) +{ + uint8_t *q; + int len; + + if (start_track > 1 && start_track != 0xaa) + return -1; + q = buf + 2; + *q++ = 1; /* first session */ + *q++ = 1; /* last session */ + if (start_track <= 1) { + *q++ = 0; /* reserved */ + *q++ = 0x14; /* ADR, control */ + *q++ = 1; /* track number */ + *q++ = 0; /* reserved */ + if (msf) { + *q++ = 0; /* reserved */ + lba_to_msf(q, 0); + q += 3; + } else { + /* sector 0 */ + cpu_to_be32wu((uint32_t *)q, 0); + q += 4; + } + } + /* lead out track */ + *q++ = 0; /* reserved */ + *q++ = 0x16; /* ADR, control */ + *q++ = 0xaa; /* track number */ + *q++ = 0; /* reserved */ + if (msf) { + *q++ = 0; /* reserved */ + lba_to_msf(q, nb_sectors); + q += 3; + } else { + cpu_to_be32wu((uint32_t *)q, nb_sectors); + q += 4; + } + len = q - buf; + cpu_to_be16wu((uint16_t *)buf, len - 2); + return len; +} + +/* mostly same info as PearPc */ +int cdrom_read_toc_raw(int nb_sectors, uint8_t *buf, int msf, int session_num) +{ + uint8_t *q; + int len; + + q = buf + 2; + *q++ = 1; /* first session */ + *q++ = 1; /* last session */ + + *q++ = 1; /* session number */ + *q++ = 0x14; /* data track */ + *q++ = 0; /* track number */ + *q++ = 0xa0; /* lead-in */ + *q++ = 0; /* min */ + *q++ = 0; /* sec */ + *q++ = 0; /* frame */ + *q++ = 0; + *q++ = 1; /* first track */ + *q++ = 0x00; /* disk type */ + *q++ = 0x00; + + *q++ = 1; /* session number */ + *q++ = 0x14; /* data track */ + *q++ = 0; /* track number */ + *q++ = 0xa1; + *q++ = 0; /* min */ + *q++ = 0; /* sec */ + *q++ = 0; /* frame */ + *q++ = 0; + *q++ = 1; /* last track */ + *q++ = 0x00; + *q++ = 0x00; + + *q++ = 1; /* session number */ + *q++ = 0x14; /* data track */ + *q++ = 0; /* track number */ + *q++ = 0xa2; /* lead-out */ + *q++ = 0; /* min */ + *q++ = 0; /* sec */ + *q++ = 0; /* frame */ + if (msf) { + *q++ = 0; /* reserved */ + lba_to_msf(q, nb_sectors); + q += 3; + } else { + cpu_to_be32wu((uint32_t *)q, nb_sectors); + q += 4; + } + + *q++ = 1; /* session number */ + *q++ = 0x14; /* ADR, control */ + *q++ = 0; /* track number */ + *q++ = 1; /* point */ + *q++ = 0; /* min */ + *q++ = 0; /* sec */ + *q++ = 0; /* frame */ + if (msf) { + *q++ = 0; + lba_to_msf(q, 0); + q += 3; + } else { + *q++ = 0; + *q++ = 0; + *q++ = 0; + *q++ = 0; + } + + len = q - buf; + cpu_to_be16wu((uint16_t *)buf, len - 2); + return len; +} + + diff --git a/hw/devices.h b/hw/devices.h new file mode 100644 index 0000000..45fead9 --- /dev/null +++ b/hw/devices.h @@ -0,0 +1,74 @@ +#ifndef QEMU_DEVICES_H +#define QEMU_DEVICES_H + +/* Devices that have nowhere better to go. */ + +/* smc91c111.c */ +void smc91c111_init(NICInfo *, uint32_t, qemu_irq); + +/* ssd0323.c */ +int ssd0323_xfer_ssi(void *opaque, int data); +void *ssd0323_init(DisplayState *ds, qemu_irq *cmd_p); + +/* ads7846.c */ +struct ads7846_state_s; +uint32_t ads7846_read(void *opaque); +void ads7846_write(void *opaque, uint32_t value); +struct ads7846_state_s *ads7846_init(qemu_irq penirq); + +/* tsc210x.c */ +struct uwire_slave_s; +struct mouse_transform_info_s; +struct uwire_slave_s *tsc2102_init(qemu_irq pint, AudioState *audio); +struct uwire_slave_s *tsc2301_init(qemu_irq penirq, qemu_irq kbirq, + qemu_irq dav, AudioState *audio); +struct i2s_codec_s *tsc210x_codec(struct uwire_slave_s *chip); +uint32_t tsc210x_txrx(void *opaque, uint32_t value, int len); +void tsc210x_set_transform(struct uwire_slave_s *chip, + struct mouse_transform_info_s *info); +void tsc210x_key_event(struct uwire_slave_s *chip, int key, int down); + +/* tsc2005.c */ +void *tsc2005_init(qemu_irq pintdav); +uint32_t tsc2005_txrx(void *opaque, uint32_t value, int len); +void tsc2005_set_transform(void *opaque, struct mouse_transform_info_s *info); + +/* stellaris_input.c */ +void stellaris_gamepad_init(int n, qemu_irq *irq, const int *keycode); + +/* blizzard.c */ +void *s1d13745_init(qemu_irq gpio_int, DisplayState *ds); +void s1d13745_write(void *opaque, int dc, uint16_t value); +void s1d13745_write_block(void *opaque, int dc, + void *buf, size_t len, int pitch); +uint16_t s1d13745_read(void *opaque, int dc); + +/* cbus.c */ +struct cbus_s { + qemu_irq clk; + qemu_irq dat; + qemu_irq sel; +}; +struct cbus_s *cbus_init(qemu_irq dat_out); +void cbus_attach(struct cbus_s *bus, void *slave_opaque); + +void *retu_init(qemu_irq irq, int vilma); +void *tahvo_init(qemu_irq irq, int betty); + +void retu_key_event(void *retu, int state); + +/* tusb6010.c */ +struct tusb_s; +struct tusb_s *tusb6010_init(qemu_irq intr); +int tusb6010_sync_io(struct tusb_s *s); +int tusb6010_async_io(struct tusb_s *s); +void tusb6010_power(struct tusb_s *s, int on); + +/* tc6393xb.c */ +struct tc6393xb_s; +struct tc6393xb_s *tc6393xb_init(uint32_t base, qemu_irq irq); +void tc6393xb_gpio_out_set(struct tc6393xb_s *s, int line, + qemu_irq handler); +qemu_irq *tc6393xb_gpio_in_get(struct tc6393xb_s *s); + +#endif diff --git a/hw/dma.c b/hw/dma.c new file mode 100644 index 0000000..00c6332 --- /dev/null +++ b/hw/dma.c @@ -0,0 +1,548 @@ +/* + * QEMU DMA emulation + * + * Copyright (c) 2003-2004 Vassili Karpov (malc) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "hw.h" +#include "isa.h" + +/* #define DEBUG_DMA */ + +#define dolog(...) fprintf (stderr, "dma: " __VA_ARGS__) +#ifdef DEBUG_DMA +#define lwarn(...) fprintf (stderr, "dma: " __VA_ARGS__) +#define linfo(...) fprintf (stderr, "dma: " __VA_ARGS__) +#define ldebug(...) fprintf (stderr, "dma: " __VA_ARGS__) +#else +#define lwarn(...) +#define linfo(...) +#define ldebug(...) +#endif + +#define LENOFA(a) ((int) (sizeof(a)/sizeof(a[0]))) + +struct dma_regs { + int now[2]; + uint16_t base[2]; + uint8_t mode; + uint8_t page; + uint8_t pageh; + uint8_t dack; + uint8_t eop; + DMA_transfer_handler transfer_handler; + void *opaque; +}; + +#define ADDR 0 +#define COUNT 1 + +static struct dma_cont { + uint8_t status; + uint8_t command; + uint8_t mask; + uint8_t flip_flop; + int dshift; + struct dma_regs regs[4]; +} dma_controllers[2]; + +enum { + CMD_MEMORY_TO_MEMORY = 0x01, + CMD_FIXED_ADDRESS = 0x02, + CMD_BLOCK_CONTROLLER = 0x04, + CMD_COMPRESSED_TIME = 0x08, + CMD_CYCLIC_PRIORITY = 0x10, + CMD_EXTENDED_WRITE = 0x20, + CMD_LOW_DREQ = 0x40, + CMD_LOW_DACK = 0x80, + CMD_NOT_SUPPORTED = CMD_MEMORY_TO_MEMORY | CMD_FIXED_ADDRESS + | CMD_COMPRESSED_TIME | CMD_CYCLIC_PRIORITY | CMD_EXTENDED_WRITE + | CMD_LOW_DREQ | CMD_LOW_DACK + +}; + +static int channels[8] = {-1, 2, 3, 1, -1, -1, -1, 0}; + +static void write_page (void *opaque, uint32_t nport, uint32_t data) +{ + struct dma_cont *d = opaque; + int ichan; + + ichan = channels[nport & 7]; + if (-1 == ichan) { + dolog ("invalid channel %#x %#x\n", nport, data); + return; + } + d->regs[ichan].page = data; +} + +static void write_pageh (void *opaque, uint32_t nport, uint32_t data) +{ + struct dma_cont *d = opaque; + int ichan; + + ichan = channels[nport & 7]; + if (-1 == ichan) { + dolog ("invalid channel %#x %#x\n", nport, data); + return; + } + d->regs[ichan].pageh = data; +} + +static uint32_t read_page (void *opaque, uint32_t nport) +{ + struct dma_cont *d = opaque; + int ichan; + + ichan = channels[nport & 7]; + if (-1 == ichan) { + dolog ("invalid channel read %#x\n", nport); + return 0; + } + return d->regs[ichan].page; +} + +static uint32_t read_pageh (void *opaque, uint32_t nport) +{ + struct dma_cont *d = opaque; + int ichan; + + ichan = channels[nport & 7]; + if (-1 == ichan) { + dolog ("invalid channel read %#x\n", nport); + return 0; + } + return d->regs[ichan].pageh; +} + +static inline void init_chan (struct dma_cont *d, int ichan) +{ + struct dma_regs *r; + + r = d->regs + ichan; + r->now[ADDR] = r->base[ADDR] << d->dshift; + r->now[COUNT] = 0; +} + +static inline int getff (struct dma_cont *d) +{ + int ff; + + ff = d->flip_flop; + d->flip_flop = !ff; + return ff; +} + +static uint32_t read_chan (void *opaque, uint32_t nport) +{ + struct dma_cont *d = opaque; + int ichan, nreg, iport, ff, val, dir; + struct dma_regs *r; + + iport = (nport >> d->dshift) & 0x0f; + ichan = iport >> 1; + nreg = iport & 1; + r = d->regs + ichan; + + dir = ((r->mode >> 5) & 1) ? -1 : 1; + ff = getff (d); + if (nreg) + val = (r->base[COUNT] << d->dshift) - r->now[COUNT]; + else + val = r->now[ADDR] + r->now[COUNT] * dir; + + ldebug ("read_chan %#x -> %d\n", iport, val); + return (val >> (d->dshift + (ff << 3))) & 0xff; +} + +static void write_chan (void *opaque, uint32_t nport, uint32_t data) +{ + struct dma_cont *d = opaque; + int iport, ichan, nreg; + struct dma_regs *r; + + iport = (nport >> d->dshift) & 0x0f; + ichan = iport >> 1; + nreg = iport & 1; + r = d->regs + ichan; + if (getff (d)) { + r->base[nreg] = (r->base[nreg] & 0xff) | ((data << 8) & 0xff00); + init_chan (d, ichan); + } else { + r->base[nreg] = (r->base[nreg] & 0xff00) | (data & 0xff); + } +} + +static void write_cont (void *opaque, uint32_t nport, uint32_t data) +{ + struct dma_cont *d = opaque; + int iport, ichan = 0; + + iport = (nport >> d->dshift) & 0x0f; + switch (iport) { + case 0x08: /* command */ + if ((data != 0) && (data & CMD_NOT_SUPPORTED)) { + dolog ("command %#x not supported\n", data); + return; + } + d->command = data; + break; + + case 0x09: + ichan = data & 3; + if (data & 4) { + d->status |= 1 << (ichan + 4); + } + else { + d->status &= ~(1 << (ichan + 4)); + } + d->status &= ~(1 << ichan); + break; + + case 0x0a: /* single mask */ + if (data & 4) + d->mask |= 1 << (data & 3); + else + d->mask &= ~(1 << (data & 3)); + break; + + case 0x0b: /* mode */ + { + ichan = data & 3; +#ifdef DEBUG_DMA + { + int op, ai, dir, opmode; + op = (data >> 2) & 3; + ai = (data >> 4) & 1; + dir = (data >> 5) & 1; + opmode = (data >> 6) & 3; + + linfo ("ichan %d, op %d, ai %d, dir %d, opmode %d\n", + ichan, op, ai, dir, opmode); + } +#endif + d->regs[ichan].mode = data; + break; + } + + case 0x0c: /* clear flip flop */ + d->flip_flop = 0; + break; + + case 0x0d: /* reset */ + d->flip_flop = 0; + d->mask = ~0; + d->status = 0; + d->command = 0; + break; + + case 0x0e: /* clear mask for all channels */ + d->mask = 0; + break; + + case 0x0f: /* write mask for all channels */ + d->mask = data; + break; + + default: + dolog ("unknown iport %#x\n", iport); + break; + } + +#ifdef DEBUG_DMA + if (0xc != iport) { + linfo ("write_cont: nport %#06x, ichan % 2d, val %#06x\n", + nport, ichan, data); + } +#endif +} + +static uint32_t read_cont (void *opaque, uint32_t nport) +{ + struct dma_cont *d = opaque; + int iport, val; + + iport = (nport >> d->dshift) & 0x0f; + switch (iport) { + case 0x08: /* status */ + val = d->status; + d->status &= 0xf0; + break; + case 0x0f: /* mask */ + val = d->mask; + break; + default: + val = 0; + break; + } + + ldebug ("read_cont: nport %#06x, iport %#04x val %#x\n", nport, iport, val); + return val; +} + +int DMA_get_channel_mode (int nchan) +{ + return dma_controllers[nchan > 3].regs[nchan & 3].mode; +} + +void DMA_hold_DREQ (int nchan) +{ + int ncont, ichan; + + ncont = nchan > 3; + ichan = nchan & 3; + linfo ("held cont=%d chan=%d\n", ncont, ichan); + dma_controllers[ncont].status |= 1 << (ichan + 4); +} + +void DMA_release_DREQ (int nchan) +{ + int ncont, ichan; + + ncont = nchan > 3; + ichan = nchan & 3; + linfo ("released cont=%d chan=%d\n", ncont, ichan); + dma_controllers[ncont].status &= ~(1 << (ichan + 4)); +} + +static void channel_run (int ncont, int ichan) +{ + int n; + struct dma_regs *r = &dma_controllers[ncont].regs[ichan]; +#ifdef DEBUG_DMA + int dir, opmode; + + dir = (r->mode >> 5) & 1; + opmode = (r->mode >> 6) & 3; + + if (dir) { + dolog ("DMA in address decrement mode\n"); + } + if (opmode != 1) { + dolog ("DMA not in single mode select %#x\n", opmode); + } +#endif + + r = dma_controllers[ncont].regs + ichan; + n = r->transfer_handler (r->opaque, ichan + (ncont << 2), + r->now[COUNT], (r->base[COUNT] + 1) << ncont); + r->now[COUNT] = n; + ldebug ("dma_pos %d size %d\n", n, (r->base[COUNT] + 1) << ncont); +} + +void DMA_run (void) +{ + struct dma_cont *d; + int icont, ichan; + + d = dma_controllers; + + for (icont = 0; icont < 2; icont++, d++) { + for (ichan = 0; ichan < 4; ichan++) { + int mask; + + mask = 1 << ichan; + + if ((0 == (d->mask & mask)) && (0 != (d->status & (mask << 4)))) + channel_run (icont, ichan); + } + } +} + +void DMA_register_channel (int nchan, + DMA_transfer_handler transfer_handler, + void *opaque) +{ + struct dma_regs *r; + int ichan, ncont; + + ncont = nchan > 3; + ichan = nchan & 3; + + r = dma_controllers[ncont].regs + ichan; + r->transfer_handler = transfer_handler; + r->opaque = opaque; +} + +int DMA_read_memory (int nchan, void *buf, int pos, int len) +{ + struct dma_regs *r = &dma_controllers[nchan > 3].regs[nchan & 3]; + target_phys_addr_t addr = ((r->pageh & 0x7f) << 24) | (r->page << 16) | r->now[ADDR]; + + if (r->mode & 0x20) { + int i; + uint8_t *p = buf; + + cpu_physical_memory_read (addr - pos - len, buf, len); + /* What about 16bit transfers? */ + for (i = 0; i < len >> 1; i++) { + uint8_t b = p[len - i - 1]; + p[i] = b; + } + } + else + cpu_physical_memory_read (addr + pos, buf, len); + + return len; +} + +int DMA_write_memory (int nchan, void *buf, int pos, int len) +{ + struct dma_regs *r = &dma_controllers[nchan > 3].regs[nchan & 3]; + target_phys_addr_t addr = ((r->pageh & 0x7f) << 24) | (r->page << 16) | r->now[ADDR]; + + if (r->mode & 0x20) { + int i; + uint8_t *p = buf; + + cpu_physical_memory_write (addr - pos - len, buf, len); + /* What about 16bit transfers? */ + for (i = 0; i < len; i++) { + uint8_t b = p[len - i - 1]; + p[i] = b; + } + } + else + cpu_physical_memory_write (addr + pos, buf, len); + + return len; +} + +/* request the emulator to transfer a new DMA memory block ASAP */ +void DMA_schedule(int nchan) +{ + CPUState *env = cpu_single_env; + if (env) + cpu_interrupt(env, CPU_INTERRUPT_EXIT); +} + +static void dma_reset(void *opaque) +{ + struct dma_cont *d = opaque; + write_cont (d, (0x0d << d->dshift), 0); +} + +static int dma_phony_handler (void *opaque, int nchan, int dma_pos, int dma_len) +{ + dolog ("unregistered DMA channel used nchan=%d dma_pos=%d dma_len=%d\n", + nchan, dma_pos, dma_len); + return dma_pos; +} + +/* dshift = 0: 8 bit DMA, 1 = 16 bit DMA */ +static void dma_init2(struct dma_cont *d, int base, int dshift, + int page_base, int pageh_base) +{ + static const int page_port_list[] = { 0x1, 0x2, 0x3, 0x7 }; + int i; + + d->dshift = dshift; + for (i = 0; i < 8; i++) { + register_ioport_write (base + (i << dshift), 1, 1, write_chan, d); + register_ioport_read (base + (i << dshift), 1, 1, read_chan, d); + } + for (i = 0; i < LENOFA (page_port_list); i++) { + register_ioport_write (page_base + page_port_list[i], 1, 1, + write_page, d); + register_ioport_read (page_base + page_port_list[i], 1, 1, + read_page, d); + if (pageh_base >= 0) { + register_ioport_write (pageh_base + page_port_list[i], 1, 1, + write_pageh, d); + register_ioport_read (pageh_base + page_port_list[i], 1, 1, + read_pageh, d); + } + } + for (i = 0; i < 8; i++) { + register_ioport_write (base + ((i + 8) << dshift), 1, 1, + write_cont, d); + register_ioport_read (base + ((i + 8) << dshift), 1, 1, + read_cont, d); + } + qemu_register_reset(dma_reset, d); + dma_reset(d); + for (i = 0; i < LENOFA (d->regs); ++i) { + d->regs[i].transfer_handler = dma_phony_handler; + } +} + +static void dma_save (QEMUFile *f, void *opaque) +{ + struct dma_cont *d = opaque; + int i; + + /* qemu_put_8s (f, &d->status); */ + qemu_put_8s (f, &d->command); + qemu_put_8s (f, &d->mask); + qemu_put_8s (f, &d->flip_flop); + qemu_put_be32 (f, d->dshift); + + for (i = 0; i < 4; ++i) { + struct dma_regs *r = &d->regs[i]; + qemu_put_be32 (f, r->now[0]); + qemu_put_be32 (f, r->now[1]); + qemu_put_be16s (f, &r->base[0]); + qemu_put_be16s (f, &r->base[1]); + qemu_put_8s (f, &r->mode); + qemu_put_8s (f, &r->page); + qemu_put_8s (f, &r->pageh); + qemu_put_8s (f, &r->dack); + qemu_put_8s (f, &r->eop); + } +} + +static int dma_load (QEMUFile *f, void *opaque, int version_id) +{ + struct dma_cont *d = opaque; + int i; + + if (version_id != 1) + return -EINVAL; + + /* qemu_get_8s (f, &d->status); */ + qemu_get_8s (f, &d->command); + qemu_get_8s (f, &d->mask); + qemu_get_8s (f, &d->flip_flop); + d->dshift=qemu_get_be32 (f); + + for (i = 0; i < 4; ++i) { + struct dma_regs *r = &d->regs[i]; + r->now[0]=qemu_get_be32 (f); + r->now[1]=qemu_get_be32 (f); + qemu_get_be16s (f, &r->base[0]); + qemu_get_be16s (f, &r->base[1]); + qemu_get_8s (f, &r->mode); + qemu_get_8s (f, &r->page); + qemu_get_8s (f, &r->pageh); + qemu_get_8s (f, &r->dack); + qemu_get_8s (f, &r->eop); + } + return 0; +} + +void DMA_init (int high_page_enable) +{ + dma_init2(&dma_controllers[0], 0x00, 0, 0x80, + high_page_enable ? 0x480 : -1); + dma_init2(&dma_controllers[1], 0xc0, 1, 0x88, + high_page_enable ? 0x488 : -1); + register_savevm ("dma", 0, 1, dma_save, dma_load, &dma_controllers[0]); + register_savevm ("dma", 1, 1, dma_save, dma_load, &dma_controllers[1]); +} diff --git a/hw/goldfish_audio.c b/hw/goldfish_audio.c new file mode 100644 index 0000000..d0a44b5 --- /dev/null +++ b/hw/goldfish_audio.c @@ -0,0 +1,533 @@ +/* Copyright (C) 2007-2008 The Android Open Source Project +** +** This software is licensed under the terms of the GNU General Public +** License version 2, as published by the Free Software Foundation, and +** may be copied, distributed, and modified under those terms. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +*/ +#include "qemu_file.h" +#include "goldfish_device.h" +#include "audio/audio.h" +#include "qemu_debug.h" +#include "android/globals.h" + +#define DEBUG 1 + +#if DEBUG +# define D(...) VERBOSE_PRINT(audio,__VA_ARGS__) +#else +# define D(...) ((void)0) +#endif + +extern void dprint(const char* fmt, ...); + +/* define USE_QEMU_AUDIO_IN to 1 to use QEMU's audio subsystem to + * implement the audio input. if 0, this will try to read a .wav file + * directly... + */ +#define USE_QEMU_AUDIO_IN 1 + +enum { + /* audio status register */ + AUDIO_INT_STATUS = 0x00, + /* set this to enable IRQ */ + AUDIO_INT_ENABLE = 0x04, + /* set these to specify buffer addresses */ + AUDIO_SET_WRITE_BUFFER_1 = 0x08, + AUDIO_SET_WRITE_BUFFER_2 = 0x0C, + /* set number of bytes in buffer to write */ + AUDIO_WRITE_BUFFER_1 = 0x10, + AUDIO_WRITE_BUFFER_2 = 0x14, + + /* true if audio input is supported */ + AUDIO_READ_SUPPORTED = 0x18, + /* buffer to use for audio input */ + AUDIO_SET_READ_BUFFER = 0x1C, + + /* driver writes number of bytes to read */ + AUDIO_START_READ = 0x20, + + /* number of bytes available in read buffer */ + AUDIO_READ_BUFFER_AVAILABLE = 0x24, + + /* AUDIO_INT_STATUS bits */ + + /* this bit set when it is safe to write more bytes to the buffer */ + AUDIO_INT_WRITE_BUFFER_1_EMPTY = 1U << 0, + AUDIO_INT_WRITE_BUFFER_2_EMPTY = 1U << 1, + AUDIO_INT_READ_BUFFER_FULL = 1U << 2, +}; + + +struct goldfish_audio_state { + struct goldfish_device dev; + // pointers to our two write buffers + uint32_t buffer_1, buffer_2; + uint32_t read_buffer; + // buffer flags + uint32_t int_status; + // irq enable mask for int_status + uint32_t int_enable; + +#if USE_QEMU_AUDIO_IN + uint32_t read_pos; + uint32_t read_size; +#else + // path to file or device to use for input + const char* input_source; + // true if input is a wav file + int input_is_wav; + // true if we need to convert stereo -> mono + int input_is_stereo; + // file descriptor to use for input + int input_fd; +#endif + + // number of bytes available in the read buffer + int read_buffer_available; + + // set to 1 or 2 to indicate which buffer we are writing from, or zero if both buffers are empty + int current_buffer; + + // current data to write + uint8* data_1; + uint32_t data_1_length; + uint8* data_2; + uint32_t data_2_length; + + + // for QEMU sound output + QEMUSoundCard card; + SWVoiceOut *voice; +#if USE_QEMU_AUDIO_IN + SWVoiceIn* voicein; +#endif +}; + +/* update this whenever you change the goldfish_audio_state structure */ +#define AUDIO_STATE_SAVE_VERSION 1 + +#define QFIELD_STRUCT struct goldfish_audio_state +QFIELD_BEGIN(audio_state_fields) + QFIELD_INT32(buffer_1), + QFIELD_INT32(buffer_2), + QFIELD_INT32(read_buffer), + QFIELD_INT32(int_status), + QFIELD_INT32(int_enable), +#if USE_QEMU_AUDIO_IN + QFIELD_INT32(read_pos), + QFIELD_INT32(read_size), +#endif + QFIELD_INT32(read_buffer_available), + QFIELD_INT32(current_buffer), + QFIELD_INT32(data_1_length), + QFIELD_INT32(data_2_length), +QFIELD_END + +static void audio_state_save( QEMUFile* f, void* opaque ) +{ + struct goldfish_audio_state* s = opaque; + + qemu_put_struct(f, audio_state_fields, s); + + /* we can't write data_1 and data_2 directly */ + qemu_put_be32( f, s->data_1 - phys_ram_base ); + qemu_put_be32( f, s->data_2 - phys_ram_base ); +} + +static int audio_state_load( QEMUFile* f, void* opaque, int version_id ) +{ + struct goldfish_audio_state* s = opaque; + int ret; + + if (version_id != AUDIO_STATE_SAVE_VERSION) + return -1; + + ret = qemu_get_struct(f, audio_state_fields, s); + if (!ret) { + s->data_1 = qemu_get_be32(f) + phys_ram_base; + s->data_2 = qemu_get_be32(f) + phys_ram_base; + } + return -1; +} + +static void enable_audio(struct goldfish_audio_state *s, int enable) +{ + // enable or disable the output voice + if (s->voice != NULL) + AUD_set_active_out(s->voice, (enable & (AUDIO_INT_WRITE_BUFFER_1_EMPTY | AUDIO_INT_WRITE_BUFFER_2_EMPTY)) != 0); + + if (s->voicein) + AUD_set_active_in (s->voicein, (enable & AUDIO_INT_READ_BUFFER_FULL) != 0); + // reset buffer information + s->data_1_length = 0; + s->data_2_length = 0; + s->current_buffer = 0; + s->read_pos = 0; +} + +#if USE_QEMU_AUDIO_IN +static void start_read(struct goldfish_audio_state *s, uint32_t count) +{ + //printf( "... goldfish audio start_read, count=%d\n", count ); + s->read_size = count; + s->read_buffer_available = 0; + s->read_pos = 0; +} +#else +static void start_read(struct goldfish_audio_state *s, uint32_t count) +{ + uint8 wav_header[44]; + int result; + + if (!s->input_source) return; + + if (s->input_fd < 0) { + s->input_fd = open(s->input_source, O_BINARY | O_RDONLY); + + if (s->input_fd < 0) { + fprintf(stderr, "goldfish_audio could not open %s for audio input\n", s->input_source); + s->input_source = NULL; // set to to avoid endless retries + return; + } + + // skip WAV header if we have a WAV file + if (s->input_is_wav) { + if (read(s->input_fd, wav_header, sizeof(wav_header)) != sizeof(wav_header)) { + fprintf(stderr, "goldfish_audio could not read WAV file header %s\n", s->input_source); + s->input_fd = -1; + s->input_source = NULL; // set to to avoid endless retries + return; + } + + // is the WAV file stereo? + s->input_is_stereo = (wav_header[22] == 2); + } else { + // assume input from an audio device is stereo + s->input_is_stereo = 1; + } + } + + uint8* buffer = (uint8*)phys_ram_base + s->read_buffer; + if (s->input_is_stereo) { + // need to read twice as much data + count *= 2; + } + +try_again: + result = read(s->input_fd, buffer, count); + if (result == 0 && s->input_is_wav) { + // end of file, so seek back to the beginning + lseek(s->input_fd, sizeof(wav_header), SEEK_SET); + goto try_again; + } + + if (result > 0 && s->input_is_stereo) { + // we need to convert stereo to mono + uint8* src = (uint8*)buffer; + uint8* dest = src; + int count = result/2; + while (count-- > 0) { + int sample1 = src[0] | (src[1] << 8); + int sample2 = src[2] | (src[3] << 8); + int sample = (sample1 + sample2) >> 1; + dst[0] = (uint8_t) sample; + dst[1] = (uint8_t)(sample >> 8); + src += 4; + dst += 2; + } + + // we reduced the number of bytes by 2 + result /= 2; + } + + s->read_buffer_available = (result > 0 ? result : 0); + s->int_status |= AUDIO_INT_READ_BUFFER_FULL; + goldfish_device_set_irq(&s->dev, 0, (s->int_status & s->int_enable)); +} +#endif + +static uint32_t goldfish_audio_read(void *opaque, target_phys_addr_t offset) +{ + uint32_t ret; + struct goldfish_audio_state *s = opaque; + offset -= s->dev.base; + switch(offset) { + case AUDIO_INT_STATUS: + // return current buffer status flags + ret = s->int_status & s->int_enable; + if(ret) { + goldfish_device_set_irq(&s->dev, 0, 0); + } + return ret; + + case AUDIO_READ_SUPPORTED: +#if USE_QEMU_AUDIO_IN + D("%s: AUDIO_READ_SUPPORTED returns %d", __FUNCTION__, + (s->voicein != NULL)); + return (s->voicein != NULL); +#else + return (s->input_source ? 1 : 0); +#endif + + case AUDIO_READ_BUFFER_AVAILABLE: + D("%s: AUDIO_READ_BUFFER_AVAILABLE returns %d", __FUNCTION__, + s->read_buffer_available); + return s->read_buffer_available; + + default: + cpu_abort (cpu_single_env, "goldfish_audio_read: Bad offset %x\n", offset); + return 0; + } +} + +static void goldfish_audio_write(void *opaque, target_phys_addr_t offset, uint32_t val) +{ + struct goldfish_audio_state *s = opaque; + offset -= s->dev.base; + + switch(offset) { + case AUDIO_INT_ENABLE: + /* enable buffer empty interrupts */ + D("%s: AUDIO_INT_ENABLE %d", __FUNCTION__, val ); + enable_audio(s, val); + s->int_enable = val; + s->int_status = (AUDIO_INT_WRITE_BUFFER_1_EMPTY | AUDIO_INT_WRITE_BUFFER_2_EMPTY); + goldfish_device_set_irq(&s->dev, 0, (s->int_status & s->int_enable)); + break; + case AUDIO_SET_WRITE_BUFFER_1: + /* save pointer to buffer 1 */ + s->buffer_1 = val; + break; + case AUDIO_SET_WRITE_BUFFER_2: + /* save pointer to buffer 2 */ + s->buffer_2 = val; + break; + case AUDIO_WRITE_BUFFER_1: + /* record that data in buffer 1 is ready to write */ + if (s->current_buffer == 0) s->current_buffer = 1; + s->data_1 = phys_ram_base + s->buffer_1; + s->data_1_length = val; + s->int_status &= ~AUDIO_INT_WRITE_BUFFER_1_EMPTY; + break; + case AUDIO_WRITE_BUFFER_2: + /* record that data in buffer 2 is ready to write */ + if (s->current_buffer == 0) s->current_buffer = 2; + s->data_2 = phys_ram_base + s->buffer_2; + s->data_2_length = val; + s->int_status &= ~AUDIO_INT_WRITE_BUFFER_2_EMPTY; + break; + + case AUDIO_SET_READ_BUFFER: + /* save pointer to the read buffer */ + s->read_buffer = val; + D( "%s: AUDIO_SET_READ_BUFFER %p", __FUNCTION__, (void*)val ); + break; + + case AUDIO_START_READ: + D( "%s: AUDIO_START_READ %d", __FUNCTION__, val ); + start_read(s, val); + s->int_status &= ~AUDIO_INT_READ_BUFFER_FULL; + goldfish_device_set_irq(&s->dev, 0, (s->int_status & s->int_enable)); + break; + + default: + cpu_abort (cpu_single_env, "goldfish_audio_write: Bad offset %x\n", offset); + } +} + +static void goldfish_audio_callback(void *opaque, int free) +{ + struct goldfish_audio_state *s = opaque; + int new_status = 0; + + /* loop until free is zero or both buffers are empty */ + while (free && s->current_buffer) { + + /* write data in buffer 1 */ + while (free && s->current_buffer == 1) { + int write = s->data_1_length; + if (write > free) write = free; + + int written = AUD_write(s->voice, s->data_1, write); + if (written) { + D("%s: sent %d bytes to audio output", __FUNCTION__, write); + s->data_1 += written; + s->data_1_length -= written; + free -= written; + + if (s->data_1_length == 0) { + new_status |= AUDIO_INT_WRITE_BUFFER_1_EMPTY; + s->current_buffer = (s->data_2_length ? 2 : 0); + } + } else { + break; + } + } + + /* write data in buffer 2 */ + while (free && s->current_buffer == 2) { + int write = s->data_2_length; + if (write > free) write = free; + + int written = AUD_write(s->voice, s->data_2, write); + if (written) { + D("%s: sent %d bytes to audio output", __FUNCTION__, write); + s->data_2 += written; + s->data_2_length -= written; + free -= written; + + if (s->data_2_length == 0) { + new_status |= AUDIO_INT_WRITE_BUFFER_2_EMPTY; + s->current_buffer = (s->data_1_length ? 1 : 0); + } + } else { + break; + } + } + } + + if (new_status && new_status != s->int_status) { + s->int_status |= new_status; + goldfish_device_set_irq(&s->dev, 0, (s->int_status & s->int_enable)); + } +} + +#if USE_QEMU_AUDIO_IN +static void +goldfish_audio_in_callback(void *opaque, int avail) +{ + struct goldfish_audio_state *s = opaque; + int new_status = 0; + + if (s->read_pos >= s->read_size) + return; + + if (0 && s->read_size > 0) + D("%s: in %d (pos=%d size=%d)", __FUNCTION__, + avail, s->read_pos, s->read_size ); + + while (avail > 0) { + int pos = s->read_pos; + int missing = s->read_size - pos; + uint8* buffer = (uint8*)phys_ram_base + s->read_buffer + pos; + int read; + int avail2 = (avail > missing) ? missing : avail; + + read = AUD_read(s->voicein, buffer, avail2); + if (read == 0) + break; + + if (avail2 > 0) + D("%s: AUD_read(%d) returned %d", __FUNCTION__, avail2, read); + + s->read_buffer_available += read; + + avail -= read; + pos += read; + if (pos == s->read_size) { + new_status |= AUDIO_INT_READ_BUFFER_FULL; + D("%s: AUDIO_INT_READ_BUFFER_FULL available=%d", __FUNCTION__, s->read_buffer_available); + } + s->read_pos = pos; + } + + if (new_status && new_status != s->int_status) { + s->int_status |= new_status; + goldfish_device_set_irq(&s->dev, 0, (s->int_status & s->int_enable)); + } +} +#endif /* USE_QEMU_AUDIO_IN */ + +static CPUReadMemoryFunc *goldfish_audio_readfn[] = { + goldfish_audio_read, + goldfish_audio_read, + goldfish_audio_read +}; + +static CPUWriteMemoryFunc *goldfish_audio_writefn[] = { + goldfish_audio_write, + goldfish_audio_write, + goldfish_audio_write +}; + +void goldfish_audio_init(uint32_t base, int id, const char* input_source) +{ + struct goldfish_audio_state *s; + audsettings_t as; + + /* nothing to do if no audio input and output */ + if (!android_hw->hw_audioOutput && !android_hw->hw_audioInput) + return; + + s = (struct goldfish_audio_state *)qemu_mallocz(sizeof(*s)); + s->dev.name = "goldfish_audio"; + s->dev.id = id; + s->dev.base = base; + s->dev.size = 0x1000; + s->dev.irq_count = 1; + +#ifndef USE_QEMU_AUDIO_IN + s->input_fd = -1; + if (input_source) { + s->input_source = input_source; + char* extension = strrchr(input_source, '.'); + if (extension && strcasecmp(extension, ".wav") == 0) { + s->input_is_wav = 1; + } + } +#endif + + AUD_register_card( &glob_audio_state, "goldfish_audio", &s->card); + + as.freq = 44100; + as.nchannels = 2; + as.fmt = AUD_FMT_S16; + as.endianness = AUDIO_HOST_ENDIANNESS; + + if (android_hw->hw_audioOutput) { + s->voice = AUD_open_out ( + &s->card, + s->voice, + "goldfish_audio", + s, + goldfish_audio_callback, + &as + ); + if (!s->voice) { + dprint("warning: opening audio output failed\n"); + return; + } + } + +#if USE_QEMU_AUDIO_IN + as.freq = 8000; + as.nchannels = 1; + as.fmt = AUD_FMT_S16; + as.endianness = AUDIO_HOST_ENDIANNESS; + + if (android_hw->hw_audioInput) { + s->voicein = AUD_open_in ( + &s->card, + NULL, + "goldfish_audio_in", + s, + goldfish_audio_in_callback, + &as + ); + if (!s->voicein) { + dprint("warning: opening audio input failed\n"); + } + } +#endif + + goldfish_device_add(&s->dev, goldfish_audio_readfn, goldfish_audio_writefn, s); + + register_savevm( "audio_state", 0, AUDIO_STATE_SAVE_VERSION, + audio_state_save, audio_state_load, s ); +} + diff --git a/hw/goldfish_battery.c b/hw/goldfish_battery.c new file mode 100644 index 0000000..d9ef785 --- /dev/null +++ b/hw/goldfish_battery.c @@ -0,0 +1,261 @@ +/* Copyright (C) 2007-2008 The Android Open Source Project +** +** This software is licensed under the terms of the GNU General Public +** License version 2, as published by the Free Software Foundation, and +** may be copied, distributed, and modified under those terms. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +*/ +#include "qemu_file.h" +#include "goldfish_device.h" +#include "power_supply.h" + + +enum { + /* status register */ + BATTERY_INT_STATUS = 0x00, + /* set this to enable IRQ */ + BATTERY_INT_ENABLE = 0x04, + + BATTERY_AC_ONLINE = 0x08, + BATTERY_STATUS = 0x0C, + BATTERY_HEALTH = 0x10, + BATTERY_PRESENT = 0x14, + BATTERY_CAPACITY = 0x18, + + BATTERY_STATUS_CHANGED = 1U << 0, + AC_STATUS_CHANGED = 1U << 1, + BATTERY_INT_MASK = BATTERY_STATUS_CHANGED | AC_STATUS_CHANGED, +}; + + +struct goldfish_battery_state { + struct goldfish_device dev; + // IRQs + uint32_t int_status; + // irq enable mask for int_status + uint32_t int_enable; + + int ac_online; + int status; + int health; + int present; + int capacity; +}; + +/* update this each time you update the battery_state struct */ +#define BATTERY_STATE_SAVE_VERSION 1 + +#define QFIELD_STRUCT struct goldfish_battery_state +QFIELD_BEGIN(goldfish_battery_fields) + QFIELD_INT32(int_status), + QFIELD_INT32(int_enable), + QFIELD_INT32(ac_online), + QFIELD_INT32(status), + QFIELD_INT32(health), + QFIELD_INT32(present), + QFIELD_INT32(capacity), +QFIELD_END + +static void goldfish_battery_save(QEMUFile* f, void* opaque) +{ + struct goldfish_battery_state* s = opaque; + + qemu_put_struct(f, goldfish_battery_fields, s); +} + +static int goldfish_battery_load(QEMUFile* f, void* opaque, int version_id) +{ + struct goldfish_battery_state* s = opaque; + + if (version_id != BATTERY_STATE_SAVE_VERSION) + return -1; + + return qemu_get_struct(f, goldfish_battery_fields, s); +} + +static struct goldfish_battery_state *battery_state; + +static uint32_t goldfish_battery_read(void *opaque, target_phys_addr_t offset) +{ + uint32_t ret; + struct goldfish_battery_state *s = opaque; + offset -= s->dev.base; + switch(offset) { + case BATTERY_INT_STATUS: + // return current buffer status flags + ret = s->int_status & s->int_enable; + if (ret) { + goldfish_device_set_irq(&s->dev, 0, 0); + s->int_status = 0; + } + return ret; + + case BATTERY_INT_ENABLE: + return s->int_enable; + case BATTERY_AC_ONLINE: + return s->ac_online; + case BATTERY_STATUS: + return s->status; + case BATTERY_HEALTH: + return s->health; + case BATTERY_PRESENT: + return s->present; + case BATTERY_CAPACITY: + return s->capacity; + + default: + cpu_abort (cpu_single_env, "goldfish_battery_read: Bad offset %x\n", offset); + return 0; + } +} + +static void goldfish_battery_write(void *opaque, target_phys_addr_t offset, uint32_t val) +{ + struct goldfish_battery_state *s = opaque; + offset -= s->dev.base; + + switch(offset) { + case BATTERY_INT_ENABLE: + /* enable interrupts */ + s->int_enable = val; +// s->int_status = (AUDIO_INT_WRITE_BUFFER_1_EMPTY | AUDIO_INT_WRITE_BUFFER_2_EMPTY); +// goldfish_device_set_irq(&s->dev, 0, (s->int_status & s->int_enable)); + break; + + default: + cpu_abort (cpu_single_env, "goldfish_audio_write: Bad offset %x\n", offset); + } +} + +static CPUReadMemoryFunc *goldfish_battery_readfn[] = { + goldfish_battery_read, + goldfish_battery_read, + goldfish_battery_read +}; + + +static CPUWriteMemoryFunc *goldfish_battery_writefn[] = { + goldfish_battery_write, + goldfish_battery_write, + goldfish_battery_write +}; + +void goldfish_battery_init() +{ + struct goldfish_battery_state *s; + + s = (struct goldfish_battery_state *)qemu_mallocz(sizeof(*s)); + s->dev.name = "goldfish-battery"; + s->dev.base = 0; // will be allocated dynamically + s->dev.size = 0x1000; + s->dev.irq_count = 1; + + // default values for the battery + s->ac_online = 1; + s->status = POWER_SUPPLY_STATUS_CHARGING; + s->health = POWER_SUPPLY_HEALTH_GOOD; + s->present = 1; // battery is present + s->capacity = 50; // 50% charged + + battery_state = s; + + goldfish_device_add(&s->dev, goldfish_battery_readfn, goldfish_battery_writefn, s); + + register_savevm( "battery_state", 0, BATTERY_STATE_SAVE_VERSION, + goldfish_battery_save, goldfish_battery_load, s); +} + +void goldfish_battery_set_prop(int ac, int property, int value) +{ + int new_status = (ac ? AC_STATUS_CHANGED : BATTERY_STATUS_CHANGED); + + if (ac) { + switch (property) { + case POWER_SUPPLY_PROP_ONLINE: + battery_state->ac_online = value; + break; + } + } else { + switch (property) { + case POWER_SUPPLY_PROP_STATUS: + battery_state->status = value; + break; + case POWER_SUPPLY_PROP_HEALTH: + battery_state->health = value; + break; + case POWER_SUPPLY_PROP_PRESENT: + battery_state->present = value; + break; + case POWER_SUPPLY_PROP_CAPACITY: + battery_state->capacity = value; + break; + } + } + + if (new_status != battery_state->int_status) { + battery_state->int_status |= new_status; + goldfish_device_set_irq(&battery_state->dev, 0, (battery_state->int_status & battery_state->int_enable)); + } +} + +void goldfish_battery_display(void (* callback)(void *data, const char* string), void *data) +{ + char buffer[100]; + char* value; + + sprintf(buffer, "AC: %s\r\n", (battery_state->ac_online ? "online" : "offline")); + callback(data, buffer); + + switch (battery_state->status) { + case POWER_SUPPLY_STATUS_CHARGING: + value = "Charging"; + break; + case POWER_SUPPLY_STATUS_DISCHARGING: + value = "Discharging"; + break; + case POWER_SUPPLY_STATUS_NOT_CHARGING: + value = "Not charging"; + break; + case POWER_SUPPLY_STATUS_FULL: + value = "Full"; + break; + default: + value = "Unknown"; + break; + } + sprintf(buffer, "status: %s\r\n", value); + callback(data, buffer); + + switch (battery_state->health) { + case POWER_SUPPLY_HEALTH_GOOD: + value = "Good"; + break; + case POWER_SUPPLY_HEALTH_OVERHEAT: + value = "Overhead"; + break; + case POWER_SUPPLY_HEALTH_DEAD: + value = "Dead"; + break; + case POWER_SUPPLY_HEALTH_OVERVOLTAGE: + value = "Overvoltage"; + break; + case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE: + value = "Unspecified failure"; + break; + default: + value = "Unknown"; + break; + } + sprintf(buffer, "health: %s\r\n", value); + callback(data, buffer); + + sprintf(buffer, "present: %s\r\n", (battery_state->present ? "true" : "false")); + callback(data, buffer); + + sprintf(buffer, "capacity: %d\r\n", battery_state->capacity); + callback(data, buffer); +} diff --git a/hw/goldfish_device.c b/hw/goldfish_device.c new file mode 100644 index 0000000..2c9dd6e --- /dev/null +++ b/hw/goldfish_device.c @@ -0,0 +1,200 @@ +/* Copyright (C) 2007-2008 The Android Open Source Project +** +** This software is licensed under the terms of the GNU General Public +** License version 2, as published by the Free Software Foundation, and +** may be copied, distributed, and modified under those terms. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +*/ +#include "qemu_file.h" +#include "arm_pic.h" +#include "goldfish_device.h" + +#define PDEV_BUS_OP_DONE (0x00) +#define PDEV_BUS_OP_REMOVE_DEV (0x04) +#define PDEV_BUS_OP_ADD_DEV (0x08) + +#define PDEV_BUS_OP_INIT (0x00) + +#define PDEV_BUS_OP (0x00) +#define PDEV_BUS_GET_NAME (0x04) +#define PDEV_BUS_NAME_LEN (0x08) +#define PDEV_BUS_ID (0x0c) +#define PDEV_BUS_IO_BASE (0x10) +#define PDEV_BUS_IO_SIZE (0x14) +#define PDEV_BUS_IRQ (0x18) +#define PDEV_BUS_IRQ_COUNT (0x1c) + +struct bus_state { + struct goldfish_device dev; + struct goldfish_device *current; +}; + +qemu_irq *goldfish_pic; +static struct goldfish_device *first_device; +static struct goldfish_device *last_device; +uint32_t goldfish_free_base; +uint32_t goldfish_free_irq; + +void goldfish_device_set_irq(struct goldfish_device *dev, int irq, int level) +{ + if(irq >= dev->irq_count) + cpu_abort (cpu_single_env, "goldfish_device_set_irq: Bad irq %d >= %d\n", irq, dev->irq_count); + else + qemu_set_irq(goldfish_pic[dev->irq + irq], level); +} + +int goldfish_add_device_no_io(struct goldfish_device *dev) +{ + if(dev->base == 0) { + dev->base = goldfish_free_base; + goldfish_free_base += dev->size; + } + if(dev->irq == 0 && dev->irq_count > 0) { + dev->irq = goldfish_free_irq; + goldfish_free_irq += dev->irq_count; + } + //printf("goldfish_add_device: %s, base %x %x, irq %d %d\n", + // dev->name, dev->base, dev->size, dev->irq, dev->irq_count); + dev->next = NULL; + if(last_device) { + last_device->next = dev; + } + else { + first_device = dev; + } + last_device = dev; + return 0; +} + +int goldfish_device_add(struct goldfish_device *dev, + CPUReadMemoryFunc **mem_read, + CPUWriteMemoryFunc **mem_write, + void *opaque) +{ + int iomemtype; + goldfish_add_device_no_io(dev); + iomemtype = cpu_register_io_memory(0, mem_read, + mem_write, opaque); + cpu_register_physical_memory(dev->base, dev->size, iomemtype); + return 0; +} + +static uint32_t goldfish_bus_read(void *opaque, target_phys_addr_t offset) +{ + struct bus_state *s = (struct bus_state *)opaque; + offset -= s->dev.base; + + switch (offset) { + case PDEV_BUS_OP: + if(s->current) { + s->current->reported_state = 1; + s->current = s->current->next; + } + else { + s->current = first_device; + } + while(s->current && s->current->reported_state == 1) + s->current = s->current->next; + if(s->current) + return PDEV_BUS_OP_ADD_DEV; + else { + goldfish_device_set_irq(&s->dev, 0, 0); + return PDEV_BUS_OP_DONE; + } + + case PDEV_BUS_NAME_LEN: + return s->current ? strlen(s->current->name) : 0; + case PDEV_BUS_ID: + return s->current ? s->current->id : 0; + case PDEV_BUS_IO_BASE: + return s->current ? s->current->base : 0; + case PDEV_BUS_IO_SIZE: + return s->current ? s->current->size : 0; + case PDEV_BUS_IRQ: + return s->current ? s->current->irq : 0; + case PDEV_BUS_IRQ_COUNT: + return s->current ? s->current->irq_count : 0; + default: + cpu_abort (cpu_single_env, "goldfish_bus_read: Bad offset %x\n", offset); + return 0; + } +} + +static void goldfish_bus_op_init(struct bus_state *s) +{ + struct goldfish_device *dev = first_device; + while(dev) { + dev->reported_state = 0; + dev = dev->next; + } + s->current = NULL; + goldfish_device_set_irq(&s->dev, 0, first_device != NULL); +} + +static void goldfish_bus_write(void *opaque, target_phys_addr_t offset, uint32_t value) +{ + struct bus_state *s = (struct bus_state *)opaque; + offset -= s->dev.base; + + switch(offset) { + case PDEV_BUS_OP: + switch(value) { + case PDEV_BUS_OP_INIT: + goldfish_bus_op_init(s); + break; + default: + cpu_abort (cpu_single_env, "goldfish_bus_write: Bad PDEV_BUS_OP value %x\n", value); + }; + break; + case PDEV_BUS_GET_NAME: + if(s->current) + pmemcpy(value, s->current->name, strlen(s->current->name)); + break; + default: + cpu_abort (cpu_single_env, "goldfish_bus_write: Bad offset %x\n", offset); + } +} + +static CPUReadMemoryFunc *goldfish_bus_readfn[] = { + goldfish_bus_read, + goldfish_bus_read, + goldfish_bus_read +}; + +static CPUWriteMemoryFunc *goldfish_bus_writefn[] = { + goldfish_bus_write, + goldfish_bus_write, + goldfish_bus_write +}; + + +static struct bus_state bus_state = { + .dev = { + .name = "goldfish_device_bus", + .id = -1, + .base = 0x10001000, + .size = 0x1000, + .irq = 1, + .irq_count = 1, + } +}; + +void goldfish_device_init(qemu_irq *pic, uint32_t base, uint32_t size, uint32_t irq, uint32_t irq_count) +{ + goldfish_pic = pic; + goldfish_free_base = base; + goldfish_free_irq = irq; +} + +int goldfish_device_bus_init(uint32_t base, uint32_t irq) +{ + bus_state.dev.base = base; + bus_state.dev.irq = irq; + + return goldfish_device_add(&bus_state.dev, goldfish_bus_readfn, goldfish_bus_writefn, &bus_state); +} + diff --git a/hw/goldfish_device.h b/hw/goldfish_device.h new file mode 100644 index 0000000..abe102e --- /dev/null +++ b/hw/goldfish_device.h @@ -0,0 +1,58 @@ +/* Copyright (C) 2007-2008 The Android Open Source Project +** +** This software is licensed under the terms of the GNU General Public +** License version 2, as published by the Free Software Foundation, and +** may be copied, distributed, and modified under those terms. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +*/ +#ifndef GOLDFISH_DEVICE_H +#define GOLDFISH_DEVICE_H + +struct goldfish_device { + struct goldfish_device *next; + struct goldfish_device *prev; + uint32_t reported_state; + void *cookie; + const char *name; + uint32_t id; + uint32_t base; // filled in by goldfish_device_add if 0 + uint32_t size; + uint32_t irq; // filled in by goldfish_device_add if 0 + uint32_t irq_count; +}; + + +void goldfish_device_set_irq(struct goldfish_device *dev, int irq, int level); +int goldfish_device_add(struct goldfish_device *dev, + CPUReadMemoryFunc **mem_read, + CPUWriteMemoryFunc **mem_write, + void *opaque); + +int goldfish_add_device_no_io(struct goldfish_device *dev); + +void goldfish_device_init(qemu_irq *pic, uint32_t base, uint32_t size, uint32_t irq, uint32_t irq_count); +int goldfish_device_bus_init(uint32_t base, uint32_t irq); + +// device init functions: +qemu_irq *goldfish_interrupt_init(uint32_t base, qemu_irq parent_irq, qemu_irq parent_fiq); +void goldfish_timer_and_rtc_init(uint32_t timerbase, int timerirq); +int goldfish_tty_add(CharDriverState *cs, int id, uint32_t base, int irq); +void goldfish_fb_init(DisplayState *ds, int id); +void goldfish_audio_init(uint32_t base, int id, const char* input_source); +void goldfish_battery_init(); +void goldfish_battery_set_prop(int ac, int property, int value); +void goldfish_battery_display(void (* callback)(void *data, const char* string), void *data); +void goldfish_mmc_init(uint32_t base, int id, BlockDriverState* bs); +void *goldfish_switch_add(char *name, uint32_t (*writefn)(void *opaque, uint32_t state), void *writeopaque, int id); +void goldfish_switch_set_state(void *opaque, uint32_t state); + +// these do not add a device +void trace_dev_init(uint32_t base); +void events_dev_init(uint32_t base, qemu_irq irq); +void nand_dev_init(uint32_t base); + +#endif diff --git a/hw/goldfish_events_device.c b/hw/goldfish_events_device.c new file mode 100644 index 0000000..4cb2904 --- /dev/null +++ b/hw/goldfish_events_device.c @@ -0,0 +1,423 @@ +/* Copyright (C) 2007-2008 The Android Open Source Project +** +** This software is licensed under the terms of the GNU General Public +** License version 2, as published by the Free Software Foundation, and +** may be copied, distributed, and modified under those terms. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +*/ +#include "qemu_file.h" +#include "android/hw-events.h" +#include "irq.h" + +#if 0 +// From kernel... +#define EV_SYN 0x00 +#define EV_KEY 0x01 +#define EV_REL 0x02 +#define EV_ABS 0x03 +#define EV_MSC 0x04 +#define EV_SW 0x05 +#define EV_LED 0x11 +#define EV_SND 0x12 +#define EV_REP 0x14 +#define EV_FF 0x15 +#define EV_PWR 0x16 +#define EV_FF_STATUS 0x17 +#define EV_MAX 0x1f + +#define BTN_MISC 0x100 +#define BTN_0 0x100 +#define BTN_1 0x101 +#define BTN_2 0x102 +#define BTN_3 0x103 +#define BTN_4 0x104 +#define BTN_5 0x105 +#define BTN_6 0x106 +#define BTN_7 0x107 +#define BTN_8 0x108 +#define BTN_9 0x109 + +#define BTN_MOUSE 0x110 +#define BTN_LEFT 0x110 +#define BTN_RIGHT 0x111 +#define BTN_MIDDLE 0x112 +#define BTN_SIDE 0x113 +#define BTN_EXTRA 0x114 +#define BTN_FORWARD 0x115 +#define BTN_BACK 0x116 +#define BTN_TASK 0x117 + +#define BTN_JOYSTICK 0x120 +#define BTN_TRIGGER 0x120 +#define BTN_THUMB 0x121 +#define BTN_THUMB2 0x122 +#define BTN_TOP 0x123 +#define BTN_TOP2 0x124 +#define BTN_PINKIE 0x125 +#define BTN_BASE 0x126 +#define BTN_BASE2 0x127 +#define BTN_BASE3 0x128 +#define BTN_BASE4 0x129 +#define BTN_BASE5 0x12a +#define BTN_BASE6 0x12b +#define BTN_DEAD 0x12f + +#define BTN_GAMEPAD 0x130 +#define BTN_A 0x130 +#define BTN_B 0x131 +#define BTN_C 0x132 +#define BTN_X 0x133 +#define BTN_Y 0x134 +#define BTN_Z 0x135 +#define BTN_TL 0x136 +#define BTN_TR 0x137 +#define BTN_TL2 0x138 +#define BTN_TR2 0x139 +#define BTN_SELECT 0x13a +#define BTN_START 0x13b +#define BTN_MODE 0x13c +#define BTN_THUMBL 0x13d +#define BTN_THUMBR 0x13e + +#define BTN_DIGI 0x140 +#define BTN_TOOL_PEN 0x140 +#define BTN_TOOL_RUBBER 0x141 +#define BTN_TOOL_BRUSH 0x142 +#define BTN_TOOL_PENCIL 0x143 +#define BTN_TOOL_AIRBRUSH 0x144 +#define BTN_TOOL_FINGER 0x145 +#define BTN_TOOL_MOUSE 0x146 +#define BTN_TOOL_LENS 0x147 +#define BTN_TOUCH 0x14a +#define BTN_STYLUS 0x14b +#define BTN_STYLUS2 0x14c +#define BTN_TOOL_DOUBLETAP 0x14d +#define BTN_TOOL_TRIPLETAP 0x14e + +#define BTN_WHEEL 0x150 +#define BTN_GEAR_DOWN 0x150 +#define BTN_GEAR_UP 0x151 + +#define REL_X 0x00 +#define REL_Y 0x01 + +#define ABS_X 0x00 +#define ABS_Y 0x01 +#define ABS_Z 0x02 +#define ABS_RX 0x03 +#define ABS_RY 0x04 +#define ABS_RZ 0x05 +#define ABS_THROTTLE 0x06 +#define ABS_RUDDER 0x07 +#define ABS_WHEEL 0x08 +#define ABS_GAS 0x09 +#define ABS_BRAKE 0x0a +#define ABS_HAT0X 0x10 +#define ABS_HAT0Y 0x11 +#define ABS_HAT1X 0x12 +#define ABS_HAT1Y 0x13 +#define ABS_HAT2X 0x14 +#define ABS_HAT2Y 0x15 +#define ABS_HAT3X 0x16 +#define ABS_HAT3Y 0x17 +#define ABS_PRESSURE 0x18 +#define ABS_DISTANCE 0x19 +#define ABS_TILT_X 0x1a +#define ABS_TILT_Y 0x1b +#define ABS_TOOL_WIDTH 0x1c +#define ABS_VOLUME 0x20 +#define ABS_MISC 0x28 +#define ABS_MAX 0x3f +#endif + +#define MAX_EVENTS 256*4 + +enum { + REG_READ = 0x00, + REG_SET_PAGE = 0x00, + REG_LEN = 0x04, + REG_DATA = 0x08, + + PAGE_NAME = 0x00000, + PAGE_EVBITS = 0x10000, + PAGE_ABSDATA = 0x20000 | EV_ABS, +}; + +typedef struct +{ + uint32_t base; + qemu_irq irq; + int pending; + int page; + + unsigned events[MAX_EVENTS]; + unsigned first; + unsigned last; + + const char *name; + struct { + size_t len; + uint8_t *bits; + } ev_bits[EV_MAX + 1]; + int32_t *abs_info; + size_t abs_info_count; +} events_state; + +/* modify this each time you change the events_device structure. you + * will also need to upadte events_state_load and events_state_save + */ +#define EVENTS_STATE_SAVE_VERSION 1 + +#undef QFIELD_STRUCT +#define QFIELD_STRUCT events_state + +QFIELD_BEGIN(events_state_fields) + QFIELD_INT32(pending), + QFIELD_INT32(page), + QFIELD_BUFFER(events), + QFIELD_INT32(first), + QFIELD_INT32(last), +QFIELD_END + +static void events_state_save(QEMUFile* f, void* opaque) +{ + events_state* s = opaque; + + qemu_put_struct(f, events_state_fields, s); +} + +static int events_state_load(QEMUFile* f, void* opaque, int version_id) +{ + events_state* s = opaque; + + if (version_id != EVENTS_STATE_SAVE_VERSION) + return -1; + + return qemu_get_struct(f, events_state_fields, s); +} + +extern const char* android_skin_keycharmap; + +static void enqueue_event(events_state *s, unsigned int type, unsigned int code, int value) +{ + int enqueued = s->last - s->first; + + if (enqueued < 0) + enqueued += MAX_EVENTS; + + if (enqueued + 3 >= MAX_EVENTS-1) { + fprintf(stderr, "##KBD: Full queue, lose event\n"); + return; + } + + if(s->first == s->last){ + qemu_irq_raise(s->irq); + } + + //fprintf(stderr, "##KBD: type=%d code=%d value=%d\n", type, code, value); + + s->events[s->last] = type; + s->last = (s->last + 1) & (MAX_EVENTS-1); + s->events[s->last] = code; + s->last = (s->last + 1) & (MAX_EVENTS-1); + s->events[s->last] = value; + s->last = (s->last + 1) & (MAX_EVENTS-1); +} + +static unsigned dequeue_event(events_state *s) +{ + unsigned n; + + if(s->first == s->last) { + return 0; + } + + n = s->events[s->first]; + + s->first = (s->first + 1) & (MAX_EVENTS - 1); + + if(s->first == s->last) { + qemu_irq_lower(s->irq); + } + + return n; +} + +static int get_page_len(events_state *s) +{ + int page = s->page; + if (page == PAGE_NAME) + return strlen(s->name); + if (page >= PAGE_EVBITS && page <= PAGE_EVBITS + EV_MAX) + return s->ev_bits[page - PAGE_EVBITS].len; + if (page == PAGE_ABSDATA) + return s->abs_info_count * sizeof(s->abs_info[0]); + return 0; +} + +static int get_page_data(events_state *s, int offset) +{ + int page_len = get_page_len(s); + int page = s->page; + if (offset > page_len) + return 0; + if (page == PAGE_NAME) + return s->name[offset]; + if (page >= PAGE_EVBITS && page <= PAGE_EVBITS + EV_MAX) + return s->ev_bits[page - PAGE_EVBITS].bits[offset]; + if (page == PAGE_ABSDATA) + return s->abs_info[offset / sizeof(s->abs_info[0])]; + return 0; +} + +static uint32_t events_read(void *x, target_phys_addr_t off) +{ + events_state *s = (events_state *) x; + int offset = off - s->base; + if (offset == REG_READ) + return dequeue_event(s); + else if (offset == REG_LEN) + return get_page_len(s); + else if (offset >= REG_DATA) + return get_page_data(s, offset - REG_DATA); + return 0; // this shouldn't happen, if the driver does the right thing +} + +static void events_write(void *x, target_phys_addr_t off, uint32_t val) +{ + events_state *s = (events_state *) x; + int offset = off - s->base; + if (offset == REG_SET_PAGE) + s->page = val; +} + +static CPUReadMemoryFunc *events_readfn[] = { + events_read, + events_read, + events_read +}; + +static CPUWriteMemoryFunc *events_writefn[] = { + events_write, + events_write, + events_write +}; + +static void events_put_keycode(void *x, int keycode) +{ + events_state *s = (events_state *) x; + + enqueue_event(s, EV_KEY, keycode&0x1ff, (keycode&0x200) ? 1 : 0); +} + +static void events_put_mouse(void *opaque, int dx, int dy, int dz, int buttons_state) +{ + events_state *s = (events_state *) opaque; + if (dz == 0) { + enqueue_event(s, EV_ABS, ABS_X, dx); + enqueue_event(s, EV_ABS, ABS_Y, dy); + enqueue_event(s, EV_ABS, ABS_Z, dz); + enqueue_event(s, EV_KEY, BTN_TOUCH, buttons_state&1); + } else { + enqueue_event(s, EV_REL, REL_X, dx); + enqueue_event(s, EV_REL, REL_Y, dy); + } + enqueue_event(s, EV_SYN, 0, 0); +} + +static void events_put_generic(void* opaque, int type, int code, int value) +{ + events_state *s = (events_state *) opaque; + + enqueue_event(s, type, code, value); +} + +static int events_set_bits(events_state *s, int type, int bitl, int bith) +{ + uint8_t *bits; + uint8_t maskl, maskh; + int il, ih; + il = bitl / 8; + ih = bith / 8; + if (ih >= s->ev_bits[type].len) { + bits = qemu_mallocz(ih + 1); + if (bits == NULL) + return -ENOMEM; + memcpy(bits, s->ev_bits[type].bits, s->ev_bits[type].len); + qemu_free(s->ev_bits[type].bits); + s->ev_bits[type].bits = bits; + s->ev_bits[type].len = ih + 1; + } + else + bits = s->ev_bits[type].bits; + maskl = 0xffU << (bitl & 7); + maskh = 0xffU >> (7 - (bith & 7)); + if (il >= ih) + maskh &= maskl; + else { + bits[il] |= maskl; + while (++il < ih) + bits[il] = 0xff; + } + bits[ih] |= maskh; + return 0; +} + +#if 0 +static int events_set_abs_info(events_state *s, int axis, int32_t min, int32_t max, int32_t fuzz, int32_t flat) +{ + int32_t *info; + if (axis * 4 >= s->abs_info_count) { + info = qemu_mallocz((axis + 1) * 4 * sizeof(int32_t)); + if (info == NULL) + return -ENOMEM; + memcpy(info, s->abs_info, s->abs_info_count); + qemu_free(s->abs_info); + s->abs_info = info; + s->abs_info_count = (axis + 1) * 4; + } + else + info = s->abs_info; + info += axis * 4; + *info++ = min; + *info++ = max; + *info++ = fuzz; + *info++ = flat; +} +#endif + +void events_dev_init(uint32_t base, qemu_irq irq) +{ + events_state *s; + int iomemtype; + + s = (events_state *) qemu_mallocz(sizeof(events_state)); + s->name = android_skin_keycharmap; + events_set_bits(s, EV_SYN, EV_SYN, EV_ABS); + events_set_bits(s, EV_SYN, EV_SW, EV_SW); + events_set_bits(s, EV_KEY, 1, 0x1ff); + events_set_bits(s, EV_REL, REL_X, REL_Y); + events_set_bits(s, EV_ABS, ABS_X, ABS_Z); + events_set_bits(s, EV_SW, 0, 0); + iomemtype = cpu_register_io_memory(0, events_readfn, events_writefn, s); + + cpu_register_physical_memory(base, 0xfff, iomemtype); + + qemu_add_kbd_event_handler(events_put_keycode, s); + qemu_add_mouse_event_handler(events_put_mouse, s, 1); + qemu_add_generic_event_handler(events_put_generic, s); + + s->base = base; + s->irq = irq; + + s->first = 0; + s->last = 0; + + register_savevm( "events_state", 0, EVENTS_STATE_SAVE_VERSION, + events_state_save, events_state_load, s ); +} + diff --git a/hw/goldfish_fb.c b/hw/goldfish_fb.c new file mode 100644 index 0000000..71cede2 --- /dev/null +++ b/hw/goldfish_fb.c @@ -0,0 +1,405 @@ +/* Copyright (C) 2007-2008 The Android Open Source Project +** +** This software is licensed under the terms of the GNU General Public +** License version 2, as published by the Free Software Foundation, and +** may be copied, distributed, and modified under those terms. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +*/ +#include "qemu_file.h" +#include "android/android.h" +#include "goldfish_device.h" +#include "framebuffer.h" + +enum { + FB_GET_WIDTH = 0x00, + FB_GET_HEIGHT = 0x04, + FB_INT_STATUS = 0x08, + FB_INT_ENABLE = 0x0c, + FB_SET_BASE = 0x10, + FB_SET_ROTATION = 0x14, + FB_SET_BLANK = 0x18, + FB_GET_PHYS_WIDTH = 0x1c, + FB_GET_PHYS_HEIGHT = 0x20, + + FB_INT_VSYNC = 1U << 0, + FB_INT_BASE_UPDATE_DONE = 1U << 1 +}; + +struct goldfish_fb_state { + struct goldfish_device dev; + QFrameBuffer* qfbuff; + uint32_t fb_base; + uint32_t base_valid : 1; + uint32_t need_update : 1; + uint32_t need_int : 1; + uint32_t set_rotation : 2; + uint32_t blank : 1; + uint32_t int_status; + uint32_t int_enable; + int rotation; /* 0, 1, 2 or 3 */ +}; + +#define GOLDFISH_FB_SAVE_VERSION 1 + +static void goldfish_fb_save(QEMUFile* f, void* opaque) +{ + struct goldfish_fb_state* s = opaque; + + QFrameBuffer* q = s->qfbuff; + + qemu_put_be32(f, q->width); + qemu_put_be32(f, q->height); + qemu_put_be32(f, q->pitch); + qemu_put_byte(f, q->rotation); + + qemu_put_be32(f, s->fb_base); + qemu_put_byte(f, s->base_valid); + qemu_put_byte(f, s->need_update); + qemu_put_byte(f, s->need_int); + qemu_put_byte(f, s->set_rotation); + qemu_put_byte(f, s->blank); + qemu_put_be32(f, s->int_status); + qemu_put_be32(f, s->int_enable); + qemu_put_be32(f, s->rotation); +} + +static int goldfish_fb_load(QEMUFile* f, void* opaque, int version_id) +{ + struct goldfish_fb_state* s = opaque; + + QFrameBuffer* q = s->qfbuff; + int ret = -1; + int ds_w, ds_h, ds_pitch, ds_rot; + + if (version_id != GOLDFISH_FB_SAVE_VERSION) + goto Exit; + + ds_w = qemu_get_be32(f); + ds_h = qemu_get_be32(f); + ds_pitch = qemu_get_be32(f); + ds_rot = qemu_get_byte(f); + + if (q->width != ds_w || + q->height != ds_h || + q->pitch != ds_pitch || + q->rotation != ds_rot ) + { + /* XXX: We should be able to force a resize/rotation from here ? */ + fprintf(stderr, "%s: framebuffer dimensions mismatch\n", __FUNCTION__); + goto Exit; + } + + s->fb_base = qemu_get_be32(f); + s->base_valid = qemu_get_byte(f); + s->need_update = qemu_get_byte(f); + s->need_int = qemu_get_byte(f); + s->set_rotation = qemu_get_byte(f); + s->blank = qemu_get_byte(f); + s->int_status = qemu_get_be32(f); + s->int_enable = qemu_get_be32(f); + s->rotation = qemu_get_be32(f); + + /* force a refresh */ + s->need_update = 1; + + ret = 0; +Exit: + return ret; +} + + +#define STATS 0 + +#if STATS +static int stats_counter; +static long stats_total; +static int stats_full_updates; +static long stats_total_full_updates; +#endif + +static void goldfish_fb_update_display(void *opaque) +{ + struct goldfish_fb_state *s = (struct goldfish_fb_state *)opaque; + uint32_t addr; + uint32_t base; + + uint8_t* dst_line; + uint8_t* src_line; + int y_first, y_last = 0; + int full_update = 0; + int width, height, pitch; + + base = s->fb_base; + if(base == 0) + return; + + if((s->int_enable & FB_INT_VSYNC) && !(s->int_status & FB_INT_VSYNC)) { + s->int_status |= FB_INT_VSYNC; + goldfish_device_set_irq(&s->dev, 0, 1); + } + + y_first = -1; + addr = base; + if(s->need_update) { + full_update = 1; + if(s->need_int) { + s->int_status |= FB_INT_BASE_UPDATE_DONE; + if(s->int_enable & FB_INT_BASE_UPDATE_DONE) + goldfish_device_set_irq(&s->dev, 0, 1); + } + s->need_int = 0; + s->need_update = 0; + } + + src_line = phys_ram_base + base; + dst_line = s->qfbuff->pixels; + pitch = s->qfbuff->pitch; + width = s->qfbuff->width; + height = s->qfbuff->height; + +#if STATS + if (full_update) + stats_full_updates += 1; + if (++stats_counter == 120) { + stats_total += stats_counter; + stats_total_full_updates += stats_full_updates; + + printf( "full update stats: peak %.2f %% total %.2f %%\n", + stats_full_updates*100.0/stats_counter, + stats_total_full_updates*100.0/stats_total ); + + stats_counter = 0; + stats_full_updates = 0; + } +#endif /* STATS */ + + if (s->blank) + { + memset( dst_line, 0, height*pitch ); + y_first = 0; + y_last = height-1; + } + else if (full_update) + { + int yy; + + for (yy = 0; yy < height; yy++, dst_line += pitch, src_line += width*2) + { + uint16_t* src = (uint16_t*) src_line; + uint16_t* dst = (uint16_t*) dst_line; + int nn; + + for (nn = 0; nn < width; nn++) { + unsigned spix = src[nn]; + unsigned dpix = dst[nn]; +#if WORDS_BIGENDIAN + spix = ((spix << 8) | (spix >> 8)) & 0xffff; +#else + if (spix != dpix) + break; +#endif + } + + if (nn == width) + continue; + +#if WORDS_BIGENDIAN + for ( ; nn < width; nn++ ) { + unsigned spix = src[nn]; + dst[nn] = (uint16_t)((spix << 8) | (spix >> 8)); + } +#else + memcpy( dst+nn, src+nn, (width-nn)*2 ); +#endif + + y_first = (y_first < 0) ? yy : y_first; + y_last = yy; + } + } + else /* not a full update, should not happen very often with Android */ + { + int yy; + + for (yy = 0; yy < height; yy++, dst_line += pitch, src_line += width*2) + { + uint16_t* src = (uint16_t*) src_line; + uint16_t* dst = (uint16_t*) dst_line; + int len = width*2; +#if WORDS_BIGENDIAN + int nn; +#endif + int dirty = 0; + + while (len > 0) { + int len2 = TARGET_PAGE_SIZE - (addr & (TARGET_PAGE_SIZE-1)); + + if (len2 > len) + len2 = len; + + dirty |= cpu_physical_memory_get_dirty(addr, VGA_DIRTY_FLAG); + addr += len2; + len -= len2; + } + + if (!dirty) + continue; + +#if WORDS_BIGENDIAN + for (nn = 0; nn < width; nn++ ) { + unsigned spix = src[nn]; + dst[nn] = (uint16_t)((spix << 8) | (spix >> 8)); + } +#else + memcpy( dst, src, width*2 ); +#endif + + y_first = (y_first < 0) ? yy : y_first; + y_last = yy; + } + } + + if (y_first < 0) + return; + + y_last += 1; + //printf("goldfish_fb_update_display %d %d, base %x\n", first, last, base); + + cpu_physical_memory_reset_dirty(base + y_first * width * 2, + base + y_last * width * 2, + VGA_DIRTY_FLAG); + + qframebuffer_update( s->qfbuff, 0, y_first, width, y_last-y_first ); +} + +static void goldfish_fb_invalidate_display(void * opaque) +{ + // is this called? + struct goldfish_fb_state *s = (struct goldfish_fb_state *)opaque; + s->need_update = 1; +} + +static void goldfish_fb_detach_display(void* opaque) +{ + struct goldfish_fb_state *s = (struct goldfish_fb_state *)opaque; + s->qfbuff = NULL; +} + +static uint32_t goldfish_fb_read(void *opaque, target_phys_addr_t offset) +{ + uint32_t ret; + struct goldfish_fb_state *s = opaque; + offset -= s->dev.base; + switch(offset) { + case FB_GET_WIDTH: + ret = s->qfbuff->width; + //printf("FB_GET_WIDTH => %d\n", ret); + return ret; + + case FB_GET_HEIGHT: + ret = s->qfbuff->height; + //printf( "FB_GET_HEIGHT = %d\n", ret); + return ret; + + case FB_INT_STATUS: + ret = s->int_status & s->int_enable; + if(ret) { + s->int_status &= ~ret; + goldfish_device_set_irq(&s->dev, 0, 0); + } + return ret; + + case FB_GET_PHYS_WIDTH: + ret = s->qfbuff->phys_width_mm; + //printf( "FB_GET_PHYS_WIDTH => %d\n", ret ); + return ret; + + case FB_GET_PHYS_HEIGHT: + ret = s->qfbuff->phys_height_mm; + //printf( "FB_GET_PHYS_HEIGHT => %d\n", ret ); + return ret; + + default: + cpu_abort (cpu_single_env, "goldfish_fb_read: Bad offset %x\n", offset); + return 0; + } +} + +static void goldfish_fb_write(void *opaque, target_phys_addr_t offset, + uint32_t val) +{ + struct goldfish_fb_state *s = opaque; + offset -= s->dev.base; + switch(offset) { + case FB_INT_ENABLE: + s->int_enable = val; + goldfish_device_set_irq(&s->dev, 0, (s->int_status & s->int_enable)); + break; + case FB_SET_BASE: { + int need_resize = !s->base_valid; + s->fb_base = val; + s->int_status &= ~FB_INT_BASE_UPDATE_DONE; + s->need_update = 1; + s->need_int = 1; + s->base_valid = 1; + if(s->set_rotation != s->rotation) { + //printf("FB_SET_BASE: rotation : %d => %d\n", s->rotation, s->set_rotation); + s->rotation = s->set_rotation; + need_resize = 1; + } + goldfish_device_set_irq(&s->dev, 0, (s->int_status & s->int_enable)); + if (need_resize) { + //printf("FB_SET_BASE: need resize (rotation=%d)\n", s->rotation ); + qframebuffer_rotate( s->qfbuff, s->rotation ); + } + } break; + case FB_SET_ROTATION: + //printf( "FB_SET_ROTATION %d\n", val); + s->set_rotation = val; + break; + case FB_SET_BLANK: + s->blank = val; + s->need_update = 1; + break; + default: + cpu_abort (cpu_single_env, "goldfish_fb_write: Bad offset %x\n", offset); + } +} + +static CPUReadMemoryFunc *goldfish_fb_readfn[] = { + goldfish_fb_read, + goldfish_fb_read, + goldfish_fb_read +}; + +static CPUWriteMemoryFunc *goldfish_fb_writefn[] = { + goldfish_fb_write, + goldfish_fb_write, + goldfish_fb_write +}; + +void goldfish_fb_init(DisplayState *ds, int id) +{ + struct goldfish_fb_state *s; + + s = (struct goldfish_fb_state *)qemu_mallocz(sizeof(*s)); + s->dev.name = "goldfish_fb"; + s->dev.id = id; + s->dev.size = 0x1000; + s->dev.irq_count = 1; + + s->qfbuff = qframebuffer_fifo_get(); + qframebuffer_set_producer( s->qfbuff, s, + goldfish_fb_update_display, + goldfish_fb_invalidate_display, + goldfish_fb_detach_display ); + + goldfish_device_add(&s->dev, goldfish_fb_readfn, goldfish_fb_writefn, s); + + register_savevm( "goldfish_fb", 0, GOLDFISH_FB_SAVE_VERSION, + goldfish_fb_save, goldfish_fb_load, s); +} + diff --git a/hw/goldfish_interrupt.c b/hw/goldfish_interrupt.c new file mode 100644 index 0000000..2cba649 --- /dev/null +++ b/hw/goldfish_interrupt.c @@ -0,0 +1,190 @@ +/* Copyright (C) 2007-2008 The Android Open Source Project +** +** This software is licensed under the terms of the GNU General Public +** License version 2, as published by the Free Software Foundation, and +** may be copied, distributed, and modified under those terms. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +*/ +#include "qemu_file.h" +#include "arm_pic.h" +#include "goldfish_device.h" +#include "irq.h" + +enum { + INTERRUPT_STATUS = 0x00, // number of pending interrupts + INTERRUPT_NUMBER = 0x04, + INTERRUPT_DISABLE_ALL = 0x08, + INTERRUPT_DISABLE = 0x0c, + INTERRUPT_ENABLE = 0x10 +}; + +struct goldfish_int_state { + struct goldfish_device dev; + uint32_t level; + uint32_t pending_count; + uint32_t irq_enabled; + uint32_t fiq_enabled; + qemu_irq parent_irq; + qemu_irq parent_fiq; +}; + +#define GOLDFISH_INT_SAVE_VERSION 1 + +#define QFIELD_STRUCT struct goldfish_int_state +QFIELD_BEGIN(goldfish_int_fields) + QFIELD_INT32(level), + QFIELD_INT32(pending_count), + QFIELD_INT32(irq_enabled), + QFIELD_INT32(fiq_enabled), +QFIELD_END + +static void goldfish_int_save(QEMUFile* f, void* opaque) +{ + struct goldfish_int_state* s = opaque; + + qemu_put_struct(f, goldfish_int_fields, s); +} + +static int goldfish_int_load(QEMUFile* f, void* opaque, int version_id) +{ + struct goldfish_int_state* s = opaque; + + if (version_id != GOLDFISH_INT_SAVE_VERSION) + return -1; + + return qemu_get_struct(f, goldfish_int_fields, s); +} + +static void goldfish_int_update(struct goldfish_int_state *s) +{ + uint32_t flags; + + flags = (s->level & s->irq_enabled); + qemu_set_irq(s->parent_irq, flags != 0); + + flags = (s->level & s->fiq_enabled); + qemu_set_irq(s->parent_fiq, flags != 0); +} + +static void goldfish_int_set_irq(void *opaque, int irq, int level) +{ + struct goldfish_int_state *s = (struct goldfish_int_state *)opaque; + uint32_t mask = (1U << irq); + + if(level) { + if(!(s->level & mask)) { + if(s->irq_enabled & mask) + s->pending_count++; + s->level |= mask; + } + } + else { + if(s->level & mask) { + if(s->irq_enabled & mask) + s->pending_count--; + s->level &= ~mask; + } + } + goldfish_int_update(s); +} + +static uint32_t goldfish_int_read(void *opaque, target_phys_addr_t offset) +{ + struct goldfish_int_state *s = (struct goldfish_int_state *)opaque; + offset -= s->dev.base; + + switch (offset) { + case INTERRUPT_STATUS: /* IRQ_STATUS */ + return s->pending_count; + case INTERRUPT_NUMBER: { + int i; + uint32_t pending = s->level & s->irq_enabled; + for(i = 0; i < 32; i++) { + if(pending & (1U << i)) + return i; + } + return 0; + } + default: + cpu_abort (cpu_single_env, "goldfish_int_read: Bad offset %x\n", offset); + return 0; + } +} + +static void goldfish_int_write(void *opaque, target_phys_addr_t offset, uint32_t value) +{ + struct goldfish_int_state *s = (struct goldfish_int_state *)opaque; + uint32_t mask = (1U << value); + offset -= s->dev.base; + + switch (offset) { + case INTERRUPT_DISABLE_ALL: + s->pending_count = 0; + s->level = 0; + break; + + case INTERRUPT_DISABLE: + if(s->irq_enabled & mask) { + if(s->level & mask) + s->pending_count--; + s->irq_enabled &= ~mask; + } + break; + case INTERRUPT_ENABLE: + if(!(s->irq_enabled & mask)) { + s->irq_enabled |= mask; + if(s->level & mask) + s->pending_count++; + } + break; + + default: + cpu_abort (cpu_single_env, "goldfish_int_write: Bad offset %x\n", offset); + return; + } + goldfish_int_update(s); +} + +static CPUReadMemoryFunc *goldfish_int_readfn[] = { + goldfish_int_read, + goldfish_int_read, + goldfish_int_read +}; + +static CPUWriteMemoryFunc *goldfish_int_writefn[] = { + goldfish_int_write, + goldfish_int_write, + goldfish_int_write +}; + +qemu_irq* goldfish_interrupt_init(uint32_t base, qemu_irq parent_irq, qemu_irq parent_fiq) +{ + int ret; + struct goldfish_int_state *s; + qemu_irq* qi; + + s = qemu_mallocz(sizeof(*s)); + qi = qemu_allocate_irqs(goldfish_int_set_irq, s, 32); + s->dev.name = "goldfish_interrupt_controller"; + s->dev.id = -1; + s->dev.base = base; + s->dev.size = 0x1000; + s->parent_irq = parent_irq; + s->parent_fiq = parent_fiq; + + ret = goldfish_device_add(&s->dev, goldfish_int_readfn, goldfish_int_writefn, s); + if(ret) { + qemu_free(s); + return NULL; + } + + register_savevm( "goldfish_int", 0, GOLDFISH_INT_SAVE_VERSION, + goldfish_int_save, goldfish_int_load, s); + + return qi; +} + diff --git a/hw/goldfish_memlog.c b/hw/goldfish_memlog.c new file mode 100644 index 0000000..98fcffc --- /dev/null +++ b/hw/goldfish_memlog.c @@ -0,0 +1,78 @@ +/* Copyright (C) 2007-2008 The Android Open Source Project +** +** This software is licensed under the terms of the GNU General Public +** License version 2, as published by the Free Software Foundation, and +** may be copied, distributed, and modified under those terms. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +*/ +#include <unistd.h> +#include <fcntl.h> +#include <string.h> + +#include "qemu_file.h" +#include "goldfish_device.h" +#include "audio/audio.h" + +extern void dprint(const char* fmt, ...); + +int fd = -1; + +static uint32_t memlog_read(void *opaque, target_phys_addr_t offset) +{ + struct goldfish_device *dev = opaque; + offset -= dev->base; + + return 0; +} + +unsigned info[8]; + +static void memlog_write(void *opaque, target_phys_addr_t offset, uint32_t val) +{ + char buf[128]; + struct goldfish_device *dev = opaque; + offset -= dev->base; + + info[offset / 4] = val; + + if (offset == 0) { + /* write PID and VADDR to logfile */ + sprintf(buf,"%08x %08x\n", info[0], info[1]); + write(fd, buf, strlen(buf)); + } +} + + +static CPUReadMemoryFunc *memlog_readfn[] = { + memlog_read, + memlog_read, + memlog_read +}; + +static CPUWriteMemoryFunc *memlog_writefn[] = { + memlog_write, + memlog_write, + memlog_write +}; + +struct goldfish_device memlog_dev; + +void goldfish_memlog_init(uint32_t base) +{ + struct goldfish_device *dev = &memlog_dev; + + dev->name = "goldfish_memlog"; + dev->id = 0; + dev->base = base; + dev->size = 0x1000; + dev->irq_count = 0; + + fd = open("mem.log", /* O_CREAT | */ O_TRUNC | O_WRONLY, 0644); + + goldfish_device_add(dev, memlog_readfn, memlog_writefn, dev); +} + diff --git a/hw/goldfish_mmc.c b/hw/goldfish_mmc.c new file mode 100644 index 0000000..272f403 --- /dev/null +++ b/hw/goldfish_mmc.c @@ -0,0 +1,468 @@ +/* Copyright (C) 2007-2008 The Android Open Source Project +** +** This software is licensed under the terms of the GNU General Public +** License version 2, as published by the Free Software Foundation, and +** may be copied, distributed, and modified under those terms. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +*/ +#include "qemu_file.h" +#include "goldfish_device.h" +#include "mmc.h" +#include "sd.h" +#include "block.h" + +enum { + /* status register */ + MMC_INT_STATUS = 0x00, + /* set this to enable IRQ */ + MMC_INT_ENABLE = 0x04, + /* set this to specify buffer address */ + MMC_SET_BUFFER = 0x08, + + /* MMC command number */ + MMC_CMD = 0x0C, + + /* MMC argument */ + MMC_ARG = 0x10, + + /* MMC response (or R2 bits 0 - 31) */ + MMC_RESP_0 = 0x14, + + /* MMC R2 response bits 32 - 63 */ + MMC_RESP_1 = 0x18, + + /* MMC R2 response bits 64 - 95 */ + MMC_RESP_2 = 0x1C, + + /* MMC R2 response bits 96 - 127 */ + MMC_RESP_3 = 0x20, + + MMC_BLOCK_LENGTH = 0x24, + MMC_BLOCK_COUNT = 0x28, + + /* MMC state flags */ + MMC_STATE = 0x2C, + + /* MMC_INT_STATUS bits */ + + MMC_STAT_END_OF_CMD = 1U << 0, + MMC_STAT_END_OF_DATA = 1U << 1, + MMC_STAT_STATE_CHANGE = 1U << 2, + + /* MMC_STATE bits */ + MMC_STATE_INSERTED = 1U << 0, + MMC_STATE_READ_ONLY = 1U << 1, +}; + + +struct goldfish_mmc_state { + struct goldfish_device dev; + BlockDriverState *bs; + // pointer to our buffer + uint8_t* buffer; + // offsets for read and write operations + uint32_t read_offset, write_offset; + // buffer status flags + uint32_t int_status; + // irq enable mask for int_status + uint32_t int_enable; + + // MMC command argument + uint32_t arg; + uint32_t resp[4]; + + uint32_t block_length; + uint32_t block_count; + int is_SDHC; +}; + +#define GOLDFISH_MMC_SAVE_VERSION 1 +#define QFIELD_STRUCT struct goldfish_mmc_state +QFIELD_BEGIN(goldfish_mmc_fields) + QFIELD_INT32(read_offset), + QFIELD_INT32(write_offset), + QFIELD_INT32(int_status), + QFIELD_INT32(int_enable), + QFIELD_INT32(arg), + QFIELD_INT32(resp[0]), + QFIELD_INT32(resp[1]), + QFIELD_INT32(resp[2]), + QFIELD_INT32(resp[3]), + QFIELD_INT32(block_length), + QFIELD_INT32(block_count), + QFIELD_INT32(is_SDHC), +QFIELD_END + +static void goldfish_mmc_save(QEMUFile* f, void* opaque) +{ + struct goldfish_mmc_state* s = opaque; + + qemu_put_be32(f, s->buffer - phys_ram_base); + qemu_put_struct(f, goldfish_mmc_fields, s); +} + +static int goldfish_mmc_load(QEMUFile* f, void* opaque, int version_id) +{ + struct goldfish_mmc_state* s = opaque; + + if (version_id != GOLDFISH_MMC_SAVE_VERSION) + return -1; + + s->buffer = qemu_get_be32(f) + phys_ram_base; + return qemu_get_struct(f, goldfish_mmc_fields, s); +} + +struct mmc_opcode { + const char* name; + int cmd; +} mmc_opcodes[] = { + { "MMC_GO_IDLE_STATE", 0 }, + { "MMC_SEND_OP_COND", 1 }, + { "MMC_ALL_SEND_CID", 2 }, + { "MMC_SET_RELATIVE_ADDR", 3 }, + { "MMC_SET_DSR", 4 }, + { "MMC_SWITCH", 6 }, + { "MMC_SELECT_CARD", 7 }, + { "MMC_SEND_EXT_CSD", 8 }, + { "MMC_SEND_CSD", 9 }, + { "MMC_SEND_CID", 10 }, + { "MMC_READ_DAT_UNTIL_STOP", 11 }, + { "MMC_STOP_TRANSMISSION", 12 }, + { "MMC_SEND_STATUS", 13 }, + { "MMC_GO_INACTIVE_STATE", 15 }, + { "MMC_SET_BLOCKLEN", 16 }, + { "MMC_READ_SINGLE_BLOCK", 17 }, + { "MMC_READ_MULTIPLE_BLOCK", 18 }, + { "MMC_WRITE_DAT_UNTIL_STOP", 20 }, + { "MMC_SET_BLOCK_COUNT", 23 }, + { "MMC_WRITE_BLOCK", 24 }, + { "MMC_WRITE_MULTIPLE_BLOCK", 25 }, + { "MMC_PROGRAM_CID", 26 }, + { "MMC_PROGRAM_CSD", 27 }, + { "MMC_SET_WRITE_PROT", 28 }, + { "MMC_CLR_WRITE_PROT", 29 }, + { "MMC_SEND_WRITE_PROT", 30 }, + { "MMC_ERASE_GROUP_START", 35 }, + { "MMC_ERASE_GROUP_END", 36 }, + { "MMC_ERASE", 38 }, + { "MMC_FAST_IO", 39 }, + { "MMC_GO_IRQ_STATE", 40 }, + { "MMC_LOCK_UNLOCK", 42 }, + { "MMC_APP_CMD", 55 }, + { "MMC_GEN_CMD", 56 }, + { "SD_APP_OP_COND", 41 }, + { "SD_APP_SEND_SCR", 51 }, + { "UNKNOWN", -1 } +}; + +#if 0 +static const char* get_command_name(int command) +{ + struct mmc_opcode* opcode = mmc_opcodes; + + while (opcode->cmd != command && opcode->cmd != -1) opcode++; + return opcode->name; +} +#endif + +static void goldfish_mmc_do_command(struct goldfish_mmc_state *s, uint32_t cmd, uint32_t arg) +{ + int result; + int new_status = MMC_STAT_END_OF_CMD; + int opcode = cmd & 63; + +// fprintf(stderr, "goldfish_mmc_do_command opcode: %s (0x%04X), arg: %d\n", get_command_name(opcode), cmd, arg); + + s->resp[0] = 0; + s->resp[1] = 0; + s->resp[2] = 0; + s->resp[3] = 0; + +#define SET_R1_CURRENT_STATE(s) ((s << 9) & 0x00001E00) /* sx, b (4 bits) */ + + switch (opcode) { + case MMC_SEND_CSD: { + int64_t sector_count = 0; + uint64_t capacity; + uint8_t exponent; + uint32_t m; + + bdrv_get_geometry(s->bs, (uint64_t*)§or_count); + capacity = sector_count * 512; + if (capacity > 2147483648U) { + // if storages is > 2 gig, then emulate SDHC card + s->is_SDHC = 1; + + // CSD bits borrowed from a real SDHC card, with capacity bits zeroed out + s->resp[3] = 0x400E0032; + s->resp[2] = 0x5B590000; + s->resp[1] = 0x00007F80; + s->resp[0] = 0x0A4040DF; + + // stuff in the real capacity + // m = UNSTUFF_BITS(resp, 48, 22); + m = (uint32_t)(capacity / (512*1024)) - 1; + // m must fit into 22 bits + if (m & 0xFFC00000) { + fprintf(stderr, "SD card too big (%lld bytes). Maximum SDHC card size is 128 gigabytes.\n", capacity); + abort(); + } + + // low 16 bits go in high end of resp[1] + s->resp[1] |= ((m & 0x0000FFFF) << 16); + // high 6 bits go in low end of resp[2] + s->resp[2] |= (m >> 16); + } else { + // emulate standard SD card + s->is_SDHC = 0; + + // CSD bits borrowed from a real SD card, with capacity bits zeroed out + s->resp[3] = 0x00260032; + s->resp[2] = 0x5F5A8000; + s->resp[1] = 0x3EF84FFF; + s->resp[0] = 0x928040CB; + + // stuff in the real capacity + // e = UNSTUFF_BITS(resp, 47, 3); + // m = UNSTUFF_BITS(resp, 62, 12); + // csd->capacity = (1 + m) << (e + 2); + // need to reverse the formula and calculate e and m + exponent = 0; + capacity = sector_count * 512; + if (capacity > 2147483648U) { + fprintf(stderr, "SD card too big (%lld bytes). Maximum SD card size is 2 gigabytes.\n", capacity); + abort(); + } + capacity >>= 10; // convert to Kbytes + while (capacity > 4096) { + // (capacity - 1) must fit into 12 bits + exponent++; + capacity >>= 1; + } + capacity -= 1; + exponent -= 2; + if (exponent > 7) + cpu_abort(cpu_single_env, "exponent %d too big\n", exponent); + + s->resp[2] |= (((uint32_t)capacity >> 2) & 0x3FF); // high 10 bits to bottom of resp[2] + s->resp[1] |= (((uint32_t)capacity & 3) << 30); // low 2 bits to top of resp[1] + s->resp[1] |= (exponent << (47 - 32)); + } + break; + } + + case MMC_SEND_EXT_CSD: + s->resp[0] = arg; + break; + + case MMC_APP_CMD: + s->resp[0] = SET_R1_CURRENT_STATE(4) | R1_READY_FOR_DATA | R1_APP_CMD; //2336 + break; + + case SD_APP_OP_COND: + s->resp[0] = 0x80FF8000; + break; + + case SD_APP_SEND_SCR: + { + uint32_t* scr = (uint32_t*)s->buffer; + scr[0] = 0x00002502; + scr[1] = 0x00000000; + s->resp[0] = SET_R1_CURRENT_STATE(4) | R1_READY_FOR_DATA | R1_APP_CMD; //2336 + new_status |= MMC_STAT_END_OF_DATA; + break; + } + case MMC_SET_RELATIVE_ADDR: + s->resp[0] = -518519520; + break; + + case MMC_ALL_SEND_CID: + s->resp[3] = 55788627; + s->resp[2] = 1429221959; + s->resp[1] = -2147479692; + s->resp[0] = -436179883; + break; + + case MMC_SELECT_CARD: + s->resp[0] = SET_R1_CURRENT_STATE(3) | R1_READY_FOR_DATA; // 1792 + break; + + case MMC_SWITCH: + if (arg == 0x00FFFFF1 || arg == 0x80FFFFF1) { + uint8_t* switchbuf = s->buffer; + memset(switchbuf, 0, 64); + switchbuf[13] = 2; + new_status |= MMC_STAT_END_OF_DATA; + } + s->resp[0] = SET_R1_CURRENT_STATE(4) | R1_READY_FOR_DATA | R1_APP_CMD; //2336 + break; + + case MMC_SET_BLOCKLEN: + s->block_length = arg; + s->resp[0] = SET_R1_CURRENT_STATE(4) | R1_READY_FOR_DATA; // 2304 + break; + + case MMC_READ_SINGLE_BLOCK: + s->block_count = 1; + // fall through + case MMC_READ_MULTIPLE_BLOCK: { + if (s->is_SDHC) { + // arg is block offset + } else { + // arg is byte offset + if (arg & 511) fprintf(stderr, "offset %d is not multiple of 512 when reading\n", arg); + arg /= s->block_length; + } + result = bdrv_read(s->bs, arg, s->buffer, s->block_count); + new_status |= MMC_STAT_END_OF_DATA; + s->resp[0] = SET_R1_CURRENT_STATE(4) | R1_READY_FOR_DATA; // 2304 + break; + } + + case MMC_WRITE_BLOCK: + s->block_count = 1; + // fall through + case MMC_WRITE_MULTIPLE_BLOCK: { + if (s->is_SDHC) { + // arg is block offset + } else { + // arg is byte offset + if (arg & 511) fprintf(stderr, "offset %d is not multiple of 512 when writing\n", arg); + arg /= s->block_length; + } + // arg is byte offset + result = bdrv_write(s->bs, arg, s->buffer, s->block_count); +// bdrv_flush(s->bs); + new_status |= MMC_STAT_END_OF_DATA; + s->resp[0] = SET_R1_CURRENT_STATE(4) | R1_READY_FOR_DATA; // 2304 + break; + } + + case MMC_STOP_TRANSMISSION: + s->resp[0] = SET_R1_CURRENT_STATE(5) | R1_READY_FOR_DATA; // 2816 + break; + + case MMC_SEND_STATUS: + s->resp[0] = SET_R1_CURRENT_STATE(4) | R1_READY_FOR_DATA; // 2304 + break; + } + + s->int_status |= new_status; + + if ((s->int_status & s->int_enable)) { + goldfish_device_set_irq(&s->dev, 0, (s->int_status & s->int_enable)); + } +} + +static uint32_t goldfish_mmc_read(void *opaque, target_phys_addr_t offset) +{ + uint32_t ret; + struct goldfish_mmc_state *s = opaque; + + offset -= s->dev.base; + switch(offset) { + case MMC_INT_STATUS: + // return current buffer status flags + return s->int_status & s->int_enable; + case MMC_RESP_0: + return s->resp[0]; + case MMC_RESP_1: + return s->resp[1]; + case MMC_RESP_2: + return s->resp[2]; + case MMC_RESP_3: + return s->resp[3]; + case MMC_STATE: { + ret = MMC_STATE_INSERTED; + if (bdrv_is_read_only(s->bs)) { + ret |= MMC_STATE_READ_ONLY; + } + return ret; + } + default: + cpu_abort(cpu_single_env, "goldfish_mmc_read: Bad offset %x\n", offset); + return 0; + } +} + +static void goldfish_mmc_write(void *opaque, target_phys_addr_t offset, uint32_t val) +{ + struct goldfish_mmc_state *s = opaque; + int status, old_status; + + offset -= s->dev.base; + + switch(offset) { + + case MMC_INT_STATUS: + status = s->int_status; + old_status = status; + status &= ~val; + s->int_status = status; + if(status != old_status) { + goldfish_device_set_irq(&s->dev, 0, status); + } + break; + + case MMC_INT_ENABLE: + /* enable buffer interrupts */ + s->int_enable = val; + s->int_status = 0; + goldfish_device_set_irq(&s->dev, 0, (s->int_status & s->int_enable)); + break; + case MMC_SET_BUFFER: + /* save pointer to buffer 1 */ + s->buffer = phys_ram_base + val; + break; + case MMC_CMD: + goldfish_mmc_do_command(s, val, s->arg); + break; + case MMC_ARG: + s->arg = val; + break; + case MMC_BLOCK_LENGTH: + s->block_length = val + 1; + break; + case MMC_BLOCK_COUNT: + s->block_count = val + 1; + break; + + default: + cpu_abort (cpu_single_env, "goldfish_mmc_write: Bad offset %x\n", offset); + } +} + +static CPUReadMemoryFunc *goldfish_mmc_readfn[] = { + goldfish_mmc_read, + goldfish_mmc_read, + goldfish_mmc_read +}; + +static CPUWriteMemoryFunc *goldfish_mmc_writefn[] = { + goldfish_mmc_write, + goldfish_mmc_write, + goldfish_mmc_write +}; + +void goldfish_mmc_init(uint32_t base, int id, BlockDriverState* bs) +{ + struct goldfish_mmc_state *s; + + s = (struct goldfish_mmc_state *)qemu_mallocz(sizeof(*s)); + s->dev.name = "goldfish_mmc"; + s->dev.id = id; + s->dev.base = base; + s->dev.size = 0x1000; + s->dev.irq_count = 1; + s->bs = bs; + + goldfish_device_add(&s->dev, goldfish_mmc_readfn, goldfish_mmc_writefn, s); + + register_savevm( "goldfish_mmc", 0, GOLDFISH_MMC_SAVE_VERSION, + goldfish_mmc_save, goldfish_mmc_load, s); +} + diff --git a/hw/goldfish_nand.c b/hw/goldfish_nand.c new file mode 100644 index 0000000..61b075e --- /dev/null +++ b/hw/goldfish_nand.c @@ -0,0 +1,636 @@ +/* Copyright (C) 2007-2008 The Android Open Source Project +** +** This software is licensed under the terms of the GNU General Public +** License version 2, as published by the Free Software Foundation, and +** may be copied, distributed, and modified under those terms. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +*/ +#include "qemu_file.h" +#include "goldfish_nand_reg.h" +#include "goldfish_nand.h" +#include "android/utils/tempfile.h" +#include "qemu_debug.h" +#include "android/android.h" + +#define DEBUG 1 +#if DEBUG +# define D(...) VERBOSE_PRINT(nand,__VA_ARGS__) +# define D_ACTIVE VERBOSE_CHECK(nand) +# define T(...) VERBOSE_PRINT(nand_limits,__VA_ARGS__) +# define T_ACTIVE VERBOSE_CHECK(nand_limits) +#else +# define D(...) ((void)0) +# define D_ACTIVE 0 +# define T(...) ((void)0) +# define T_ACTIVE 0 +#endif + +/* lseek uses 64-bit offsets on Darwin. */ +/* prefer lseek64 on Linux */ +#ifdef __APPLE__ +# define llseek lseek +#elif defined(__linux__) +# define llseek lseek64 +#endif + +#define XLOG xlog + +static void +xlog( const char* format, ... ) +{ + va_list args; + va_start(args, format); + fprintf(stderr, "NAND: "); + vfprintf(stderr, format, args); + va_end(args); +} + +typedef struct { + char* devname; + size_t devname_len; + char* data; + int fd; + uint32_t flags; + uint32_t page_size; + uint32_t extra_size; + uint32_t erase_size; + uint64_t size; +} nand_dev; + +nand_threshold android_nand_write_threshold; +nand_threshold android_nand_read_threshold; + +#ifdef CONFIG_NAND_THRESHOLD + +/* update a threshold, return 1 if limit is hit, 0 otherwise */ +static void +nand_threshold_update( nand_threshold* t, uint32_t len ) +{ + if (t->counter < t->limit) { + uint64_t avail = t->limit - t->counter; + if (avail > len) + avail = len; + + if (t->counter == 0) { + T("%s: starting threshold counting to %lld", + __FUNCTION__, t->limit); + } + t->counter += avail; + if (t->counter >= t->limit) { + /* threshold reach, send a signal to an external process */ + T( "%s: sending signal %d to pid %d !", + __FUNCTION__, t->signal, t->pid ); + + kill( t->pid, t->signal ); + } + } + return; +} + +#define NAND_UPDATE_READ_THRESHOLD(len) \ + nand_threshold_update( &android_nand_read_threshold, (uint32_t)(len) ) + +#define NAND_UPDATE_WRITE_THRESHOLD(len) \ + nand_threshold_update( &android_nand_write_threshold, (uint32_t)(len) ) + +#else /* !NAND_THRESHOLD */ + +#define NAND_UPDATE_READ_THRESHOLD(len) \ + do {} while (0) + +#define NAND_UPDATE_WRITE_THRESHOLD(len) \ + do {} while (0) + +#endif /* !NAND_THRESHOLD */ + +static nand_dev *nand_devs = NULL; +static uint32_t nand_dev_count = 0; + +typedef struct { + uint32_t base; + + // register state + uint32_t dev; + uint32_t addr_low; + uint32_t addr_high; + uint32_t transfer_size; + uint32_t data; + uint32_t result; +} nand_dev_state; + +/* update this everytime you change the nand_dev_state structure */ +#define NAND_DEV_STATE_SAVE_VERSION 1 + +#define QFIELD_STRUCT nand_dev_state +QFIELD_BEGIN(nand_dev_state_fields) + QFIELD_INT32(dev), + QFIELD_INT32(addr_low), + QFIELD_INT32(addr_high), + QFIELD_INT32(transfer_size), + QFIELD_INT32(data), + QFIELD_INT32(result), +QFIELD_END + +static void nand_dev_state_save(QEMUFile* f, void* opaque) +{ + nand_dev_state* s = opaque; + + qemu_put_struct(f, nand_dev_state_fields, s); +} + +static int nand_dev_state_load(QEMUFile* f, void* opaque, int version_id) +{ + nand_dev_state* s = opaque; + + if (version_id != NAND_DEV_STATE_SAVE_VERSION) + return -1; + + return qemu_get_struct(f, nand_dev_state_fields, s); +} + + +static int do_read(int fd, void* buf, size_t size) +{ + int ret; + do { + ret = read(fd, buf, size); + } while (ret < 0 && errno == EINTR); + + return ret; +} + +static int do_write(int fd, const void* buf, size_t size) +{ + int ret; + do { + ret = write(fd, buf, size); + } while (ret < 0 && errno == EINTR); + + return ret; +} + +static uint32_t nand_dev_read_file(nand_dev *dev, uint32_t data, uint64_t addr, uint32_t total_len) +{ + uint32_t len = total_len; + size_t read_len = dev->erase_size; + int eof = 0; + + NAND_UPDATE_READ_THRESHOLD(total_len); + + lseek(dev->fd, addr, SEEK_SET); + while(len > 0) { + if(read_len < dev->erase_size) { + memset(dev->data, 0xff, dev->erase_size); + read_len = dev->erase_size; + eof = 1; + } + if(len < read_len) + read_len = len; + if(!eof) { + read_len = do_read(dev->fd, dev->data, read_len); + } + pmemcpy(data, dev->data, read_len); + data += read_len; + len -= read_len; + } + return total_len; +} + +static uint32_t nand_dev_write_file(nand_dev *dev, uint32_t data, uint64_t addr, uint32_t total_len) +{ + uint32_t len = total_len; + size_t write_len = dev->erase_size; + int ret; + + NAND_UPDATE_WRITE_THRESHOLD(total_len); + + lseek(dev->fd, addr, SEEK_SET); + while(len > 0) { + if(len < write_len) + write_len = len; + vmemcpy(data, dev->data, write_len); + ret = do_write(dev->fd, dev->data, write_len); + if(ret < write_len) { + XLOG("nand_dev_write_file, write failed: %s\n", strerror(errno)); + break; + } + data += write_len; + len -= write_len; + } + return total_len - len; +} + +static uint32_t nand_dev_erase_file(nand_dev *dev, uint64_t addr, uint32_t total_len) +{ + uint32_t len = total_len; + size_t write_len = dev->erase_size; + int ret; + + lseek(dev->fd, addr, SEEK_SET); + memset(dev->data, 0xff, dev->erase_size); + while(len > 0) { + if(len < write_len) + write_len = len; + ret = do_write(dev->fd, dev->data, write_len); + if(ret < write_len) { + XLOG( "nand_dev_write_file, write failed: %s\n", strerror(errno)); + break; + } + len -= write_len; + } + return total_len - len; +} + +/* this is a huge hack required to make the PowerPC emulator binary usable + * on Mac OS X. If you define this function as 'static', the emulated kernel + * will panic when attempting to mount the /data partition. + * + * worse, if you do *not* define the function as static on Linux-x86, the + * emulated kernel will also panic !? + * + * I still wonder if this is a compiler bug, or due to some nasty thing the + * emulator does with CPU registers during execution of the translated code. + */ +#if !(defined __APPLE__ && defined __powerpc__) +static +#endif +uint32_t nand_dev_do_cmd(nand_dev_state *s, uint32_t cmd) +{ + uint32_t size; + uint64_t addr; + nand_dev *dev; + + addr = s->addr_low | ((uint64_t)s->addr_high << 32); + size = s->transfer_size; + if(s->dev >= nand_dev_count) + return 0; + dev = nand_devs + s->dev; + + switch(cmd) { + case NAND_CMD_GET_DEV_NAME: + if(size > dev->devname_len) + size = dev->devname_len; + pmemcpy(s->data, dev->devname, size); + return size; + case NAND_CMD_READ: + if(addr >= dev->size) + return 0; + if(size + addr > dev->size) + size = dev->size - addr; + if(dev->fd >= 0) + return nand_dev_read_file(dev, s->data, addr, size); + pmemcpy(s->data, &dev->data[addr], size); + return size; + case NAND_CMD_WRITE: + if(dev->flags & NAND_DEV_FLAG_READ_ONLY) + return 0; + if(addr >= dev->size) + return 0; + if(size + addr > dev->size) + size = dev->size - addr; + if(dev->fd >= 0) + return nand_dev_write_file(dev, s->data, addr, size); + vmemcpy(s->data, &dev->data[addr], size); + return size; + case NAND_CMD_ERASE: + if(dev->flags & NAND_DEV_FLAG_READ_ONLY) + return 0; + if(addr >= dev->size) + return 0; + if(size + addr > dev->size) + size = dev->size - addr; + if(dev->fd >= 0) + return nand_dev_erase_file(dev, addr, size); + memset(&dev->data[addr], 0xff, size); + return size; + case NAND_CMD_BLOCK_BAD_GET: // no bad block support + return 0; + case NAND_CMD_BLOCK_BAD_SET: + if(dev->flags & NAND_DEV_FLAG_READ_ONLY) + return 0; + return 0; + default: + cpu_abort(cpu_single_env, "nand_dev_do_cmd: Bad command %x\n", cmd); + return 0; + } +} + +/* I/O write */ +static void nand_dev_write(void *opaque, target_phys_addr_t offset, uint32_t value) +{ + nand_dev_state *s = (nand_dev_state *)opaque; + + offset -= s->base; + switch (offset) { + case NAND_DEV: + s->dev = value; + if(s->dev >= nand_dev_count) { + cpu_abort(cpu_single_env, "nand_dev_write: Bad dev %x\n", value); + } + break; + case NAND_ADDR_HIGH: + s->addr_high = value; + break; + case NAND_ADDR_LOW: + s->addr_low = value; + break; + case NAND_TRANSFER_SIZE: + s->transfer_size = value; + break; + case NAND_DATA: + s->data = value; + break; + case NAND_COMMAND: + s->result = nand_dev_do_cmd(s, value); + break; + default: + cpu_abort(cpu_single_env, "nand_dev_write: Bad offset %x\n", offset); + break; + } +} + +/* I/O read */ +static uint32_t nand_dev_read(void *opaque, target_phys_addr_t offset) +{ + nand_dev_state *s = (nand_dev_state *)opaque; + nand_dev *dev; + + offset -= s->base; + switch (offset) { + case NAND_VERSION: + return NAND_VERSION_CURRENT; + case NAND_NUM_DEV: + return nand_dev_count; + case NAND_RESULT: + return s->result; + } + + if(s->dev >= nand_dev_count) + return 0; + + dev = nand_devs + s->dev; + + switch (offset) { + case NAND_DEV_FLAGS: + return dev->flags; + + case NAND_DEV_NAME_LEN: + return dev->devname_len; + + case NAND_DEV_PAGE_SIZE: + return dev->page_size; + + case NAND_DEV_EXTRA_SIZE: + return dev->extra_size; + + case NAND_DEV_ERASE_SIZE: + return dev->erase_size; + + case NAND_DEV_SIZE_LOW: + return (uint32_t)dev->size; + + case NAND_DEV_SIZE_HIGH: + return (uint32_t)(dev->size >> 32); + + default: + cpu_abort(cpu_single_env, "nand_dev_read: Bad offset %x\n", offset); + return 0; + } +} + +static CPUReadMemoryFunc *nand_dev_readfn[] = { + nand_dev_read, + nand_dev_read, + nand_dev_read +}; + +static CPUWriteMemoryFunc *nand_dev_writefn[] = { + nand_dev_write, + nand_dev_write, + nand_dev_write +}; + +/* initialize the QFB device */ +void nand_dev_init(uint32_t base) +{ + int iomemtype; + static int instance_id = 0; + nand_dev_state *s; + + s = (nand_dev_state *)qemu_mallocz(sizeof(nand_dev_state)); + iomemtype = cpu_register_io_memory(0, nand_dev_readfn, nand_dev_writefn, s); + cpu_register_physical_memory(base, 0x00000fff, iomemtype); + s->base = base; + + register_savevm( "nand_dev", instance_id++, NAND_DEV_STATE_SAVE_VERSION, + nand_dev_state_save, nand_dev_state_load, s); +} + +static int arg_match(const char *a, const char *b, size_t b_len) +{ + while(*a && b_len--) { + if(*a++ != *b++) + return 0; + } + return b_len == 0; +} + +void nand_add_dev(const char *arg) +{ + uint64_t dev_size = 0; + const char *next_arg; + const char *value; + size_t arg_len, value_len; + nand_dev *new_devs, *dev; + char *devname = NULL; + size_t devname_len = 0; + char *initfilename = NULL; + char *rwfilename = NULL; + int initfd = -1; + int rwfd = -1; + int read_only = 0; + int pad; + ssize_t read_size; + uint32_t page_size = 2048; + uint32_t extra_size = 64; + uint32_t erase_pages = 64; + + while(arg) { + next_arg = strchr(arg, ','); + value = strchr(arg, '='); + if(next_arg != NULL) { + arg_len = next_arg - arg; + next_arg++; + if(value >= next_arg) + value = NULL; + } + else + arg_len = strlen(arg); + if(value != NULL) { + size_t new_arg_len = value - arg; + value_len = arg_len - new_arg_len - 1; + arg_len = new_arg_len; + value++; + } + else + value_len = 0; + + if(devname == NULL) { + if(value != NULL) + goto bad_arg_and_value; + devname_len = arg_len; + devname = malloc(arg_len); + if(devname == NULL) + goto out_of_memory; + memcpy(devname, arg, arg_len); + } + else if(value == NULL) { + if(arg_match("readonly", arg, arg_len)) { + read_only = 1; + } + else { + XLOG("bad arg: %.*s\n", arg_len, arg); + exit(1); + } + } + else { + if(arg_match("size", arg, arg_len)) { + char *ep; + dev_size = strtoull(value, &ep, 0); + if(ep != value + value_len) + goto bad_arg_and_value; + } + else if(arg_match("pagesize", arg, arg_len)) { + char *ep; + page_size = strtoul(value, &ep, 0); + if(ep != value + value_len) + goto bad_arg_and_value; + } + else if(arg_match("extrasize", arg, arg_len)) { + char *ep; + extra_size = strtoul(value, &ep, 0); + if(ep != value + value_len) + goto bad_arg_and_value; + } + else if(arg_match("erasepages", arg, arg_len)) { + char *ep; + erase_pages = strtoul(value, &ep, 0); + if(ep != value + value_len) + goto bad_arg_and_value; + } + else if(arg_match("initfile", arg, arg_len)) { + initfilename = malloc(value_len + 1); + if(initfilename == NULL) + goto out_of_memory; + memcpy(initfilename, value, value_len); + initfilename[value_len] = '\0'; + } + else if(arg_match("file", arg, arg_len)) { + rwfilename = malloc(value_len + 1); + if(rwfilename == NULL) + goto out_of_memory; + memcpy(rwfilename, value, value_len); + rwfilename[value_len] = '\0'; + } + else { + goto bad_arg_and_value; + } + } + + arg = next_arg; + } + + if (rwfilename == NULL) { + /* we create a temporary file to store everything */ + TempFile* tmp = tempfile_create(); + + if (tmp == NULL) { + XLOG("could not create temp file for %.*s NAND disk image: %s", + devname_len, devname, strerror(errno)); + exit(1); + } + rwfilename = (char*) tempfile_path(tmp); + if (VERBOSE_CHECK(init)) + dprint( "mapping '%.*s' NAND image to %s", devname_len, devname, rwfilename); + } + + if(rwfilename) { + rwfd = open(rwfilename, O_BINARY | (read_only ? O_RDONLY : O_RDWR)); + if(rwfd < 0 && read_only) { + XLOG("could not open file %s, %s\n", rwfilename, strerror(errno)); + exit(1); + } + /* this could be a writable temporary file. use atexit_close_fd to ensure + * that it is properly cleaned up at exit on Win32 + */ + if (!read_only) + atexit_close_fd(rwfd); + } + + if(initfilename) { + initfd = open(initfilename, O_BINARY | O_RDONLY); + if(initfd < 0) { + XLOG("could not open file %s, %s\n", initfilename, strerror(errno)); + exit(1); + } + if(dev_size == 0) { + dev_size = lseek(initfd, 0, SEEK_END); + lseek(initfd, 0, SEEK_SET); + } + } + + new_devs = realloc(nand_devs, sizeof(nand_devs[0]) * (nand_dev_count + 1)); + if(new_devs == NULL) + goto out_of_memory; + nand_devs = new_devs; + dev = &new_devs[nand_dev_count]; + + dev->page_size = page_size; + dev->extra_size = extra_size; + dev->erase_size = erase_pages * (page_size + extra_size); + pad = dev_size % dev->erase_size; + if (pad != 0) { + dev_size += (dev->erase_size - pad); + XLOG("rounding devsize up to a full eraseunit, now %llx\n", dev_size); + } + dev->devname = devname; + dev->devname_len = devname_len; + dev->size = dev_size; + dev->data = malloc(dev->erase_size); + if(dev->data == NULL) + goto out_of_memory; + dev->flags = read_only ? NAND_DEV_FLAG_READ_ONLY : 0; + + if (initfd >= 0) { + do { + read_size = do_read(initfd, dev->data, dev->erase_size); + if(read_size < 0) { + XLOG("could not read file %s, %s\n", initfilename, strerror(errno)); + exit(1); + } + if(do_write(rwfd, dev->data, read_size) != read_size) { + XLOG("could not write file %s, %s\n", initfilename, strerror(errno)); + exit(1); + } + } while(read_size == dev->erase_size); + close(initfd); + } + dev->fd = rwfd; + + nand_dev_count++; + + return; + +out_of_memory: + XLOG("out of memory\n"); + exit(1); + +bad_arg_and_value: + XLOG("bad arg: %.*s=%.*s\n", arg_len, arg, value_len, value); + exit(1); +} + diff --git a/hw/goldfish_nand.h b/hw/goldfish_nand.h new file mode 100644 index 0000000..dcc59d8 --- /dev/null +++ b/hw/goldfish_nand.h @@ -0,0 +1,28 @@ +/* Copyright (C) 2007-2008 The Android Open Source Project +** +** This software is licensed under the terms of the GNU General Public +** License version 2, as published by the Free Software Foundation, and +** may be copied, distributed, and modified under those terms. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +*/ +#ifndef NAND_DEVICE_H +#define NAND_DEVICE_H + +void nand_dev_init(uint32_t base); +void nand_add_dev(const char *arg); + +typedef struct { + uint64_t limit; + uint64_t counter; + int pid; + int signal; +} nand_threshold; + +extern nand_threshold android_nand_read_threshold; +extern nand_threshold android_nand_write_threshold; + +#endif diff --git a/hw/goldfish_nand_reg.h b/hw/goldfish_nand_reg.h new file mode 100644 index 0000000..ea91461 --- /dev/null +++ b/hw/goldfish_nand_reg.h @@ -0,0 +1,54 @@ +/* Copyright (C) 2007-2008 The Android Open Source Project +** +** This software is licensed under the terms of the GNU General Public +** License version 2, as published by the Free Software Foundation, and +** may be copied, distributed, and modified under those terms. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +*/ +#ifndef NAND_DEVICE_REG_H +#define NAND_DEVICE_REG_H + +enum nand_cmd { + NAND_CMD_GET_DEV_NAME, // Write device name for NAND_DEV to NAND_DATA (vaddr) + NAND_CMD_READ, + NAND_CMD_WRITE, + NAND_CMD_ERASE, + NAND_CMD_BLOCK_BAD_GET, // NAND_RESULT is 1 if block is bad, 0 if it is not + NAND_CMD_BLOCK_BAD_SET +}; + +enum nand_dev_flags { + NAND_DEV_FLAG_READ_ONLY = 0x00000001 +}; + +#define NAND_VERSION_CURRENT (1) + +enum nand_reg { + // Global + NAND_VERSION = 0x000, + NAND_NUM_DEV = 0x004, + NAND_DEV = 0x008, + + // Dev info + NAND_DEV_FLAGS = 0x010, + NAND_DEV_NAME_LEN = 0x014, + NAND_DEV_PAGE_SIZE = 0x018, + NAND_DEV_EXTRA_SIZE = 0x01c, + NAND_DEV_ERASE_SIZE = 0x020, + NAND_DEV_SIZE_LOW = 0x028, + NAND_DEV_SIZE_HIGH = 0x02c, + + // Command + NAND_RESULT = 0x040, + NAND_COMMAND = 0x044, + NAND_DATA = 0x048, + NAND_TRANSFER_SIZE = 0x04c, + NAND_ADDR_LOW = 0x050, + NAND_ADDR_HIGH = 0x054, +}; + +#endif diff --git a/hw/goldfish_switch.c b/hw/goldfish_switch.c new file mode 100644 index 0000000..8a12d66 --- /dev/null +++ b/hw/goldfish_switch.c @@ -0,0 +1,172 @@ +/* Copyright (C) 2007-2008 The Android Open Source Project +** +** This software is licensed under the terms of the GNU General Public +** License version 2, as published by the Free Software Foundation, and +** may be copied, distributed, and modified under those terms. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +*/ +#include "qemu_file.h" +#include "goldfish_device.h" + +enum { + SW_NAME_LEN = 0x00, + SW_NAME_PTR = 0x04, + SW_FLAGS = 0x08, + SW_STATE = 0x0c, + SW_INT_STATUS = 0x10, + SW_INT_ENABLE = 0x14, + + SW_FLAGS_OUTPUT = 1U << 0 +}; + + +struct switch_state { + struct goldfish_device dev; + char *name; + uint32_t state; + uint32_t state_changed : 1; + uint32_t int_enable : 1; + uint32_t (*writefn)(void *opaque, uint32_t state); + void *writeopaque; +}; + +#define GOLDFISH_SWITCH_SAVE_VERSION 1 + +static void goldfish_switch_save(QEMUFile* f, void* opaque) +{ + struct switch_state* s = opaque; + + qemu_put_be32(f, s->state); + qemu_put_byte(f, s->state_changed); + qemu_put_byte(f, s->int_enable); +} + +static int goldfish_switch_load(QEMUFile* f, void* opaque, int version_id) +{ + struct switch_state* s = opaque; + + if (version_id != GOLDFISH_SWITCH_SAVE_VERSION) + return -1; + + s->state = qemu_get_be32(f); + s->state_changed = qemu_get_byte(f); + s->int_enable = qemu_get_byte(f); + + return 0; +} + +static uint32_t goldfish_switch_read(void *opaque, target_phys_addr_t offset) +{ + struct switch_state *s = (struct switch_state *)opaque; + offset -= s->dev.base; + + //printf("goldfish_switch_read %x %x\n", offset, size); + + switch (offset) { + case SW_NAME_LEN: + return strlen(s->name); + case SW_FLAGS: + return s->writefn ? SW_FLAGS_OUTPUT : 0; + case SW_STATE: + return s->state; + case SW_INT_STATUS: + if(s->state_changed && s->int_enable) { + s->state_changed = 0; + goldfish_device_set_irq(&s->dev, 0, 0); + return 1; + } + return 0; + default: + cpu_abort (cpu_single_env, "goldfish_switch_read: Bad offset %x\n", offset); + return 0; + } +} + +static void goldfish_switch_write(void *opaque, target_phys_addr_t offset, uint32_t value) +{ + struct switch_state *s = (struct switch_state *)opaque; + offset -= s->dev.base; + + //printf("goldfish_switch_read %x %x %x\n", offset, value, size); + + switch(offset) { + case SW_NAME_PTR: + pmemcpy(value, s->name, strlen(s->name)); + break; + + case SW_STATE: + if(s->writefn) { + uint32_t new_state; + new_state = s->writefn(s->writeopaque, value); + if(new_state != s->state) { + goldfish_switch_set_state(s, new_state); + } + } + else + cpu_abort (cpu_single_env, "goldfish_switch_write: write to SW_STATE on input\n"); + break; + + case SW_INT_ENABLE: + value &= 1; + if(s->state_changed && s->int_enable != value) + goldfish_device_set_irq(&s->dev, 0, value); + s->int_enable = value; + break; + + default: + cpu_abort (cpu_single_env, "goldfish_switch_write: Bad offset %x\n", offset); + } +} + +static CPUReadMemoryFunc *goldfish_switch_readfn[] = { + goldfish_switch_read, + goldfish_switch_read, + goldfish_switch_read +}; + +static CPUWriteMemoryFunc *goldfish_switch_writefn[] = { + goldfish_switch_write, + goldfish_switch_write, + goldfish_switch_write +}; + +void goldfish_switch_set_state(void *opaque, uint32_t state) +{ + struct switch_state *s = opaque; + s->state_changed = 1; + s->state = state; + if(s->int_enable) + goldfish_device_set_irq(&s->dev, 0, 1); +} + +void *goldfish_switch_add(char *name, uint32_t (*writefn)(void *opaque, uint32_t state), void *writeopaque, int id) +{ + int ret; + struct switch_state *s; + + s = qemu_mallocz(sizeof(*s)); + s->dev.name = "goldfish-switch"; + s->dev.id = id; + s->dev.size = 0x1000; + s->dev.irq_count = 1; + s->name = name; + s->writefn = writefn; + s->writeopaque = writeopaque; + + + ret = goldfish_device_add(&s->dev, goldfish_switch_readfn, goldfish_switch_writefn, s); + if(ret) { + qemu_free(s); + return NULL; + } + + register_savevm( "goldfish_switch", 0, GOLDFISH_SWITCH_SAVE_VERSION, + goldfish_switch_save, goldfish_switch_load, s); + + return s; +} + diff --git a/hw/goldfish_timer.c b/hw/goldfish_timer.c new file mode 100644 index 0000000..73f1455 --- /dev/null +++ b/hw/goldfish_timer.c @@ -0,0 +1,256 @@ +/* Copyright (C) 2007-2008 The Android Open Source Project +** +** This software is licensed under the terms of the GNU General Public +** License version 2, as published by the Free Software Foundation, and +** may be copied, distributed, and modified under those terms. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +*/ +#include "qemu-timer.h" +#include "cpu.h" +#include "arm_pic.h" +#include "goldfish_device.h" + +enum { + TIMER_TIME_LOW = 0x00, // get low bits of current time and update TIMER_TIME_HIGH + TIMER_TIME_HIGH = 0x04, // get high bits of time at last TIMER_TIME_LOW read + TIMER_ALARM_LOW = 0x08, // set low bits of alarm and activate it + TIMER_ALARM_HIGH = 0x0c, // set high bits of next alarm + TIMER_CLEAR_INTERRUPT = 0x10, + TIMER_CLEAR_ALARM = 0x14 +}; + +struct timer_state { + struct goldfish_device dev; + uint32_t alarm_low; + int32_t alarm_high; + int64_t now; + int armed; + QEMUTimer *timer; +}; + +#define GOLDFISH_TIMER_SAVE_VERSION 1 + +static void goldfish_timer_save(QEMUFile* f, void* opaque) +{ + struct timer_state* s = opaque; + + qemu_put_be64(f, s->now); /* in case the kernel is in the middle of a timer read */ + qemu_put_byte(f, s->armed); + if (s->armed) { + int64_t now = qemu_get_clock(vm_clock); + int64_t alarm = muldiv64(s->alarm_low | (int64_t)s->alarm_high << 32, ticks_per_sec, 1000000000); + qemu_put_be64(f, alarm-now); + } +} + +static int goldfish_timer_load(QEMUFile* f, void* opaque, int version_id) +{ + struct timer_state* s = opaque; + + if (version_id != GOLDFISH_TIMER_SAVE_VERSION) + return -1; + + s->now = qemu_get_be64(f); + s->armed = qemu_get_byte(f); + if (s->armed) { + int64_t now = qemu_get_clock(vm_clock); + int64_t diff = qemu_get_be64(f); + int64_t alarm = now + diff; + + if (alarm <= now) { + goldfish_device_set_irq(&s->dev, 0, 1); + s->armed = 0; + } else { + qemu_mod_timer(s->timer, alarm); + } + } + return 0; +} + +static uint32_t goldfish_timer_read(void *opaque, target_phys_addr_t offset) +{ + struct timer_state *s = (struct timer_state *)opaque; + offset -= s->dev.base; + switch(offset) { + case TIMER_TIME_LOW: + s->now = muldiv64(qemu_get_clock(vm_clock), 1000000000, ticks_per_sec); + return s->now; + case TIMER_TIME_HIGH: + return s->now >> 32; + default: + cpu_abort (cpu_single_env, "goldfish_timer_read: Bad offset %x\n", offset); + return 0; + } +} + +static void goldfish_timer_write(void *opaque, target_phys_addr_t offset, uint32_t value) +{ + struct timer_state *s = (struct timer_state *)opaque; + int64_t alarm, now; + offset -= s->dev.base; + switch(offset) { + case TIMER_ALARM_LOW: + s->alarm_low = value; + alarm = muldiv64(s->alarm_low | (int64_t)s->alarm_high << 32, ticks_per_sec, 1000000000); + now = qemu_get_clock(vm_clock); + if (alarm <= now) { + goldfish_device_set_irq(&s->dev, 0, 1); + } else { + qemu_mod_timer(s->timer, alarm); + s->armed = 1; + } + break; + case TIMER_ALARM_HIGH: + s->alarm_high = value; + //printf("alarm_high %d\n", s->alarm_high); + break; + case TIMER_CLEAR_ALARM: + qemu_del_timer(s->timer); + s->armed = 0; + /* fall through */ + case TIMER_CLEAR_INTERRUPT: + goldfish_device_set_irq(&s->dev, 0, 0); + break; + default: + cpu_abort (cpu_single_env, "goldfish_timer_write: Bad offset %x\n", offset); + } +} + +static void goldfish_timer_tick(void *opaque) +{ + struct timer_state *s = (struct timer_state *)opaque; + + s->armed = 0; + goldfish_device_set_irq(&s->dev, 0, 1); +} + +struct rtc_state { + struct goldfish_device dev; + uint32_t alarm_low; + int32_t alarm_high; + int64_t now; +}; + +/* we save the RTC for the case where the kernel is in the middle of a rtc_read + * (i.e. it has read the low 32-bit of s->now, but not the high 32-bits yet */ +#define GOLDFISH_RTC_SAVE_VERSION 1 + +static void goldfish_rtc_save(QEMUFile* f, void* opaque) +{ + struct rtc_state* s = opaque; + + qemu_put_be64(f, s->now); +} + +static int goldfish_rtc_load(QEMUFile* f, void* opaque, int version_id) +{ + struct rtc_state* s = opaque; + + if (version_id != GOLDFISH_RTC_SAVE_VERSION) + return -1; + + /* this is an old value that is not correct. but that's ok anyway */ + s->now = qemu_get_be64(f); + return 0; +} + +static uint32_t goldfish_rtc_read(void *opaque, target_phys_addr_t offset) +{ + struct rtc_state *s = (struct rtc_state *)opaque; + offset -= s->dev.base; + switch(offset) { + case 0x0: + s->now = (int64_t)time(NULL) * 1000000000; + return s->now; + case 0x4: + return s->now >> 32; + default: + cpu_abort (cpu_single_env, "goldfish_rtc_read: Bad offset %x\n", offset); + return 0; + } +} + +static void goldfish_rtc_write(void *opaque, target_phys_addr_t offset, uint32_t value) +{ + struct rtc_state *s = (struct rtc_state *)opaque; + int64_t alarm; + offset -= s->dev.base; + switch(offset) { + case 0x8: + s->alarm_low = value; + alarm = s->alarm_low | (int64_t)s->alarm_high << 32; + //printf("next alarm at %lld, tps %lld\n", alarm, ticks_per_sec); + //qemu_mod_timer(s->timer, alarm); + break; + case 0xc: + s->alarm_high = value; + //printf("alarm_high %d\n", s->alarm_high); + break; + case 0x10: + goldfish_device_set_irq(&s->dev, 0, 0); + break; + default: + cpu_abort (cpu_single_env, "goldfish_rtc_write: Bad offset %x\n", offset); + } +} + +static struct timer_state timer_state = { + .dev = { + .name = "goldfish_timer", + .id = -1, + .size = 0x1000, + .irq_count = 1, + } +}; + +static struct timer_state rtc_state = { + .dev = { + .name = "goldfish_rtc", + .id = -1, + .size = 0x1000, + .irq_count = 1, + } +}; + +static CPUReadMemoryFunc *goldfish_timer_readfn[] = { + goldfish_timer_read, + goldfish_timer_read, + goldfish_timer_read +}; + +static CPUWriteMemoryFunc *goldfish_timer_writefn[] = { + goldfish_timer_write, + goldfish_timer_write, + goldfish_timer_write +}; + +static CPUReadMemoryFunc *goldfish_rtc_readfn[] = { + goldfish_rtc_read, + goldfish_rtc_read, + goldfish_rtc_read +}; + +static CPUWriteMemoryFunc *goldfish_rtc_writefn[] = { + goldfish_rtc_write, + goldfish_rtc_write, + goldfish_rtc_write +}; + +void goldfish_timer_and_rtc_init(uint32_t timerbase, int timerirq) +{ + timer_state.dev.base = timerbase; + timer_state.dev.irq = timerirq; + timer_state.timer = qemu_new_timer(vm_clock, goldfish_timer_tick, &timer_state); + goldfish_device_add(&timer_state.dev, goldfish_timer_readfn, goldfish_timer_writefn, &timer_state); + register_savevm( "goldfish_timer", 0, GOLDFISH_TIMER_SAVE_VERSION, + goldfish_timer_save, goldfish_timer_load, &timer_state); + + goldfish_device_add(&rtc_state.dev, goldfish_rtc_readfn, goldfish_rtc_writefn, &rtc_state); + register_savevm( "goldfish_rtc", 0, GOLDFISH_RTC_SAVE_VERSION, + goldfish_rtc_save, goldfish_rtc_load, &rtc_state); +} + diff --git a/hw/goldfish_trace.c b/hw/goldfish_trace.c new file mode 100644 index 0000000..ad0eba5 --- /dev/null +++ b/hw/goldfish_trace.c @@ -0,0 +1,251 @@ +/* Copyright (C) 2007-2008 The Android Open Source Project +** +** This software is licensed under the terms of the GNU General Public +** License version 2, as published by the Free Software Foundation, and +** may be copied, distributed, and modified under those terms. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +*/ +/* + * Virtual hardware for bridging the FUSE kernel module + * in the emulated OS and outside file system + */ +#include "qemu_file.h" +#include "goldfish_trace.h" + +//#define DEBUG 1 + +extern void cpu_loop_exit(void); + +extern int tracing; + +/* for execve */ +static char path[CLIENT_PAGE_SIZE]; +static char arg[CLIENT_PAGE_SIZE]; +static unsigned long vstart; // VM start +static unsigned long vend; // VM end +static unsigned long eoff; // offset in EXE file +static unsigned cmdlen; // cmdline length +static unsigned pid; // PID (really thread id) +static unsigned tgid; // thread group id (really process id) +static unsigned long dsaddr; // dynamic symbol address +static unsigned long unmap_start; // start address to unmap + +/* for context switch */ +//static unsigned long cs_pid; // context switch PID + +/* I/O write */ +static void trace_dev_write(void *opaque, target_phys_addr_t offset, uint32_t value) +{ + trace_dev_state *s = (trace_dev_state *)opaque; + + offset -= s->base; + switch (offset >> 2) { + case TRACE_DEV_REG_SWITCH: // context switch, switch to pid + trace_switch(value); +#ifdef DEBUG + printf("QEMU.trace: kernel, context switch %u\n", value); +#endif + break; + case TRACE_DEV_REG_TGID: // save the tgid for the following fork/clone + tgid = value; +#ifdef DEBUG + printf("QEMU.trace: kernel, tgid %u\n", value); +#endif + break; + case TRACE_DEV_REG_FORK: // fork, fork new pid + trace_fork(tgid, value); +#ifdef DEBUG + printf("QEMU.trace: kernel, fork %u\n", value); +#endif + break; + case TRACE_DEV_REG_CLONE: // fork, clone new pid (i.e. thread) + trace_clone(tgid, value); +#ifdef DEBUG + printf("QEMU.trace: kernel, clone %u\n", value); +#endif + break; + case TRACE_DEV_REG_EXECVE_VMSTART: // execve, vstart + vstart = value; + break; + case TRACE_DEV_REG_EXECVE_VMEND: // execve, vend + vend = value; + break; + case TRACE_DEV_REG_EXECVE_OFFSET: // execve, offset in EXE + eoff = value; + break; + case TRACE_DEV_REG_EXECVE_EXEPATH: // init exec, path of EXE + vstrcpy(value, path, CLIENT_PAGE_SIZE); + trace_init_exec(vstart, vend, eoff, path); +#ifdef DEBUG + printf("QEMU.trace: kernel, init exec [%lx,%lx]@%lx [%s]\n", vstart, vend, eoff, path); +#endif + path[0] = 0; + break; + case TRACE_DEV_REG_CMDLINE_LEN: // execve, process cmdline length + cmdlen = value; + break; + case TRACE_DEV_REG_CMDLINE: // execve, process cmdline + vmemcpy(value, arg, cmdlen); + trace_execve(arg, cmdlen); +#ifdef DEBUG + { + int i; + for (i = 0; i < cmdlen; i ++) + if (i != cmdlen - 1 && arg[i] == 0) + arg[i] = ' '; + printf("QEMU.trace: kernel, execve %s[%d]\n", arg, cmdlen); + } +#endif + arg[0] = 0; + break; + case TRACE_DEV_REG_EXIT: // exit, exit current process with exit code + trace_exit(value); +#ifdef DEBUG + printf("QEMU.trace: kernel, exit %x\n", value); +#endif + break; + case TRACE_DEV_REG_NAME: // record thread name + vstrcpy(value, path, CLIENT_PAGE_SIZE); + + // Remove the trailing newline if it exists + int len = strlen(path); + if (path[len - 1] == '\n') { + path[len - 1] = 0; + } + trace_name(path); +#ifdef DEBUG + printf("QEMU.trace: kernel, name %s\n", path); +#endif + break; + case TRACE_DEV_REG_MMAP_EXEPATH: // mmap, path of EXE, the others are same as execve + vstrcpy(value, path, CLIENT_PAGE_SIZE); + trace_mmap(vstart, vend, eoff, path); +#ifdef DEBUG + printf("QEMU.trace: kernel, mmap [%lx,%lx]@%lx [%s]\n", vstart, vend, eoff, path); +#endif + path[0] = 0; + break; + case TRACE_DEV_REG_INIT_PID: // init, name the pid that starts before device registered + pid = value; + break; + case TRACE_DEV_REG_INIT_NAME: // init, the comm of the init pid + vstrcpy(value, path, CLIENT_PAGE_SIZE); + trace_init_name(tgid, pid, path); +#ifdef DEBUG + printf("QEMU.trace: kernel, init name %u [%s]\n", pid, path); +#endif + path[0] = 0; + break; + + case TRACE_DEV_REG_DYN_SYM_ADDR: // dynamic symbol address + dsaddr = value; + break; + case TRACE_DEV_REG_DYN_SYM: // add dynamic symbol + vstrcpy(value, arg, CLIENT_PAGE_SIZE); + trace_dynamic_symbol_add(dsaddr, arg); +#ifdef DEBUG + printf("QEMU.trace: dynamic symbol %lx:%s\n", dsaddr, arg); +#endif + arg[0] = 0; + break; + case TRACE_DEV_REG_REMOVE_ADDR: // remove dynamic symbol addr + trace_dynamic_symbol_remove(value); +#ifdef DEBUG + printf("QEMU.trace: dynamic symbol remove %lx\n", dsaddr); +#endif + arg[0] = 0; + break; + + case TRACE_DEV_REG_PRINT_STR: // print string + vstrcpy(value, arg, CLIENT_PAGE_SIZE); + printf("%s", arg); + arg[0] = 0; + break; + case TRACE_DEV_REG_PRINT_NUM_DEC: // print number in decimal + printf("%d", value); + break; + case TRACE_DEV_REG_PRINT_NUM_HEX: // print number in hexical + printf("%x", value); + break; + + case TRACE_DEV_REG_STOP_EMU: // stop the VM execution + // To ensure that the number of instructions executed in this + // block is correct, we pretend that there was an exception. + trace_exception(0); + + cpu_single_env->exception_index = EXCP_HLT; + cpu_single_env->halted = 1; + qemu_system_shutdown_request(); + cpu_loop_exit(); + break; + + case TRACE_DEV_REG_ENABLE: // tracing enable: 0 = stop, 1 = start + if (value == 1) + start_tracing(); + else if (value == 0) { + stop_tracing(); + + // To ensure that the number of instructions executed in this + // block is correct, we pretend that there was an exception. + trace_exception(0); + } + break; + + case TRACE_DEV_REG_UNMAP_START: + unmap_start = value; + break; + case TRACE_DEV_REG_UNMAP_END: + trace_munmap(unmap_start, value); + break; + + default: + cpu_abort(cpu_single_env, "trace_dev_write: Bad offset %x\n", offset); + break; + } +} + +/* I/O read */ +static uint32_t trace_dev_read(void *opaque, target_phys_addr_t offset) +{ + trace_dev_state *s = (trace_dev_state *)opaque; + + offset -= s->base; + switch (offset >> 2) { + case TRACE_DEV_REG_ENABLE: // tracing enable + return tracing; + default: + cpu_abort(cpu_single_env, "trace_dev_read: Bad offset %x\n", offset); + return 0; + } + return 0; +} + +static CPUReadMemoryFunc *trace_dev_readfn[] = { + trace_dev_read, + trace_dev_read, + trace_dev_read +}; + +static CPUWriteMemoryFunc *trace_dev_writefn[] = { + trace_dev_write, + trace_dev_write, + trace_dev_write +}; + +/* initialize the trace device */ +void trace_dev_init(uint32_t base) +{ + int iomemtype; + trace_dev_state *s; + + s = (trace_dev_state *)qemu_mallocz(sizeof(trace_dev_state)); + iomemtype = cpu_register_io_memory(0, trace_dev_readfn, trace_dev_writefn, s); + cpu_register_physical_memory(base, 0x00000fff, iomemtype); + s->base = base; + + path[0] = arg[0] = '\0'; +} diff --git a/hw/goldfish_trace.h b/hw/goldfish_trace.h new file mode 100644 index 0000000..44190ee --- /dev/null +++ b/hw/goldfish_trace.h @@ -0,0 +1,79 @@ +/* Copyright (C) 2007-2008 The Android Open Source Project +** +** This software is licensed under the terms of the GNU General Public +** License version 2, as published by the Free Software Foundation, and +** may be copied, distributed, and modified under those terms. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +*/ +#ifndef _TRACE_DEV_H_ +#define _TRACE_DEV_H_ + +#define CLIENT_PAGE_SIZE 4096 + +/* trace device registers */ +#define TRACE_DEV_REG_SWITCH 0 +#define TRACE_DEV_REG_FORK 1 +#define TRACE_DEV_REG_EXECVE_PID 2 +#define TRACE_DEV_REG_EXECVE_VMSTART 3 +#define TRACE_DEV_REG_EXECVE_VMEND 4 +#define TRACE_DEV_REG_EXECVE_OFFSET 5 +#define TRACE_DEV_REG_EXECVE_EXEPATH 6 +#define TRACE_DEV_REG_EXIT 7 +#define TRACE_DEV_REG_CMDLINE 8 +#define TRACE_DEV_REG_CMDLINE_LEN 9 +#define TRACE_DEV_REG_MMAP_EXEPATH 10 +#define TRACE_DEV_REG_INIT_PID 11 +#define TRACE_DEV_REG_INIT_NAME 12 +#define TRACE_DEV_REG_CLONE 13 +#define TRACE_DEV_REG_UNMAP_START 14 +#define TRACE_DEV_REG_UNMAP_END 15 +#define TRACE_DEV_REG_NAME 16 +#define TRACE_DEV_REG_TGID 17 +#define TRACE_DEV_REG_DYN_SYM 50 +#define TRACE_DEV_REG_DYN_SYM_ADDR 51 +#define TRACE_DEV_REG_REMOVE_ADDR 52 +#define TRACE_DEV_REG_PRINT_STR 60 +#define TRACE_DEV_REG_PRINT_NUM_DEC 61 +#define TRACE_DEV_REG_PRINT_NUM_HEX 62 +#define TRACE_DEV_REG_STOP_EMU 90 +#define TRACE_DEV_REG_ENABLE 100 + +/* the virtual trace device state */ +typedef struct { + uint32_t base; +} trace_dev_state; + +/* + * interfaces for copy from virtual space + * from target-arm/op_helper.c + */ +extern target_phys_addr_t v2p(target_ulong ptr, int is_user); +extern void vmemcpy(target_ulong ptr, char *buf, int size); +extern void pmemcpy(target_ulong ptr, const char* buf, int size); +extern void vstrcpy(target_ulong ptr, char *buf, int max); + +/* + * interfaces to trace module to signal kernel events + */ +extern void trace_switch(int pid); +extern void trace_fork(int tgid, int pid); +extern void trace_clone(int tgid, int pid); +extern void trace_execve(const char *arg, int len); +extern void trace_exit(int exitcode); +extern void trace_mmap(unsigned long vstart, unsigned long vend, + unsigned long offset, const char *path); +extern void trace_munmap(unsigned long vstart, unsigned long vend); +extern void trace_dynamic_symbol_add(unsigned long vaddr, const char *name); +extern void trace_dynamic_symbol_remove(unsigned long vaddr); +extern void trace_init_name(int tgid, int pid, const char *name); +extern void trace_init_exec(unsigned long start, unsigned long end, + unsigned long offset, const char *exe); +extern void start_tracing(void); +extern void stop_tracing(void); +extern void trace_exception(uint32 target_pc); + +#endif diff --git a/hw/goldfish_tty.c b/hw/goldfish_tty.c new file mode 100644 index 0000000..aa62d75 --- /dev/null +++ b/hw/goldfish_tty.c @@ -0,0 +1,226 @@ +/* Copyright (C) 2007-2008 The Android Open Source Project +** +** This software is licensed under the terms of the GNU General Public +** License version 2, as published by the Free Software Foundation, and +** may be copied, distributed, and modified under those terms. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +*/ +#include "qemu_file.h" +#include "qemu-char.h" +#include "goldfish_device.h" + +enum { + TTY_PUT_CHAR = 0x00, + TTY_BYTES_READY = 0x04, + TTY_CMD = 0x08, + + TTY_DATA_PTR = 0x10, + TTY_DATA_LEN = 0x14, + + TTY_CMD_INT_DISABLE = 0, + TTY_CMD_INT_ENABLE = 1, + TTY_CMD_WRITE_BUFFER = 2, + TTY_CMD_READ_BUFFER = 3, +}; + +struct tty_state { + struct goldfish_device dev; + CharDriverState *cs; + uint32_t ptr; + uint32_t ptr_len; + uint32_t ready; + uint8_t data[128]; + uint32_t data_count; +}; + +#define GOLDFISH_TTY_SAVE_VERSION 1 + +static void goldfish_tty_save(QEMUFile* f, void* opaque) +{ + struct tty_state* s = opaque; + + qemu_put_be32( f, s->ptr ); + qemu_put_be32( f, s->ptr_len ); + qemu_put_byte( f, s->ready ); + qemu_put_byte( f, s->data_count ); + qemu_put_buffer( f, s->data, s->data_count ); +} + +static int goldfish_tty_load(QEMUFile* f, void* opaque, int version_id) +{ + struct tty_state* s = opaque; + + if (version_id != GOLDFISH_TTY_SAVE_VERSION) + return -1; + + s->ptr = qemu_get_be32(f); + s->ptr_len = qemu_get_be32(f); + s->ready = qemu_get_byte(f); + s->data_count = qemu_get_byte(f); + qemu_get_buffer(f, s->data, s->data_count); + + return 0; +} + +static uint32_t goldfish_tty_read(void *opaque, target_phys_addr_t offset) +{ + struct tty_state *s = (struct tty_state *)opaque; + offset -= s->dev.base; + + //printf("goldfish_tty_read %x %x\n", offset, size); + + switch (offset) { + case TTY_BYTES_READY: + return s->data_count; + default: + cpu_abort (cpu_single_env, "goldfish_tty_read: Bad offset %x\n", offset); + return 0; + } +} + +static void goldfish_tty_write(void *opaque, target_phys_addr_t offset, uint32_t value) +{ + struct tty_state *s = (struct tty_state *)opaque; + offset -= s->dev.base; + + //printf("goldfish_tty_read %x %x %x\n", offset, value, size); + + switch(offset) { + case TTY_PUT_CHAR: { + uint8_t ch = value; + if(s->cs) + qemu_chr_write(s->cs, &ch, 1); + } break; + + case TTY_CMD: + switch(value) { + case TTY_CMD_INT_DISABLE: + if(s->ready) { + if(s->data_count > 0) + goldfish_device_set_irq(&s->dev, 0, 0); + s->ready = 0; + } + break; + + case TTY_CMD_INT_ENABLE: + if(!s->ready) { + if(s->data_count > 0) + goldfish_device_set_irq(&s->dev, 0, 1); + s->ready = 1; + } + break; + + case TTY_CMD_WRITE_BUFFER: + if(s->cs) { + int len; + target_ulong buf; + + buf = s->ptr; + len = s->ptr_len; + + while(len) { + int page_remain = TARGET_PAGE_SIZE - (buf & ~TARGET_PAGE_MASK); + int to_write = len; + uint8_t *phys = (uint8_t *)v2p(buf, 0); + if(to_write > page_remain) + to_write = page_remain; + qemu_chr_write(s->cs, phys, to_write); + buf += to_write; + len -= to_write; + } + //printf("goldfish_tty_write: got %d bytes from %x\n", s->ptr_len, s->ptr); + } + break; + + case TTY_CMD_READ_BUFFER: + if(s->ptr_len > s->data_count) + cpu_abort (cpu_single_env, "goldfish_tty_write: reading more data than available %d %d\n", s->ptr_len, s->data_count); + pmemcpy(s->ptr, s->data, s->ptr_len); + //printf("goldfish_tty_write: read %d bytes to %x\n", s->ptr_len, s->ptr); + if(s->data_count > s->ptr_len) + memmove(s->data, s->data + s->ptr_len, s->data_count - s->ptr_len); + s->data_count -= s->ptr_len; + if(s->data_count == 0 && s->ready) + goldfish_device_set_irq(&s->dev, 0, 0); + break; + + default: + cpu_abort (cpu_single_env, "goldfish_tty_write: Bad command %x\n", value); + }; + break; + + case TTY_DATA_PTR: + s->ptr = value; + break; + + case TTY_DATA_LEN: + s->ptr_len = value; + break; + + default: + cpu_abort (cpu_single_env, "goldfish_tty_write: Bad offset %x\n", offset); + } +} + +static int tty_can_receive(void *opaque) +{ + struct tty_state *s = opaque; + + return (sizeof(s->data) - s->data_count); +} + +static void tty_receive(void *opaque, const uint8_t *buf, int size) +{ + struct tty_state *s = opaque; + + memcpy(s->data + s->data_count, buf, size); + s->data_count += size; + if(s->data_count > 0 && s->ready) + goldfish_device_set_irq(&s->dev, 0, 1); +} + +static CPUReadMemoryFunc *goldfish_tty_readfn[] = { + goldfish_tty_read, + goldfish_tty_read, + goldfish_tty_read +}; + +static CPUWriteMemoryFunc *goldfish_tty_writefn[] = { + goldfish_tty_write, + goldfish_tty_write, + goldfish_tty_write +}; + +int goldfish_tty_add(CharDriverState *cs, int id, uint32_t base, int irq) +{ + int ret; + struct tty_state *s; + static int instance_id = 0; + + s = qemu_mallocz(sizeof(*s)); + s->dev.name = "goldfish_tty"; + s->dev.id = id; + s->dev.base = base; + s->dev.size = 0x1000; + s->dev.irq = irq; + s->dev.irq_count = 1; + s->cs = cs; + + if(cs) { + qemu_chr_add_handlers(cs, tty_can_receive, tty_receive, NULL, s); + } + + ret = goldfish_device_add(&s->dev, goldfish_tty_readfn, goldfish_tty_writefn, s); + if(ret) { + qemu_free(s); + } else { + register_savevm( "goldfish_tty", instance_id++, GOLDFISH_TTY_SAVE_VERSION, + goldfish_tty_save, goldfish_tty_load, s); + } + return ret; +} + @@ -0,0 +1,110 @@ +/* Declarations for use by hardware emulation. */ +#ifndef QEMU_HW_H +#define QEMU_HW_H + +#include "qemu-common.h" +#include "irq.h" +#include "cpu.h" + +/* VM Load/Save */ + +QEMUFile *qemu_fopen(const char *filename, const char *mode); +void qemu_fflush(QEMUFile *f); +void qemu_fclose(QEMUFile *f); +void qemu_put_buffer(QEMUFile *f, const uint8_t *buf, int size); +void qemu_put_byte(QEMUFile *f, int v); +void qemu_put_be16(QEMUFile *f, unsigned int v); +void qemu_put_be32(QEMUFile *f, unsigned int v); +void qemu_put_be64(QEMUFile *f, uint64_t v); +int qemu_get_buffer(QEMUFile *f, uint8_t *buf, int size); +int qemu_get_byte(QEMUFile *f); +unsigned int qemu_get_be16(QEMUFile *f); +unsigned int qemu_get_be32(QEMUFile *f); +uint64_t qemu_get_be64(QEMUFile *f); + +static inline void qemu_put_be64s(QEMUFile *f, const uint64_t *pv) +{ + qemu_put_be64(f, *pv); +} + +static inline void qemu_put_be32s(QEMUFile *f, const uint32_t *pv) +{ + qemu_put_be32(f, *pv); +} + +static inline void qemu_put_be16s(QEMUFile *f, const uint16_t *pv) +{ + qemu_put_be16(f, *pv); +} + +static inline void qemu_put_8s(QEMUFile *f, const uint8_t *pv) +{ + qemu_put_byte(f, *pv); +} + +static inline void qemu_get_be64s(QEMUFile *f, uint64_t *pv) +{ + *pv = qemu_get_be64(f); +} + +static inline void qemu_get_be32s(QEMUFile *f, uint32_t *pv) +{ + *pv = qemu_get_be32(f); +} + +static inline void qemu_get_be16s(QEMUFile *f, uint16_t *pv) +{ + *pv = qemu_get_be16(f); +} + +static inline void qemu_get_8s(QEMUFile *f, uint8_t *pv) +{ + *pv = qemu_get_byte(f); +} + +#ifdef NEED_CPU_H +#if TARGET_LONG_BITS == 64 +#define qemu_put_betl qemu_put_be64 +#define qemu_get_betl qemu_get_be64 +#define qemu_put_betls qemu_put_be64s +#define qemu_get_betls qemu_get_be64s +#else +#define qemu_put_betl qemu_put_be32 +#define qemu_get_betl qemu_get_be32 +#define qemu_put_betls qemu_put_be32s +#define qemu_get_betls qemu_get_be32s +#endif +#endif + +int64_t qemu_ftell(QEMUFile *f); +int64_t qemu_fseek(QEMUFile *f, int64_t pos, int whence); + +typedef void SaveStateHandler(QEMUFile *f, void *opaque); +typedef int LoadStateHandler(QEMUFile *f, void *opaque, int version_id); + +int register_savevm(const char *idstr, + int instance_id, + int version_id, + SaveStateHandler *save_state, + LoadStateHandler *load_state, + void *opaque); + +typedef void QEMUResetHandler(void *opaque); + +void qemu_register_reset(QEMUResetHandler *func, void *opaque); + +/* handler to set the boot_device for a specific type of QEMUMachine */ +/* return 0 if success */ +typedef int QEMUBootSetHandler(const char *boot_device); +extern QEMUBootSetHandler *qemu_boot_set_handler; +void qemu_register_boot_set(QEMUBootSetHandler *func); + +/* These should really be in isa.h, but are here to make pc.h happy. */ +typedef void (IOPortWriteFunc)(void *opaque, uint32_t address, uint32_t data); +typedef uint32_t (IOPortReadFunc)(void *opaque, uint32_t address); + + +/* ANDROID: copy memory from the QEMU buffer to simulated virtual space */ +extern void pmemcpy(target_ulong ptr, const char *buf, int size); + +#endif diff --git a/hw/irq.c b/hw/irq.c new file mode 100644 index 0000000..eca707d --- /dev/null +++ b/hw/irq.c @@ -0,0 +1,71 @@ +/* + * QEMU IRQ/GPIO common code. + * + * Copyright (c) 2007 CodeSourcery. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "qemu-common.h" +#include "irq.h" + +struct IRQState { + qemu_irq_handler handler; + void *opaque; + int n; +}; + +void qemu_set_irq(qemu_irq irq, int level) +{ + if (!irq) + return; + + irq->handler(irq->opaque, irq->n, level); +} + +qemu_irq *qemu_allocate_irqs(qemu_irq_handler handler, void *opaque, int n) +{ + qemu_irq *s; + struct IRQState *p; + int i; + + s = (qemu_irq *)qemu_mallocz(sizeof(qemu_irq) * n); + p = (struct IRQState *)qemu_mallocz(sizeof(struct IRQState) * n); + for (i = 0; i < n; i++) { + p->handler = handler; + p->opaque = opaque; + p->n = i; + s[i] = p; + p++; + } + return s; +} + +static void qemu_notirq(void *opaque, int line, int level) +{ + struct IRQState *irq = opaque; + + irq->handler(irq->opaque, irq->n, !level); +} + +qemu_irq qemu_irq_invert(qemu_irq irq) +{ + /* The default state for IRQs is low, so raise the output now. */ + qemu_irq_raise(irq); + return qemu_allocate_irqs(qemu_notirq, irq, 1)[0]; +} diff --git a/hw/irq.h b/hw/irq.h new file mode 100644 index 0000000..0880ad2 --- /dev/null +++ b/hw/irq.h @@ -0,0 +1,34 @@ +#ifndef QEMU_IRQ_H +#define QEMU_IRQ_H + +/* Generic IRQ/GPIO pin infrastructure. */ + +/* FIXME: Rmove one of these. */ +typedef void (*qemu_irq_handler)(void *opaque, int n, int level); +typedef void SetIRQFunc(void *opaque, int irq_num, int level); + +void qemu_set_irq(qemu_irq irq, int level); + +static inline void qemu_irq_raise(qemu_irq irq) +{ + qemu_set_irq(irq, 1); +} + +static inline void qemu_irq_lower(qemu_irq irq) +{ + qemu_set_irq(irq, 0); +} + +static inline void qemu_irq_pulse(qemu_irq irq) +{ + qemu_set_irq(irq, 1); + qemu_set_irq(irq, 0); +} + +/* Returns an array of N IRQs. */ +qemu_irq *qemu_allocate_irqs(qemu_irq_handler handler, void *opaque, int n); + +/* Returns a new IRQ with opposite polarity. */ +qemu_irq qemu_irq_invert(qemu_irq irq); + +#endif diff --git a/hw/isa.h b/hw/isa.h new file mode 100644 index 0000000..222e4f3 --- /dev/null +++ b/hw/isa.h @@ -0,0 +1,27 @@ +#ifndef HW_ISA_H +#define HW_ISA_H +/* ISA bus */ + +extern target_phys_addr_t isa_mem_base; + +int register_ioport_read(int start, int length, int size, + IOPortReadFunc *func, void *opaque); +int register_ioport_write(int start, int length, int size, + IOPortWriteFunc *func, void *opaque); +void isa_unassign_ioport(int start, int length); + +void isa_mmio_init(target_phys_addr_t base, target_phys_addr_t size); + +/* dma.c */ +int DMA_get_channel_mode (int nchan); +int DMA_read_memory (int nchan, void *buf, int pos, int size); +int DMA_write_memory (int nchan, void *buf, int pos, int size); +void DMA_hold_DREQ (int nchan); +void DMA_release_DREQ (int nchan); +void DMA_schedule(int nchan); +void DMA_run (void); +void DMA_init (int high_page_enable); +void DMA_register_channel (int nchan, + DMA_transfer_handler transfer_handler, + void *opaque); +#endif diff --git a/hw/mmc.h b/hw/mmc.h new file mode 100644 index 0000000..3ae3ea9 --- /dev/null +++ b/hw/mmc.h @@ -0,0 +1,214 @@ +/* + * Header for MultiMediaCard (MMC) + * + * Copyright 2002 Hewlett-Packard Company + * + * Use consistent with the GNU GPL is permitted, + * provided that this copyright notice is + * preserved in its entirety in all copies and derived works. + * + * HEWLETT-PACKARD COMPANY MAKES NO WARRANTIES, EXPRESSED OR IMPLIED, + * AS TO THE USEFULNESS OR CORRECTNESS OF THIS CODE OR ITS + * FITNESS FOR ANY PARTICULAR PURPOSE. + * + * Many thanks to Alessandro Rubini and Jonathan Corbet! + * + * Based strongly on code by: + * + * Author: Yong-iL Joh <tolkien@mizi.com> + * Date : $Date: 2002/06/18 12:37:30 $ + * + * Author: Andrew Christian + * 15 May 2002 + */ + +#ifndef MMC_MMC_H +#define MMC_MMC_H + +/* Standard MMC commands (4.1) type argument response */ + /* class 1 */ +#define MMC_GO_IDLE_STATE 0 /* bc */ +#define MMC_SEND_OP_COND 1 /* bcr [31:0] OCR R3 */ +#define MMC_ALL_SEND_CID 2 /* bcr R2 */ +#define MMC_SET_RELATIVE_ADDR 3 /* ac [31:16] RCA R1 */ +#define MMC_SET_DSR 4 /* bc [31:16] RCA */ +#define MMC_SWITCH 6 /* ac [31:0] See below R1b */ +#define MMC_SELECT_CARD 7 /* ac [31:16] RCA R1 */ +#define MMC_SEND_EXT_CSD 8 /* adtc R1 */ +#define MMC_SEND_CSD 9 /* ac [31:16] RCA R2 */ +#define MMC_SEND_CID 10 /* ac [31:16] RCA R2 */ +#define MMC_READ_DAT_UNTIL_STOP 11 /* adtc [31:0] dadr R1 */ +#define MMC_STOP_TRANSMISSION 12 /* ac R1b */ +#define MMC_SEND_STATUS 13 /* ac [31:16] RCA R1 */ +#define MMC_GO_INACTIVE_STATE 15 /* ac [31:16] RCA */ + + /* class 2 */ +#define MMC_SET_BLOCKLEN 16 /* ac [31:0] block len R1 */ +#define MMC_READ_SINGLE_BLOCK 17 /* adtc [31:0] data addr R1 */ +#define MMC_READ_MULTIPLE_BLOCK 18 /* adtc [31:0] data addr R1 */ + + /* class 3 */ +#define MMC_WRITE_DAT_UNTIL_STOP 20 /* adtc [31:0] data addr R1 */ + + /* class 4 */ +#define MMC_SET_BLOCK_COUNT 23 /* adtc [31:0] data addr R1 */ +#define MMC_WRITE_BLOCK 24 /* adtc [31:0] data addr R1 */ +#define MMC_WRITE_MULTIPLE_BLOCK 25 /* adtc R1 */ +#define MMC_PROGRAM_CID 26 /* adtc R1 */ +#define MMC_PROGRAM_CSD 27 /* adtc R1 */ + + /* class 6 */ +#define MMC_SET_WRITE_PROT 28 /* ac [31:0] data addr R1b */ +#define MMC_CLR_WRITE_PROT 29 /* ac [31:0] data addr R1b */ +#define MMC_SEND_WRITE_PROT 30 /* adtc [31:0] wpdata addr R1 */ + + /* class 5 */ +#define MMC_ERASE_GROUP_START 35 /* ac [31:0] data addr R1 */ +#define MMC_ERASE_GROUP_END 36 /* ac [31:0] data addr R1 */ +#define MMC_ERASE 38 /* ac R1b */ + + /* class 9 */ +#define MMC_FAST_IO 39 /* ac <Complex> R4 */ +#define MMC_GO_IRQ_STATE 40 /* bcr R5 */ + + /* class 7 */ +#define MMC_LOCK_UNLOCK 42 /* adtc R1b */ + + /* class 8 */ +#define MMC_APP_CMD 55 /* ac [31:16] RCA R1 */ +#define MMC_GEN_CMD 56 /* adtc [0] RD/WR R1 */ + +/* + * MMC_SWITCH argument format: + * + * [31:26] Always 0 + * [25:24] Access Mode + * [23:16] Location of target Byte in EXT_CSD + * [15:08] Value Byte + * [07:03] Always 0 + * [02:00] Command Set + */ + +/* + MMC status in R1 + Type + e : error bit + s : status bit + r : detected and set for the actual command response + x : detected and set during command execution. the host must poll + the card by sending status command in order to read these bits. + Clear condition + a : according to the card state + b : always related to the previous command. Reception of + a valid command will clear it (with a delay of one command) + c : clear by read + */ + +#define R1_OUT_OF_RANGE (1 << 31) /* er, c */ +#define R1_ADDRESS_ERROR (1 << 30) /* erx, c */ +#define R1_BLOCK_LEN_ERROR (1 << 29) /* er, c */ +#define R1_ERASE_SEQ_ERROR (1 << 28) /* er, c */ +#define R1_ERASE_PARAM (1 << 27) /* ex, c */ +#define R1_WP_VIOLATION (1 << 26) /* erx, c */ +#define R1_CARD_IS_LOCKED (1 << 25) /* sx, a */ +#define R1_LOCK_UNLOCK_FAILED (1 << 24) /* erx, c */ +#define R1_COM_CRC_ERROR (1 << 23) /* er, b */ +#define R1_ILLEGAL_COMMAND (1 << 22) /* er, b */ +#define R1_CARD_ECC_FAILED (1 << 21) /* ex, c */ +#define R1_CC_ERROR (1 << 20) /* erx, c */ +#define R1_ERROR (1 << 19) /* erx, c */ +#define R1_UNDERRUN (1 << 18) /* ex, c */ +#define R1_OVERRUN (1 << 17) /* ex, c */ +#define R1_CID_CSD_OVERWRITE (1 << 16) /* erx, c, CID/CSD overwrite */ +#define R1_WP_ERASE_SKIP (1 << 15) /* sx, c */ +#define R1_CARD_ECC_DISABLED (1 << 14) /* sx, a */ +#define R1_ERASE_RESET (1 << 13) /* sr, c */ +#define R1_STATUS(x) (x & 0xFFFFE000) +#define R1_CURRENT_STATE(x) ((x & 0x00001E00) >> 9) /* sx, b (4 bits) */ +#define R1_READY_FOR_DATA (1 << 8) /* sx, a */ +#define R1_APP_CMD (1 << 5) /* sr, c */ + + +/* + * OCR bits are mostly in host.h + */ +#define MMC_CARD_BUSY 0x80000000 /* Card Power up status bit */ + +/* + * Card Command Classes (CCC) + */ +#define CCC_BASIC (1<<0) /* (0) Basic protocol functions */ + /* (CMD0,1,2,3,4,7,9,10,12,13,15) */ +#define CCC_STREAM_READ (1<<1) /* (1) Stream read commands */ + /* (CMD11) */ +#define CCC_BLOCK_READ (1<<2) /* (2) Block read commands */ + /* (CMD16,17,18) */ +#define CCC_STREAM_WRITE (1<<3) /* (3) Stream write commands */ + /* (CMD20) */ +#define CCC_BLOCK_WRITE (1<<4) /* (4) Block write commands */ + /* (CMD16,24,25,26,27) */ +#define CCC_ERASE (1<<5) /* (5) Ability to erase blocks */ + /* (CMD32,33,34,35,36,37,38,39) */ +#define CCC_WRITE_PROT (1<<6) /* (6) Able to write protect blocks */ + /* (CMD28,29,30) */ +#define CCC_LOCK_CARD (1<<7) /* (7) Able to lock down card */ + /* (CMD16,CMD42) */ +#define CCC_APP_SPEC (1<<8) /* (8) Application specific */ + /* (CMD55,56,57,ACMD*) */ +#define CCC_IO_MODE (1<<9) /* (9) I/O mode */ + /* (CMD5,39,40,52,53) */ +#define CCC_SWITCH (1<<10) /* (10) High speed switch */ + /* (CMD6,34,35,36,37,50) */ + /* (11) Reserved */ + /* (CMD?) */ + +/* + * CSD field definitions + */ + +#define CSD_STRUCT_VER_1_0 0 /* Valid for system specification 1.0 - 1.2 */ +#define CSD_STRUCT_VER_1_1 1 /* Valid for system specification 1.4 - 2.2 */ +#define CSD_STRUCT_VER_1_2 2 /* Valid for system specification 3.1 - 3.2 - 3.31 - 4.0 - 4.1 */ +#define CSD_STRUCT_EXT_CSD 3 /* Version is coded in CSD_STRUCTURE in EXT_CSD */ + +#define CSD_SPEC_VER_0 0 /* Implements system specification 1.0 - 1.2 */ +#define CSD_SPEC_VER_1 1 /* Implements system specification 1.4 */ +#define CSD_SPEC_VER_2 2 /* Implements system specification 2.0 - 2.2 */ +#define CSD_SPEC_VER_3 3 /* Implements system specification 3.1 - 3.2 - 3.31 */ +#define CSD_SPEC_VER_4 4 /* Implements system specification 4.0 - 4.1 */ + +/* + * EXT_CSD fields + */ + +#define EXT_CSD_BUS_WIDTH 183 /* R/W */ +#define EXT_CSD_HS_TIMING 185 /* R/W */ +#define EXT_CSD_CARD_TYPE 196 /* RO */ +#define EXT_CSD_SEC_CNT 212 /* RO, 4 bytes */ + +/* + * EXT_CSD field definitions + */ + +#define EXT_CSD_CMD_SET_NORMAL (1<<0) +#define EXT_CSD_CMD_SET_SECURE (1<<1) +#define EXT_CSD_CMD_SET_CPSECURE (1<<2) + +#define EXT_CSD_CARD_TYPE_26 (1<<0) /* Card can run at 26MHz */ +#define EXT_CSD_CARD_TYPE_52 (1<<1) /* Card can run at 52MHz */ + +#define EXT_CSD_BUS_WIDTH_1 0 /* Card is in 1 bit mode */ +#define EXT_CSD_BUS_WIDTH_4 1 /* Card is in 4 bit mode */ +#define EXT_CSD_BUS_WIDTH_8 2 /* Card is in 8 bit mode */ + +/* + * MMC_SWITCH access modes + */ + +#define MMC_SWITCH_MODE_CMD_SET 0x00 /* Change the command set */ +#define MMC_SWITCH_MODE_SET_BITS 0x01 /* Set bits which are 1 in value */ +#define MMC_SWITCH_MODE_CLEAR_BITS 0x02 /* Clear bits which are 1 in value */ +#define MMC_SWITCH_MODE_WRITE_BYTE 0x03 /* Set target to value */ + +#endif /* MMC_MMC_PROTOCOL_H */ + @@ -0,0 +1,148 @@ +#ifndef HW_PC_H +#define HW_PC_H +/* PC-style peripherals (also used by other machines). */ + +/* serial.c */ + +SerialState *serial_init(int base, qemu_irq irq, int baudbase, + CharDriverState *chr); +SerialState *serial_mm_init (target_phys_addr_t base, int it_shift, + qemu_irq irq, int baudbase, + CharDriverState *chr, int ioregister); +uint32_t serial_mm_readb (void *opaque, target_phys_addr_t addr); +void serial_mm_writeb (void *opaque, target_phys_addr_t addr, uint32_t value); +uint32_t serial_mm_readw (void *opaque, target_phys_addr_t addr); +void serial_mm_writew (void *opaque, target_phys_addr_t addr, uint32_t value); +uint32_t serial_mm_readl (void *opaque, target_phys_addr_t addr); +void serial_mm_writel (void *opaque, target_phys_addr_t addr, uint32_t value); + +/* parallel.c */ + +typedef struct ParallelState ParallelState; +ParallelState *parallel_init(int base, qemu_irq irq, CharDriverState *chr); +ParallelState *parallel_mm_init(target_phys_addr_t base, int it_shift, qemu_irq irq, CharDriverState *chr); + +/* i8259.c */ + +typedef struct PicState2 PicState2; +extern PicState2 *isa_pic; +void pic_set_irq(int irq, int level); +void pic_set_irq_new(void *opaque, int irq, int level); +qemu_irq *i8259_init(qemu_irq parent_irq); +void pic_set_alt_irq_func(PicState2 *s, SetIRQFunc *alt_irq_func, + void *alt_irq_opaque); +int pic_read_irq(PicState2 *s); +void pic_update_irq(PicState2 *s); +uint32_t pic_intack_read(PicState2 *s); +void pic_info(void); +void irq_info(void); + +/* APIC */ +typedef struct IOAPICState IOAPICState; + +int apic_init(CPUState *env); +int apic_accept_pic_intr(CPUState *env); +void apic_deliver_pic_intr(CPUState *env, int level); +int apic_get_interrupt(CPUState *env); +IOAPICState *ioapic_init(void); +void ioapic_set_irq(void *opaque, int vector, int level); + +/* i8254.c */ + +#define PIT_FREQ 1193182 + +typedef struct PITState PITState; + +PITState *pit_init(int base, qemu_irq irq); +void pit_set_gate(PITState *pit, int channel, int val); +int pit_get_gate(PITState *pit, int channel); +int pit_get_initial_count(PITState *pit, int channel); +int pit_get_mode(PITState *pit, int channel); +int pit_get_out(PITState *pit, int channel, int64_t current_time); + +/* vmport.c */ +void vmport_init(void); +void vmport_register(unsigned char command, IOPortReadFunc *func, void *opaque); + +/* vmmouse.c */ +void *vmmouse_init(void *m); + +/* pckbd.c */ + +void i8042_init(qemu_irq kbd_irq, qemu_irq mouse_irq, uint32_t io_base); +void i8042_mm_init(qemu_irq kbd_irq, qemu_irq mouse_irq, + target_phys_addr_t base, int it_shift); + +/* mc146818rtc.c */ + +typedef struct RTCState RTCState; + +RTCState *rtc_init(int base, qemu_irq irq); +RTCState *rtc_mm_init(target_phys_addr_t base, int it_shift, qemu_irq irq); +void rtc_set_memory(RTCState *s, int addr, int val); +void rtc_set_date(RTCState *s, const struct tm *tm); + +/* pc.c */ +extern int fd_bootchk; + +void ioport_set_a20(int enable); +int ioport_get_a20(void); + +/* acpi.c */ +extern int acpi_enabled; +i2c_bus *piix4_pm_init(PCIBus *bus, int devfn, uint32_t smb_io_base, + qemu_irq sci_irq); +void piix4_smbus_register_device(SMBusDevice *dev, uint8_t addr); +void acpi_bios_init(void); + +/* pcspk.c */ +void pcspk_init(PITState *); +int pcspk_audio_init(AudioState *, qemu_irq *pic); + +/* piix_pci.c */ +PCIBus *i440fx_init(PCIDevice **pi440fx_state, qemu_irq *pic); +void i440fx_set_smm(PCIDevice *d, int val); +int piix3_init(PCIBus *bus, int devfn); +void i440fx_init_memory_mappings(PCIDevice *d); + +int piix4_init(PCIBus *bus, int devfn); + +/* vga.c */ + +#ifndef TARGET_SPARC +#define VGA_RAM_SIZE (8192 * 1024) +#else +#define VGA_RAM_SIZE (9 * 1024 * 1024) +#endif + +int isa_vga_init(DisplayState *ds, uint8_t *vga_ram_base, + unsigned long vga_ram_offset, int vga_ram_size); +int pci_vga_init(PCIBus *bus, DisplayState *ds, uint8_t *vga_ram_base, + unsigned long vga_ram_offset, int vga_ram_size, + unsigned long vga_bios_offset, int vga_bios_size); +int isa_vga_mm_init(DisplayState *ds, uint8_t *vga_ram_base, + unsigned long vga_ram_offset, int vga_ram_size, + target_phys_addr_t vram_base, target_phys_addr_t ctrl_base, + int it_shift); + +/* cirrus_vga.c */ +void pci_cirrus_vga_init(PCIBus *bus, DisplayState *ds, uint8_t *vga_ram_base, + unsigned long vga_ram_offset, int vga_ram_size); +void isa_cirrus_vga_init(DisplayState *ds, uint8_t *vga_ram_base, + unsigned long vga_ram_offset, int vga_ram_size); + +/* ide.c */ +void isa_ide_init(int iobase, int iobase2, qemu_irq irq, + BlockDriverState *hd0, BlockDriverState *hd1); +void pci_cmd646_ide_init(PCIBus *bus, BlockDriverState **hd_table, + int secondary_ide_enabled); +void pci_piix3_ide_init(PCIBus *bus, BlockDriverState **hd_table, int devfn, + qemu_irq *pic); +void pci_piix4_ide_init(PCIBus *bus, BlockDriverState **hd_table, int devfn, + qemu_irq *pic); + +/* ne2000.c */ + +void isa_ne2000_init(int base, qemu_irq irq, NICInfo *nd); + +#endif diff --git a/hw/pci.c b/hw/pci.c new file mode 100644 index 0000000..5f7004a --- /dev/null +++ b/hw/pci.c @@ -0,0 +1,701 @@ +/* + * QEMU PCI bus manager + * + * Copyright (c) 2004 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "hw.h" +#include "pci.h" +#include "console.h" +#include "net.h" + +//#define DEBUG_PCI + +struct PCIBus { + int bus_num; + int devfn_min; + pci_set_irq_fn set_irq; + pci_map_irq_fn map_irq; + uint32_t config_reg; /* XXX: suppress */ + /* low level pic */ + SetIRQFunc *low_set_irq; + qemu_irq *irq_opaque; + PCIDevice *devices[256]; + PCIDevice *parent_dev; + PCIBus *next; + /* The bus IRQ state is the logical OR of the connected devices. + Keep a count of the number of devices with raised IRQs. */ + int nirq; + int irq_count[]; +}; + +static void pci_update_mappings(PCIDevice *d); +static void pci_set_irq(void *opaque, int irq_num, int level); + +target_phys_addr_t pci_mem_base; +static int pci_irq_index; +static PCIBus *first_bus; + +static void pcibus_save(QEMUFile *f, void *opaque) +{ + PCIBus *bus = (PCIBus *)opaque; + int i; + + qemu_put_be32(f, bus->nirq); + for (i = 0; i < bus->nirq; i++) + qemu_put_be32(f, bus->irq_count[i]); +} + +static int pcibus_load(QEMUFile *f, void *opaque, int version_id) +{ + PCIBus *bus = (PCIBus *)opaque; + int i, nirq; + + if (version_id != 1) + return -EINVAL; + + nirq = qemu_get_be32(f); + if (bus->nirq != nirq) { + fprintf(stderr, "pcibus_load: nirq mismatch: src=%d dst=%d\n", + nirq, bus->nirq); + return -EINVAL; + } + + for (i = 0; i < nirq; i++) + bus->irq_count[i] = qemu_get_be32(f); + + return 0; +} + +PCIBus *pci_register_bus(pci_set_irq_fn set_irq, pci_map_irq_fn map_irq, + qemu_irq *pic, int devfn_min, int nirq) +{ + PCIBus *bus; + static int nbus = 0; + + bus = qemu_mallocz(sizeof(PCIBus) + (nirq * sizeof(int))); + bus->set_irq = set_irq; + bus->map_irq = map_irq; + bus->irq_opaque = pic; + bus->devfn_min = devfn_min; + bus->nirq = nirq; + first_bus = bus; + register_savevm("PCIBUS", nbus++, 1, pcibus_save, pcibus_load, bus); + return bus; +} + +static PCIBus *pci_register_secondary_bus(PCIDevice *dev, pci_map_irq_fn map_irq) +{ + PCIBus *bus; + bus = qemu_mallocz(sizeof(PCIBus)); + bus->map_irq = map_irq; + bus->parent_dev = dev; + bus->next = dev->bus->next; + dev->bus->next = bus; + return bus; +} + +int pci_bus_num(PCIBus *s) +{ + return s->bus_num; +} + +void pci_device_save(PCIDevice *s, QEMUFile *f) +{ + int i; + + qemu_put_be32(f, 2); /* PCI device version */ + qemu_put_buffer(f, s->config, 256); + for (i = 0; i < 4; i++) + qemu_put_be32(f, s->irq_state[i]); +} + +int pci_device_load(PCIDevice *s, QEMUFile *f) +{ + uint32_t version_id; + int i; + + version_id = qemu_get_be32(f); + if (version_id > 2) + return -EINVAL; + qemu_get_buffer(f, s->config, 256); + pci_update_mappings(s); + + if (version_id >= 2) + for (i = 0; i < 4; i ++) + s->irq_state[i] = qemu_get_be32(f); + + return 0; +} + +/* -1 for devfn means auto assign */ +PCIDevice *pci_register_device(PCIBus *bus, const char *name, + int instance_size, int devfn, + PCIConfigReadFunc *config_read, + PCIConfigWriteFunc *config_write) +{ + PCIDevice *pci_dev; + + if (pci_irq_index >= PCI_DEVICES_MAX) + return NULL; + + if (devfn < 0) { + for(devfn = bus->devfn_min ; devfn < 256; devfn += 8) { + if (!bus->devices[devfn]) + goto found; + } + return NULL; + found: ; + } + pci_dev = qemu_mallocz(instance_size); + if (!pci_dev) + return NULL; + pci_dev->bus = bus; + pci_dev->devfn = devfn; + pstrcpy(pci_dev->name, sizeof(pci_dev->name), name); + memset(pci_dev->irq_state, 0, sizeof(pci_dev->irq_state)); + + if (!config_read) + config_read = pci_default_read_config; + if (!config_write) + config_write = pci_default_write_config; + pci_dev->config_read = config_read; + pci_dev->config_write = config_write; + pci_dev->irq_index = pci_irq_index++; + bus->devices[devfn] = pci_dev; + pci_dev->irq = qemu_allocate_irqs(pci_set_irq, pci_dev, 4); + return pci_dev; +} + +void pci_register_io_region(PCIDevice *pci_dev, int region_num, + uint32_t size, int type, + PCIMapIORegionFunc *map_func) +{ + PCIIORegion *r; + uint32_t addr; + + if ((unsigned int)region_num >= PCI_NUM_REGIONS) + return; + r = &pci_dev->io_regions[region_num]; + r->addr = -1; + r->size = size; + r->type = type; + r->map_func = map_func; + if (region_num == PCI_ROM_SLOT) { + addr = 0x30; + } else { + addr = 0x10 + region_num * 4; + } + *(uint32_t *)(pci_dev->config + addr) = cpu_to_le32(type); +} + +static target_phys_addr_t pci_to_cpu_addr(target_phys_addr_t addr) +{ + return addr + pci_mem_base; +} + +static void pci_update_mappings(PCIDevice *d) +{ + PCIIORegion *r; + int cmd, i; + uint32_t last_addr, new_addr, config_ofs; + + cmd = le16_to_cpu(*(uint16_t *)(d->config + PCI_COMMAND)); + for(i = 0; i < PCI_NUM_REGIONS; i++) { + r = &d->io_regions[i]; + if (i == PCI_ROM_SLOT) { + config_ofs = 0x30; + } else { + config_ofs = 0x10 + i * 4; + } + if (r->size != 0) { + if (r->type & PCI_ADDRESS_SPACE_IO) { + if (cmd & PCI_COMMAND_IO) { + new_addr = le32_to_cpu(*(uint32_t *)(d->config + + config_ofs)); + new_addr = new_addr & ~(r->size - 1); + last_addr = new_addr + r->size - 1; + /* NOTE: we have only 64K ioports on PC */ + if (last_addr <= new_addr || new_addr == 0 || + last_addr >= 0x10000) { + new_addr = -1; + } + } else { + new_addr = -1; + } + } else { + if (cmd & PCI_COMMAND_MEMORY) { + new_addr = le32_to_cpu(*(uint32_t *)(d->config + + config_ofs)); + /* the ROM slot has a specific enable bit */ + if (i == PCI_ROM_SLOT && !(new_addr & 1)) + goto no_mem_map; + new_addr = new_addr & ~(r->size - 1); + last_addr = new_addr + r->size - 1; + /* NOTE: we do not support wrapping */ + /* XXX: as we cannot support really dynamic + mappings, we handle specific values as invalid + mappings. */ + if (last_addr <= new_addr || new_addr == 0 || + last_addr == -1) { + new_addr = -1; + } + } else { + no_mem_map: + new_addr = -1; + } + } + /* now do the real mapping */ + if (new_addr != r->addr) { + if (r->addr != -1) { + if (r->type & PCI_ADDRESS_SPACE_IO) { + int class; + /* NOTE: specific hack for IDE in PC case: + only one byte must be mapped. */ + class = d->config[0x0a] | (d->config[0x0b] << 8); + if (class == 0x0101 && r->size == 4) { + isa_unassign_ioport(r->addr + 2, 1); + } else { + isa_unassign_ioport(r->addr, r->size); + } + } else { + cpu_register_physical_memory(pci_to_cpu_addr(r->addr), + r->size, + IO_MEM_UNASSIGNED); + } + } + r->addr = new_addr; + if (r->addr != -1) { + r->map_func(d, i, r->addr, r->size, r->type); + } + } + } + } +} + +uint32_t pci_default_read_config(PCIDevice *d, + uint32_t address, int len) +{ + uint32_t val; + + switch(len) { + default: + case 4: + if (address <= 0xfc) { + val = le32_to_cpu(*(uint32_t *)(d->config + address)); + break; + } + /* fall through */ + case 2: + if (address <= 0xfe) { + val = le16_to_cpu(*(uint16_t *)(d->config + address)); + break; + } + /* fall through */ + case 1: + val = d->config[address]; + break; + } + return val; +} + +void pci_default_write_config(PCIDevice *d, + uint32_t address, uint32_t val, int len) +{ + int can_write, i; + uint32_t end, addr; + + if (len == 4 && ((address >= 0x10 && address < 0x10 + 4 * 6) || + (address >= 0x30 && address < 0x34))) { + PCIIORegion *r; + int reg; + + if ( address >= 0x30 ) { + reg = PCI_ROM_SLOT; + }else{ + reg = (address - 0x10) >> 2; + } + r = &d->io_regions[reg]; + if (r->size == 0) + goto default_config; + /* compute the stored value */ + if (reg == PCI_ROM_SLOT) { + /* keep ROM enable bit */ + val &= (~(r->size - 1)) | 1; + } else { + val &= ~(r->size - 1); + val |= r->type; + } + *(uint32_t *)(d->config + address) = cpu_to_le32(val); + pci_update_mappings(d); + return; + } + default_config: + /* not efficient, but simple */ + addr = address; + for(i = 0; i < len; i++) { + /* default read/write accesses */ + switch(d->config[0x0e]) { + case 0x00: + case 0x80: + switch(addr) { + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x08: + case 0x09: + case 0x0a: + case 0x0b: + case 0x0e: + case 0x10 ... 0x27: /* base */ + case 0x30 ... 0x33: /* rom */ + case 0x3d: + can_write = 0; + break; + default: + can_write = 1; + break; + } + break; + default: + case 0x01: + switch(addr) { + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x08: + case 0x09: + case 0x0a: + case 0x0b: + case 0x0e: + case 0x38 ... 0x3b: /* rom */ + case 0x3d: + can_write = 0; + break; + default: + can_write = 1; + break; + } + break; + } + if (can_write) { + d->config[addr] = val; + } + if (++addr > 0xff) + break; + val >>= 8; + } + + end = address + len; + if (end > PCI_COMMAND && address < (PCI_COMMAND + 2)) { + /* if the command register is modified, we must modify the mappings */ + pci_update_mappings(d); + } +} + +void pci_data_write(void *opaque, uint32_t addr, uint32_t val, int len) +{ + PCIBus *s = opaque; + PCIDevice *pci_dev; + int config_addr, bus_num; + +#if defined(DEBUG_PCI) && 0 + printf("pci_data_write: addr=%08x val=%08x len=%d\n", + addr, val, len); +#endif + bus_num = (addr >> 16) & 0xff; + while (s && s->bus_num != bus_num) + s = s->next; + if (!s) + return; + pci_dev = s->devices[(addr >> 8) & 0xff]; + if (!pci_dev) + return; + config_addr = addr & 0xff; +#if defined(DEBUG_PCI) + printf("pci_config_write: %s: addr=%02x val=%08x len=%d\n", + pci_dev->name, config_addr, val, len); +#endif + pci_dev->config_write(pci_dev, config_addr, val, len); +} + +uint32_t pci_data_read(void *opaque, uint32_t addr, int len) +{ + PCIBus *s = opaque; + PCIDevice *pci_dev; + int config_addr, bus_num; + uint32_t val; + + bus_num = (addr >> 16) & 0xff; + while (s && s->bus_num != bus_num) + s= s->next; + if (!s) + goto fail; + pci_dev = s->devices[(addr >> 8) & 0xff]; + if (!pci_dev) { + fail: + switch(len) { + case 1: + val = 0xff; + break; + case 2: + val = 0xffff; + break; + default: + case 4: + val = 0xffffffff; + break; + } + goto the_end; + } + config_addr = addr & 0xff; + val = pci_dev->config_read(pci_dev, config_addr, len); +#if defined(DEBUG_PCI) + printf("pci_config_read: %s: addr=%02x val=%08x len=%d\n", + pci_dev->name, config_addr, val, len); +#endif + the_end: +#if defined(DEBUG_PCI) && 0 + printf("pci_data_read: addr=%08x val=%08x len=%d\n", + addr, val, len); +#endif + return val; +} + +/***********************************************************/ +/* generic PCI irq support */ + +/* 0 <= irq_num <= 3. level must be 0 or 1 */ +static void pci_set_irq(void *opaque, int irq_num, int level) +{ + PCIDevice *pci_dev = (PCIDevice *)opaque; + PCIBus *bus; + int change; + + change = level - pci_dev->irq_state[irq_num]; + if (!change) + return; + + pci_dev->irq_state[irq_num] = level; + for (;;) { + bus = pci_dev->bus; + irq_num = bus->map_irq(pci_dev, irq_num); + if (bus->set_irq) + break; + pci_dev = bus->parent_dev; + } + bus->irq_count[irq_num] += change; + bus->set_irq(bus->irq_opaque, irq_num, bus->irq_count[irq_num] != 0); +} + +/***********************************************************/ +/* monitor info on PCI */ + +typedef struct { + uint16_t class; + const char *desc; +} pci_class_desc; + +static pci_class_desc pci_class_descriptions[] = +{ + { 0x0100, "SCSI controller"}, + { 0x0101, "IDE controller"}, + { 0x0102, "Floppy controller"}, + { 0x0103, "IPI controller"}, + { 0x0104, "RAID controller"}, + { 0x0106, "SATA controller"}, + { 0x0107, "SAS controller"}, + { 0x0180, "Storage controller"}, + { 0x0200, "Ethernet controller"}, + { 0x0201, "Token Ring controller"}, + { 0x0202, "FDDI controller"}, + { 0x0203, "ATM controller"}, + { 0x0280, "Network controller"}, + { 0x0300, "VGA controller"}, + { 0x0301, "XGA controller"}, + { 0x0302, "3D controller"}, + { 0x0380, "Display controller"}, + { 0x0400, "Video controller"}, + { 0x0401, "Audio controller"}, + { 0x0402, "Phone"}, + { 0x0480, "Multimedia controller"}, + { 0x0500, "RAM controller"}, + { 0x0501, "Flash controller"}, + { 0x0580, "Memory controller"}, + { 0x0600, "Host bridge"}, + { 0x0601, "ISA bridge"}, + { 0x0602, "EISA bridge"}, + { 0x0603, "MC bridge"}, + { 0x0604, "PCI bridge"}, + { 0x0605, "PCMCIA bridge"}, + { 0x0606, "NUBUS bridge"}, + { 0x0607, "CARDBUS bridge"}, + { 0x0608, "RACEWAY bridge"}, + { 0x0680, "Bridge"}, + { 0x0c03, "USB controller"}, + { 0, NULL} +}; + +static void pci_info_device(PCIDevice *d) +{ + int i, class; + PCIIORegion *r; + pci_class_desc *desc; + + term_printf(" Bus %2d, device %3d, function %d:\n", + d->bus->bus_num, d->devfn >> 3, d->devfn & 7); + class = le16_to_cpu(*((uint16_t *)(d->config + PCI_CLASS_DEVICE))); + term_printf(" "); + desc = pci_class_descriptions; + while (desc->desc && class != desc->class) + desc++; + if (desc->desc) { + term_printf("%s", desc->desc); + } else { + term_printf("Class %04x", class); + } + term_printf(": PCI device %04x:%04x\n", + le16_to_cpu(*((uint16_t *)(d->config + PCI_VENDOR_ID))), + le16_to_cpu(*((uint16_t *)(d->config + PCI_DEVICE_ID)))); + + if (d->config[PCI_INTERRUPT_PIN] != 0) { + term_printf(" IRQ %d.\n", d->config[PCI_INTERRUPT_LINE]); + } + if (class == 0x0604) { + term_printf(" BUS %d.\n", d->config[0x19]); + } + for(i = 0;i < PCI_NUM_REGIONS; i++) { + r = &d->io_regions[i]; + if (r->size != 0) { + term_printf(" BAR%d: ", i); + if (r->type & PCI_ADDRESS_SPACE_IO) { + term_printf("I/O at 0x%04x [0x%04x].\n", + r->addr, r->addr + r->size - 1); + } else { + term_printf("32 bit memory at 0x%08x [0x%08x].\n", + r->addr, r->addr + r->size - 1); + } + } + } + if (class == 0x0604 && d->config[0x19] != 0) { + pci_for_each_device(d->config[0x19], pci_info_device); + } +} + +void pci_for_each_device(int bus_num, void (*fn)(PCIDevice *d)) +{ + PCIBus *bus = first_bus; + PCIDevice *d; + int devfn; + + while (bus && bus->bus_num != bus_num) + bus = bus->next; + if (bus) { + for(devfn = 0; devfn < 256; devfn++) { + d = bus->devices[devfn]; + if (d) + fn(d); + } + } +} + +void pci_info(void) +{ + pci_for_each_device(0, pci_info_device); +} + +/* Initialize a PCI NIC. */ +void pci_nic_init(PCIBus *bus, NICInfo *nd, int devfn) +{ +#if 0 + if (strcmp(nd->model, "ne2k_pci") == 0) { + pci_ne2000_init(bus, nd, devfn); + } else if (strcmp(nd->model, "i82551") == 0) { + pci_i82551_init(bus, nd, devfn); + } else if (strcmp(nd->model, "i82557b") == 0) { + pci_i82557b_init(bus, nd, devfn); + } else if (strcmp(nd->model, "i82559er") == 0) { + pci_i82559er_init(bus, nd, devfn); + } else if (strcmp(nd->model, "rtl8139") == 0) { + pci_rtl8139_init(bus, nd, devfn); + } else if (strcmp(nd->model, "e1000") == 0) { + pci_e1000_init(bus, nd, devfn); + } else if (strcmp(nd->model, "pcnet") == 0) { + pci_pcnet_init(bus, nd, devfn); + } else if (strcmp(nd->model, "?") == 0) { + fprintf(stderr, "qemu: Supported PCI NICs: i82551 i82557b i82559er" + " ne2k_pci pcnet rtl8139 e1000\n"); + exit (1); + } else { + fprintf(stderr, "qemu: Unsupported NIC: %s\n", nd->model); + exit (1); + } +#endif +} + +typedef struct { + PCIDevice dev; + PCIBus *bus; +} PCIBridge; + +static void pci_bridge_write_config(PCIDevice *d, + uint32_t address, uint32_t val, int len) +{ + PCIBridge *s = (PCIBridge *)d; + + if (address == 0x19 || (address == 0x18 && len > 1)) { + if (address == 0x19) + s->bus->bus_num = val & 0xff; + else + s->bus->bus_num = (val >> 8) & 0xff; +#if defined(DEBUG_PCI) + printf ("pci-bridge: %s: Assigned bus %d\n", d->name, s->bus->bus_num); +#endif + } + pci_default_write_config(d, address, val, len); +} + +PCIBus *pci_bridge_init(PCIBus *bus, int devfn, uint32_t id, + pci_map_irq_fn map_irq, const char *name) +{ + PCIBridge *s; + s = (PCIBridge *)pci_register_device(bus, name, sizeof(PCIBridge), + devfn, NULL, pci_bridge_write_config); + s->dev.config[0x00] = id >> 16; + s->dev.config[0x01] = id >> 24; + s->dev.config[0x02] = id; // device_id + s->dev.config[0x03] = id >> 8; + s->dev.config[0x04] = 0x06; // command = bus master, pci mem + s->dev.config[0x05] = 0x00; + s->dev.config[0x06] = 0xa0; // status = fast back-to-back, 66MHz, no error + s->dev.config[0x07] = 0x00; // status = fast devsel + s->dev.config[0x08] = 0x00; // revision + s->dev.config[0x09] = 0x00; // programming i/f + s->dev.config[0x0A] = 0x04; // class_sub = PCI to PCI bridge + s->dev.config[0x0B] = 0x06; // class_base = PCI_bridge + s->dev.config[0x0D] = 0x10; // latency_timer + s->dev.config[0x0E] = 0x81; // header_type + s->dev.config[0x1E] = 0xa0; // secondary status + + s->bus = pci_register_secondary_bus(&s->dev, map_irq); + return s->bus; +} diff --git a/hw/pci.h b/hw/pci.h new file mode 100644 index 0000000..e870987 --- /dev/null +++ b/hw/pci.h @@ -0,0 +1,142 @@ +#ifndef QEMU_PCI_H +#define QEMU_PCI_H + +/* PCI includes legacy ISA access. */ +#include "isa.h" + +/* PCI bus */ + +extern target_phys_addr_t pci_mem_base; + +typedef void PCIConfigWriteFunc(PCIDevice *pci_dev, + uint32_t address, uint32_t data, int len); +typedef uint32_t PCIConfigReadFunc(PCIDevice *pci_dev, + uint32_t address, int len); +typedef void PCIMapIORegionFunc(PCIDevice *pci_dev, int region_num, + uint32_t addr, uint32_t size, int type); + +#define PCI_ADDRESS_SPACE_MEM 0x00 +#define PCI_ADDRESS_SPACE_IO 0x01 +#define PCI_ADDRESS_SPACE_MEM_PREFETCH 0x08 + +typedef struct PCIIORegion { + uint32_t addr; /* current PCI mapping address. -1 means not mapped */ + uint32_t size; + uint8_t type; + PCIMapIORegionFunc *map_func; +} PCIIORegion; + +#define PCI_ROM_SLOT 6 +#define PCI_NUM_REGIONS 7 + +#define PCI_DEVICES_MAX 64 + +#define PCI_VENDOR_ID 0x00 /* 16 bits */ +#define PCI_DEVICE_ID 0x02 /* 16 bits */ +#define PCI_COMMAND 0x04 /* 16 bits */ +#define PCI_COMMAND_IO 0x1 /* Enable response in I/O space */ +#define PCI_COMMAND_MEMORY 0x2 /* Enable response in Memory space */ +#define PCI_CLASS_DEVICE 0x0a /* Device class */ +#define PCI_INTERRUPT_LINE 0x3c /* 8 bits */ +#define PCI_INTERRUPT_PIN 0x3d /* 8 bits */ +#define PCI_MIN_GNT 0x3e /* 8 bits */ +#define PCI_MAX_LAT 0x3f /* 8 bits */ + +struct PCIDevice { + /* PCI config space */ + uint8_t config[256]; + + /* the following fields are read only */ + PCIBus *bus; + int devfn; + char name[64]; + PCIIORegion io_regions[PCI_NUM_REGIONS]; + + /* do not access the following fields */ + PCIConfigReadFunc *config_read; + PCIConfigWriteFunc *config_write; + /* ??? This is a PC-specific hack, and should be removed. */ + int irq_index; + + /* IRQ objects for the INTA-INTD pins. */ + qemu_irq *irq; + + /* Current IRQ levels. Used internally by the generic PCI code. */ + int irq_state[4]; +}; + +PCIDevice *pci_register_device(PCIBus *bus, const char *name, + int instance_size, int devfn, + PCIConfigReadFunc *config_read, + PCIConfigWriteFunc *config_write); + +void pci_register_io_region(PCIDevice *pci_dev, int region_num, + uint32_t size, int type, + PCIMapIORegionFunc *map_func); + +uint32_t pci_default_read_config(PCIDevice *d, + uint32_t address, int len); +void pci_default_write_config(PCIDevice *d, + uint32_t address, uint32_t val, int len); +void pci_device_save(PCIDevice *s, QEMUFile *f); +int pci_device_load(PCIDevice *s, QEMUFile *f); + +typedef void (*pci_set_irq_fn)(qemu_irq *pic, int irq_num, int level); +typedef int (*pci_map_irq_fn)(PCIDevice *pci_dev, int irq_num); +PCIBus *pci_register_bus(pci_set_irq_fn set_irq, pci_map_irq_fn map_irq, + qemu_irq *pic, int devfn_min, int nirq); + +void pci_nic_init(PCIBus *bus, NICInfo *nd, int devfn); +void pci_data_write(void *opaque, uint32_t addr, uint32_t val, int len); +uint32_t pci_data_read(void *opaque, uint32_t addr, int len); +int pci_bus_num(PCIBus *s); +void pci_for_each_device(int bus_num, void (*fn)(PCIDevice *d)); + +void pci_info(void); +PCIBus *pci_bridge_init(PCIBus *bus, int devfn, uint32_t id, + pci_map_irq_fn map_irq, const char *name); + +/* lsi53c895a.c */ +#define LSI_MAX_DEVS 7 +void lsi_scsi_attach(void *opaque, BlockDriverState *bd, int id); +void *lsi_scsi_init(PCIBus *bus, int devfn); + +/* vmware_vga.c */ +void pci_vmsvga_init(PCIBus *bus, DisplayState *ds, uint8_t *vga_ram_base, + unsigned long vga_ram_offset, int vga_ram_size); + +/* usb-uhci.c */ +void usb_uhci_piix3_init(PCIBus *bus, int devfn); +void usb_uhci_piix4_init(PCIBus *bus, int devfn); + +/* usb-ohci.c */ +void usb_ohci_init_pci(struct PCIBus *bus, int num_ports, int devfn); + +/* eepro100.c */ + +void pci_i82551_init(PCIBus *bus, NICInfo *nd, int devfn); +void pci_i82557b_init(PCIBus *bus, NICInfo *nd, int devfn); +void pci_i82559er_init(PCIBus *bus, NICInfo *nd, int devfn); + +/* ne2000.c */ + +void pci_ne2000_init(PCIBus *bus, NICInfo *nd, int devfn); + +/* rtl8139.c */ + +void pci_rtl8139_init(PCIBus *bus, NICInfo *nd, int devfn); + +/* e1000.c */ +void pci_e1000_init(PCIBus *bus, NICInfo *nd, int devfn); + +/* pcnet.c */ +void pci_pcnet_init(PCIBus *bus, NICInfo *nd, int devfn); + +/* prep_pci.c */ +PCIBus *pci_prep_init(qemu_irq *pic); + +/* apb_pci.c */ +PCIBus *pci_apb_init(target_phys_addr_t special_base, target_phys_addr_t mem_base, + qemu_irq *pic); + +#endif diff --git a/hw/pci_host.h b/hw/pci_host.h new file mode 100644 index 0000000..49a0c59 --- /dev/null +++ b/hw/pci_host.h @@ -0,0 +1,93 @@ +/* + * QEMU Common PCI Host bridge configuration data space access routines. + * + * Copyright (c) 2006 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* Worker routines for a PCI host controller that uses an {address,data} + register pair to access PCI configuration space. */ + +typedef struct { + uint32_t config_reg; + PCIBus *bus; +} PCIHostState; + +static void pci_host_data_writeb(void* opaque, pci_addr_t addr, uint32_t val) +{ + PCIHostState *s = opaque; + if (s->config_reg & (1u << 31)) + pci_data_write(s->bus, s->config_reg | (addr & 3), val, 1); +} + +static void pci_host_data_writew(void* opaque, pci_addr_t addr, uint32_t val) +{ + PCIHostState *s = opaque; +#ifdef TARGET_WORDS_BIGENDIAN + val = bswap16(val); +#endif + if (s->config_reg & (1u << 31)) + pci_data_write(s->bus, s->config_reg | (addr & 3), val, 2); +} + +static void pci_host_data_writel(void* opaque, pci_addr_t addr, uint32_t val) +{ + PCIHostState *s = opaque; +#ifdef TARGET_WORDS_BIGENDIAN + val = bswap32(val); +#endif + if (s->config_reg & (1u << 31)) + pci_data_write(s->bus, s->config_reg, val, 4); +} + +static uint32_t pci_host_data_readb(void* opaque, pci_addr_t addr) +{ + PCIHostState *s = opaque; + if (!(s->config_reg & (1 << 31))) + return 0xff; + return pci_data_read(s->bus, s->config_reg | (addr & 3), 1); +} + +static uint32_t pci_host_data_readw(void* opaque, pci_addr_t addr) +{ + PCIHostState *s = opaque; + uint32_t val; + if (!(s->config_reg & (1 << 31))) + return 0xffff; + val = pci_data_read(s->bus, s->config_reg | (addr & 3), 2); +#ifdef TARGET_WORDS_BIGENDIAN + val = bswap16(val); +#endif + return val; +} + +static uint32_t pci_host_data_readl(void* opaque, pci_addr_t addr) +{ + PCIHostState *s = opaque; + uint32_t val; + if (!(s->config_reg & (1 << 31))) + return 0xffffffff; + val = pci_data_read(s->bus, s->config_reg | (addr & 3), 4); +#ifdef TARGET_WORDS_BIGENDIAN + val = bswap32(val); +#endif + return val; +} + diff --git a/hw/pcmcia.h b/hw/pcmcia.h new file mode 100644 index 0000000..bfa23ba --- /dev/null +++ b/hw/pcmcia.h @@ -0,0 +1,50 @@ +/* PCMCIA/Cardbus */ + +struct pcmcia_socket_s { + qemu_irq irq; + int attached; + const char *slot_string; + const char *card_string; +}; + +void pcmcia_socket_register(struct pcmcia_socket_s *socket); +void pcmcia_socket_unregister(struct pcmcia_socket_s *socket); +void pcmcia_info(void); + +struct pcmcia_card_s { + void *state; + struct pcmcia_socket_s *slot; + int (*attach)(void *state); + int (*detach)(void *state); + const uint8_t *cis; + int cis_len; + + /* Only valid if attached */ + uint8_t (*attr_read)(void *state, uint32_t address); + void (*attr_write)(void *state, uint32_t address, uint8_t value); + uint16_t (*common_read)(void *state, uint32_t address); + void (*common_write)(void *state, uint32_t address, uint16_t value); + uint16_t (*io_read)(void *state, uint32_t address); + void (*io_write)(void *state, uint32_t address, uint16_t value); +}; + +#define CISTPL_DEVICE 0x01 /* 5V Device Information Tuple */ +#define CISTPL_NO_LINK 0x14 /* No Link Tuple */ +#define CISTPL_VERS_1 0x15 /* Level 1 Version Tuple */ +#define CISTPL_JEDEC_C 0x18 /* JEDEC ID Tuple */ +#define CISTPL_JEDEC_A 0x19 /* JEDEC ID Tuple */ +#define CISTPL_CONFIG 0x1a /* Configuration Tuple */ +#define CISTPL_CFTABLE_ENTRY 0x1b /* 16-bit PCCard Configuration */ +#define CISTPL_DEVICE_OC 0x1c /* Additional Device Information */ +#define CISTPL_DEVICE_OA 0x1d /* Additional Device Information */ +#define CISTPL_DEVICE_GEO 0x1e /* Additional Device Information */ +#define CISTPL_DEVICE_GEO_A 0x1f /* Additional Device Information */ +#define CISTPL_MANFID 0x20 /* Manufacture ID Tuple */ +#define CISTPL_FUNCID 0x21 /* Function ID Tuple */ +#define CISTPL_FUNCE 0x22 /* Function Extension Tuple */ +#define CISTPL_END 0xff /* Tuple End */ +#define CISTPL_ENDMARK 0xff + +/* dscm1xxxx.c */ +struct pcmcia_card_s *dscm1xxxx_init(BlockDriverState *bdrv); + diff --git a/hw/power_supply.h b/hw/power_supply.h new file mode 100644 index 0000000..b85edc7 --- /dev/null +++ b/hw/power_supply.h @@ -0,0 +1,109 @@ +/* + * Universal power supply monitor class + * + * Copyright © 2007 Anton Vorontsov <cbou@mail.ru> + * Copyright © 2004 Szabolcs Gyurko + * Copyright © 2003 Ian Molton <spyro@f2s.com> + * + * Modified: 2004, Oct Szabolcs Gyurko + * + * You may use this code as per GPL version 2 + */ + +#ifndef __LINUX_POWER_SUPPLY_H__ +#define __LINUX_POWER_SUPPLY_H__ + +/* + * All voltages, currents, charges, energies, time and temperatures in uV, + * µA, µAh, µWh, seconds and tenths of degree Celsius unless otherwise + * stated. It's driver's job to convert its raw values to units in which + * this class operates. + */ + +/* + * For systems where the charger determines the maximum battery capacity + * the min and max fields should be used to present these values to user + * space. Unused/unknown fields will not appear in sysfs. + */ + +enum { + POWER_SUPPLY_STATUS_UNKNOWN = 0, + POWER_SUPPLY_STATUS_CHARGING, + POWER_SUPPLY_STATUS_DISCHARGING, + POWER_SUPPLY_STATUS_NOT_CHARGING, + POWER_SUPPLY_STATUS_FULL, +}; + +enum { + POWER_SUPPLY_HEALTH_UNKNOWN = 0, + POWER_SUPPLY_HEALTH_GOOD, + POWER_SUPPLY_HEALTH_OVERHEAT, + POWER_SUPPLY_HEALTH_DEAD, + POWER_SUPPLY_HEALTH_OVERVOLTAGE, + POWER_SUPPLY_HEALTH_UNSPEC_FAILURE, +}; + +enum { + POWER_SUPPLY_TECHNOLOGY_UNKNOWN = 0, + POWER_SUPPLY_TECHNOLOGY_NiMH, + POWER_SUPPLY_TECHNOLOGY_LION, + POWER_SUPPLY_TECHNOLOGY_LIPO, + POWER_SUPPLY_TECHNOLOGY_LiFe, + POWER_SUPPLY_TECHNOLOGY_NiCd, +}; + +enum { + POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN = 0, + POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL, + POWER_SUPPLY_CAPACITY_LEVEL_LOW, + POWER_SUPPLY_CAPACITY_LEVEL_NORMAL, + POWER_SUPPLY_CAPACITY_LEVEL_HIGH, + POWER_SUPPLY_CAPACITY_LEVEL_FULL, +}; + +enum power_supply_property { + /* Properties of type `int' */ + POWER_SUPPLY_PROP_STATUS = 0, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_EMPTY, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_AVG, + POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, + POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN, + POWER_SUPPLY_PROP_ENERGY_FULL, + POWER_SUPPLY_PROP_ENERGY_EMPTY, + POWER_SUPPLY_PROP_ENERGY_NOW, + POWER_SUPPLY_PROP_ENERGY_AVG, + POWER_SUPPLY_PROP_CAPACITY, /* in percents! */ + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TEMP_AMBIENT, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, + POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, + POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, + /* Properties of type `const char *' */ + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +enum power_supply_type { + POWER_SUPPLY_TYPE_BATTERY = 0, + POWER_SUPPLY_TYPE_UPS, + POWER_SUPPLY_TYPE_MAINS, + POWER_SUPPLY_TYPE_USB, +}; + +#endif /* __LINUX_POWER_SUPPLY_H__ */ diff --git a/hw/pxa.h b/hw/pxa.h new file mode 100644 index 0000000..16a68d9 --- /dev/null +++ b/hw/pxa.h @@ -0,0 +1,227 @@ +/* + * Intel XScale PXA255/270 processor support. + * + * Copyright (c) 2006 Openedhand Ltd. + * Written by Andrzej Zaborowski <balrog@zabor.org> + * + * This code is licenced under the GNU GPL v2. + */ +#ifndef PXA_H +# define PXA_H "pxa.h" + +/* Interrupt numbers */ +# define PXA2XX_PIC_SSP3 0 +# define PXA2XX_PIC_USBH2 2 +# define PXA2XX_PIC_USBH1 3 +# define PXA2XX_PIC_KEYPAD 4 +# define PXA2XX_PIC_PWRI2C 6 +# define PXA25X_PIC_HWUART 7 +# define PXA27X_PIC_OST_4_11 7 +# define PXA2XX_PIC_GPIO_0 8 +# define PXA2XX_PIC_GPIO_1 9 +# define PXA2XX_PIC_GPIO_X 10 +# define PXA2XX_PIC_I2S 13 +# define PXA26X_PIC_ASSP 15 +# define PXA25X_PIC_NSSP 16 +# define PXA27X_PIC_SSP2 16 +# define PXA2XX_PIC_LCD 17 +# define PXA2XX_PIC_I2C 18 +# define PXA2XX_PIC_ICP 19 +# define PXA2XX_PIC_STUART 20 +# define PXA2XX_PIC_BTUART 21 +# define PXA2XX_PIC_FFUART 22 +# define PXA2XX_PIC_MMC 23 +# define PXA2XX_PIC_SSP 24 +# define PXA2XX_PIC_DMA 25 +# define PXA2XX_PIC_OST_0 26 +# define PXA2XX_PIC_RTC1HZ 30 +# define PXA2XX_PIC_RTCALARM 31 + +/* DMA requests */ +# define PXA2XX_RX_RQ_I2S 2 +# define PXA2XX_TX_RQ_I2S 3 +# define PXA2XX_RX_RQ_BTUART 4 +# define PXA2XX_TX_RQ_BTUART 5 +# define PXA2XX_RX_RQ_FFUART 6 +# define PXA2XX_TX_RQ_FFUART 7 +# define PXA2XX_RX_RQ_SSP1 13 +# define PXA2XX_TX_RQ_SSP1 14 +# define PXA2XX_RX_RQ_SSP2 15 +# define PXA2XX_TX_RQ_SSP2 16 +# define PXA2XX_RX_RQ_ICP 17 +# define PXA2XX_TX_RQ_ICP 18 +# define PXA2XX_RX_RQ_STUART 19 +# define PXA2XX_TX_RQ_STUART 20 +# define PXA2XX_RX_RQ_MMCI 21 +# define PXA2XX_TX_RQ_MMCI 22 +# define PXA2XX_USB_RQ(x) ((x) + 24) +# define PXA2XX_RX_RQ_SSP3 66 +# define PXA2XX_TX_RQ_SSP3 67 + +# define PXA2XX_SDRAM_BASE 0xa0000000 +# define PXA2XX_INTERNAL_BASE 0x5c000000 +# define PXA2XX_INTERNAL_SIZE 0x40000 + +/* pxa2xx_pic.c */ +qemu_irq *pxa2xx_pic_init(target_phys_addr_t base, CPUState *env); + +/* pxa2xx_timer.c */ +void pxa25x_timer_init(target_phys_addr_t base, qemu_irq *irqs); +void pxa27x_timer_init(target_phys_addr_t base, qemu_irq *irqs, qemu_irq irq4); + +/* pxa2xx_gpio.c */ +struct pxa2xx_gpio_info_s; +struct pxa2xx_gpio_info_s *pxa2xx_gpio_init(target_phys_addr_t base, + CPUState *env, qemu_irq *pic, int lines); +qemu_irq *pxa2xx_gpio_in_get(struct pxa2xx_gpio_info_s *s); +void pxa2xx_gpio_out_set(struct pxa2xx_gpio_info_s *s, + int line, qemu_irq handler); +void pxa2xx_gpio_read_notifier(struct pxa2xx_gpio_info_s *s, qemu_irq handler); + +/* pxa2xx_dma.c */ +struct pxa2xx_dma_state_s; +struct pxa2xx_dma_state_s *pxa255_dma_init(target_phys_addr_t base, + qemu_irq irq); +struct pxa2xx_dma_state_s *pxa27x_dma_init(target_phys_addr_t base, + qemu_irq irq); +void pxa2xx_dma_request(struct pxa2xx_dma_state_s *s, int req_num, int on); + +/* pxa2xx_lcd.c */ +struct pxa2xx_lcdc_s; +struct pxa2xx_lcdc_s *pxa2xx_lcdc_init(target_phys_addr_t base, + qemu_irq irq, DisplayState *ds); +void pxa2xx_lcd_vsync_notifier(struct pxa2xx_lcdc_s *s, qemu_irq handler); +void pxa2xx_lcdc_oritentation(void *opaque, int angle); + +/* pxa2xx_mmci.c */ +struct pxa2xx_mmci_s; +struct pxa2xx_mmci_s *pxa2xx_mmci_init(target_phys_addr_t base, + BlockDriverState *bd, qemu_irq irq, void *dma); +void pxa2xx_mmci_handlers(struct pxa2xx_mmci_s *s, qemu_irq readonly, + qemu_irq coverswitch); + +/* pxa2xx_pcmcia.c */ +struct pxa2xx_pcmcia_s; +struct pxa2xx_pcmcia_s *pxa2xx_pcmcia_init(target_phys_addr_t base); +int pxa2xx_pcmcia_attach(void *opaque, struct pcmcia_card_s *card); +int pxa2xx_pcmcia_dettach(void *opaque); +void pxa2xx_pcmcia_set_irq_cb(void *opaque, qemu_irq irq, qemu_irq cd_irq); + +/* pxa2xx_keypad.c */ +struct keymap { + int column; + int row; +}; +struct pxa2xx_keypad_s; +struct pxa2xx_keypad_s *pxa27x_keypad_init(target_phys_addr_t base, + qemu_irq irq); +void pxa27x_register_keypad(struct pxa2xx_keypad_s *kp, struct keymap *map, + int size); + +/* pxa2xx.c */ +struct pxa2xx_ssp_s; +void pxa2xx_ssp_attach(struct pxa2xx_ssp_s *port, + uint32_t (*readfn)(void *opaque), + void (*writefn)(void *opaque, uint32_t value), void *opaque); + +struct pxa2xx_i2c_s; +struct pxa2xx_i2c_s *pxa2xx_i2c_init(target_phys_addr_t base, + qemu_irq irq, uint32_t page_size); +i2c_bus *pxa2xx_i2c_bus(struct pxa2xx_i2c_s *s); + +struct pxa2xx_i2s_s; +struct pxa2xx_fir_s; + +struct pxa2xx_state_s { + CPUState *env; + qemu_irq *pic; + qemu_irq reset; + struct pxa2xx_dma_state_s *dma; + struct pxa2xx_gpio_info_s *gpio; + struct pxa2xx_lcdc_s *lcd; + struct pxa2xx_ssp_s **ssp; + struct pxa2xx_i2c_s *i2c[2]; + struct pxa2xx_mmci_s *mmc; + struct pxa2xx_pcmcia_s *pcmcia[2]; + struct pxa2xx_i2s_s *i2s; + struct pxa2xx_fir_s *fir; + struct pxa2xx_keypad_s *kp; + + /* Power management */ + target_phys_addr_t pm_base; + uint32_t pm_regs[0x40]; + + /* Clock management */ + target_phys_addr_t cm_base; + uint32_t cm_regs[4]; + uint32_t clkcfg; + + /* Memory management */ + target_phys_addr_t mm_base; + uint32_t mm_regs[0x1a]; + + /* Performance monitoring */ + uint32_t pmnc; + + /* Real-Time clock */ + target_phys_addr_t rtc_base; + uint32_t rttr; + uint32_t rtsr; + uint32_t rtar; + uint32_t rdar1; + uint32_t rdar2; + uint32_t ryar1; + uint32_t ryar2; + uint32_t swar1; + uint32_t swar2; + uint32_t piar; + uint32_t last_rcnr; + uint32_t last_rdcr; + uint32_t last_rycr; + uint32_t last_swcr; + uint32_t last_rtcpicr; + int64_t last_hz; + int64_t last_sw; + int64_t last_pi; + QEMUTimer *rtc_hz; + QEMUTimer *rtc_rdal1; + QEMUTimer *rtc_rdal2; + QEMUTimer *rtc_swal1; + QEMUTimer *rtc_swal2; + QEMUTimer *rtc_pi; +}; + +struct pxa2xx_i2s_s { + target_phys_addr_t base; + qemu_irq irq; + struct pxa2xx_dma_state_s *dma; + void (*data_req)(void *, int, int); + + uint32_t control[2]; + uint32_t status; + uint32_t mask; + uint32_t clk; + + int enable; + int rx_len; + int tx_len; + void (*codec_out)(void *, uint32_t); + uint32_t (*codec_in)(void *); + void *opaque; + + int fifo_len; + uint32_t fifo[16]; +}; + +# define PA_FMT "0x%08lx" +# define REG_FMT "0x" TARGET_FMT_plx + +struct pxa2xx_state_s *pxa270_init(unsigned int sdram_size, DisplayState *ds, + const char *revision); +struct pxa2xx_state_s *pxa255_init(unsigned int sdram_size, DisplayState *ds); + +/* usb-ohci.c */ +void usb_ohci_init_pxa(target_phys_addr_t base, int num_ports, int devfn, + qemu_irq irq); + +#endif /* PXA_H */ diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c new file mode 100644 index 0000000..16b3215 --- /dev/null +++ b/hw/scsi-disk.c @@ -0,0 +1,809 @@ +/* + * SCSI Device emulation + * + * Copyright (c) 2006 CodeSourcery. + * Based on code by Fabrice Bellard + * + * Written by Paul Brook + * + * This code is licenced under the LGPL. + * + * Note that this file only handles the SCSI architecture model and device + * commands. Emulation of interface/link layer protocols is handled by + * the host adapter emulator. + */ + +//#define DEBUG_SCSI + +#ifdef DEBUG_SCSI +#define DPRINTF(fmt, args...) \ +do { printf("scsi-disk: " fmt , ##args); } while (0) +#else +#define DPRINTF(fmt, args...) do {} while(0) +#endif + +#define BADF(fmt, args...) \ +do { fprintf(stderr, "scsi-disk: " fmt , ##args); } while (0) + +#include "qemu-common.h" +#include "block.h" +#include "scsi-disk.h" + +#define SENSE_NO_SENSE 0 +#define SENSE_NOT_READY 2 +#define SENSE_HARDWARE_ERROR 4 +#define SENSE_ILLEGAL_REQUEST 5 + +#define SCSI_DMA_BUF_SIZE 65536 + +typedef struct SCSIRequest { + SCSIDeviceState *dev; + uint32_t tag; + /* ??? We should probably keep track of whether the data trasfer is + a read or a write. Currently we rely on the host getting it right. */ + /* Both sector and sector_count are in terms of qemu 512 byte blocks. */ + int sector; + int sector_count; + /* The amounnt of data in the buffer. */ + int buf_len; + uint8_t *dma_buf; + BlockDriverAIOCB *aiocb; + struct SCSIRequest *next; +} SCSIRequest; + +struct SCSIDeviceState +{ + BlockDriverState *bdrv; + SCSIRequest *requests; + /* The qemu block layer uses a fixed 512 byte sector size. + This is the number of 512 byte blocks in a single scsi sector. */ + int cluster_size; + int sense; + int tcq; + /* Completion functions may be called from either scsi_{read,write}_data + or from the AIO completion routines. */ + scsi_completionfn completion; + void *opaque; +}; + +/* Global pool of SCSIRequest structures. */ +static SCSIRequest *free_requests = NULL; + +static SCSIRequest *scsi_new_request(SCSIDeviceState *s, uint32_t tag) +{ + SCSIRequest *r; + + if (free_requests) { + r = free_requests; + free_requests = r->next; + } else { + r = qemu_malloc(sizeof(SCSIRequest)); + r->dma_buf = qemu_memalign(512, SCSI_DMA_BUF_SIZE); + } + r->dev = s; + r->tag = tag; + r->sector_count = 0; + r->buf_len = 0; + r->aiocb = NULL; + + r->next = s->requests; + s->requests = r; + return r; +} + +static void scsi_remove_request(SCSIRequest *r) +{ + SCSIRequest *last; + SCSIDeviceState *s = r->dev; + + if (s->requests == r) { + s->requests = r->next; + } else { + last = s->requests; + while (last && last->next != r) + last = last->next; + if (last) { + last->next = r->next; + } else { + BADF("Orphaned request\n"); + } + } + r->next = free_requests; + free_requests = r; +} + +static SCSIRequest *scsi_find_request(SCSIDeviceState *s, uint32_t tag) +{ + SCSIRequest *r; + + r = s->requests; + while (r && r->tag != tag) + r = r->next; + + return r; +} + +/* Helper function for command completion. */ +static void scsi_command_complete(SCSIRequest *r, int sense) +{ + SCSIDeviceState *s = r->dev; + uint32_t tag; + DPRINTF("Command complete tag=0x%x sense=%d\n", r->tag, sense); + s->sense = sense; + tag = r->tag; + scsi_remove_request(r); + s->completion(s->opaque, SCSI_REASON_DONE, tag, sense); +} + +/* Cancel a pending data transfer. */ +static void scsi_cancel_io(SCSIDevice *d, uint32_t tag) +{ + SCSIDeviceState *s = d->state; + SCSIRequest *r; + DPRINTF("Cancel tag=0x%x\n", tag); + r = scsi_find_request(s, tag); + if (r) { + if (r->aiocb) + bdrv_aio_cancel(r->aiocb); + r->aiocb = NULL; + scsi_remove_request(r); + } +} + +static void scsi_read_complete(void * opaque, int ret) +{ + SCSIRequest *r = (SCSIRequest *)opaque; + SCSIDeviceState *s = r->dev; + + if (ret) { + DPRINTF("IO error\n"); + scsi_command_complete(r, SENSE_HARDWARE_ERROR); + return; + } + DPRINTF("Data ready tag=0x%x len=%d\n", r->tag, r->buf_len); + + s->completion(s->opaque, SCSI_REASON_DATA, r->tag, r->buf_len); +} + +/* Read more data from scsi device into buffer. */ +static void scsi_read_data(SCSIDevice *d, uint32_t tag) +{ + SCSIDeviceState *s = d->state; + SCSIRequest *r; + uint32_t n; + + r = scsi_find_request(s, tag); + if (!r) { + BADF("Bad read tag 0x%x\n", tag); + /* ??? This is the wrong error. */ + scsi_command_complete(r, SENSE_HARDWARE_ERROR); + return; + } + if (r->sector_count == (uint32_t)-1) { + DPRINTF("Read buf_len=%d\n", r->buf_len); + r->sector_count = 0; + s->completion(s->opaque, SCSI_REASON_DATA, r->tag, r->buf_len); + return; + } + DPRINTF("Read sector_count=%d\n", r->sector_count); + if (r->sector_count == 0) { + scsi_command_complete(r, SENSE_NO_SENSE); + return; + } + + n = r->sector_count; + if (n > SCSI_DMA_BUF_SIZE / 512) + n = SCSI_DMA_BUF_SIZE / 512; + + r->buf_len = n * 512; + r->aiocb = bdrv_aio_read(s->bdrv, r->sector, r->dma_buf, n, + scsi_read_complete, r); + if (r->aiocb == NULL) + scsi_command_complete(r, SENSE_HARDWARE_ERROR); + r->sector += n; + r->sector_count -= n; +} + +static void scsi_write_complete(void * opaque, int ret) +{ + SCSIRequest *r = (SCSIRequest *)opaque; + SCSIDeviceState *s = r->dev; + uint32_t len; + + if (ret) { + fprintf(stderr, "scsi-disc: IO write error\n"); + exit(1); + } + + r->aiocb = NULL; + if (r->sector_count == 0) { + scsi_command_complete(r, SENSE_NO_SENSE); + } else { + len = r->sector_count * 512; + if (len > SCSI_DMA_BUF_SIZE) { + len = SCSI_DMA_BUF_SIZE; + } + r->buf_len = len; + DPRINTF("Write complete tag=0x%x more=%d\n", r->tag, len); + s->completion(s->opaque, SCSI_REASON_DATA, r->tag, len); + } +} + +/* Write data to a scsi device. Returns nonzero on failure. + The transfer may complete asynchronously. */ +static int scsi_write_data(SCSIDevice *d, uint32_t tag) +{ + SCSIDeviceState *s = d->state; + SCSIRequest *r; + uint32_t n; + + DPRINTF("Write data tag=0x%x\n", tag); + r = scsi_find_request(s, tag); + if (!r) { + BADF("Bad write tag 0x%x\n", tag); + scsi_command_complete(r, SENSE_HARDWARE_ERROR); + return 1; + } + if (r->aiocb) + BADF("Data transfer already in progress\n"); + n = r->buf_len / 512; + if (n) { + r->aiocb = bdrv_aio_write(s->bdrv, r->sector, r->dma_buf, n, + scsi_write_complete, r); + if (r->aiocb == NULL) + scsi_command_complete(r, SENSE_HARDWARE_ERROR); + r->sector += n; + r->sector_count -= n; + } else { + /* Invoke completion routine to fetch data from host. */ + scsi_write_complete(r, 0); + } + + return 0; +} + +/* Return a pointer to the data buffer. */ +static uint8_t *scsi_get_buf(SCSIDevice *d, uint32_t tag) +{ + SCSIDeviceState *s = d->state; + SCSIRequest *r; + + r = scsi_find_request(s, tag); + if (!r) { + BADF("Bad buffer tag 0x%x\n", tag); + return NULL; + } + return r->dma_buf; +} + +/* Execute a scsi command. Returns the length of the data expected by the + command. This will be Positive for data transfers from the device + (eg. disk reads), negative for transfers to the device (eg. disk writes), + and zero if the command does not transfer any data. */ + +static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag, + uint8_t *buf, int lun) +{ + SCSIDeviceState *s = d->state; + uint64_t nb_sectors; + uint32_t lba; + uint32_t len; + int cmdlen; + int is_write; + uint8_t command; + uint8_t *outbuf; + SCSIRequest *r; + + command = buf[0]; + r = scsi_find_request(s, tag); + if (r) { + BADF("Tag 0x%x already in use\n", tag); + scsi_cancel_io(d, tag); + } + /* ??? Tags are not unique for different luns. We only implement a + single lun, so this should not matter. */ + r = scsi_new_request(s, tag); + outbuf = r->dma_buf; + is_write = 0; + DPRINTF("Command: lun=%d tag=0x%x data=0x%02x", lun, tag, buf[0]); + switch (command >> 5) { + case 0: + lba = buf[3] | (buf[2] << 8) | ((buf[1] & 0x1f) << 16); + len = buf[4]; + cmdlen = 6; + break; + case 1: + case 2: + lba = buf[5] | (buf[4] << 8) | (buf[3] << 16) | (buf[2] << 24); + len = buf[8] | (buf[7] << 8); + cmdlen = 10; + break; + case 4: + lba = buf[5] | (buf[4] << 8) | (buf[3] << 16) | (buf[2] << 24); + len = buf[13] | (buf[12] << 8) | (buf[11] << 16) | (buf[10] << 24); + cmdlen = 16; + break; + case 5: + lba = buf[5] | (buf[4] << 8) | (buf[3] << 16) | (buf[2] << 24); + len = buf[9] | (buf[8] << 8) | (buf[7] << 16) | (buf[6] << 24); + cmdlen = 12; + break; + default: + BADF("Unsupported command length, command %x\n", command); + goto fail; + } +#ifdef DEBUG_SCSI + { + int i; + for (i = 1; i < cmdlen; i++) { + printf(" 0x%02x", buf[i]); + } + printf("\n"); + } +#endif + if (lun || buf[1] >> 5) { + /* Only LUN 0 supported. */ + DPRINTF("Unimplemented LUN %d\n", lun ? lun : buf[1] >> 5); + goto fail; + } + switch (command) { + case 0x0: + DPRINTF("Test Unit Ready\n"); + break; + case 0x03: + DPRINTF("Request Sense (len %d)\n", len); + if (len < 4) + goto fail; + memset(outbuf, 0, 4); + outbuf[0] = 0xf0; + outbuf[1] = 0; + outbuf[2] = s->sense; + r->buf_len = 4; + break; + case 0x12: + DPRINTF("Inquiry (len %d)\n", len); + if (buf[1] & 0x2) { + /* Command support data - optional, not implemented */ + BADF("optional INQUIRY command support request not implemented\n"); + goto fail; + } + else if (buf[1] & 0x1) { + /* Vital product data */ + uint8_t page_code = buf[2]; + if (len < 4) { + BADF("Error: Inquiry (EVPD[%02X]) buffer size %d is " + "less than 4\n", page_code, len); + goto fail; + } + + switch (page_code) { + case 0x00: + { + /* Supported page codes, mandatory */ + DPRINTF("Inquiry EVPD[Supported pages] " + "buffer size %d\n", len); + + r->buf_len = 0; + + if (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM) { + outbuf[r->buf_len++] = 5; + } else { + outbuf[r->buf_len++] = 0; + } + + outbuf[r->buf_len++] = 0x00; // this page + outbuf[r->buf_len++] = 0x00; + outbuf[r->buf_len++] = 3; // number of pages + outbuf[r->buf_len++] = 0x00; // list of supported pages (this page) + outbuf[r->buf_len++] = 0x80; // unit serial number + outbuf[r->buf_len++] = 0x83; // device identification + } + break; + case 0x80: + { + /* Device serial number, optional */ + if (len < 4) { + BADF("Error: EVPD[Serial number] Inquiry buffer " + "size %d too small, %d needed\n", len, 4); + goto fail; + } + + DPRINTF("Inquiry EVPD[Serial number] buffer size %d\n", len); + + r->buf_len = 0; + + /* Supported page codes */ + if (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM) { + outbuf[r->buf_len++] = 5; + } else { + outbuf[r->buf_len++] = 0; + } + + outbuf[r->buf_len++] = 0x80; // this page + outbuf[r->buf_len++] = 0x00; + outbuf[r->buf_len++] = 0x01; // 1 byte data follow + + outbuf[r->buf_len++] = '0'; // 1 byte data follow + } + + break; + case 0x83: + { + /* Device identification page, mandatory */ + int max_len = 255 - 8; + int id_len = strlen(bdrv_get_device_name(s->bdrv)); + if (id_len > max_len) + id_len = max_len; + + DPRINTF("Inquiry EVPD[Device identification] " + "buffer size %d\n", len); + r->buf_len = 0; + if (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM) { + outbuf[r->buf_len++] = 5; + } else { + outbuf[r->buf_len++] = 0; + } + + outbuf[r->buf_len++] = 0x83; // this page + outbuf[r->buf_len++] = 0x00; + outbuf[r->buf_len++] = 3 + id_len; + + outbuf[r->buf_len++] = 0x2; // ASCII + outbuf[r->buf_len++] = 0; // not officially assigned + outbuf[r->buf_len++] = 0; // reserved + outbuf[r->buf_len++] = id_len; // length of data following + + memcpy(&outbuf[r->buf_len], + bdrv_get_device_name(s->bdrv), id_len); + r->buf_len += id_len; + } + break; + default: + BADF("Error: unsupported Inquiry (EVPD[%02X]) " + "buffer size %d\n", page_code, len); + goto fail; + } + /* done with EVPD */ + break; + } + else { + /* Standard INQUIRY data */ + if (buf[2] != 0) { + BADF("Error: Inquiry (STANDARD) page or code " + "is non-zero [%02X]\n", buf[2]); + goto fail; + } + + /* PAGE CODE == 0 */ + if (len < 5) { + BADF("Error: Inquiry (STANDARD) buffer size %d " + "is less than 5\n", len); + goto fail; + } + + if (len < 36) { + BADF("Error: Inquiry (STANDARD) buffer size %d " + "is less than 36 (TODO: only 5 required)\n", len); + } + } + memset(outbuf, 0, 36); + if (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM) { + outbuf[0] = 5; + outbuf[1] = 0x80; + memcpy(&outbuf[16], "QEMU CD-ROM ", 16); + } else { + outbuf[0] = 0; + memcpy(&outbuf[16], "QEMU HARDDISK ", 16); + } + memcpy(&outbuf[8], "QEMU ", 8); + memcpy(&outbuf[32], QEMU_VERSION, 4); + /* Identify device as SCSI-3 rev 1. + Some later commands are also implemented. */ + outbuf[2] = 3; + outbuf[3] = 2; /* Format 2 */ + outbuf[4] = 31; + /* Sync data transfer and TCQ. */ + outbuf[7] = 0x10 | (s->tcq ? 0x02 : 0); + r->buf_len = 36; + break; + case 0x16: + DPRINTF("Reserve(6)\n"); + if (buf[1] & 1) + goto fail; + break; + case 0x17: + DPRINTF("Release(6)\n"); + if (buf[1] & 1) + goto fail; + break; + case 0x1a: + case 0x5a: + { + uint8_t *p; + int page; + + page = buf[2] & 0x3f; + DPRINTF("Mode Sense (page %d, len %d)\n", page, len); + p = outbuf; + memset(p, 0, 4); + outbuf[1] = 0; /* Default media type. */ + outbuf[3] = 0; /* Block descriptor length. */ + if (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM) { + outbuf[2] = 0x80; /* Readonly. */ + } + p += 4; + if (page == 4) { + int cylinders, heads, secs; + + /* Rigid disk device geometry page. */ + p[0] = 4; + p[1] = 0x16; + /* if a geometry hint is available, use it */ + bdrv_get_geometry_hint(s->bdrv, &cylinders, &heads, &secs); + p[2] = (cylinders >> 16) & 0xff; + p[3] = (cylinders >> 8) & 0xff; + p[4] = cylinders & 0xff; + p[5] = heads & 0xff; + /* Write precomp start cylinder, disabled */ + p[6] = (cylinders >> 16) & 0xff; + p[7] = (cylinders >> 8) & 0xff; + p[8] = cylinders & 0xff; + /* Reduced current start cylinder, disabled */ + p[9] = (cylinders >> 16) & 0xff; + p[10] = (cylinders >> 8) & 0xff; + p[11] = cylinders & 0xff; + /* Device step rate [ns], 200ns */ + p[12] = 0; + p[13] = 200; + /* Landing zone cylinder */ + p[14] = 0xff; + p[15] = 0xff; + p[16] = 0xff; + /* Medium rotation rate [rpm], 5400 rpm */ + p[20] = (5400 >> 8) & 0xff; + p[21] = 5400 & 0xff; + p += 0x16; + } else if (page == 5) { + int cylinders, heads, secs; + + /* Flexible disk device geometry page. */ + p[0] = 5; + p[1] = 0x1e; + /* Transfer rate [kbit/s], 5Mbit/s */ + p[2] = 5000 >> 8; + p[3] = 5000 & 0xff; + /* if a geometry hint is available, use it */ + bdrv_get_geometry_hint(s->bdrv, &cylinders, &heads, &secs); + p[4] = heads & 0xff; + p[5] = secs & 0xff; + p[6] = s->cluster_size * 2; + p[8] = (cylinders >> 8) & 0xff; + p[9] = cylinders & 0xff; + /* Write precomp start cylinder, disabled */ + p[10] = (cylinders >> 8) & 0xff; + p[11] = cylinders & 0xff; + /* Reduced current start cylinder, disabled */ + p[12] = (cylinders >> 8) & 0xff; + p[13] = cylinders & 0xff; + /* Device step rate [100us], 100us */ + p[14] = 0; + p[15] = 1; + /* Device step pulse width [us], 1us */ + p[16] = 1; + /* Device head settle delay [100us], 100us */ + p[17] = 0; + p[18] = 1; + /* Motor on delay [0.1s], 0.1s */ + p[19] = 1; + /* Motor off delay [0.1s], 0.1s */ + p[20] = 1; + /* Medium rotation rate [rpm], 5400 rpm */ + p[28] = (5400 >> 8) & 0xff; + p[29] = 5400 & 0xff; + p += 0x1e; + } else if ((page == 8 || page == 0x3f)) { + /* Caching page. */ + memset(p,0,20); + p[0] = 8; + p[1] = 0x12; + p[2] = 4; /* WCE */ + p += 20; + } + if ((page == 0x3f || page == 0x2a) + && (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM)) { + /* CD Capabilities and Mechanical Status page. */ + p[0] = 0x2a; + p[1] = 0x14; + p[2] = 3; // CD-R & CD-RW read + p[3] = 0; // Writing not supported + p[4] = 0x7f; /* Audio, composite, digital out, + mode 2 form 1&2, multi session */ + p[5] = 0xff; /* CD DA, DA accurate, RW supported, + RW corrected, C2 errors, ISRC, + UPC, Bar code */ + p[6] = 0x2d | (bdrv_is_locked(s->bdrv)? 2 : 0); + /* Locking supported, jumper present, eject, tray */ + p[7] = 0; /* no volume & mute control, no + changer */ + p[8] = (50 * 176) >> 8; // 50x read speed + p[9] = (50 * 176) & 0xff; + p[10] = 0 >> 8; // No volume + p[11] = 0 & 0xff; + p[12] = 2048 >> 8; // 2M buffer + p[13] = 2048 & 0xff; + p[14] = (16 * 176) >> 8; // 16x read speed current + p[15] = (16 * 176) & 0xff; + p[18] = (16 * 176) >> 8; // 16x write speed + p[19] = (16 * 176) & 0xff; + p[20] = (16 * 176) >> 8; // 16x write speed current + p[21] = (16 * 176) & 0xff; + p += 22; + } + r->buf_len = p - outbuf; + outbuf[0] = r->buf_len - 4; + if (r->buf_len > len) + r->buf_len = len; + } + break; + case 0x1b: + DPRINTF("Start Stop Unit\n"); + break; + case 0x1e: + DPRINTF("Prevent Allow Medium Removal (prevent = %d)\n", buf[4] & 3); + bdrv_set_locked(s->bdrv, buf[4] & 1); + break; + case 0x25: + DPRINTF("Read Capacity\n"); + /* The normal LEN field for this command is zero. */ + memset(outbuf, 0, 8); + bdrv_get_geometry(s->bdrv, &nb_sectors); + /* Returned value is the address of the last sector. */ + if (nb_sectors) { + nb_sectors--; + outbuf[0] = (nb_sectors >> 24) & 0xff; + outbuf[1] = (nb_sectors >> 16) & 0xff; + outbuf[2] = (nb_sectors >> 8) & 0xff; + outbuf[3] = nb_sectors & 0xff; + outbuf[4] = 0; + outbuf[5] = 0; + outbuf[6] = s->cluster_size * 2; + outbuf[7] = 0; + r->buf_len = 8; + } else { + scsi_command_complete(r, SENSE_NOT_READY); + return 0; + } + break; + case 0x08: + case 0x28: + DPRINTF("Read (sector %d, count %d)\n", lba, len); + r->sector = lba * s->cluster_size; + r->sector_count = len * s->cluster_size; + break; + case 0x0a: + case 0x2a: + DPRINTF("Write (sector %d, count %d)\n", lba, len); + r->sector = lba * s->cluster_size; + r->sector_count = len * s->cluster_size; + is_write = 1; + break; + case 0x35: + DPRINTF("Synchronise cache (sector %d, count %d)\n", lba, len); + bdrv_flush(s->bdrv); + break; + case 0x43: + { + int start_track, format, msf, toclen; + + msf = buf[1] & 2; + format = buf[2] & 0xf; + start_track = buf[6]; + bdrv_get_geometry(s->bdrv, &nb_sectors); + DPRINTF("Read TOC (track %d format %d msf %d)\n", start_track, format, msf >> 1); + switch(format) { + case 0: + toclen = cdrom_read_toc(nb_sectors, outbuf, msf, start_track); + break; + case 1: + /* multi session : only a single session defined */ + toclen = 12; + memset(outbuf, 0, 12); + outbuf[1] = 0x0a; + outbuf[2] = 0x01; + outbuf[3] = 0x01; + break; + case 2: + toclen = cdrom_read_toc_raw(nb_sectors, outbuf, msf, start_track); + break; + default: + goto error_cmd; + } + if (toclen > 0) { + if (len > toclen) + len = toclen; + r->buf_len = len; + break; + } + error_cmd: + DPRINTF("Read TOC error\n"); + goto fail; + } + case 0x46: + DPRINTF("Get Configuration (rt %d, maxlen %d)\n", buf[1] & 3, len); + memset(outbuf, 0, 8); + /* ??? This should probably return much more information. For now + just return the basic header indicating the CD-ROM profile. */ + outbuf[7] = 8; // CD-ROM + r->buf_len = 8; + break; + case 0x56: + DPRINTF("Reserve(10)\n"); + if (buf[1] & 3) + goto fail; + break; + case 0x57: + DPRINTF("Release(10)\n"); + if (buf[1] & 3) + goto fail; + break; + case 0xa0: + DPRINTF("Report LUNs (len %d)\n", len); + if (len < 16) + goto fail; + memset(outbuf, 0, 16); + outbuf[3] = 8; + r->buf_len = 16; + break; + default: + DPRINTF("Unknown SCSI command (%2.2x)\n", buf[0]); + fail: + scsi_command_complete(r, SENSE_ILLEGAL_REQUEST); + return 0; + } + if (r->sector_count == 0 && r->buf_len == 0) { + scsi_command_complete(r, SENSE_NO_SENSE); + } + len = r->sector_count * 512 + r->buf_len; + if (is_write) { + return -len; + } else { + if (!r->sector_count) + r->sector_count = -1; + return len; + } +} + +static void scsi_destroy(SCSIDevice *d) +{ + qemu_free(d->state); + qemu_free(d); +} + +SCSIDevice *scsi_disk_init(BlockDriverState *bdrv, int tcq, + scsi_completionfn completion, void *opaque) +{ + SCSIDevice *d; + SCSIDeviceState *s; + + s = (SCSIDeviceState *)qemu_mallocz(sizeof(SCSIDeviceState)); + s->bdrv = bdrv; + s->tcq = tcq; + s->completion = completion; + s->opaque = opaque; + if (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM) { + s->cluster_size = 4; + } else { + s->cluster_size = 1; + } + + d = (SCSIDevice *)qemu_mallocz(sizeof(SCSIDevice)); + d->state = s; + d->destroy = scsi_destroy; + d->send_command = scsi_send_command; + d->read_data = scsi_read_data; + d->write_data = scsi_write_data; + d->cancel_io = scsi_cancel_io; + d->get_buf = scsi_get_buf; + + return d; +} diff --git a/hw/scsi-disk.h b/hw/scsi-disk.h new file mode 100644 index 0000000..f42212b --- /dev/null +++ b/hw/scsi-disk.h @@ -0,0 +1,36 @@ +#ifndef SCSI_DISK_H +#define SCSI_DISK_H + +/* scsi-disk.c */ +enum scsi_reason { + SCSI_REASON_DONE, /* Command complete. */ + SCSI_REASON_DATA /* Transfer complete, more data required. */ +}; + +typedef struct SCSIDeviceState SCSIDeviceState; +typedef struct SCSIDevice SCSIDevice; +typedef void (*scsi_completionfn)(void *opaque, int reason, uint32_t tag, + uint32_t arg); + +struct SCSIDevice +{ + SCSIDeviceState *state; + void (*destroy)(SCSIDevice *s); + int32_t (*send_command)(SCSIDevice *s, uint32_t tag, uint8_t *buf, + int lun); + void (*read_data)(SCSIDevice *s, uint32_t tag); + int (*write_data)(SCSIDevice *s, uint32_t tag); + void (*cancel_io)(SCSIDevice *s, uint32_t tag); + uint8_t *(*get_buf)(SCSIDevice *s, uint32_t tag); +}; + +SCSIDevice *scsi_disk_init(BlockDriverState *bdrv, int tcq, + scsi_completionfn completion, void *opaque); +SCSIDevice *scsi_generic_init(BlockDriverState *bdrv, int tcq, + scsi_completionfn completion, void *opaque); + +/* cdrom.c */ +int cdrom_read_toc(int nb_sectors, uint8_t *buf, int msf, int start_track); +int cdrom_read_toc_raw(int nb_sectors, uint8_t *buf, int msf, int session_num); + +#endif @@ -0,0 +1,83 @@ +/* + * include/linux/mmc/sd.h + * + * Copyright (C) 2005-2007 Pierre Ossman, All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#ifndef MMC_SD_H +#define MMC_SD_H + +/* SD commands type argument response */ + /* class 0 */ +/* This is basically the same command as for MMC with some quirks. */ +#define SD_SEND_RELATIVE_ADDR 3 /* bcr R6 */ +#define SD_SEND_IF_COND 8 /* bcr [11:0] See below R7 */ + + /* class 10 */ +#define SD_SWITCH 6 /* adtc [31:0] See below R1 */ + + /* Application commands */ +#define SD_APP_SET_BUS_WIDTH 6 /* ac [1:0] bus width R1 */ +#define SD_APP_SEND_NUM_WR_BLKS 22 /* adtc R1 */ +#define SD_APP_OP_COND 41 /* bcr [31:0] OCR R3 */ +#define SD_APP_SEND_SCR 51 /* adtc R1 */ + +/* + * SD_SWITCH argument format: + * + * [31] Check (0) or switch (1) + * [30:24] Reserved (0) + * [23:20] Function group 6 + * [19:16] Function group 5 + * [15:12] Function group 4 + * [11:8] Function group 3 + * [7:4] Function group 2 + * [3:0] Function group 1 + */ + +/* + * SD_SEND_IF_COND argument format: + * + * [31:12] Reserved (0) + * [11:8] Host Voltage Supply Flags + * [7:0] Check Pattern (0xAA) + */ + +/* + * SCR field definitions + */ + +#define SCR_SPEC_VER_0 0 /* Implements system specification 1.0 - 1.01 */ +#define SCR_SPEC_VER_1 1 /* Implements system specification 1.10 */ +#define SCR_SPEC_VER_2 2 /* Implements system specification 2.00 */ + +/* + * SD bus widths + */ +#define SD_BUS_WIDTH_1 0 +#define SD_BUS_WIDTH_4 2 + +/* + * SD_SWITCH mode + */ +#define SD_SWITCH_CHECK 0 +#define SD_SWITCH_SET 1 + +/* + * SD_SWITCH function groups + */ +#define SD_SWITCH_GRP_ACCESS 0 + +/* + * SD_SWITCH access modes + */ +#define SD_SWITCH_ACCESS_DEF 0 +#define SD_SWITCH_ACCESS_HS 1 + +#endif + diff --git a/hw/smc91c111.c b/hw/smc91c111.c new file mode 100644 index 0000000..410051d --- /dev/null +++ b/hw/smc91c111.c @@ -0,0 +1,715 @@ +/* + * SMSC 91C111 Ethernet interface emulation + * + * Copyright (c) 2005 CodeSourcery, LLC. + * Written by Paul Brook + * + * This code is licenced under the GPL + */ + +#include "hw.h" +#include "net.h" +#include "devices.h" +/* For crc32 */ +#include <zlib.h> + +/* Number of 2k memory pages available. */ +#define NUM_PACKETS 4 + +typedef struct { + uint32_t base; + VLANClientState *vc; + uint16_t tcr; + uint16_t rcr; + uint16_t cr; + uint16_t ctr; + uint16_t gpr; + uint16_t ptr; + uint16_t ercv; + qemu_irq irq; + int bank; + int packet_num; + int tx_alloc; + /* Bitmask of allocated packets. */ + int allocated; + int tx_fifo_len; + int tx_fifo[NUM_PACKETS]; + int rx_fifo_len; + int rx_fifo[NUM_PACKETS]; + int tx_fifo_done_len; + int tx_fifo_done[NUM_PACKETS]; + /* Packet buffer memory. */ + uint8_t data[NUM_PACKETS][2048]; + uint8_t int_level; + uint8_t int_mask; + uint8_t macaddr[6]; +} smc91c111_state; + +#define RCR_SOFT_RST 0x8000 +#define RCR_STRIP_CRC 0x0200 +#define RCR_RXEN 0x0100 + +#define TCR_EPH_LOOP 0x2000 +#define TCR_NOCRC 0x0100 +#define TCR_PAD_EN 0x0080 +#define TCR_FORCOL 0x0004 +#define TCR_LOOP 0x0002 +#define TCR_TXEN 0x0001 + +#define INT_MD 0x80 +#define INT_ERCV 0x40 +#define INT_EPH 0x20 +#define INT_RX_OVRN 0x10 +#define INT_ALLOC 0x08 +#define INT_TX_EMPTY 0x04 +#define INT_TX 0x02 +#define INT_RCV 0x01 + +#define CTR_AUTO_RELEASE 0x0800 +#define CTR_RELOAD 0x0002 +#define CTR_STORE 0x0001 + +#define RS_ALGNERR 0x8000 +#define RS_BRODCAST 0x4000 +#define RS_BADCRC 0x2000 +#define RS_ODDFRAME 0x1000 +#define RS_TOOLONG 0x0800 +#define RS_TOOSHORT 0x0400 +#define RS_MULTICAST 0x0001 + +/* Update interrupt status. */ +static void smc91c111_update(smc91c111_state *s) +{ + int level; + + if (s->tx_fifo_len == 0) + s->int_level |= INT_TX_EMPTY; + if (s->tx_fifo_done_len != 0) + s->int_level |= INT_TX; + level = (s->int_level & s->int_mask) != 0; + qemu_set_irq(s->irq, level); +} + +/* Try to allocate a packet. Returns 0x80 on failure. */ +static int smc91c111_allocate_packet(smc91c111_state *s) +{ + int i; + if (s->allocated == (1 << NUM_PACKETS) - 1) { + return 0x80; + } + + for (i = 0; i < NUM_PACKETS; i++) { + if ((s->allocated & (1 << i)) == 0) + break; + } + s->allocated |= 1 << i; + return i; +} + + +/* Process a pending TX allocate. */ +static void smc91c111_tx_alloc(smc91c111_state *s) +{ + s->tx_alloc = smc91c111_allocate_packet(s); + if (s->tx_alloc == 0x80) + return; + s->int_level |= INT_ALLOC; + smc91c111_update(s); +} + +/* Remove and item from the RX FIFO. */ +static void smc91c111_pop_rx_fifo(smc91c111_state *s) +{ + int i; + + s->rx_fifo_len--; + if (s->rx_fifo_len) { + for (i = 0; i < s->rx_fifo_len; i++) + s->rx_fifo[i] = s->rx_fifo[i + 1]; + s->int_level |= INT_RCV; + } else { + s->int_level &= ~INT_RCV; + } + smc91c111_update(s); +} + +/* Remove an item from the TX completion FIFO. */ +static void smc91c111_pop_tx_fifo_done(smc91c111_state *s) +{ + int i; + + if (s->tx_fifo_done_len == 0) + return; + s->tx_fifo_done_len--; + for (i = 0; i < s->tx_fifo_done_len; i++) + s->tx_fifo_done[i] = s->tx_fifo_done[i + 1]; +} + +/* Release the memory allocated to a packet. */ +static void smc91c111_release_packet(smc91c111_state *s, int packet) +{ + s->allocated &= ~(1 << packet); + if (s->tx_alloc == 0x80) + smc91c111_tx_alloc(s); +} + +/* Flush the TX FIFO. */ +static void smc91c111_do_tx(smc91c111_state *s) +{ + int i; + int len; + int control; + int add_crc; + int packetnum; + uint8_t *p; + + if ((s->tcr & TCR_TXEN) == 0) + return; + if (s->tx_fifo_len == 0) + return; + for (i = 0; i < s->tx_fifo_len; i++) { + packetnum = s->tx_fifo[i]; + p = &s->data[packetnum][0]; + /* Set status word. */ + *(p++) = 0x01; + *(p++) = 0x40; + len = *(p++); + len |= ((int)*(p++)) << 8; + len -= 6; + control = p[len + 1]; + if (control & 0x20) + len++; + /* ??? This overwrites the data following the buffer. + Don't know what real hardware does. */ + if (len < 64 && (s->tcr & TCR_PAD_EN)) { + memset(p + len, 0, 64 - len); + len = 64; + } +#if 0 + /* The card is supposed to append the CRC to the frame. However + none of the other network traffic has the CRC appended. + Suspect this is low level ethernet detail we don't need to worry + about. */ + add_crc = (control & 0x10) || (s->tcr & TCR_NOCRC) == 0; + if (add_crc) { + uint32_t crc; + + crc = crc32(~0, p, len); + memcpy(p + len, &crc, 4); + len += 4; + } +#else + add_crc = 0; +#endif + if (s->ctr & CTR_AUTO_RELEASE) + /* Race? */ + smc91c111_release_packet(s, packetnum); + else if (s->tx_fifo_done_len < NUM_PACKETS) + s->tx_fifo_done[s->tx_fifo_done_len++] = packetnum; + qemu_send_packet(s->vc, p, len); + } + s->tx_fifo_len = 0; + smc91c111_update(s); +} + +/* Add a packet to the TX FIFO. */ +static void smc91c111_queue_tx(smc91c111_state *s, int packet) +{ + if (s->tx_fifo_len == NUM_PACKETS) + return; + s->tx_fifo[s->tx_fifo_len++] = packet; + smc91c111_do_tx(s); +} + +static void smc91c111_reset(smc91c111_state *s) +{ + s->bank = 0; + s->tx_fifo_len = 0; + s->tx_fifo_done_len = 0; + s->rx_fifo_len = 0; + s->allocated = 0; + s->packet_num = 0; + s->tx_alloc = 0; + s->tcr = 0; + s->rcr = 0; + s->cr = 0xa0b1; + s->ctr = 0x1210; + s->ptr = 0; + s->ercv = 0x1f; + s->int_level = INT_TX_EMPTY; + s->int_mask = 0; + smc91c111_update(s); +} + +#define SET_LOW(name, val) s->name = (s->name & 0xff00) | val +#define SET_HIGH(name, val) s->name = (s->name & 0xff) | (val << 8) + +static void smc91c111_writeb(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + smc91c111_state *s = (smc91c111_state *)opaque; + + offset -= s->base; + if (offset == 14) { + s->bank = value; + return; + } + if (offset == 15) + return; + switch (s->bank) { + case 0: + switch (offset) { + case 0: /* TCR */ + SET_LOW(tcr, value); + return; + case 1: + SET_HIGH(tcr, value); + return; + case 4: /* RCR */ + SET_LOW(rcr, value); + return; + case 5: + SET_HIGH(rcr, value); + if (s->rcr & RCR_SOFT_RST) + smc91c111_reset(s); + return; + case 10: case 11: /* RPCR */ + /* Ignored */ + return; + } + break; + + case 1: + switch (offset) { + case 0: /* CONFIG */ + SET_LOW(cr, value); + return; + case 1: + SET_HIGH(cr,value); + return; + case 2: case 3: /* BASE */ + case 4: case 5: case 6: case 7: case 8: case 9: /* IA */ + /* Not implemented. */ + return; + case 10: /* Genral Purpose */ + SET_LOW(gpr, value); + return; + case 11: + SET_HIGH(gpr, value); + return; + case 12: /* Control */ + if (value & 1) + fprintf(stderr, "smc91c111:EEPROM store not implemented\n"); + if (value & 2) + fprintf(stderr, "smc91c111:EEPROM reload not implemented\n"); + value &= ~3; + SET_LOW(ctr, value); + return; + case 13: + SET_HIGH(ctr, value); + return; + } + break; + + case 2: + switch (offset) { + case 0: /* MMU Command */ + switch (value >> 5) { + case 0: /* no-op */ + break; + case 1: /* Allocate for TX. */ + s->tx_alloc = 0x80; + s->int_level &= ~INT_ALLOC; + smc91c111_update(s); + smc91c111_tx_alloc(s); + break; + case 2: /* Reset MMU. */ + s->allocated = 0; + s->tx_fifo_len = 0; + s->tx_fifo_done_len = 0; + s->rx_fifo_len = 0; + s->tx_alloc = 0; + break; + case 3: /* Remove from RX FIFO. */ + smc91c111_pop_rx_fifo(s); + break; + case 4: /* Remove from RX FIFO and release. */ + if (s->rx_fifo_len > 0) { + smc91c111_release_packet(s, s->rx_fifo[0]); + } + smc91c111_pop_rx_fifo(s); + break; + case 5: /* Release. */ + smc91c111_release_packet(s, s->packet_num); + break; + case 6: /* Add to TX FIFO. */ + smc91c111_queue_tx(s, s->packet_num); + break; + case 7: /* Reset TX FIFO. */ + s->tx_fifo_len = 0; + s->tx_fifo_done_len = 0; + break; + } + return; + case 1: + /* Ignore. */ + return; + case 2: /* Packet Number Register */ + s->packet_num = value; + return; + case 3: case 4: case 5: + /* Should be readonly, but linux writes to them anyway. Ignore. */ + return; + case 6: /* Pointer */ + SET_LOW(ptr, value); + return; + case 7: + SET_HIGH(ptr, value); + return; + case 8: case 9: case 10: case 11: /* Data */ + { + int p; + int n; + + if (s->ptr & 0x8000) + n = s->rx_fifo[0]; + else + n = s->packet_num; + p = s->ptr & 0x07ff; + if (s->ptr & 0x4000) { + s->ptr = (s->ptr & 0xf800) | ((s->ptr + 1) & 0x7ff); + } else { + p += (offset & 3); + } + s->data[n][p] = value; + } + return; + case 12: /* Interrupt ACK. */ + s->int_level &= ~(value & 0xd6); + if (value & INT_TX) + smc91c111_pop_tx_fifo_done(s); + smc91c111_update(s); + return; + case 13: /* Interrupt mask. */ + s->int_mask = value; + smc91c111_update(s); + return; + } + break;; + + case 3: + switch (offset) { + case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: + /* Multicast table. */ + /* Not implemented. */ + return; + case 8: case 9: /* Management Interface. */ + /* Not implemented. */ + return; + case 12: /* Early receive. */ + s->ercv = value & 0x1f; + case 13: + /* Ignore. */ + return; + } + break; + } + cpu_abort (cpu_single_env, "smc91c111_write: Bad reg %d:%x\n", + s->bank, (int)offset); +} + +static uint32_t smc91c111_readb(void *opaque, target_phys_addr_t offset) +{ + smc91c111_state *s = (smc91c111_state *)opaque; + + offset -= s->base; + if (offset == 14) { + return s->bank; + } + if (offset == 15) + return 0x33; + switch (s->bank) { + case 0: + switch (offset) { + case 0: /* TCR */ + return s->tcr & 0xff; + case 1: + return s->tcr >> 8; + case 2: /* EPH Status */ + return 0; + case 3: + return 0x40; + case 4: /* RCR */ + return s->rcr & 0xff; + case 5: + return s->rcr >> 8; + case 6: /* Counter */ + case 7: + /* Not implemented. */ + return 0; + case 8: /* Memory size. */ + return NUM_PACKETS; + case 9: /* Free memory available. */ + { + int i; + int n; + n = 0; + for (i = 0; i < NUM_PACKETS; i++) { + if (s->allocated & (1 << i)) + n++; + } + return n; + } + case 10: case 11: /* RPCR */ + /* Not implemented. */ + return 0; + } + break; + + case 1: + switch (offset) { + case 0: /* CONFIG */ + return s->cr & 0xff; + case 1: + return s->cr >> 8; + case 2: case 3: /* BASE */ + /* Not implemented. */ + return 0; + case 4: case 5: case 6: case 7: case 8: case 9: /* IA */ + return s->macaddr[offset - 4]; + case 10: /* General Purpose */ + return s->gpr & 0xff; + case 11: + return s->gpr >> 8; + case 12: /* Control */ + return s->ctr & 0xff; + case 13: + return s->ctr >> 8; + } + break; + + case 2: + switch (offset) { + case 0: case 1: /* MMUCR Busy bit. */ + return 0; + case 2: /* Packet Number. */ + return s->packet_num; + case 3: /* Allocation Result. */ + return s->tx_alloc; + case 4: /* TX FIFO */ + if (s->tx_fifo_done_len == 0) + return 0x80; + else + return s->tx_fifo_done[0]; + case 5: /* RX FIFO */ + if (s->rx_fifo_len == 0) + return 0x80; + else + return s->rx_fifo[0]; + case 6: /* Pointer */ + return s->ptr & 0xff; + case 7: + return (s->ptr >> 8) & 0xf7; + case 8: case 9: case 10: case 11: /* Data */ + { + int p; + int n; + + if (s->ptr & 0x8000) + n = s->rx_fifo[0]; + else + n = s->packet_num; + p = s->ptr & 0x07ff; + if (s->ptr & 0x4000) { + s->ptr = (s->ptr & 0xf800) | ((s->ptr + 1) & 0x07ff); + } else { + p += (offset & 3); + } + return s->data[n][p]; + } + case 12: /* Interrupt status. */ + return s->int_level; + case 13: /* Interrupt mask. */ + return s->int_mask; + } + break; + + case 3: + switch (offset) { + case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: + /* Multicast table. */ + /* Not implemented. */ + return 0; + case 8: /* Management Interface. */ + /* Not implemented. */ + return 0x30; + case 9: + return 0x33; + case 10: /* Revision. */ + return 0x91; + case 11: + return 0x33; + case 12: + return s->ercv; + case 13: + return 0; + } + break; + } + cpu_abort (cpu_single_env, "smc91c111_read: Bad reg %d:%x\n", + s->bank, (int)offset); + return 0; +} + +static void smc91c111_writew(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + smc91c111_writeb(opaque, offset, value & 0xff); + smc91c111_writeb(opaque, offset + 1, value >> 8); +} + +static void smc91c111_writel(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + smc91c111_state *s = (smc91c111_state *)opaque; + /* 32-bit writes to offset 0xc only actually write to the bank select + register (offset 0xe) */ + if (offset != s->base + 0xc) + smc91c111_writew(opaque, offset, value & 0xffff); + smc91c111_writew(opaque, offset + 2, value >> 16); +} + +static uint32_t smc91c111_readw(void *opaque, target_phys_addr_t offset) +{ + uint32_t val; + val = smc91c111_readb(opaque, offset); + val |= smc91c111_readb(opaque, offset + 1) << 8; + return val; +} + +static uint32_t smc91c111_readl(void *opaque, target_phys_addr_t offset) +{ + uint32_t val; + val = smc91c111_readw(opaque, offset); + val |= smc91c111_readw(opaque, offset + 2) << 16; + return val; +} + +static int smc91c111_can_receive(void *opaque) +{ + smc91c111_state *s = (smc91c111_state *)opaque; + + if ((s->rcr & RCR_RXEN) == 0 || (s->rcr & RCR_SOFT_RST)) + return 1; + if (s->allocated == (1 << NUM_PACKETS) - 1) + return 0; + return 1; +} + +static void smc91c111_receive(void *opaque, const uint8_t *buf, int size) +{ + smc91c111_state *s = (smc91c111_state *)opaque; + int status; + int packetsize; + uint32_t crc; + int packetnum; + uint8_t *p; + + if ((s->rcr & RCR_RXEN) == 0 || (s->rcr & RCR_SOFT_RST)) + return; + /* Short packets are padded with zeros. Receiving a packet + < 64 bytes long is considered an error condition. */ + if (size < 64) + packetsize = 64; + else + packetsize = (size & ~1); + packetsize += 6; + crc = (s->rcr & RCR_STRIP_CRC) == 0; + if (crc) + packetsize += 4; + /* TODO: Flag overrun and receive errors. */ + if (packetsize > 2048) + return; + packetnum = smc91c111_allocate_packet(s); + if (packetnum == 0x80) + return; + s->rx_fifo[s->rx_fifo_len++] = packetnum; + + p = &s->data[packetnum][0]; + /* ??? Multicast packets? */ + status = 0; + if (size > 1518) + status |= RS_TOOLONG; + if (size & 1) + status |= RS_ODDFRAME; + *(p++) = status & 0xff; + *(p++) = status >> 8; + *(p++) = packetsize & 0xff; + *(p++) = packetsize >> 8; + memcpy(p, buf, size & ~1); + p += (size & ~1); + /* Pad short packets. */ + if (size < 64) { + int pad; + + if (size & 1) + *(p++) = buf[size - 1]; + pad = 64 - size; + memset(p, 0, pad); + p += pad; + size = 64; + } + /* It's not clear if the CRC should go before or after the last byte in + odd sized packets. Linux disables the CRC, so that's no help. + The pictures in the documentation show the CRC aligned on a 16-bit + boundary before the last odd byte, so that's what we do. */ + if (crc) { + crc = crc32(~0, buf, size); + *(p++) = crc & 0xff; crc >>= 8; + *(p++) = crc & 0xff; crc >>= 8; + *(p++) = crc & 0xff; crc >>= 8; + *(p++) = crc & 0xff; crc >>= 8; + } + if (size & 1) { + *(p++) = buf[size - 1]; + *(p++) = 0x60; + } else { + *(p++) = 0; + *(p++) = 0x40; + } + /* TODO: Raise early RX interrupt? */ + s->int_level |= INT_RCV; + smc91c111_update(s); +} + +static CPUReadMemoryFunc *smc91c111_readfn[] = { + smc91c111_readb, + smc91c111_readw, + smc91c111_readl +}; + +static CPUWriteMemoryFunc *smc91c111_writefn[] = { + smc91c111_writeb, + smc91c111_writew, + smc91c111_writel +}; + +void smc91c111_init(NICInfo *nd, uint32_t base, qemu_irq irq) +{ + smc91c111_state *s; + int iomemtype; + + s = (smc91c111_state *)qemu_mallocz(sizeof(smc91c111_state)); + iomemtype = cpu_register_io_memory(0, smc91c111_readfn, + smc91c111_writefn, s); + cpu_register_physical_memory(base, 16, iomemtype); + s->base = base; + s->irq = irq; + memcpy(s->macaddr, nd->macaddr, 6); + + smc91c111_reset(s); + + s->vc = qemu_new_vlan_client(nd->vlan, smc91c111_receive, + smc91c111_can_receive, s); + /* ??? Save/restore. */ +} diff --git a/hw/usb-hid.c b/hw/usb-hid.c new file mode 100644 index 0000000..406c9ab --- /dev/null +++ b/hw/usb-hid.c @@ -0,0 +1,896 @@ +/* + * QEMU USB HID devices + * + * Copyright (c) 2005 Fabrice Bellard + * Copyright (c) 2007 OpenMoko, Inc. (andrew@openedhand.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "hw.h" +#include "console.h" +#include "usb.h" + +/* HID interface requests */ +#define GET_REPORT 0xa101 +#define GET_IDLE 0xa102 +#define GET_PROTOCOL 0xa103 +#define SET_REPORT 0x2109 +#define SET_IDLE 0x210a +#define SET_PROTOCOL 0x210b + +/* HID descriptor types */ +#define USB_DT_HID 0x21 +#define USB_DT_REPORT 0x22 +#define USB_DT_PHY 0x23 + +#define USB_MOUSE 1 +#define USB_TABLET 2 +#define USB_KEYBOARD 3 + +typedef struct USBMouseState { + int dx, dy, dz, buttons_state; + int x, y; + int mouse_grabbed; + QEMUPutMouseEntry *eh_entry; +} USBMouseState; + +typedef struct USBKeyboardState { + uint16_t modifiers; + uint8_t leds; + uint8_t key[16]; + int keys; +} USBKeyboardState; + +typedef struct USBHIDState { + USBDevice dev; + union { + USBMouseState ptr; + USBKeyboardState kbd; + }; + int kind; + int protocol; + int idle; + int changed; +} USBHIDState; + +/* mostly the same values as the Bochs USB Mouse device */ +static const uint8_t qemu_mouse_dev_descriptor[] = { + 0x12, /* u8 bLength; */ + 0x01, /* u8 bDescriptorType; Device */ + 0x00, 0x01, /* u16 bcdUSB; v1.0 */ + + 0x00, /* u8 bDeviceClass; */ + 0x00, /* u8 bDeviceSubClass; */ + 0x00, /* u8 bDeviceProtocol; [ low/full speeds only ] */ + 0x08, /* u8 bMaxPacketSize0; 8 Bytes */ + + 0x27, 0x06, /* u16 idVendor; */ + 0x01, 0x00, /* u16 idProduct; */ + 0x00, 0x00, /* u16 bcdDevice */ + + 0x03, /* u8 iManufacturer; */ + 0x02, /* u8 iProduct; */ + 0x01, /* u8 iSerialNumber; */ + 0x01 /* u8 bNumConfigurations; */ +}; + +static const uint8_t qemu_mouse_config_descriptor[] = { + /* one configuration */ + 0x09, /* u8 bLength; */ + 0x02, /* u8 bDescriptorType; Configuration */ + 0x22, 0x00, /* u16 wTotalLength; */ + 0x01, /* u8 bNumInterfaces; (1) */ + 0x01, /* u8 bConfigurationValue; */ + 0x04, /* u8 iConfiguration; */ + 0xa0, /* u8 bmAttributes; + Bit 7: must be set, + 6: Self-powered, + 5: Remote wakeup, + 4..0: resvd */ + 50, /* u8 MaxPower; */ + + /* USB 1.1: + * USB 2.0, single TT organization (mandatory): + * one interface, protocol 0 + * + * USB 2.0, multiple TT organization (optional): + * two interfaces, protocols 1 (like single TT) + * and 2 (multiple TT mode) ... config is + * sometimes settable + * NOT IMPLEMENTED + */ + + /* one interface */ + 0x09, /* u8 if_bLength; */ + 0x04, /* u8 if_bDescriptorType; Interface */ + 0x00, /* u8 if_bInterfaceNumber; */ + 0x00, /* u8 if_bAlternateSetting; */ + 0x01, /* u8 if_bNumEndpoints; */ + 0x03, /* u8 if_bInterfaceClass; */ + 0x01, /* u8 if_bInterfaceSubClass; */ + 0x02, /* u8 if_bInterfaceProtocol; [usb1.1 or single tt] */ + 0x07, /* u8 if_iInterface; */ + + /* HID descriptor */ + 0x09, /* u8 bLength; */ + 0x21, /* u8 bDescriptorType; */ + 0x01, 0x00, /* u16 HID_class */ + 0x00, /* u8 country_code */ + 0x01, /* u8 num_descriptors */ + 0x22, /* u8 type; Report */ + 52, 0, /* u16 len */ + + /* one endpoint (status change endpoint) */ + 0x07, /* u8 ep_bLength; */ + 0x05, /* u8 ep_bDescriptorType; Endpoint */ + 0x81, /* u8 ep_bEndpointAddress; IN Endpoint 1 */ + 0x03, /* u8 ep_bmAttributes; Interrupt */ + 0x04, 0x00, /* u16 ep_wMaxPacketSize; */ + 0x0a, /* u8 ep_bInterval; (255ms -- usb 2.0 spec) */ +}; + +static const uint8_t qemu_tablet_config_descriptor[] = { + /* one configuration */ + 0x09, /* u8 bLength; */ + 0x02, /* u8 bDescriptorType; Configuration */ + 0x22, 0x00, /* u16 wTotalLength; */ + 0x01, /* u8 bNumInterfaces; (1) */ + 0x01, /* u8 bConfigurationValue; */ + 0x05, /* u8 iConfiguration; */ + 0xa0, /* u8 bmAttributes; + Bit 7: must be set, + 6: Self-powered, + 5: Remote wakeup, + 4..0: resvd */ + 50, /* u8 MaxPower; */ + + /* USB 1.1: + * USB 2.0, single TT organization (mandatory): + * one interface, protocol 0 + * + * USB 2.0, multiple TT organization (optional): + * two interfaces, protocols 1 (like single TT) + * and 2 (multiple TT mode) ... config is + * sometimes settable + * NOT IMPLEMENTED + */ + + /* one interface */ + 0x09, /* u8 if_bLength; */ + 0x04, /* u8 if_bDescriptorType; Interface */ + 0x00, /* u8 if_bInterfaceNumber; */ + 0x00, /* u8 if_bAlternateSetting; */ + 0x01, /* u8 if_bNumEndpoints; */ + 0x03, /* u8 if_bInterfaceClass; */ + 0x01, /* u8 if_bInterfaceSubClass; */ + 0x02, /* u8 if_bInterfaceProtocol; [usb1.1 or single tt] */ + 0x07, /* u8 if_iInterface; */ + + /* HID descriptor */ + 0x09, /* u8 bLength; */ + 0x21, /* u8 bDescriptorType; */ + 0x01, 0x00, /* u16 HID_class */ + 0x00, /* u8 country_code */ + 0x01, /* u8 num_descriptors */ + 0x22, /* u8 type; Report */ + 74, 0, /* u16 len */ + + /* one endpoint (status change endpoint) */ + 0x07, /* u8 ep_bLength; */ + 0x05, /* u8 ep_bDescriptorType; Endpoint */ + 0x81, /* u8 ep_bEndpointAddress; IN Endpoint 1 */ + 0x03, /* u8 ep_bmAttributes; Interrupt */ + 0x08, 0x00, /* u16 ep_wMaxPacketSize; */ + 0x0a, /* u8 ep_bInterval; (255ms -- usb 2.0 spec) */ +}; + +static const uint8_t qemu_keyboard_config_descriptor[] = { + /* one configuration */ + 0x09, /* u8 bLength; */ + USB_DT_CONFIG, /* u8 bDescriptorType; Configuration */ + 0x22, 0x00, /* u16 wTotalLength; */ + 0x01, /* u8 bNumInterfaces; (1) */ + 0x01, /* u8 bConfigurationValue; */ + 0x06, /* u8 iConfiguration; */ + 0xa0, /* u8 bmAttributes; + Bit 7: must be set, + 6: Self-powered, + 5: Remote wakeup, + 4..0: resvd */ + 0x32, /* u8 MaxPower; */ + + /* USB 1.1: + * USB 2.0, single TT organization (mandatory): + * one interface, protocol 0 + * + * USB 2.0, multiple TT organization (optional): + * two interfaces, protocols 1 (like single TT) + * and 2 (multiple TT mode) ... config is + * sometimes settable + * NOT IMPLEMENTED + */ + + /* one interface */ + 0x09, /* u8 if_bLength; */ + USB_DT_INTERFACE, /* u8 if_bDescriptorType; Interface */ + 0x00, /* u8 if_bInterfaceNumber; */ + 0x00, /* u8 if_bAlternateSetting; */ + 0x01, /* u8 if_bNumEndpoints; */ + 0x03, /* u8 if_bInterfaceClass; HID */ + 0x01, /* u8 if_bInterfaceSubClass; Boot */ + 0x01, /* u8 if_bInterfaceProtocol; Keyboard */ + 0x07, /* u8 if_iInterface; */ + + /* HID descriptor */ + 0x09, /* u8 bLength; */ + USB_DT_HID, /* u8 bDescriptorType; */ + 0x11, 0x01, /* u16 HID_class */ + 0x00, /* u8 country_code */ + 0x01, /* u8 num_descriptors */ + USB_DT_REPORT, /* u8 type; Report */ + 0x3f, 0x00, /* u16 len */ + + /* one endpoint (status change endpoint) */ + 0x07, /* u8 ep_bLength; */ + USB_DT_ENDPOINT, /* u8 ep_bDescriptorType; Endpoint */ + USB_DIR_IN | 0x01, /* u8 ep_bEndpointAddress; IN Endpoint 1 */ + 0x03, /* u8 ep_bmAttributes; Interrupt */ + 0x08, 0x00, /* u16 ep_wMaxPacketSize; */ + 0x0a, /* u8 ep_bInterval; (255ms -- usb 2.0 spec) */ +}; + +static const uint8_t qemu_mouse_hid_report_descriptor[] = { + 0x05, 0x01, /* Usage Page (Generic Desktop) */ + 0x09, 0x02, /* Usage (Mouse) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x09, 0x01, /* Usage (Pointer) */ + 0xa1, 0x00, /* Collection (Physical) */ + 0x05, 0x09, /* Usage Page (Button) */ + 0x19, 0x01, /* Usage Minimum (1) */ + 0x29, 0x03, /* Usage Maximum (3) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x25, 0x01, /* Logical Maximum (1) */ + 0x95, 0x03, /* Report Count (3) */ + 0x75, 0x01, /* Report Size (1) */ + 0x81, 0x02, /* Input (Data, Variable, Absolute) */ + 0x95, 0x01, /* Report Count (1) */ + 0x75, 0x05, /* Report Size (5) */ + 0x81, 0x01, /* Input (Constant) */ + 0x05, 0x01, /* Usage Page (Generic Desktop) */ + 0x09, 0x30, /* Usage (X) */ + 0x09, 0x31, /* Usage (Y) */ + 0x09, 0x38, /* Usage (Wheel) */ + 0x15, 0x81, /* Logical Minimum (-0x7f) */ + 0x25, 0x7f, /* Logical Maximum (0x7f) */ + 0x75, 0x08, /* Report Size (8) */ + 0x95, 0x03, /* Report Count (3) */ + 0x81, 0x06, /* Input (Data, Variable, Relative) */ + 0xc0, /* End Collection */ + 0xc0, /* End Collection */ +}; + +static const uint8_t qemu_tablet_hid_report_descriptor[] = { + 0x05, 0x01, /* Usage Page (Generic Desktop) */ + 0x09, 0x01, /* Usage (Pointer) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x09, 0x01, /* Usage (Pointer) */ + 0xa1, 0x00, /* Collection (Physical) */ + 0x05, 0x09, /* Usage Page (Button) */ + 0x19, 0x01, /* Usage Minimum (1) */ + 0x29, 0x03, /* Usage Maximum (3) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x25, 0x01, /* Logical Maximum (1) */ + 0x95, 0x03, /* Report Count (3) */ + 0x75, 0x01, /* Report Size (1) */ + 0x81, 0x02, /* Input (Data, Variable, Absolute) */ + 0x95, 0x01, /* Report Count (1) */ + 0x75, 0x05, /* Report Size (5) */ + 0x81, 0x01, /* Input (Constant) */ + 0x05, 0x01, /* Usage Page (Generic Desktop) */ + 0x09, 0x30, /* Usage (X) */ + 0x09, 0x31, /* Usage (Y) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x26, 0xff, 0x7f, /* Logical Maximum (0x7fff) */ + 0x35, 0x00, /* Physical Minimum (0) */ + 0x46, 0xff, 0x7f, /* Physical Maximum (0x7fff) */ + 0x75, 0x10, /* Report Size (16) */ + 0x95, 0x02, /* Report Count (2) */ + 0x81, 0x02, /* Input (Data, Variable, Absolute) */ + 0x05, 0x01, /* Usage Page (Generic Desktop) */ + 0x09, 0x38, /* Usage (Wheel) */ + 0x15, 0x81, /* Logical Minimum (-0x7f) */ + 0x25, 0x7f, /* Logical Maximum (0x7f) */ + 0x35, 0x00, /* Physical Minimum (same as logical) */ + 0x45, 0x00, /* Physical Maximum (same as logical) */ + 0x75, 0x08, /* Report Size (8) */ + 0x95, 0x01, /* Report Count (1) */ + 0x81, 0x06, /* Input (Data, Variable, Relative) */ + 0xc0, /* End Collection */ + 0xc0, /* End Collection */ +}; + +static const uint8_t qemu_keyboard_hid_report_descriptor[] = { + 0x05, 0x01, /* Usage Page (Generic Desktop) */ + 0x09, 0x06, /* Usage (Keyboard) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x75, 0x01, /* Report Size (1) */ + 0x95, 0x08, /* Report Count (8) */ + 0x05, 0x07, /* Usage Page (Key Codes) */ + 0x19, 0xe0, /* Usage Minimum (224) */ + 0x29, 0xe7, /* Usage Maximum (231) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x25, 0x01, /* Logical Maximum (1) */ + 0x81, 0x02, /* Input (Data, Variable, Absolute) */ + 0x95, 0x01, /* Report Count (1) */ + 0x75, 0x08, /* Report Size (8) */ + 0x81, 0x01, /* Input (Constant) */ + 0x95, 0x05, /* Report Count (5) */ + 0x75, 0x01, /* Report Size (1) */ + 0x05, 0x08, /* Usage Page (LEDs) */ + 0x19, 0x01, /* Usage Minimum (1) */ + 0x29, 0x05, /* Usage Maximum (5) */ + 0x91, 0x02, /* Output (Data, Variable, Absolute) */ + 0x95, 0x01, /* Report Count (1) */ + 0x75, 0x03, /* Report Size (3) */ + 0x91, 0x01, /* Output (Constant) */ + 0x95, 0x06, /* Report Count (6) */ + 0x75, 0x08, /* Report Size (8) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x25, 0xff, /* Logical Maximum (255) */ + 0x05, 0x07, /* Usage Page (Key Codes) */ + 0x19, 0x00, /* Usage Minimum (0) */ + 0x29, 0xff, /* Usage Maximum (255) */ + 0x81, 0x00, /* Input (Data, Array) */ + 0xc0, /* End Collection */ +}; + +#define USB_HID_USAGE_ERROR_ROLLOVER 0x01 +#define USB_HID_USAGE_POSTFAIL 0x02 +#define USB_HID_USAGE_ERROR_UNDEFINED 0x03 + +/* Indices are QEMU keycodes, values are from HID Usage Table. Indices + * above 0x80 are for keys that come after 0xe0 or 0xe1+0x1d or 0xe1+0x9d. */ +static const uint8_t usb_hid_usage_keys[0x100] = { + 0x00, 0x29, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, + 0x24, 0x25, 0x26, 0x27, 0x2d, 0x2e, 0x2a, 0x2b, + 0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c, + 0x12, 0x13, 0x2f, 0x30, 0x28, 0xe0, 0x04, 0x16, + 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f, 0x33, + 0x34, 0x35, 0xe1, 0x31, 0x1d, 0x1b, 0x06, 0x19, + 0x05, 0x11, 0x10, 0x36, 0x37, 0x38, 0xe5, 0x55, + 0xe2, 0x2c, 0x32, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, + 0x3f, 0x40, 0x41, 0x42, 0x43, 0x53, 0x47, 0x5f, + 0x60, 0x61, 0x56, 0x5c, 0x5d, 0x5e, 0x57, 0x59, + 0x5a, 0x5b, 0x62, 0x63, 0x00, 0x00, 0x00, 0x44, + 0x45, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, + 0xe8, 0xe9, 0x71, 0x72, 0x73, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x85, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xe3, 0xe7, 0x65, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x58, 0xe4, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x00, 0x46, + 0xe6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x4a, + 0x52, 0x4b, 0x00, 0x50, 0x00, 0x4f, 0x00, 0x4d, + 0x51, 0x4e, 0x49, 0x4c, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static void usb_mouse_event(void *opaque, + int dx1, int dy1, int dz1, int buttons_state) +{ + USBHIDState *hs = opaque; + USBMouseState *s = &hs->ptr; + + s->dx += dx1; + s->dy += dy1; + s->dz += dz1; + s->buttons_state = buttons_state; + hs->changed = 1; +} + +static void usb_tablet_event(void *opaque, + int x, int y, int dz, int buttons_state) +{ + USBHIDState *hs = opaque; + USBMouseState *s = &hs->ptr; + + s->x = x; + s->y = y; + s->dz += dz; + s->buttons_state = buttons_state; + hs->changed = 1; +} + +static void usb_keyboard_event(void *opaque, int keycode) +{ + USBHIDState *hs = opaque; + USBKeyboardState *s = &hs->kbd; + uint8_t hid_code, key; + int i; + + key = keycode & 0x7f; + hid_code = usb_hid_usage_keys[key | ((s->modifiers >> 1) & (1 << 7))]; + s->modifiers &= ~(1 << 8); + + hs->changed = 1; + + switch (hid_code) { + case 0x00: + return; + + case 0xe0: + if (s->modifiers & (1 << 9)) { + s->modifiers ^= 3 << 8; + return; + } + case 0xe1 ... 0xe7: + if (keycode & (1 << 7)) { + s->modifiers &= ~(1 << (hid_code & 0x0f)); + return; + } + case 0xe8 ... 0xef: + s->modifiers |= 1 << (hid_code & 0x0f); + return; + } + + if (keycode & (1 << 7)) { + for (i = s->keys - 1; i >= 0; i --) + if (s->key[i] == hid_code) { + s->key[i] = s->key[-- s->keys]; + s->key[s->keys] = 0x00; + return; + } + } else { + for (i = s->keys - 1; i >= 0; i --) + if (s->key[i] == hid_code) + return; + if (s->keys < sizeof(s->key)) + s->key[s->keys ++] = hid_code; + } +} + +static inline int int_clamp(int val, int vmin, int vmax) +{ + if (val < vmin) + return vmin; + else if (val > vmax) + return vmax; + else + return val; +} + +static int usb_mouse_poll(USBHIDState *hs, uint8_t *buf, int len) +{ + int dx, dy, dz, b, l; + USBMouseState *s = &hs->ptr; + + if (!s->mouse_grabbed) { + s->eh_entry = qemu_add_mouse_event_handler(usb_mouse_event, hs, + 0, "QEMU USB Mouse"); + s->mouse_grabbed = 1; + } + + dx = int_clamp(s->dx, -127, 127); + dy = int_clamp(s->dy, -127, 127); + dz = int_clamp(s->dz, -127, 127); + + s->dx -= dx; + s->dy -= dy; + s->dz -= dz; + + /* Appears we have to invert the wheel direction */ + dz = 0 - dz; + + b = 0; + if (s->buttons_state & MOUSE_EVENT_LBUTTON) + b |= 0x01; + if (s->buttons_state & MOUSE_EVENT_RBUTTON) + b |= 0x02; + if (s->buttons_state & MOUSE_EVENT_MBUTTON) + b |= 0x04; + + l = 0; + if (len > l) + buf[l ++] = b; + if (len > l) + buf[l ++] = dx; + if (len > l) + buf[l ++] = dy; + if (len > l) + buf[l ++] = dz; + return l; +} + +static int usb_tablet_poll(USBHIDState *hs, uint8_t *buf, int len) +{ + int dz, b, l; + USBMouseState *s = &hs->ptr; + + if (!s->mouse_grabbed) { + s->eh_entry = qemu_add_mouse_event_handler(usb_tablet_event, hs, + 1, "QEMU USB Tablet"); + s->mouse_grabbed = 1; + } + + dz = int_clamp(s->dz, -127, 127); + s->dz -= dz; + + /* Appears we have to invert the wheel direction */ + dz = 0 - dz; + b = 0; + if (s->buttons_state & MOUSE_EVENT_LBUTTON) + b |= 0x01; + if (s->buttons_state & MOUSE_EVENT_RBUTTON) + b |= 0x02; + if (s->buttons_state & MOUSE_EVENT_MBUTTON) + b |= 0x04; + + buf[0] = b; + buf[1] = s->x & 0xff; + buf[2] = s->x >> 8; + buf[3] = s->y & 0xff; + buf[4] = s->y >> 8; + buf[5] = dz; + l = 6; + + return l; +} + +static int usb_keyboard_poll(USBKeyboardState *s, uint8_t *buf, int len) +{ + if (len < 2) + return 0; + + buf[0] = s->modifiers & 0xff; + buf[1] = 0; + if (s->keys > 6) + memset(buf + 2, USB_HID_USAGE_ERROR_ROLLOVER, MIN(8, len) - 2); + else + memcpy(buf + 2, s->key, MIN(8, len) - 2); + + return MIN(8, len); +} + +static int usb_keyboard_write(USBKeyboardState *s, uint8_t *buf, int len) +{ + if (len > 0) { + /* 0x01: Num Lock LED + * 0x02: Caps Lock LED + * 0x04: Scroll Lock LED + * 0x08: Compose LED + * 0x10: Kana LED */ + s->leds = buf[0]; + } + return 0; +} + +static void usb_mouse_handle_reset(USBDevice *dev) +{ + USBHIDState *s = (USBHIDState *)dev; + + s->ptr.dx = 0; + s->ptr.dy = 0; + s->ptr.dz = 0; + s->ptr.x = 0; + s->ptr.y = 0; + s->ptr.buttons_state = 0; + s->protocol = 1; +} + +static void usb_keyboard_handle_reset(USBDevice *dev) +{ + USBHIDState *s = (USBHIDState *)dev; + + qemu_add_kbd_event_handler(usb_keyboard_event, s); + s->protocol = 1; +} + +static int usb_hid_handle_control(USBDevice *dev, int request, int value, + int index, int length, uint8_t *data) +{ + USBHIDState *s = (USBHIDState *)dev; + int ret = 0; + + switch(request) { + case DeviceRequest | USB_REQ_GET_STATUS: + data[0] = (1 << USB_DEVICE_SELF_POWERED) | + (dev->remote_wakeup << USB_DEVICE_REMOTE_WAKEUP); + data[1] = 0x00; + ret = 2; + break; + case DeviceOutRequest | USB_REQ_CLEAR_FEATURE: + if (value == USB_DEVICE_REMOTE_WAKEUP) { + dev->remote_wakeup = 0; + } else { + goto fail; + } + ret = 0; + break; + case DeviceOutRequest | USB_REQ_SET_FEATURE: + if (value == USB_DEVICE_REMOTE_WAKEUP) { + dev->remote_wakeup = 1; + } else { + goto fail; + } + ret = 0; + break; + case DeviceOutRequest | USB_REQ_SET_ADDRESS: + dev->addr = value; + ret = 0; + break; + case DeviceRequest | USB_REQ_GET_DESCRIPTOR: + switch(value >> 8) { + case USB_DT_DEVICE: + memcpy(data, qemu_mouse_dev_descriptor, + sizeof(qemu_mouse_dev_descriptor)); + ret = sizeof(qemu_mouse_dev_descriptor); + break; + case USB_DT_CONFIG: + if (s->kind == USB_MOUSE) { + memcpy(data, qemu_mouse_config_descriptor, + sizeof(qemu_mouse_config_descriptor)); + ret = sizeof(qemu_mouse_config_descriptor); + } else if (s->kind == USB_TABLET) { + memcpy(data, qemu_tablet_config_descriptor, + sizeof(qemu_tablet_config_descriptor)); + ret = sizeof(qemu_tablet_config_descriptor); + } else if (s->kind == USB_KEYBOARD) { + memcpy(data, qemu_keyboard_config_descriptor, + sizeof(qemu_keyboard_config_descriptor)); + ret = sizeof(qemu_keyboard_config_descriptor); + } + break; + case USB_DT_STRING: + switch(value & 0xff) { + case 0: + /* language ids */ + data[0] = 4; + data[1] = 3; + data[2] = 0x09; + data[3] = 0x04; + ret = 4; + break; + case 1: + /* serial number */ + ret = set_usb_string(data, "1"); + break; + case 2: + /* product description */ + ret = set_usb_string(data, s->dev.devname); + break; + case 3: + /* vendor description */ + ret = set_usb_string(data, "QEMU " QEMU_VERSION); + break; + case 4: + ret = set_usb_string(data, "HID Mouse"); + break; + case 5: + ret = set_usb_string(data, "HID Tablet"); + break; + case 6: + ret = set_usb_string(data, "HID Keyboard"); + break; + case 7: + ret = set_usb_string(data, "Endpoint1 Interrupt Pipe"); + break; + default: + goto fail; + } + break; + default: + goto fail; + } + break; + case DeviceRequest | USB_REQ_GET_CONFIGURATION: + data[0] = 1; + ret = 1; + break; + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: + ret = 0; + break; + case DeviceRequest | USB_REQ_GET_INTERFACE: + data[0] = 0; + ret = 1; + break; + case DeviceOutRequest | USB_REQ_SET_INTERFACE: + ret = 0; + break; + /* hid specific requests */ + case InterfaceRequest | USB_REQ_GET_DESCRIPTOR: + switch(value >> 8) { + case 0x22: + if (s->kind == USB_MOUSE) { + memcpy(data, qemu_mouse_hid_report_descriptor, + sizeof(qemu_mouse_hid_report_descriptor)); + ret = sizeof(qemu_mouse_hid_report_descriptor); + } else if (s->kind == USB_TABLET) { + memcpy(data, qemu_tablet_hid_report_descriptor, + sizeof(qemu_tablet_hid_report_descriptor)); + ret = sizeof(qemu_tablet_hid_report_descriptor); + } else if (s->kind == USB_KEYBOARD) { + memcpy(data, qemu_keyboard_hid_report_descriptor, + sizeof(qemu_keyboard_hid_report_descriptor)); + ret = sizeof(qemu_keyboard_hid_report_descriptor); + } + break; + default: + goto fail; + } + break; + case GET_REPORT: + if (s->kind == USB_MOUSE) + ret = usb_mouse_poll(s, data, length); + else if (s->kind == USB_TABLET) + ret = usb_tablet_poll(s, data, length); + else if (s->kind == USB_KEYBOARD) + ret = usb_keyboard_poll(&s->kbd, data, length); + break; + case SET_REPORT: + if (s->kind == USB_KEYBOARD) + ret = usb_keyboard_write(&s->kbd, data, length); + else + goto fail; + break; + case GET_PROTOCOL: + if (s->kind != USB_KEYBOARD) + goto fail; + ret = 1; + data[0] = s->protocol; + break; + case SET_PROTOCOL: + if (s->kind != USB_KEYBOARD) + goto fail; + ret = 0; + s->protocol = value; + break; + case GET_IDLE: + ret = 1; + data[0] = s->idle; + break; + case SET_IDLE: + s->idle = value; + ret = 0; + break; + default: + fail: + ret = USB_RET_STALL; + break; + } + return ret; +} + +static int usb_hid_handle_data(USBDevice *dev, USBPacket *p) +{ + USBHIDState *s = (USBHIDState *)dev; + int ret = 0; + + switch(p->pid) { + case USB_TOKEN_IN: + if (p->devep == 1) { + /* TODO: Implement finite idle delays. */ + if (!(s->changed || s->idle)) + return USB_RET_NAK; + s->changed = 0; + if (s->kind == USB_MOUSE) + ret = usb_mouse_poll(s, p->data, p->len); + else if (s->kind == USB_TABLET) + ret = usb_tablet_poll(s, p->data, p->len); + else if (s->kind == USB_KEYBOARD) + ret = usb_keyboard_poll(&s->kbd, p->data, p->len); + } else { + goto fail; + } + break; + case USB_TOKEN_OUT: + default: + fail: + ret = USB_RET_STALL; + break; + } + return ret; +} + +static void usb_hid_handle_destroy(USBDevice *dev) +{ + USBHIDState *s = (USBHIDState *)dev; + + if (s->kind != USB_KEYBOARD) + qemu_remove_mouse_event_handler(s->ptr.eh_entry); + /* TODO: else */ + qemu_free(s); +} + +USBDevice *usb_tablet_init(void) +{ + USBHIDState *s; + + s = qemu_mallocz(sizeof(USBHIDState)); + if (!s) + return NULL; + s->dev.speed = USB_SPEED_FULL; + s->dev.handle_packet = usb_generic_handle_packet; + + s->dev.handle_reset = usb_mouse_handle_reset; + s->dev.handle_control = usb_hid_handle_control; + s->dev.handle_data = usb_hid_handle_data; + s->dev.handle_destroy = usb_hid_handle_destroy; + s->kind = USB_TABLET; + /* Force poll routine to be run and grab input the first time. */ + s->changed = 1; + + pstrcpy(s->dev.devname, sizeof(s->dev.devname), "QEMU USB Tablet"); + + return (USBDevice *)s; +} + +USBDevice *usb_mouse_init(void) +{ + USBHIDState *s; + + s = qemu_mallocz(sizeof(USBHIDState)); + if (!s) + return NULL; + s->dev.speed = USB_SPEED_FULL; + s->dev.handle_packet = usb_generic_handle_packet; + + s->dev.handle_reset = usb_mouse_handle_reset; + s->dev.handle_control = usb_hid_handle_control; + s->dev.handle_data = usb_hid_handle_data; + s->dev.handle_destroy = usb_hid_handle_destroy; + s->kind = USB_MOUSE; + /* Force poll routine to be run and grab input the first time. */ + s->changed = 1; + + pstrcpy(s->dev.devname, sizeof(s->dev.devname), "QEMU USB Mouse"); + + return (USBDevice *)s; +} + +USBDevice *usb_keyboard_init(void) +{ + USBHIDState *s; + + s = qemu_mallocz(sizeof(USBHIDState)); + if (!s) + return NULL; + s->dev.speed = USB_SPEED_FULL; + s->dev.handle_packet = usb_generic_handle_packet; + + s->dev.handle_reset = usb_keyboard_handle_reset; + s->dev.handle_control = usb_hid_handle_control; + s->dev.handle_data = usb_hid_handle_data; + s->dev.handle_destroy = usb_hid_handle_destroy; + s->kind = USB_KEYBOARD; + + pstrcpy(s->dev.devname, sizeof(s->dev.devname), "QEMU USB Keyboard"); + + return (USBDevice *) s; +} diff --git a/hw/usb-hub.c b/hw/usb-hub.c new file mode 100644 index 0000000..97c3d05 --- /dev/null +++ b/hw/usb-hub.c @@ -0,0 +1,554 @@ +/* + * QEMU USB HUB emulation + * + * Copyright (c) 2005 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "qemu-common.h" +#include "usb.h" + +//#define DEBUG + +#define MAX_PORTS 8 + +typedef struct USBHubPort { + USBPort port; + uint16_t wPortStatus; + uint16_t wPortChange; +} USBHubPort; + +typedef struct USBHubState { + USBDevice dev; + int nb_ports; + USBHubPort ports[MAX_PORTS]; +} USBHubState; + +#define ClearHubFeature (0x2000 | USB_REQ_CLEAR_FEATURE) +#define ClearPortFeature (0x2300 | USB_REQ_CLEAR_FEATURE) +#define GetHubDescriptor (0xa000 | USB_REQ_GET_DESCRIPTOR) +#define GetHubStatus (0xa000 | USB_REQ_GET_STATUS) +#define GetPortStatus (0xa300 | USB_REQ_GET_STATUS) +#define SetHubFeature (0x2000 | USB_REQ_SET_FEATURE) +#define SetPortFeature (0x2300 | USB_REQ_SET_FEATURE) + +#define PORT_STAT_CONNECTION 0x0001 +#define PORT_STAT_ENABLE 0x0002 +#define PORT_STAT_SUSPEND 0x0004 +#define PORT_STAT_OVERCURRENT 0x0008 +#define PORT_STAT_RESET 0x0010 +#define PORT_STAT_POWER 0x0100 +#define PORT_STAT_LOW_SPEED 0x0200 +#define PORT_STAT_HIGH_SPEED 0x0400 +#define PORT_STAT_TEST 0x0800 +#define PORT_STAT_INDICATOR 0x1000 + +#define PORT_STAT_C_CONNECTION 0x0001 +#define PORT_STAT_C_ENABLE 0x0002 +#define PORT_STAT_C_SUSPEND 0x0004 +#define PORT_STAT_C_OVERCURRENT 0x0008 +#define PORT_STAT_C_RESET 0x0010 + +#define PORT_CONNECTION 0 +#define PORT_ENABLE 1 +#define PORT_SUSPEND 2 +#define PORT_OVERCURRENT 3 +#define PORT_RESET 4 +#define PORT_POWER 8 +#define PORT_LOWSPEED 9 +#define PORT_HIGHSPEED 10 +#define PORT_C_CONNECTION 16 +#define PORT_C_ENABLE 17 +#define PORT_C_SUSPEND 18 +#define PORT_C_OVERCURRENT 19 +#define PORT_C_RESET 20 +#define PORT_TEST 21 +#define PORT_INDICATOR 22 + +/* same as Linux kernel root hubs */ + +static const uint8_t qemu_hub_dev_descriptor[] = { + 0x12, /* u8 bLength; */ + 0x01, /* u8 bDescriptorType; Device */ + 0x10, 0x01, /* u16 bcdUSB; v1.1 */ + + 0x09, /* u8 bDeviceClass; HUB_CLASSCODE */ + 0x00, /* u8 bDeviceSubClass; */ + 0x00, /* u8 bDeviceProtocol; [ low/full speeds only ] */ + 0x08, /* u8 bMaxPacketSize0; 8 Bytes */ + + 0x00, 0x00, /* u16 idVendor; */ + 0x00, 0x00, /* u16 idProduct; */ + 0x01, 0x01, /* u16 bcdDevice */ + + 0x03, /* u8 iManufacturer; */ + 0x02, /* u8 iProduct; */ + 0x01, /* u8 iSerialNumber; */ + 0x01 /* u8 bNumConfigurations; */ +}; + +/* XXX: patch interrupt size */ +static const uint8_t qemu_hub_config_descriptor[] = { + + /* one configuration */ + 0x09, /* u8 bLength; */ + 0x02, /* u8 bDescriptorType; Configuration */ + 0x19, 0x00, /* u16 wTotalLength; */ + 0x01, /* u8 bNumInterfaces; (1) */ + 0x01, /* u8 bConfigurationValue; */ + 0x00, /* u8 iConfiguration; */ + 0xc0, /* u8 bmAttributes; + Bit 7: must be set, + 6: Self-powered, + 5: Remote wakeup, + 4..0: resvd */ + 0x00, /* u8 MaxPower; */ + + /* USB 1.1: + * USB 2.0, single TT organization (mandatory): + * one interface, protocol 0 + * + * USB 2.0, multiple TT organization (optional): + * two interfaces, protocols 1 (like single TT) + * and 2 (multiple TT mode) ... config is + * sometimes settable + * NOT IMPLEMENTED + */ + + /* one interface */ + 0x09, /* u8 if_bLength; */ + 0x04, /* u8 if_bDescriptorType; Interface */ + 0x00, /* u8 if_bInterfaceNumber; */ + 0x00, /* u8 if_bAlternateSetting; */ + 0x01, /* u8 if_bNumEndpoints; */ + 0x09, /* u8 if_bInterfaceClass; HUB_CLASSCODE */ + 0x00, /* u8 if_bInterfaceSubClass; */ + 0x00, /* u8 if_bInterfaceProtocol; [usb1.1 or single tt] */ + 0x00, /* u8 if_iInterface; */ + + /* one endpoint (status change endpoint) */ + 0x07, /* u8 ep_bLength; */ + 0x05, /* u8 ep_bDescriptorType; Endpoint */ + 0x81, /* u8 ep_bEndpointAddress; IN Endpoint 1 */ + 0x03, /* u8 ep_bmAttributes; Interrupt */ + 0x02, 0x00, /* u16 ep_wMaxPacketSize; 1 + (MAX_ROOT_PORTS / 8) */ + 0xff /* u8 ep_bInterval; (255ms -- usb 2.0 spec) */ +}; + +static const uint8_t qemu_hub_hub_descriptor[] = +{ + 0x00, /* u8 bLength; patched in later */ + 0x29, /* u8 bDescriptorType; Hub-descriptor */ + 0x00, /* u8 bNbrPorts; (patched later) */ + 0x0a, /* u16 wHubCharacteristics; */ + 0x00, /* (per-port OC, no power switching) */ + 0x01, /* u8 bPwrOn2pwrGood; 2ms */ + 0x00 /* u8 bHubContrCurrent; 0 mA */ + + /* DeviceRemovable and PortPwrCtrlMask patched in later */ +}; + +static void usb_hub_attach(USBPort *port1, USBDevice *dev) +{ + USBHubState *s = port1->opaque; + USBHubPort *port = &s->ports[port1->index]; + + if (dev) { + if (port->port.dev) + usb_attach(port1, NULL); + + port->wPortStatus |= PORT_STAT_CONNECTION; + port->wPortChange |= PORT_STAT_C_CONNECTION; + if (dev->speed == USB_SPEED_LOW) + port->wPortStatus |= PORT_STAT_LOW_SPEED; + else + port->wPortStatus &= ~PORT_STAT_LOW_SPEED; + port->port.dev = dev; + /* send the attach message */ + usb_send_msg(dev, USB_MSG_ATTACH); + } else { + dev = port->port.dev; + if (dev) { + port->wPortStatus &= ~PORT_STAT_CONNECTION; + port->wPortChange |= PORT_STAT_C_CONNECTION; + if (port->wPortStatus & PORT_STAT_ENABLE) { + port->wPortStatus &= ~PORT_STAT_ENABLE; + port->wPortChange |= PORT_STAT_C_ENABLE; + } + /* send the detach message */ + usb_send_msg(dev, USB_MSG_DETACH); + port->port.dev = NULL; + } + } +} + +static void usb_hub_handle_reset(USBDevice *dev) +{ + /* XXX: do it */ +} + +static int usb_hub_handle_control(USBDevice *dev, int request, int value, + int index, int length, uint8_t *data) +{ + USBHubState *s = (USBHubState *)dev; + int ret; + + switch(request) { + case DeviceRequest | USB_REQ_GET_STATUS: + data[0] = (1 << USB_DEVICE_SELF_POWERED) | + (dev->remote_wakeup << USB_DEVICE_REMOTE_WAKEUP); + data[1] = 0x00; + ret = 2; + break; + case DeviceOutRequest | USB_REQ_CLEAR_FEATURE: + if (value == USB_DEVICE_REMOTE_WAKEUP) { + dev->remote_wakeup = 0; + } else { + goto fail; + } + ret = 0; + break; + case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: + if (value == 0 && index != 0x81) { /* clear ep halt */ + goto fail; + } + ret = 0; + break; + case DeviceOutRequest | USB_REQ_SET_FEATURE: + if (value == USB_DEVICE_REMOTE_WAKEUP) { + dev->remote_wakeup = 1; + } else { + goto fail; + } + ret = 0; + break; + case DeviceOutRequest | USB_REQ_SET_ADDRESS: + dev->addr = value; + ret = 0; + break; + case DeviceRequest | USB_REQ_GET_DESCRIPTOR: + switch(value >> 8) { + case USB_DT_DEVICE: + memcpy(data, qemu_hub_dev_descriptor, + sizeof(qemu_hub_dev_descriptor)); + ret = sizeof(qemu_hub_dev_descriptor); + break; + case USB_DT_CONFIG: + memcpy(data, qemu_hub_config_descriptor, + sizeof(qemu_hub_config_descriptor)); + + /* status change endpoint size based on number + * of ports */ + data[22] = (s->nb_ports + 1 + 7) / 8; + + ret = sizeof(qemu_hub_config_descriptor); + break; + case USB_DT_STRING: + switch(value & 0xff) { + case 0: + /* language ids */ + data[0] = 4; + data[1] = 3; + data[2] = 0x09; + data[3] = 0x04; + ret = 4; + break; + case 1: + /* serial number */ + ret = set_usb_string(data, "314159"); + break; + case 2: + /* product description */ + ret = set_usb_string(data, "QEMU USB Hub"); + break; + case 3: + /* vendor description */ + ret = set_usb_string(data, "QEMU " QEMU_VERSION); + break; + default: + goto fail; + } + break; + default: + goto fail; + } + break; + case DeviceRequest | USB_REQ_GET_CONFIGURATION: + data[0] = 1; + ret = 1; + break; + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: + ret = 0; + break; + case DeviceRequest | USB_REQ_GET_INTERFACE: + data[0] = 0; + ret = 1; + break; + case DeviceOutRequest | USB_REQ_SET_INTERFACE: + ret = 0; + break; + /* usb specific requests */ + case GetHubStatus: + data[0] = 0; + data[1] = 0; + data[2] = 0; + data[3] = 0; + ret = 4; + break; + case GetPortStatus: + { + unsigned int n = index - 1; + USBHubPort *port; + if (n >= s->nb_ports) + goto fail; + port = &s->ports[n]; + data[0] = port->wPortStatus; + data[1] = port->wPortStatus >> 8; + data[2] = port->wPortChange; + data[3] = port->wPortChange >> 8; + ret = 4; + } + break; + case SetHubFeature: + case ClearHubFeature: + if (value == 0 || value == 1) { + } else { + goto fail; + } + ret = 0; + break; + case SetPortFeature: + { + unsigned int n = index - 1; + USBHubPort *port; + USBDevice *dev; + if (n >= s->nb_ports) + goto fail; + port = &s->ports[n]; + dev = port->port.dev; + switch(value) { + case PORT_SUSPEND: + port->wPortStatus |= PORT_STAT_SUSPEND; + break; + case PORT_RESET: + if (dev) { + usb_send_msg(dev, USB_MSG_RESET); + port->wPortChange |= PORT_STAT_C_RESET; + /* set enable bit */ + port->wPortStatus |= PORT_STAT_ENABLE; + } + break; + case PORT_POWER: + break; + default: + goto fail; + } + ret = 0; + } + break; + case ClearPortFeature: + { + unsigned int n = index - 1; + USBHubPort *port; + USBDevice *dev; + if (n >= s->nb_ports) + goto fail; + port = &s->ports[n]; + dev = port->port.dev; + switch(value) { + case PORT_ENABLE: + port->wPortStatus &= ~PORT_STAT_ENABLE; + break; + case PORT_C_ENABLE: + port->wPortChange &= ~PORT_STAT_C_ENABLE; + break; + case PORT_SUSPEND: + port->wPortStatus &= ~PORT_STAT_SUSPEND; + break; + case PORT_C_SUSPEND: + port->wPortChange &= ~PORT_STAT_C_SUSPEND; + break; + case PORT_C_CONNECTION: + port->wPortChange &= ~PORT_STAT_C_CONNECTION; + break; + case PORT_C_OVERCURRENT: + port->wPortChange &= ~PORT_STAT_C_OVERCURRENT; + break; + case PORT_C_RESET: + port->wPortChange &= ~PORT_STAT_C_RESET; + break; + default: + goto fail; + } + ret = 0; + } + break; + case GetHubDescriptor: + { + unsigned int n, limit, var_hub_size = 0; + memcpy(data, qemu_hub_hub_descriptor, + sizeof(qemu_hub_hub_descriptor)); + data[2] = s->nb_ports; + + /* fill DeviceRemovable bits */ + limit = ((s->nb_ports + 1 + 7) / 8) + 7; + for (n = 7; n < limit; n++) { + data[n] = 0x00; + var_hub_size++; + } + + /* fill PortPwrCtrlMask bits */ + limit = limit + ((s->nb_ports + 7) / 8); + for (;n < limit; n++) { + data[n] = 0xff; + var_hub_size++; + } + + ret = sizeof(qemu_hub_hub_descriptor) + var_hub_size; + data[0] = ret; + break; + } + default: + fail: + ret = USB_RET_STALL; + break; + } + return ret; +} + +static int usb_hub_handle_data(USBDevice *dev, USBPacket *p) +{ + USBHubState *s = (USBHubState *)dev; + int ret; + + switch(p->pid) { + case USB_TOKEN_IN: + if (p->devep == 1) { + USBHubPort *port; + unsigned int status; + int i, n; + n = (s->nb_ports + 1 + 7) / 8; + if (p->len == 1) { /* FreeBSD workaround */ + n = 1; + } else if (n > p->len) { + return USB_RET_BABBLE; + } + status = 0; + for(i = 0; i < s->nb_ports; i++) { + port = &s->ports[i]; + if (port->wPortChange) + status |= (1 << (i + 1)); + } + if (status != 0) { + for(i = 0; i < n; i++) { + p->data[i] = status >> (8 * i); + } + ret = n; + } else { + ret = USB_RET_NAK; /* usb11 11.13.1 */ + } + } else { + goto fail; + } + break; + case USB_TOKEN_OUT: + default: + fail: + ret = USB_RET_STALL; + break; + } + return ret; +} + +static int usb_hub_broadcast_packet(USBHubState *s, USBPacket *p) +{ + USBHubPort *port; + USBDevice *dev; + int i, ret; + + for(i = 0; i < s->nb_ports; i++) { + port = &s->ports[i]; + dev = port->port.dev; + if (dev && (port->wPortStatus & PORT_STAT_ENABLE)) { + ret = dev->handle_packet(dev, p); + if (ret != USB_RET_NODEV) { + return ret; + } + } + } + return USB_RET_NODEV; +} + +static int usb_hub_handle_packet(USBDevice *dev, USBPacket *p) +{ + USBHubState *s = (USBHubState *)dev; + +#if defined(DEBUG) && 0 + printf("usb_hub: pid=0x%x\n", pid); +#endif + if (dev->state == USB_STATE_DEFAULT && + dev->addr != 0 && + p->devaddr != dev->addr && + (p->pid == USB_TOKEN_SETUP || + p->pid == USB_TOKEN_OUT || + p->pid == USB_TOKEN_IN)) { + /* broadcast the packet to the devices */ + return usb_hub_broadcast_packet(s, p); + } + return usb_generic_handle_packet(dev, p); +} + +static void usb_hub_handle_destroy(USBDevice *dev) +{ + USBHubState *s = (USBHubState *)dev; + + qemu_free(s); +} + +USBDevice *usb_hub_init(int nb_ports) +{ + USBHubState *s; + USBHubPort *port; + int i; + + if (nb_ports > MAX_PORTS) + return NULL; + s = qemu_mallocz(sizeof(USBHubState)); + if (!s) + return NULL; + s->dev.speed = USB_SPEED_FULL; + s->dev.handle_packet = usb_hub_handle_packet; + + /* generic USB device init */ + s->dev.handle_reset = usb_hub_handle_reset; + s->dev.handle_control = usb_hub_handle_control; + s->dev.handle_data = usb_hub_handle_data; + s->dev.handle_destroy = usb_hub_handle_destroy; + + pstrcpy(s->dev.devname, sizeof(s->dev.devname), "QEMU USB Hub"); + + s->nb_ports = nb_ports; + for(i = 0; i < s->nb_ports; i++) { + port = &s->ports[i]; + qemu_register_usb_port(&port->port, s, i, usb_hub_attach); + port->wPortStatus = PORT_STAT_POWER; + port->wPortChange = 0; + } + return (USBDevice *)s; +} diff --git a/hw/usb-msd.c b/hw/usb-msd.c new file mode 100644 index 0000000..f7ad25e --- /dev/null +++ b/hw/usb-msd.c @@ -0,0 +1,578 @@ +/* + * USB Mass Storage Device emulation + * + * Copyright (c) 2006 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the LGPL. + */ + +#include "qemu-common.h" +#include "usb.h" +#include "block.h" +#include "scsi-disk.h" + +//#define DEBUG_MSD + +#ifdef DEBUG_MSD +#define DPRINTF(fmt, args...) \ +do { printf("usb-msd: " fmt , ##args); } while (0) +#else +#define DPRINTF(fmt, args...) do {} while(0) +#endif + +/* USB requests. */ +#define MassStorageReset 0xff +#define GetMaxLun 0xfe + +enum USBMSDMode { + USB_MSDM_CBW, /* Command Block. */ + USB_MSDM_DATAOUT, /* Tranfer data to device. */ + USB_MSDM_DATAIN, /* Transfer data from device. */ + USB_MSDM_CSW /* Command Status. */ +}; + +typedef struct { + USBDevice dev; + enum USBMSDMode mode; + uint32_t scsi_len; + uint8_t *scsi_buf; + uint32_t usb_len; + uint8_t *usb_buf; + uint32_t data_len; + uint32_t residue; + uint32_t tag; + BlockDriverState *bs; + SCSIDevice *scsi_dev; + int result; + /* For async completion. */ + USBPacket *packet; +} MSDState; + +struct usb_msd_cbw { + uint32_t sig; + uint32_t tag; + uint32_t data_len; + uint8_t flags; + uint8_t lun; + uint8_t cmd_len; + uint8_t cmd[16]; +}; + +struct usb_msd_csw { + uint32_t sig; + uint32_t tag; + uint32_t residue; + uint8_t status; +}; + +static const uint8_t qemu_msd_dev_descriptor[] = { + 0x12, /* u8 bLength; */ + 0x01, /* u8 bDescriptorType; Device */ + 0x00, 0x01, /* u16 bcdUSB; v1.0 */ + + 0x00, /* u8 bDeviceClass; */ + 0x00, /* u8 bDeviceSubClass; */ + 0x00, /* u8 bDeviceProtocol; [ low/full speeds only ] */ + 0x08, /* u8 bMaxPacketSize0; 8 Bytes */ + + /* Vendor and product id are arbitrary. */ + 0x00, 0x00, /* u16 idVendor; */ + 0x00, 0x00, /* u16 idProduct; */ + 0x00, 0x00, /* u16 bcdDevice */ + + 0x01, /* u8 iManufacturer; */ + 0x02, /* u8 iProduct; */ + 0x03, /* u8 iSerialNumber; */ + 0x01 /* u8 bNumConfigurations; */ +}; + +static const uint8_t qemu_msd_config_descriptor[] = { + + /* one configuration */ + 0x09, /* u8 bLength; */ + 0x02, /* u8 bDescriptorType; Configuration */ + 0x20, 0x00, /* u16 wTotalLength; */ + 0x01, /* u8 bNumInterfaces; (1) */ + 0x01, /* u8 bConfigurationValue; */ + 0x00, /* u8 iConfiguration; */ + 0xc0, /* u8 bmAttributes; + Bit 7: must be set, + 6: Self-powered, + 5: Remote wakeup, + 4..0: resvd */ + 0x00, /* u8 MaxPower; */ + + /* one interface */ + 0x09, /* u8 if_bLength; */ + 0x04, /* u8 if_bDescriptorType; Interface */ + 0x00, /* u8 if_bInterfaceNumber; */ + 0x00, /* u8 if_bAlternateSetting; */ + 0x02, /* u8 if_bNumEndpoints; */ + 0x08, /* u8 if_bInterfaceClass; MASS STORAGE */ + 0x06, /* u8 if_bInterfaceSubClass; SCSI */ + 0x50, /* u8 if_bInterfaceProtocol; Bulk Only */ + 0x00, /* u8 if_iInterface; */ + + /* Bulk-In endpoint */ + 0x07, /* u8 ep_bLength; */ + 0x05, /* u8 ep_bDescriptorType; Endpoint */ + 0x81, /* u8 ep_bEndpointAddress; IN Endpoint 1 */ + 0x02, /* u8 ep_bmAttributes; Bulk */ + 0x40, 0x00, /* u16 ep_wMaxPacketSize; */ + 0x00, /* u8 ep_bInterval; */ + + /* Bulk-Out endpoint */ + 0x07, /* u8 ep_bLength; */ + 0x05, /* u8 ep_bDescriptorType; Endpoint */ + 0x02, /* u8 ep_bEndpointAddress; OUT Endpoint 2 */ + 0x02, /* u8 ep_bmAttributes; Bulk */ + 0x40, 0x00, /* u16 ep_wMaxPacketSize; */ + 0x00 /* u8 ep_bInterval; */ +}; + +static void usb_msd_copy_data(MSDState *s) +{ + uint32_t len; + len = s->usb_len; + if (len > s->scsi_len) + len = s->scsi_len; + if (s->mode == USB_MSDM_DATAIN) { + memcpy(s->usb_buf, s->scsi_buf, len); + } else { + memcpy(s->scsi_buf, s->usb_buf, len); + } + s->usb_len -= len; + s->scsi_len -= len; + s->usb_buf += len; + s->scsi_buf += len; + s->data_len -= len; + if (s->scsi_len == 0) { + if (s->mode == USB_MSDM_DATAIN) { + s->scsi_dev->read_data(s->scsi_dev, s->tag); + } else if (s->mode == USB_MSDM_DATAOUT) { + s->scsi_dev->write_data(s->scsi_dev, s->tag); + } + } +} + +static void usb_msd_send_status(MSDState *s) +{ + struct usb_msd_csw csw; + + csw.sig = cpu_to_le32(0x53425355); + csw.tag = cpu_to_le32(s->tag); + csw.residue = s->residue; + csw.status = s->result; + memcpy(s->usb_buf, &csw, 13); +} + +static void usb_msd_command_complete(void *opaque, int reason, uint32_t tag, + uint32_t arg) +{ + MSDState *s = (MSDState *)opaque; + USBPacket *p = s->packet; + + if (tag != s->tag) { + fprintf(stderr, "usb-msd: Unexpected SCSI Tag 0x%x\n", tag); + } + if (reason == SCSI_REASON_DONE) { + DPRINTF("Command complete %d\n", arg); + s->residue = s->data_len; + s->result = arg != 0; + if (s->packet) { + if (s->data_len == 0 && s->mode == USB_MSDM_DATAOUT) { + /* A deferred packet with no write data remaining must be + the status read packet. */ + usb_msd_send_status(s); + s->mode = USB_MSDM_CBW; + } else { + if (s->data_len) { + s->data_len -= s->usb_len; + if (s->mode == USB_MSDM_DATAIN) + memset(s->usb_buf, 0, s->usb_len); + s->usb_len = 0; + } + if (s->data_len == 0) + s->mode = USB_MSDM_CSW; + } + s->packet = NULL; + usb_packet_complete(p); + } else if (s->data_len == 0) { + s->mode = USB_MSDM_CSW; + } + return; + } + s->scsi_len = arg; + s->scsi_buf = s->scsi_dev->get_buf(s->scsi_dev, tag); + if (p) { + usb_msd_copy_data(s); + if (s->usb_len == 0) { + /* Set s->packet to NULL before calling usb_packet_complete + because annother request may be issued before + usb_packet_complete returns. */ + DPRINTF("Packet complete %p\n", p); + s->packet = NULL; + usb_packet_complete(p); + } + } +} + +static void usb_msd_handle_reset(USBDevice *dev) +{ + MSDState *s = (MSDState *)dev; + + DPRINTF("Reset\n"); + s->mode = USB_MSDM_CBW; +} + +static int usb_msd_handle_control(USBDevice *dev, int request, int value, + int index, int length, uint8_t *data) +{ + MSDState *s = (MSDState *)dev; + int ret = 0; + + switch (request) { + case DeviceRequest | USB_REQ_GET_STATUS: + data[0] = (1 << USB_DEVICE_SELF_POWERED) | + (dev->remote_wakeup << USB_DEVICE_REMOTE_WAKEUP); + data[1] = 0x00; + ret = 2; + break; + case DeviceOutRequest | USB_REQ_CLEAR_FEATURE: + if (value == USB_DEVICE_REMOTE_WAKEUP) { + dev->remote_wakeup = 0; + } else { + goto fail; + } + ret = 0; + break; + case DeviceOutRequest | USB_REQ_SET_FEATURE: + if (value == USB_DEVICE_REMOTE_WAKEUP) { + dev->remote_wakeup = 1; + } else { + goto fail; + } + ret = 0; + break; + case DeviceOutRequest | USB_REQ_SET_ADDRESS: + dev->addr = value; + ret = 0; + break; + case DeviceRequest | USB_REQ_GET_DESCRIPTOR: + switch(value >> 8) { + case USB_DT_DEVICE: + memcpy(data, qemu_msd_dev_descriptor, + sizeof(qemu_msd_dev_descriptor)); + ret = sizeof(qemu_msd_dev_descriptor); + break; + case USB_DT_CONFIG: + memcpy(data, qemu_msd_config_descriptor, + sizeof(qemu_msd_config_descriptor)); + ret = sizeof(qemu_msd_config_descriptor); + break; + case USB_DT_STRING: + switch(value & 0xff) { + case 0: + /* language ids */ + data[0] = 4; + data[1] = 3; + data[2] = 0x09; + data[3] = 0x04; + ret = 4; + break; + case 1: + /* vendor description */ + ret = set_usb_string(data, "QEMU " QEMU_VERSION); + break; + case 2: + /* product description */ + ret = set_usb_string(data, "QEMU USB HARDDRIVE"); + break; + case 3: + /* serial number */ + ret = set_usb_string(data, "1"); + break; + default: + goto fail; + } + break; + default: + goto fail; + } + break; + case DeviceRequest | USB_REQ_GET_CONFIGURATION: + data[0] = 1; + ret = 1; + break; + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: + ret = 0; + break; + case DeviceRequest | USB_REQ_GET_INTERFACE: + data[0] = 0; + ret = 1; + break; + case DeviceOutRequest | USB_REQ_SET_INTERFACE: + ret = 0; + break; + case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: + if (value == 0 && index != 0x81) { /* clear ep halt */ + goto fail; + } + ret = 0; + break; + /* Class specific requests. */ + case MassStorageReset: + /* Reset state ready for the next CBW. */ + s->mode = USB_MSDM_CBW; + ret = 0; + break; + case GetMaxLun: + data[0] = 0; + ret = 1; + break; + default: + fail: + ret = USB_RET_STALL; + break; + } + return ret; +} + +static void usb_msd_cancel_io(USBPacket *p, void *opaque) +{ + MSDState *s = opaque; + s->scsi_dev->cancel_io(s->scsi_dev, s->tag); + s->packet = NULL; + s->scsi_len = 0; +} + +static int usb_msd_handle_data(USBDevice *dev, USBPacket *p) +{ + MSDState *s = (MSDState *)dev; + int ret = 0; + struct usb_msd_cbw cbw; + uint8_t devep = p->devep; + uint8_t *data = p->data; + int len = p->len; + + switch (p->pid) { + case USB_TOKEN_OUT: + if (devep != 2) + goto fail; + + switch (s->mode) { + case USB_MSDM_CBW: + if (len != 31) { + fprintf(stderr, "usb-msd: Bad CBW size"); + goto fail; + } + memcpy(&cbw, data, 31); + if (le32_to_cpu(cbw.sig) != 0x43425355) { + fprintf(stderr, "usb-msd: Bad signature %08x\n", + le32_to_cpu(cbw.sig)); + goto fail; + } + DPRINTF("Command on LUN %d\n", cbw.lun); + if (cbw.lun != 0) { + fprintf(stderr, "usb-msd: Bad LUN %d\n", cbw.lun); + goto fail; + } + s->tag = le32_to_cpu(cbw.tag); + s->data_len = le32_to_cpu(cbw.data_len); + if (s->data_len == 0) { + s->mode = USB_MSDM_CSW; + } else if (cbw.flags & 0x80) { + s->mode = USB_MSDM_DATAIN; + } else { + s->mode = USB_MSDM_DATAOUT; + } + DPRINTF("Command tag 0x%x flags %08x len %d data %d\n", + s->tag, cbw.flags, cbw.cmd_len, s->data_len); + s->residue = 0; + s->scsi_dev->send_command(s->scsi_dev, s->tag, cbw.cmd, 0); + /* ??? Should check that USB and SCSI data transfer + directions match. */ + if (s->residue == 0) { + if (s->mode == USB_MSDM_DATAIN) { + s->scsi_dev->read_data(s->scsi_dev, s->tag); + } else if (s->mode == USB_MSDM_DATAOUT) { + s->scsi_dev->write_data(s->scsi_dev, s->tag); + } + } + ret = len; + break; + + case USB_MSDM_DATAOUT: + DPRINTF("Data out %d/%d\n", len, s->data_len); + if (len > s->data_len) + goto fail; + + s->usb_buf = data; + s->usb_len = len; + if (s->scsi_len) { + usb_msd_copy_data(s); + } + if (s->residue && s->usb_len) { + s->data_len -= s->usb_len; + if (s->data_len == 0) + s->mode = USB_MSDM_CSW; + s->usb_len = 0; + } + if (s->usb_len) { + DPRINTF("Deferring packet %p\n", p); + usb_defer_packet(p, usb_msd_cancel_io, s); + s->packet = p; + ret = USB_RET_ASYNC; + } else { + ret = len; + } + break; + + default: + DPRINTF("Unexpected write (len %d)\n", len); + goto fail; + } + break; + + case USB_TOKEN_IN: + if (devep != 1) + goto fail; + + switch (s->mode) { + case USB_MSDM_DATAOUT: + if (s->data_len != 0 || len < 13) + goto fail; + /* Waiting for SCSI write to complete. */ + usb_defer_packet(p, usb_msd_cancel_io, s); + s->packet = p; + ret = USB_RET_ASYNC; + break; + + case USB_MSDM_CSW: + DPRINTF("Command status %d tag 0x%x, len %d\n", + s->result, s->tag, len); + if (len < 13) + goto fail; + + s->usb_len = len; + s->usb_buf = data; + usb_msd_send_status(s); + s->mode = USB_MSDM_CBW; + ret = 13; + break; + + case USB_MSDM_DATAIN: + DPRINTF("Data in %d/%d\n", len, s->data_len); + if (len > s->data_len) + len = s->data_len; + s->usb_buf = data; + s->usb_len = len; + if (s->scsi_len) { + usb_msd_copy_data(s); + } + if (s->residue && s->usb_len) { + s->data_len -= s->usb_len; + memset(s->usb_buf, 0, s->usb_len); + if (s->data_len == 0) + s->mode = USB_MSDM_CSW; + s->usb_len = 0; + } + if (s->usb_len) { + DPRINTF("Deferring packet %p\n", p); + usb_defer_packet(p, usb_msd_cancel_io, s); + s->packet = p; + ret = USB_RET_ASYNC; + } else { + ret = len; + } + break; + + default: + DPRINTF("Unexpected read (len %d)\n", len); + goto fail; + } + break; + + default: + DPRINTF("Bad token\n"); + fail: + ret = USB_RET_STALL; + break; + } + + return ret; +} + +static void usb_msd_handle_destroy(USBDevice *dev) +{ + MSDState *s = (MSDState *)dev; + + s->scsi_dev->destroy(s->scsi_dev); + bdrv_delete(s->bs); + qemu_free(s); +} + +USBDevice *usb_msd_init(const char *filename) +{ + MSDState *s; + BlockDriverState *bdrv; + BlockDriver *drv = NULL; + const char *p1; + char fmt[32]; + + p1 = strchr(filename, ':'); + if (p1++) { + const char *p2; + + if (strstart(filename, "format=", &p2)) { + int len = MIN(p1 - p2, sizeof(fmt)); + pstrcpy(fmt, len, p2); + + drv = bdrv_find_format(fmt); + if (!drv) { + printf("invalid format %s\n", fmt); + return NULL; + } + } else if (*filename != ':') { + printf("unrecognized USB mass-storage option %s\n", filename); + return NULL; + } + + filename = p1; + } + + if (!*filename) { + printf("block device specification needed\n"); + return NULL; + } + + s = qemu_mallocz(sizeof(MSDState)); + if (!s) + return NULL; + + bdrv = bdrv_new("usb"); + if (bdrv_open2(bdrv, filename, 0, drv) < 0) + goto fail; + if (qemu_key_check(bdrv, filename)) + goto fail; + s->bs = bdrv; + + s->dev.speed = USB_SPEED_FULL; + s->dev.handle_packet = usb_generic_handle_packet; + + s->dev.handle_reset = usb_msd_handle_reset; + s->dev.handle_control = usb_msd_handle_control; + s->dev.handle_data = usb_msd_handle_data; + s->dev.handle_destroy = usb_msd_handle_destroy; + + snprintf(s->dev.devname, sizeof(s->dev.devname), "QEMU USB MSD(%.16s)", + filename); + + s->scsi_dev = scsi_disk_init(bdrv, 0, usb_msd_command_complete, s); + usb_msd_handle_reset((USBDevice *)s); + return (USBDevice *)s; + fail: + qemu_free(s); + return NULL; +} diff --git a/hw/usb-ohci.c b/hw/usb-ohci.c new file mode 100644 index 0000000..55cb77b --- /dev/null +++ b/hw/usb-ohci.c @@ -0,0 +1,1684 @@ +/* + * QEMU USB OHCI Emulation + * Copyright (c) 2004 Gianni Tedesco + * Copyright (c) 2006 CodeSourcery + * Copyright (c) 2006 Openedhand Ltd. + * + * 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 + * version 2 of the License, or (at your option) any later version. + * + * 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 + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * 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 Handle unrecoverable errors properly + * o BIOS work to boot from USB storage +*/ + +#include "hw.h" +#include "qemu-timer.h" +#include "usb.h" +#include "pci.h" +#include "pxa.h" + +//#define DEBUG_OHCI +/* Dump packet contents. */ +//#define DEBUG_PACKET +//#define DEBUG_ISOCH +/* This causes frames to occur 1000x slower */ +//#define OHCI_TIME_WARP 1 + +#ifdef DEBUG_OHCI +#define dprintf printf +#else +#define dprintf(...) +#endif + +/* Number of Downstream Ports on the root hub. */ + +#define OHCI_MAX_PORTS 15 + +static int64_t usb_frame_time; +static int64_t usb_bit_time; + +typedef struct OHCIPort { + USBPort port; + uint32_t ctrl; +} OHCIPort; + +enum ohci_type { + OHCI_TYPE_PCI, + OHCI_TYPE_PXA +}; + +typedef struct { + qemu_irq irq; + enum ohci_type type; + target_phys_addr_t mem_base; + int mem; + int num_ports; + const char *name; + + QEMUTimer *eof_timer; + int64_t sof_time; + + /* OHCI state */ + /* Control partition */ + uint32_t ctl, status; + uint32_t intr_status; + uint32_t intr; + + /* memory pointer partition */ + uint32_t hcca; + uint32_t ctrl_head, ctrl_cur; + uint32_t bulk_head, bulk_cur; + uint32_t per_cur; + uint32_t done; + int done_count; + + /* Frame counter partition */ + uint32_t fsmps:15; + uint32_t fit:1; + uint32_t fi:14; + uint32_t frt:1; + uint16_t frame_number; + uint16_t padding; + uint32_t pstart; + uint32_t lst; + + /* Root Hub partition */ + uint32_t rhdesc_a, rhdesc_b; + uint32_t rhstatus; + OHCIPort rhport[OHCI_MAX_PORTS]; + + /* PXA27x Non-OHCI events */ + uint32_t hstatus; + uint32_t hmask; + uint32_t hreset; + uint32_t htest; + + /* Active packets. */ + uint32_t old_ctl; + USBPacket usb_packet; + uint8_t usb_buf[8192]; + uint32_t async_td; + int async_complete; + +} OHCIState; + +/* Host Controller Communications Area */ +struct ohci_hcca { + uint32_t intr[32]; + uint16_t frame, pad; + uint32_t done; +}; + +static void ohci_bus_stop(OHCIState *ohci); + +/* Bitfields for the first word of an Endpoint Desciptor. */ +#define OHCI_ED_FA_SHIFT 0 +#define OHCI_ED_FA_MASK (0x7f<<OHCI_ED_FA_SHIFT) +#define OHCI_ED_EN_SHIFT 7 +#define OHCI_ED_EN_MASK (0xf<<OHCI_ED_EN_SHIFT) +#define OHCI_ED_D_SHIFT 11 +#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) +#define OHCI_ED_MPS_SHIFT 16 +#define OHCI_ED_MPS_MASK (0x7ff<<OHCI_ED_MPS_SHIFT) + +/* Flags in the head field of an Endpoint Desciptor. */ +#define OHCI_ED_H 1 +#define OHCI_ED_C 2 + +/* Bitfields for the first word of a Transfer Desciptor. */ +#define OHCI_TD_R (1<<18) +#define OHCI_TD_DP_SHIFT 19 +#define OHCI_TD_DP_MASK (3<<OHCI_TD_DP_SHIFT) +#define OHCI_TD_DI_SHIFT 21 +#define OHCI_TD_DI_MASK (7<<OHCI_TD_DI_SHIFT) +#define OHCI_TD_T0 (1<<24) +#define OHCI_TD_T1 (1<<24) +#define OHCI_TD_EC_SHIFT 26 +#define OHCI_TD_EC_MASK (3<<OHCI_TD_EC_SHIFT) +#define OHCI_TD_CC_SHIFT 28 +#define OHCI_TD_CC_MASK (0xf<<OHCI_TD_CC_SHIFT) + +/* Bitfields for the first word of an Isochronous Transfer Desciptor. */ +/* CC & DI - same as in the General Transfer Desciptor */ +#define OHCI_TD_SF_SHIFT 0 +#define OHCI_TD_SF_MASK (0xffff<<OHCI_TD_SF_SHIFT) +#define OHCI_TD_FC_SHIFT 24 +#define OHCI_TD_FC_MASK (7<<OHCI_TD_FC_SHIFT) + +/* Isochronous Transfer Desciptor - Offset / PacketStatusWord */ +#define OHCI_TD_PSW_CC_SHIFT 12 +#define OHCI_TD_PSW_CC_MASK (0xf<<OHCI_TD_PSW_CC_SHIFT) +#define OHCI_TD_PSW_SIZE_SHIFT 0 +#define OHCI_TD_PSW_SIZE_MASK (0xfff<<OHCI_TD_PSW_SIZE_SHIFT) + +#define OHCI_PAGE_MASK 0xfffff000 +#define OHCI_OFFSET_MASK 0xfff + +#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; \ + } while(0) + +/* 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; +}; + +/* Isochronous transfer descriptor */ +struct ohci_iso_td { + uint32_t flags; + uint32_t bp; + uint32_t next; + uint32_t be; + uint16_t offset[8]; +}; + +#define USB_HZ 12000000 + +/* OHCI Local stuff */ +#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)) +#define OHCI_USB_RESET 0x00 +#define OHCI_USB_RESUME 0x40 +#define OHCI_USB_OPERATIONAL 0x80 +#define OHCI_USB_SUSPEND 0xc0 +#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 (1<<0) /* Scheduling overrun */ +#define OHCI_INTR_WD (1<<1) /* HcDoneHead writeback */ +#define OHCI_INTR_SF (1<<2) /* Start of frame */ +#define OHCI_INTR_RD (1<<3) /* Resume detect */ +#define OHCI_INTR_UE (1<<4) /* Unrecoverable error */ +#define OHCI_INTR_FNO (1<<5) /* Frame number overflow */ +#define OHCI_INTR_RHSC (1<<6) /* Root hub status change */ +#define OHCI_INTR_OC (1<<30) /* Ownership change */ +#define OHCI_INTR_MIE (1<<31) /* Master Interrupt Enable */ + +#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 + +#define OHCI_FR_RT (1<<31) + +#define OHCI_LS_THRESH 0x628 + +#define OHCI_RHA_RW_MASK 0x00000000 /* Mask of supported features. */ +#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) +#define OHCI_RHA_POTPGT_MASK 0xff000000 + +#define OHCI_RHS_LPS (1<<0) +#define OHCI_RHS_OCI (1<<1) +#define OHCI_RHS_DRWE (1<<15) +#define OHCI_RHS_LPSC (1<<16) +#define OHCI_RHS_OCIC (1<<17) +#define OHCI_RHS_CRWE (1<<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) + +#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 + +#define OHCI_HRESET_FSBIR (1 << 0) + +/* 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; + + qemu_set_irq(ohci->irq, level); +} + +/* Set an interrupt */ +static inline void ohci_set_interrupt(OHCIState *ohci, uint32_t intr) +{ + ohci->intr_status |= intr; + ohci_intr_update(ohci); +} + +/* Attach or detach a device on a root hub port. */ +static void ohci_attach(USBPort *port1, USBDevice *dev) +{ + OHCIState *s = port1->opaque; + OHCIPort *port = &s->rhport[port1->index]; + uint32_t old_state = port->ctrl; + + if (dev) { + if (port->port.dev) { + usb_attach(port1, NULL); + } + /* set connect status */ + port->ctrl |= OHCI_PORT_CCS | OHCI_PORT_CSC; + + /* update speed */ + if (dev->speed == USB_SPEED_LOW) + port->ctrl |= OHCI_PORT_LSDA; + else + port->ctrl &= ~OHCI_PORT_LSDA; + port->port.dev = dev; + + /* notify of remote-wakeup */ + if ((s->ctl & OHCI_CTL_HCFS) == OHCI_USB_SUSPEND) + ohci_set_interrupt(s, OHCI_INTR_RD); + + /* send the attach message */ + usb_send_msg(dev, USB_MSG_ATTACH); + dprintf("usb-ohci: Attached port %d\n", port1->index); + } else { + /* 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; + } + dev = port->port.dev; + if (dev) { + /* send the detach message */ + usb_send_msg(dev, USB_MSG_DETACH); + } + port->port.dev = NULL; + dprintf("usb-ohci: Detached port %d\n", port1->index); + } + + if (old_state != port->ctrl) + ohci_set_interrupt(s, OHCI_INTR_RHSC); +} + +/* Reset the controller */ +static void ohci_reset(void *opaque) +{ + OHCIState *ohci = opaque; + OHCIPort *port; + int i; + + ohci_bus_stop(ohci); + ohci->ctl = 0; + ohci->old_ctl = 0; + 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; + + /* FSMPS is marked TBD in OCHI 1.0, what gives ffs? + * 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; + + 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) + ohci_attach(&port->port, port->port.dev); + } + if (ohci->async_td) { + usb_cancel_packet(&ohci->usb_packet); + ohci->async_td = 0; + } + dprintf("usb-ohci: Reset %s\n", ohci->name); +} + +/* Get an array of dwords from main memory */ +static inline int get_dwords(uint32_t addr, uint32_t *buf, int num) +{ + int i; + + for (i = 0; i < num; i++, buf++, addr += sizeof(*buf)) { + cpu_physical_memory_rw(addr, (uint8_t *)buf, sizeof(*buf), 0); + *buf = le32_to_cpu(*buf); + } + + return 1; +} + +/* Put an array of dwords in to main memory */ +static inline int put_dwords(uint32_t addr, uint32_t *buf, int num) +{ + int i; + + for (i = 0; i < num; i++, buf++, addr += sizeof(*buf)) { + uint32_t tmp = cpu_to_le32(*buf); + cpu_physical_memory_rw(addr, (uint8_t *)&tmp, sizeof(tmp), 1); + } + + return 1; +} + +/* Get an array of words from main memory */ +static inline int get_words(uint32_t addr, uint16_t *buf, int num) +{ + int i; + + for (i = 0; i < num; i++, buf++, addr += sizeof(*buf)) { + cpu_physical_memory_rw(addr, (uint8_t *)buf, sizeof(*buf), 0); + *buf = le16_to_cpu(*buf); + } + + return 1; +} + +/* Put an array of words in to main memory */ +static inline int put_words(uint32_t addr, uint16_t *buf, int num) +{ + int i; + + for (i = 0; i < num; i++, buf++, addr += sizeof(*buf)) { + uint16_t tmp = cpu_to_le16(*buf); + cpu_physical_memory_rw(addr, (uint8_t *)&tmp, sizeof(tmp), 1); + } + + return 1; +} + +static inline int ohci_read_ed(uint32_t addr, struct ohci_ed *ed) +{ + return get_dwords(addr, (uint32_t *)ed, sizeof(*ed) >> 2); +} + +static inline int ohci_read_td(uint32_t addr, struct ohci_td *td) +{ + return get_dwords(addr, (uint32_t *)td, sizeof(*td) >> 2); +} + +static inline int ohci_read_iso_td(uint32_t addr, struct ohci_iso_td *td) +{ + return (get_dwords(addr, (uint32_t *)td, 4) && + get_words(addr + 16, td->offset, 8)); +} + +static inline int ohci_put_ed(uint32_t addr, struct ohci_ed *ed) +{ + return put_dwords(addr, (uint32_t *)ed, sizeof(*ed) >> 2); +} + +static inline int ohci_put_td(uint32_t addr, struct ohci_td *td) +{ + return put_dwords(addr, (uint32_t *)td, sizeof(*td) >> 2); +} + +static inline int ohci_put_iso_td(uint32_t addr, struct ohci_iso_td *td) +{ + return (put_dwords(addr, (uint32_t *)td, 4) && + put_words(addr + 16, td->offset, 8)); +} + +/* Read/Write the contents of a TD from/to main memory. */ +static void ohci_copy_td(struct ohci_td *td, uint8_t *buf, int len, int write) +{ + uint32_t ptr; + uint32_t n; + + ptr = td->cbp; + n = 0x1000 - (ptr & 0xfff); + if (n > len) + n = len; + cpu_physical_memory_rw(ptr, buf, n, write); + if (n == len) + return; + ptr = td->be & ~0xfffu; + buf += n; + cpu_physical_memory_rw(ptr, buf, len - n, write); +} + +/* Read/Write the contents of an ISO TD from/to main memory. */ +static void ohci_copy_iso_td(uint32_t start_addr, uint32_t end_addr, + uint8_t *buf, int len, int write) +{ + uint32_t ptr; + uint32_t n; + + ptr = start_addr; + n = 0x1000 - (ptr & 0xfff); + if (n > len) + n = len; + cpu_physical_memory_rw(ptr, buf, n, write); + if (n == len) + return; + ptr = end_addr & ~0xfffu; + buf += n; + cpu_physical_memory_rw(ptr, buf, len - n, write); +} + +static void ohci_process_lists(OHCIState *ohci, int completion); + +static void ohci_async_complete_packet(USBPacket *packet, void *opaque) +{ + OHCIState *ohci = opaque; +#ifdef DEBUG_PACKET + dprintf("Async packet complete\n"); +#endif + ohci->async_complete = 1; + ohci_process_lists(ohci, 1); +} + +#define USUB(a, b) ((int16_t)((uint16_t)(a) - (uint16_t)(b))) + +static int ohci_service_iso_td(OHCIState *ohci, struct ohci_ed *ed, + int completion) +{ + int dir; + size_t len = 0; + const char *str = NULL; + int pid; + int ret; + int i; + USBDevice *dev; + 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; + + if (!ohci_read_iso_td(addr, &iso_td)) { + printf("usb-ohci: ISO_TD read error at %x\n", addr); + return 0; + } + + starting_frame = OHCI_BM(iso_td.flags, TD_SF); + frame_count = OHCI_BM(iso_td.flags, TD_FC); + relative_frame_number = USUB(ohci->frame_number, starting_frame); + +#ifdef DEBUG_ISOCH + printf("--- ISO_TD ED head 0x%.8x tailp 0x%.8x\n" + "0x%.8x 0x%.8x 0x%.8x 0x%.8x\n" + "0x%.8x 0x%.8x 0x%.8x 0x%.8x\n" + "0x%.8x 0x%.8x 0x%.8x 0x%.8x\n" + "frame_number 0x%.8x starting_frame 0x%.8x\n" + "frame_count 0x%.8x relative %d\n" + "di 0x%.8x cc 0x%.8x\n", + ed->head & OHCI_DPTR_MASK, ed->tail & OHCI_DPTR_MASK, + iso_td.flags, iso_td.bp, iso_td.next, iso_td.be, + 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], + ohci->frame_number, starting_frame, + frame_count, relative_frame_number, + OHCI_BM(iso_td.flags, TD_DI), OHCI_BM(iso_td.flags, TD_CC)); +#endif + + if (relative_frame_number < 0) { + dprintf("usb-ohci: ISO_TD R=%d < 0\n", relative_frame_number); + return 1; + } else if (relative_frame_number > frame_count) { + /* ISO TD expired - retire the TD to the Done Queue and continue with + the next ISO TD of the same ED */ + dprintf("usb-ohci: ISO_TD R=%d > FC=%d\n", relative_frame_number, + frame_count); + 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); + if (i < ohci->done_count) + ohci->done_count = i; + ohci_put_iso_td(addr, &iso_td); + 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: + printf("usb-ohci: Bad direction %d\n", dir); + return 1; + } + + if (!iso_td.bp || !iso_td.be) { + printf("usb-ohci: ISO_TD bp 0x%.8x be 0x%.8x\n", iso_td.bp, iso_td.be); + return 1; + } + + start_offset = iso_td.offset[relative_frame_number]; + next_offset = iso_td.offset[relative_frame_number + 1]; + + if (!(OHCI_BM(start_offset, TD_PSW_CC) & 0xe) || + ((relative_frame_number < frame_count) && + !(OHCI_BM(next_offset, TD_PSW_CC) & 0xe))) { + printf("usb-ohci: ISO_TD cc != not accessed 0x%.8x 0x%.8x\n", + start_offset, next_offset); + return 1; + } + + if ((relative_frame_number < frame_count) && (start_offset > next_offset)) { + printf("usb-ohci: ISO_TD start_offset=0x%.8x > next_offset=0x%.8x\n", + start_offset, next_offset); + 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 */ + end_addr = iso_td.be; + } + + 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; + } + + if (len && dir != OHCI_TD_DIR_IN) { + ohci_copy_iso_td(start_addr, end_addr, ohci->usb_buf, len, 0); + } + + if (completion) { + ret = ohci->usb_packet.len; + } else { + ret = USB_RET_NODEV; + for (i = 0; i < ohci->num_ports; i++) { + dev = ohci->rhport[i].port.dev; + if ((ohci->rhport[i].ctrl & OHCI_PORT_PES) == 0) + continue; + ohci->usb_packet.pid = pid; + ohci->usb_packet.devaddr = OHCI_BM(ed->flags, ED_FA); + ohci->usb_packet.devep = OHCI_BM(ed->flags, ED_EN); + ohci->usb_packet.data = ohci->usb_buf; + ohci->usb_packet.len = len; + ohci->usb_packet.complete_cb = ohci_async_complete_packet; + ohci->usb_packet.complete_opaque = ohci; + ret = dev->handle_packet(dev, &ohci->usb_packet); + if (ret != USB_RET_NODEV) + break; + } + + if (ret == USB_RET_ASYNC) { + return 1; + } + } + +#ifdef DEBUG_ISOCH + printf("so 0x%.8x eo 0x%.8x\nsa 0x%.8x ea 0x%.8x\ndir %s len %zu ret %d\n", + start_offset, end_offset, start_addr, end_addr, str, len, ret); +#endif + + /* Writeback */ + if (dir == OHCI_TD_DIR_IN && ret >= 0 && ret <= len) { + /* IN transfer succeeded */ + ohci_copy_iso_td(start_addr, end_addr, ohci->usb_buf, ret, 1); + 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 { + if (ret > (ssize_t) len) { + printf("usb-ohci: DataOverrun %d > %zu\n", ret, len); + 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) { + printf("usb-ohci: DataUnderrun %d\n", ret); + OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_CC, + OHCI_CC_DATAUNDERRUN); + } else { + switch (ret) { + 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: + printf("usb-ohci: got NAK/STALL %d\n", ret); + 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: + printf("usb-ohci: Bad device response %d\n", ret); + 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); + if (i < ohci->done_count) + ohci->done_count = i; + } + ohci_put_iso_td(addr, &iso_td); + return 1; +} + +/* Service a transport descriptor. + Returns nonzero to terminate processing of this endpoint. */ + +static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed) +{ + int dir; + size_t len = 0; + const char *str = NULL; + int pid; + int ret; + int i; + USBDevice *dev; + struct ohci_td td; + uint32_t addr; + int flag_r; + int completion; + + addr = ed->head & OHCI_DPTR_MASK; + /* See if this TD has already been submitted to the device. */ + completion = (addr == ohci->async_td); + if (completion && !ohci->async_complete) { +#ifdef DEBUG_PACKET + dprintf("Skipping async TD\n"); +#endif + return 1; + } + if (!ohci_read_td(addr, &td)) { + fprintf(stderr, "usb-ohci: TD read error at %x\n", addr); + return 0; + } + + dir = OHCI_BM(ed->flags, ED_D); + switch (dir) { + case OHCI_TD_DIR_OUT: + case OHCI_TD_DIR_IN: + /* Same value. */ + 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; + break; + default: + fprintf(stderr, "usb-ohci: Bad direction\n"); + return 1; + } + if (td.cbp && td.be) { + if ((td.cbp & 0xfffff000) != (td.be & 0xfffff000)) { + len = (td.be & 0xfff) + 0x1001 - (td.cbp & 0xfff); + } else { + len = (td.be - td.cbp) + 1; + } + + if (len && dir != OHCI_TD_DIR_IN && !completion) { + ohci_copy_td(&td, ohci->usb_buf, len, 0); + } + } + + flag_r = (td.flags & OHCI_TD_R) != 0; +#ifdef DEBUG_PACKET + dprintf(" TD @ 0x%.8x %u bytes %s r=%d cbp=0x%.8x be=0x%.8x\n", + addr, len, str, flag_r, td.cbp, td.be); + + if (len > 0 && dir != OHCI_TD_DIR_IN) { + dprintf(" data:"); + for (i = 0; i < len; i++) + printf(" %.2x", ohci->usb_buf[i]); + dprintf("\n"); + } +#endif + if (completion) { + ret = ohci->usb_packet.len; + ohci->async_td = 0; + ohci->async_complete = 0; + } else { + ret = USB_RET_NODEV; + for (i = 0; i < ohci->num_ports; i++) { + dev = ohci->rhport[i].port.dev; + if ((ohci->rhport[i].ctrl & OHCI_PORT_PES) == 0) + continue; + + if (ohci->async_td) { + /* ??? 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. + */ +#ifdef DEBUG_PACKET + dprintf("Too many pending packets\n"); +#endif + return 1; + } + ohci->usb_packet.pid = pid; + ohci->usb_packet.devaddr = OHCI_BM(ed->flags, ED_FA); + ohci->usb_packet.devep = OHCI_BM(ed->flags, ED_EN); + ohci->usb_packet.data = ohci->usb_buf; + ohci->usb_packet.len = len; + ohci->usb_packet.complete_cb = ohci_async_complete_packet; + ohci->usb_packet.complete_opaque = ohci; + ret = dev->handle_packet(dev, &ohci->usb_packet); + if (ret != USB_RET_NODEV) + break; + } +#ifdef DEBUG_PACKET + dprintf("ret=%d\n", ret); +#endif + if (ret == USB_RET_ASYNC) { + ohci->async_td = addr; + return 1; + } + } + if (ret >= 0) { + if (dir == OHCI_TD_DIR_IN) { + ohci_copy_td(&td, ohci->usb_buf, ret, 1); +#ifdef DEBUG_PACKET + dprintf(" data:"); + for (i = 0; i < ret; i++) + printf(" %.2x", ohci->usb_buf[i]); + dprintf("\n"); +#endif + } else { + ret = len; + } + } + + /* Writeback */ + if (ret == len || (dir == OHCI_TD_DIR_IN && ret >= 0 && flag_r)) { + /* Transmission succeeded. */ + if (ret == len) { + td.cbp = 0; + } else { + td.cbp += ret; + if ((td.cbp & 0xfff) + ret > 0xfff) { + td.cbp &= 0xfff; + td.cbp |= td.be & ~0xfff; + } + } + 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); + + ed->head &= ~OHCI_ED_C; + if (td.flags & OHCI_TD_T0) + ed->head |= OHCI_ED_C; + } else { + if (ret >= 0) { + dprintf("usb-ohci: Underrun\n"); + OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_DATAUNDERRUN); + } else { + switch (ret) { + case USB_RET_NODEV: + OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_DEVICENOTRESPONDING); + case USB_RET_NAK: + dprintf("usb-ohci: got NAK\n"); + return 1; + case USB_RET_STALL: + dprintf("usb-ohci: got STALL\n"); + OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_STALL); + break; + case USB_RET_BABBLE: + dprintf("usb-ohci: got BABBLE\n"); + OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_DATAOVERRUN); + break; + default: + fprintf(stderr, "usb-ohci: Bad device response %d\n", ret); + OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_UNDEXPETEDPID); + OHCI_SET_BM(td.flags, TD_EC, 3); + break; + } + } + 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); + if (i < ohci->done_count) + ohci->done_count = i; + ohci_put_td(addr, &td); + return OHCI_BM(td.flags, TD_CC) != OHCI_CC_NOERROR; +} + +/* Service an endpoint list. Returns nonzero if active TD were found. */ +static int ohci_service_ed_list(OHCIState *ohci, uint32_t head, int completion) +{ + struct ohci_ed ed; + uint32_t next_ed; + uint32_t cur; + int active; + + active = 0; + + if (head == 0) + return 0; + + for (cur = head; cur; cur = next_ed) { + if (!ohci_read_ed(cur, &ed)) { + fprintf(stderr, "usb-ohci: ED read error at %x\n", cur); + return 0; + } + + next_ed = ed.next & OHCI_DPTR_MASK; + + if ((ed.head & OHCI_ED_H) || (ed.flags & OHCI_ED_K)) { + uint32_t addr; + /* Cancel pending packets for ED that have been paused. */ + addr = ed.head & OHCI_DPTR_MASK; + if (ohci->async_td && addr == ohci->async_td) { + usb_cancel_packet(&ohci->usb_packet); + ohci->async_td = 0; + } + continue; + } + + while ((ed.head & OHCI_DPTR_MASK) != ed.tail) { +#ifdef DEBUG_PACKET + dprintf("ED @ 0x%.8x fa=%u en=%u d=%u s=%u k=%u f=%u mps=%u " + "h=%u c=%u\n head=0x%.8x tailp=0x%.8x next=0x%.8x\n", cur, + OHCI_BM(ed.flags, ED_FA), OHCI_BM(ed.flags, ED_EN), + OHCI_BM(ed.flags, ED_D), (ed.flags & OHCI_ED_S)!= 0, + (ed.flags & OHCI_ED_K) != 0, (ed.flags & OHCI_ED_F) != 0, + OHCI_BM(ed.flags, ED_MPS), (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); +#endif + active = 1; + + if ((ed.flags & OHCI_ED_F) == 0) { + if (ohci_service_td(ohci, &ed)) + break; + } else { + /* Handle isochronous endpoints */ + if (ohci_service_iso_td(ohci, &ed, completion)) + break; + } + } + + ohci_put_ed(cur, &ed); + } + + return active; +} + +/* Generate a SOF event, and set a timer for EOF */ +static void ohci_sof(OHCIState *ohci) +{ + ohci->sof_time = qemu_get_clock(vm_clock); + qemu_mod_timer(ohci->eof_timer, ohci->sof_time + usb_frame_time); + ohci_set_interrupt(ohci, OHCI_INTR_SF); +} + +/* Process Control and Bulk lists. */ +static void ohci_process_lists(OHCIState *ohci, int completion) +{ + if ((ohci->ctl & OHCI_CTL_CLE) && (ohci->status & OHCI_STATUS_CLF)) { + if (ohci->ctrl_cur && ohci->ctrl_cur != ohci->ctrl_head) + dprintf("usb-ohci: head %x, cur %x\n", + ohci->ctrl_head, ohci->ctrl_cur); + if (!ohci_service_ed_list(ohci, ohci->ctrl_head, completion)) { + ohci->ctrl_cur = 0; + ohci->status &= ~OHCI_STATUS_CLF; + } + } + + if ((ohci->ctl & OHCI_CTL_BLE) && (ohci->status & OHCI_STATUS_BLF)) { + if (!ohci_service_ed_list(ohci, ohci->bulk_head, completion)) { + ohci->bulk_cur = 0; + ohci->status &= ~OHCI_STATUS_BLF; + } + } +} + +/* Do frame processing on frame boundary */ +static void ohci_frame_boundary(void *opaque) +{ + OHCIState *ohci = opaque; + struct ohci_hcca hcca; + + cpu_physical_memory_rw(ohci->hcca, (uint8_t *)&hcca, sizeof(hcca), 0); + + /* Process all the lists at the end of the frame */ + if (ohci->ctl & OHCI_CTL_PLE) { + int n; + + n = ohci->frame_number & 0x1f; + ohci_service_ed_list(ohci, le32_to_cpu(hcca.intr[n]), 0); + } + + /* Cancel all pending packets if either of the lists has been disabled. */ + if (ohci->async_td && + ohci->old_ctl & (~ohci->ctl) & (OHCI_CTL_BLE | OHCI_CTL_CLE)) { + usb_cancel_packet(&ohci->usb_packet); + ohci->async_td = 0; + } + ohci->old_ctl = ohci->ctl; + ohci_process_lists(ohci, 0); + + /* Frame boundary, so do EOF stuf here */ + ohci->frt = ohci->fit; + + /* XXX: endianness */ + ohci->frame_number = (ohci->frame_number + 1) & 0xffff; + hcca.frame = cpu_to_le32(ohci->frame_number); + + if (ohci->done_count == 0 && !(ohci->intr_status & OHCI_INTR_WD)) { + if (!ohci->done) + abort(); + if (ohci->intr & ohci->intr_status) + ohci->done |= 1; + hcca.done = cpu_to_le32(ohci->done); + ohci->done = 0; + ohci->done_count = 7; + ohci_set_interrupt(ohci, OHCI_INTR_WD); + } + + if (ohci->done_count != 7 && ohci->done_count != 0) + ohci->done_count--; + + /* Do SOF stuff here */ + ohci_sof(ohci); + + /* Writeback HCCA */ + cpu_physical_memory_rw(ohci->hcca, (uint8_t *)&hcca, sizeof(hcca), 1); +} + +/* Start sending SOF tokens across the USB bus, lists are processed in + * next frame + */ +static int ohci_bus_start(OHCIState *ohci) +{ + ohci->eof_timer = qemu_new_timer(vm_clock, + ohci_frame_boundary, + ohci); + + if (ohci->eof_timer == NULL) { + fprintf(stderr, "usb-ohci: %s: qemu_new_timer failed\n", ohci->name); + /* TODO: Signal unrecoverable error */ + return 0; + } + + dprintf("usb-ohci: %s: USB Operational\n", ohci->name); + + ohci_sof(ohci); + + return 1; +} + +/* Stop sending SOF tokens on the bus */ +static void ohci_bus_stop(OHCIState *ohci) +{ + if (ohci->eof_timer) + qemu_del_timer(ohci->eof_timer); + ohci->eof_timer = NULL; +} + +/* Sets a flag in a port status register 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) { + /* TODO: CSC is a wakeup event */ + } + return 0; + } + + if (ohci->rhport[i].ctrl & val) + ret = 0; + + /* set the bit */ + ohci->rhport[i].ctrl |= val; + + return ret; +} + +/* Set the frame interval - frame interval toggle is manipulated by the hcd only */ +static void ohci_set_frame_interval(OHCIState *ohci, uint16_t val) +{ + val &= OHCI_FMI_FI; + + if (val != ohci->fi) { + dprintf("usb-ohci: %s: FrameInterval = 0x%x (%u)\n", + ohci->name, ohci->fi, ohci->fi); + } + + ohci->fi = val; +} + +static void ohci_port_power(OHCIState *ohci, int i, int p) +{ + if (p) { + ohci->rhport[i].ctrl |= OHCI_PORT_PPS; + } else { + ohci->rhport[i].ctrl &= ~(OHCI_PORT_PPS| + OHCI_PORT_CCS| + OHCI_PORT_PSS| + OHCI_PORT_PRS); + } +} + +/* 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 */ + if (old_state == new_state) + return; + + switch (new_state) { + case OHCI_USB_OPERATIONAL: + ohci_bus_start(ohci); + break; + case OHCI_USB_SUSPEND: + ohci_bus_stop(ohci); + dprintf("usb-ohci: %s: USB Suspended\n", ohci->name); + break; + case OHCI_USB_RESUME: + dprintf("usb-ohci: %s: USB Resume\n", ohci->name); + break; + case OHCI_USB_RESET: + ohci_reset(ohci); + dprintf("usb-ohci: %s: USB Reset\n", ohci->name); + break; + } +} + +static uint32_t ohci_get_frame_remaining(OHCIState *ohci) +{ + uint16_t fr; + int64_t tks; + + if ((ohci->ctl & OHCI_CTL_HCFS) != OHCI_USB_OPERATIONAL) + return (ohci->frt << 31); + + /* Being in USB operational state guarnatees sof_time was + * set already. + */ + tks = qemu_get_clock(vm_clock) - ohci->sof_time; + + /* avoid muldiv if possible */ + if (tks >= usb_frame_time) + return (ohci->frt << 31); + + tks = muldiv64(1, tks, usb_bit_time); + 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 */ + if (val & OHCI_RHS_OCIC) + ohci->rhstatus &= ~OHCI_RHS_OCIC; + + if (val & OHCI_RHS_LPS) { + int i; + + for (i = 0; i < ohci->num_ports; i++) + ohci_port_power(ohci, i, 0); + dprintf("usb-ohci: powered down all ports\n"); + } + + if (val & OHCI_RHS_LPSC) { + int i; + + for (i = 0; i < ohci->num_ports; i++) + ohci_port_power(ohci, i, 1); + dprintf("usb-ohci: powered up all ports\n"); + } + + if (val & OHCI_RHS_DRWE) + ohci->rhstatus |= OHCI_RHS_DRWE; + + if (val & OHCI_RHS_CRWE) + ohci->rhstatus &= ~OHCI_RHS_DRWE; + + if (old_state != ohci->rhstatus) + ohci_set_interrupt(ohci, OHCI_INTR_RHSC); +} + +/* 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 */ + if (val & OHCI_PORT_WTC) + port->ctrl &= ~(val & OHCI_PORT_WTC); + + if (val & OHCI_PORT_CCS) + port->ctrl &= ~OHCI_PORT_PES; + + ohci_port_set_if_connected(ohci, portnum, val & OHCI_PORT_PES); + + if (ohci_port_set_if_connected(ohci, portnum, val & OHCI_PORT_PSS)) + dprintf("usb-ohci: port %d: SUSPEND\n", portnum); + + if (ohci_port_set_if_connected(ohci, portnum, val & OHCI_PORT_PRS)) { + dprintf("usb-ohci: port %d: RESET\n", portnum); + usb_send_msg(port->port.dev, USB_MSG_RESET); + port->ctrl &= ~OHCI_PORT_PRS; + /* ??? Should this also set OHCI_PORT_PESC. */ + port->ctrl |= OHCI_PORT_PES | OHCI_PORT_PRSC; + } + + /* Invert order here to ensure in ambiguous case, device is + * powered up... + */ + if (val & OHCI_PORT_LSDA) + ohci_port_power(ohci, portnum, 0); + if (val & OHCI_PORT_PPS) + ohci_port_power(ohci, portnum, 1); + + if (old_state != port->ctrl) + ohci_set_interrupt(ohci, OHCI_INTR_RHSC); + + return; +} + +static uint32_t ohci_mem_read(void *ptr, target_phys_addr_t addr) +{ + OHCIState *ohci = ptr; + + addr -= ohci->mem_base; + + /* Only aligned reads are allowed on OHCI */ + if (addr & 3) { + fprintf(stderr, "usb-ohci: Mis-aligned read\n"); + return 0xffffffff; + } + + if (addr >= 0x54 && addr < 0x54 + ohci->num_ports * 4) { + /* HcRhPortStatus */ + return ohci->rhport[(addr - 0x54) >> 2].ctrl | OHCI_PORT_PPS; + } + + switch (addr >> 2) { + case 0: /* HcRevision */ + return 0x10; + + case 1: /* HcControl */ + return ohci->ctl; + + case 2: /* HcCommandStatus */ + return ohci->status; + + case 3: /* HcInterruptStatus */ + return ohci->intr_status; + + case 4: /* HcInterruptEnable */ + case 5: /* HcInterruptDisable */ + return ohci->intr; + + case 6: /* HcHCCA */ + return ohci->hcca; + + case 7: /* HcPeriodCurrentED */ + return ohci->per_cur; + + case 8: /* HcControlHeadED */ + return ohci->ctrl_head; + + case 9: /* HcControlCurrentED */ + return ohci->ctrl_cur; + + case 10: /* HcBulkHeadED */ + return ohci->bulk_head; + + case 11: /* HcBulkCurrentED */ + return ohci->bulk_cur; + + case 12: /* HcDoneHead */ + return ohci->done; + + case 13: /* HcFmInterval */ + return (ohci->fit << 31) | (ohci->fsmps << 16) | (ohci->fi); + + case 14: /* HcFmRemaining */ + return ohci_get_frame_remaining(ohci); + + case 15: /* HcFmNumber */ + return ohci->frame_number; + + case 16: /* HcPeriodicStart */ + return ohci->pstart; + + case 17: /* HcLSThreshold */ + return ohci->lst; + + case 18: /* HcRhDescriptorA */ + return ohci->rhdesc_a; + + case 19: /* HcRhDescriptorB */ + return ohci->rhdesc_b; + + case 20: /* HcRhStatus */ + return ohci->rhstatus; + + /* PXA27x specific registers */ + case 24: /* HcStatus */ + return ohci->hstatus & ohci->hmask; + + case 25: /* HcHReset */ + return ohci->hreset; + + case 26: /* HcHInterruptEnable */ + return ohci->hmask; + + case 27: /* HcHInterruptTest */ + return ohci->htest; + + default: + fprintf(stderr, "ohci_read: Bad offset %x\n", (int)addr); + return 0xffffffff; + } +} + +static void ohci_mem_write(void *ptr, target_phys_addr_t addr, uint32_t val) +{ + OHCIState *ohci = ptr; + + addr -= ohci->mem_base; + + /* Only aligned reads are allowed on OHCI */ + if (addr & 3) { + fprintf(stderr, "usb-ohci: Mis-aligned write\n"); + return; + } + + if (addr >= 0x54 && addr < 0x54 + ohci->num_ports * 4) { + /* HcRhPortStatus */ + ohci_port_set_status(ohci, (addr - 0x54) >> 2, val); + return; + } + + 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; + + if (ohci->status & OHCI_STATUS_HCR) + ohci_reset(ohci); + 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; + + 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; + + case 15: /* HcFmNumber */ + break; + + 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; + + /* PXA27x specific registers */ + case 24: /* HcStatus */ + ohci->hstatus &= ~(val & ohci->hmask); + + case 25: /* HcHReset */ + ohci->hreset = val & ~OHCI_HRESET_FSBIR; + if (val & OHCI_HRESET_FSBIR) + ohci_reset(ohci); + break; + + case 26: /* HcHInterruptEnable */ + ohci->hmask = val; + break; + + case 27: /* HcHInterruptTest */ + ohci->htest = val; + break; + + default: + fprintf(stderr, "ohci_write: Bad offset %x\n", (int)addr); + break; + } +} + +/* Only dword reads are defined on OHCI register space */ +static CPUReadMemoryFunc *ohci_readfn[3]={ + ohci_mem_read, + ohci_mem_read, + ohci_mem_read +}; + +/* Only dword writes are defined on OHCI register space */ +static CPUWriteMemoryFunc *ohci_writefn[3]={ + ohci_mem_write, + ohci_mem_write, + ohci_mem_write +}; + +static void usb_ohci_init(OHCIState *ohci, int num_ports, int devfn, + qemu_irq irq, enum ohci_type type, const char *name) +{ + int i; + + if (usb_frame_time == 0) { +#ifdef OHCI_TIME_WARP + usb_frame_time = ticks_per_sec; + usb_bit_time = muldiv64(1, ticks_per_sec, USB_HZ/1000); +#else + usb_frame_time = muldiv64(1, ticks_per_sec, 1000); + if (ticks_per_sec >= USB_HZ) { + usb_bit_time = muldiv64(1, ticks_per_sec, USB_HZ); + } else { + usb_bit_time = 1; + } +#endif + dprintf("usb-ohci: usb_bit_time=%lli usb_frame_time=%lli\n", + usb_frame_time, usb_bit_time); + } + + ohci->mem = cpu_register_io_memory(0, ohci_readfn, ohci_writefn, ohci); + ohci->name = name; + + ohci->irq = irq; + ohci->type = type; + + ohci->num_ports = num_ports; + for (i = 0; i < num_ports; i++) { + qemu_register_usb_port(&ohci->rhport[i].port, ohci, i, ohci_attach); + } + + ohci->async_td = 0; + qemu_register_reset(ohci_reset, ohci); + ohci_reset(ohci); +} + +typedef struct { + PCIDevice pci_dev; + OHCIState state; +} OHCIPCIState; + +static void ohci_mapfunc(PCIDevice *pci_dev, int i, + uint32_t addr, uint32_t size, int type) +{ + OHCIPCIState *ohci = (OHCIPCIState *)pci_dev; + ohci->state.mem_base = addr; + cpu_register_physical_memory(addr, size, ohci->state.mem); +} + +void usb_ohci_init_pci(struct PCIBus *bus, int num_ports, int devfn) +{ + OHCIPCIState *ohci; + int vid = 0x106b; + int did = 0x003f; + + ohci = (OHCIPCIState *)pci_register_device(bus, "OHCI USB", sizeof(*ohci), + devfn, NULL, NULL); + if (ohci == NULL) { + fprintf(stderr, "usb-ohci: Failed to register PCI device\n"); + return; + } + + ohci->pci_dev.config[0x00] = vid & 0xff; + ohci->pci_dev.config[0x01] = (vid >> 8) & 0xff; + ohci->pci_dev.config[0x02] = did & 0xff; + ohci->pci_dev.config[0x03] = (did >> 8) & 0xff; + ohci->pci_dev.config[0x09] = 0x10; /* OHCI */ + ohci->pci_dev.config[0x0a] = 0x3; + ohci->pci_dev.config[0x0b] = 0xc; + ohci->pci_dev.config[0x3d] = 0x01; /* interrupt pin 1 */ + + usb_ohci_init(&ohci->state, num_ports, devfn, ohci->pci_dev.irq[0], + OHCI_TYPE_PCI, ohci->pci_dev.name); + + pci_register_io_region((struct PCIDevice *)ohci, 0, 256, + PCI_ADDRESS_SPACE_MEM, ohci_mapfunc); +} + +void usb_ohci_init_pxa(target_phys_addr_t base, int num_ports, int devfn, + qemu_irq irq) +{ + OHCIState *ohci = (OHCIState *)qemu_mallocz(sizeof(OHCIState)); + + usb_ohci_init(ohci, num_ports, devfn, irq, + OHCI_TYPE_PXA, "OHCI USB"); + ohci->mem_base = base; + + cpu_register_physical_memory(ohci->mem_base, 0x1000, ohci->mem); +} diff --git a/hw/usb.c b/hw/usb.c new file mode 100644 index 0000000..c17266d --- /dev/null +++ b/hw/usb.c @@ -0,0 +1,231 @@ +/* + * QEMU USB emulation + * + * Copyright (c) 2005 Fabrice Bellard + * + * 2008 Generic packet handler rewrite by Max Krasnyansky + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "qemu-common.h" +#include "usb.h" + +void usb_attach(USBPort *port, USBDevice *dev) +{ + port->attach(port, dev); +} + +/**********************/ + +/* generic USB device helpers (you are not forced to use them when + writing your USB device driver, but they help handling the + protocol) +*/ + +#define SETUP_STATE_IDLE 0 +#define SETUP_STATE_DATA 1 +#define SETUP_STATE_ACK 2 + +static int do_token_setup(USBDevice *s, USBPacket *p) +{ + int request, value, index; + int ret = 0; + + if (p->len != 8) + return USB_RET_STALL; + + memcpy(s->setup_buf, p->data, 8); + s->setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6]; + s->setup_index = 0; + + request = (s->setup_buf[0] << 8) | s->setup_buf[1]; + value = (s->setup_buf[3] << 8) | s->setup_buf[2]; + index = (s->setup_buf[5] << 8) | s->setup_buf[4]; + + if (s->setup_buf[0] & USB_DIR_IN) { + ret = s->handle_control(s, request, value, index, + s->setup_len, s->data_buf); + if (ret < 0) + return ret; + + if (ret < s->setup_len) + s->setup_len = ret; + s->setup_state = SETUP_STATE_DATA; + } else { + if (s->setup_len == 0) + s->setup_state = SETUP_STATE_ACK; + else + s->setup_state = SETUP_STATE_DATA; + } + + return ret; +} + +static int do_token_in(USBDevice *s, USBPacket *p) +{ + int request, value, index; + int ret = 0; + + if (p->devep != 0) + return s->handle_data(s, p); + + request = (s->setup_buf[0] << 8) | s->setup_buf[1]; + value = (s->setup_buf[3] << 8) | s->setup_buf[2]; + index = (s->setup_buf[5] << 8) | s->setup_buf[4]; + + switch(s->setup_state) { + case SETUP_STATE_ACK: + if (!(s->setup_buf[0] & USB_DIR_IN)) { + s->setup_state = SETUP_STATE_IDLE; + ret = s->handle_control(s, request, value, index, + s->setup_len, s->data_buf); + if (ret > 0) + return 0; + return ret; + } + + /* return 0 byte */ + return 0; + + case SETUP_STATE_DATA: + if (s->setup_buf[0] & USB_DIR_IN) { + int len = s->setup_len - s->setup_index; + if (len > p->len) + len = p->len; + memcpy(p->data, s->data_buf + s->setup_index, len); + s->setup_index += len; + if (s->setup_index >= s->setup_len) + s->setup_state = SETUP_STATE_ACK; + return len; + } + + s->setup_state = SETUP_STATE_IDLE; + return USB_RET_STALL; + + default: + return USB_RET_STALL; + } +} + +static int do_token_out(USBDevice *s, USBPacket *p) +{ + if (p->devep != 0) + return s->handle_data(s, p); + + switch(s->setup_state) { + case SETUP_STATE_ACK: + if (s->setup_buf[0] & USB_DIR_IN) { + s->setup_state = SETUP_STATE_IDLE; + /* transfer OK */ + } else { + /* ignore additional output */ + } + return 0; + + case SETUP_STATE_DATA: + if (!(s->setup_buf[0] & USB_DIR_IN)) { + int len = s->setup_len - s->setup_index; + if (len > p->len) + len = p->len; + memcpy(s->data_buf + s->setup_index, p->data, len); + s->setup_index += len; + if (s->setup_index >= s->setup_len) + s->setup_state = SETUP_STATE_ACK; + return len; + } + + s->setup_state = SETUP_STATE_IDLE; + return USB_RET_STALL; + + default: + return USB_RET_STALL; + } +} + +/* + * Generic packet handler. + * Called by the HC (host controller). + * + * Returns length of the transaction or one of the USB_RET_XXX codes. + */ +int usb_generic_handle_packet(USBDevice *s, USBPacket *p) +{ + switch(p->pid) { + case USB_MSG_ATTACH: + s->state = USB_STATE_ATTACHED; + return 0; + + case USB_MSG_DETACH: + s->state = USB_STATE_NOTATTACHED; + return 0; + + case USB_MSG_RESET: + s->remote_wakeup = 0; + s->addr = 0; + s->state = USB_STATE_DEFAULT; + s->handle_reset(s); + return 0; + } + + /* Rest of the PIDs must match our address */ + if (s->state < USB_STATE_DEFAULT || p->devaddr != s->addr) + return USB_RET_NODEV; + + switch (p->pid) { + case USB_TOKEN_SETUP: + return do_token_setup(s, p); + + case USB_TOKEN_IN: + return do_token_in(s, p); + + case USB_TOKEN_OUT: + return do_token_out(s, p); + + default: + return USB_RET_STALL; + } +} + +/* XXX: fix overflow */ +int set_usb_string(uint8_t *buf, const char *str) +{ + int len, i; + uint8_t *q; + + q = buf; + len = strlen(str); + *q++ = 2 * len + 2; + *q++ = 3; + for(i = 0; i < len; i++) { + *q++ = str[i]; + *q++ = 0; + } + return q - buf; +} + +/* Send an internal message to a USB device. */ +void usb_send_msg(USBDevice *dev, int msg) +{ + USBPacket p; + memset(&p, 0, sizeof(p)); + p.pid = msg; + dev->handle_packet(dev, &p); + + /* This _must_ be synchronous */ +} diff --git a/hw/usb.h b/hw/usb.h new file mode 100644 index 0000000..1a353bb --- /dev/null +++ b/hw/usb.h @@ -0,0 +1,291 @@ +/* + * QEMU USB API + * + * Copyright (c) 2005 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#define USB_TOKEN_SETUP 0x2d +#define USB_TOKEN_IN 0x69 /* device -> host */ +#define USB_TOKEN_OUT 0xe1 /* host -> device */ + +/* specific usb messages, also sent in the 'pid' parameter */ +#define USB_MSG_ATTACH 0x100 +#define USB_MSG_DETACH 0x101 +#define USB_MSG_RESET 0x102 + +#define USB_RET_NODEV (-1) +#define USB_RET_NAK (-2) +#define USB_RET_STALL (-3) +#define USB_RET_BABBLE (-4) +#define USB_RET_ASYNC (-5) + +#define USB_SPEED_LOW 0 +#define USB_SPEED_FULL 1 +#define USB_SPEED_HIGH 2 + +#define USB_STATE_NOTATTACHED 0 +#define USB_STATE_ATTACHED 1 +//#define USB_STATE_POWERED 2 +#define USB_STATE_DEFAULT 3 +//#define USB_STATE_ADDRESS 4 +//#define USB_STATE_CONFIGURED 5 +#define USB_STATE_SUSPENDED 6 + +#define USB_CLASS_AUDIO 1 +#define USB_CLASS_COMM 2 +#define USB_CLASS_HID 3 +#define USB_CLASS_PHYSICAL 5 +#define USB_CLASS_STILL_IMAGE 6 +#define USB_CLASS_PRINTER 7 +#define USB_CLASS_MASS_STORAGE 8 +#define USB_CLASS_HUB 9 +#define USB_CLASS_CDC_DATA 0x0a +#define USB_CLASS_CSCID 0x0b +#define USB_CLASS_CONTENT_SEC 0x0d +#define USB_CLASS_APP_SPEC 0xfe +#define USB_CLASS_VENDOR_SPEC 0xff + +#define USB_DIR_OUT 0 +#define USB_DIR_IN 0x80 + +#define USB_TYPE_MASK (0x03 << 5) +#define USB_TYPE_STANDARD (0x00 << 5) +#define USB_TYPE_CLASS (0x01 << 5) +#define USB_TYPE_VENDOR (0x02 << 5) +#define USB_TYPE_RESERVED (0x03 << 5) + +#define USB_RECIP_MASK 0x1f +#define USB_RECIP_DEVICE 0x00 +#define USB_RECIP_INTERFACE 0x01 +#define USB_RECIP_ENDPOINT 0x02 +#define USB_RECIP_OTHER 0x03 + +#define DeviceRequest ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_DEVICE)<<8) +#define DeviceOutRequest ((USB_DIR_OUT|USB_TYPE_STANDARD|USB_RECIP_DEVICE)<<8) +#define InterfaceRequest \ + ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_INTERFACE)<<8) +#define InterfaceOutRequest \ + ((USB_DIR_OUT|USB_TYPE_STANDARD|USB_RECIP_INTERFACE)<<8) +#define EndpointRequest ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_ENDPOINT)<<8) +#define EndpointOutRequest \ + ((USB_DIR_OUT|USB_TYPE_STANDARD|USB_RECIP_ENDPOINT)<<8) + +#define USB_REQ_GET_STATUS 0x00 +#define USB_REQ_CLEAR_FEATURE 0x01 +#define USB_REQ_SET_FEATURE 0x03 +#define USB_REQ_SET_ADDRESS 0x05 +#define USB_REQ_GET_DESCRIPTOR 0x06 +#define USB_REQ_SET_DESCRIPTOR 0x07 +#define USB_REQ_GET_CONFIGURATION 0x08 +#define USB_REQ_SET_CONFIGURATION 0x09 +#define USB_REQ_GET_INTERFACE 0x0A +#define USB_REQ_SET_INTERFACE 0x0B +#define USB_REQ_SYNCH_FRAME 0x0C + +#define USB_DEVICE_SELF_POWERED 0 +#define USB_DEVICE_REMOTE_WAKEUP 1 + +#define USB_DT_DEVICE 0x01 +#define USB_DT_CONFIG 0x02 +#define USB_DT_STRING 0x03 +#define USB_DT_INTERFACE 0x04 +#define USB_DT_ENDPOINT 0x05 + +#define USB_ENDPOINT_XFER_CONTROL 0 +#define USB_ENDPOINT_XFER_ISOC 1 +#define USB_ENDPOINT_XFER_BULK 2 +#define USB_ENDPOINT_XFER_INT 3 + +typedef struct USBPort USBPort; +typedef struct USBDevice USBDevice; +typedef struct USBPacket USBPacket; + +/* definition of a USB device */ +struct USBDevice { + void *opaque; + + /* + * Process USB packet. + * Called by the HC (Host Controller). + * + * Returns length of the transaction + * or one of the USB_RET_XXX codes. + */ + int (*handle_packet)(USBDevice *dev, USBPacket *p); + + /* + * Called when device is destroyed. + */ + void (*handle_destroy)(USBDevice *dev); + + int speed; + + /* The following fields are used by the generic USB device + layer. They are here just to avoid creating a new structure + for them. */ + + /* + * Reset the device + */ + void (*handle_reset)(USBDevice *dev); + + /* + * Process control request. + * Called from handle_packet(). + * + * Returns length or one of the USB_RET_ codes. + */ + int (*handle_control)(USBDevice *dev, int request, int value, + int index, int length, uint8_t *data); + + /* + * Process data transfers (both BULK and ISOC). + * Called from handle_packet(). + * + * Returns length or one of the USB_RET_ codes. + */ + int (*handle_data)(USBDevice *dev, USBPacket *p); + + uint8_t addr; + char devname[32]; + + int state; + uint8_t setup_buf[8]; + uint8_t data_buf[1024]; + int remote_wakeup; + int setup_state; + int setup_len; + int setup_index; +}; + +typedef void (*usb_attachfn)(USBPort *port, USBDevice *dev); + +/* USB port on which a device can be connected */ +struct USBPort { + USBDevice *dev; + usb_attachfn attach; + void *opaque; + int index; /* internal port index, may be used with the opaque */ + struct USBPort *next; /* Used internally by qemu. */ +}; + +typedef void USBCallback(USBPacket * packet, void *opaque); + +/* Structure used to hold information about an active USB packet. */ +struct USBPacket { + /* Data fields for use by the driver. */ + int pid; + uint8_t devaddr; + uint8_t devep; + uint8_t *data; + int len; + /* Internal use by the USB layer. */ + USBCallback *complete_cb; + void *complete_opaque; + USBCallback *cancel_cb; + void *cancel_opaque; +}; + +/* Defer completion of a USB packet. The hadle_packet routine should then + return USB_RET_ASYNC. Packets that complete immediately (before + handle_packet returns) should not call this method. */ +static inline void usb_defer_packet(USBPacket *p, USBCallback *cancel, + void * opaque) +{ + p->cancel_cb = cancel; + p->cancel_opaque = opaque; +} + +/* Notify the controller that an async packet is complete. This should only + be called for packets previously deferred with usb_defer_packet, and + should never be called from within handle_packet. */ +static inline void usb_packet_complete(USBPacket *p) +{ + p->complete_cb(p, p->complete_opaque); +} + +/* Cancel an active packet. The packed must have been deferred with + usb_defer_packet, and not yet completed. */ +static inline void usb_cancel_packet(USBPacket * p) +{ + p->cancel_cb(p, p->cancel_opaque); +} + +int usb_device_add_dev(USBDevice *dev); +int usb_device_del_addr(int bus_num, int addr); +void usb_attach(USBPort *port, USBDevice *dev); +int usb_generic_handle_packet(USBDevice *s, USBPacket *p); +int set_usb_string(uint8_t *buf, const char *str); +void usb_send_msg(USBDevice *dev, int msg); + +/* usb hub */ +USBDevice *usb_hub_init(int nb_ports); + +/* usb-linux.c */ +USBDevice *usb_host_device_open(const char *devname); +int usb_host_device_close(const char *devname); +void usb_host_info(void); + +/* usb-hid.c */ +USBDevice *usb_mouse_init(void); +USBDevice *usb_tablet_init(void); +USBDevice *usb_keyboard_init(void); + +/* usb-msd.c */ +USBDevice *usb_msd_init(const char *filename); + +/* usb-net.c */ +USBDevice *usb_net_init(NICInfo *nd); + +/* usb-wacom.c */ +USBDevice *usb_wacom_init(void); + +/* usb-serial.c */ +USBDevice *usb_serial_init(const char *filename); + +/* usb ports of the VM */ + +void qemu_register_usb_port(USBPort *port, void *opaque, int index, + usb_attachfn attach); + +#define VM_USB_HUB_SIZE 8 + +/* usb-musb.c */ +enum musb_irq_source_e { + musb_irq_suspend = 0, + musb_irq_resume, + musb_irq_rst_babble, + musb_irq_sof, + musb_irq_connect, + musb_irq_disconnect, + musb_irq_vbus_request, + musb_irq_vbus_error, + musb_irq_rx, + musb_irq_tx, + musb_set_vbus, + musb_set_session, + __musb_irq_max, +}; + +struct musb_s; +struct musb_s *musb_init(qemu_irq *irqs); +uint32_t musb_core_intr_get(struct musb_s *s); +void musb_core_intr_clear(struct musb_s *s, uint32_t mask); +void musb_set_size(struct musb_s *s, int epnum, int size, int is_tx); |