diff options
Diffstat (limited to 'hw')
-rw-r--r-- | hw/android_arm.c | 23 | ||||
-rw-r--r-- | hw/arm-misc.h | 10 | ||||
-rw-r--r-- | hw/arm_boot.c | 169 | ||||
-rw-r--r-- | hw/arm_gic.c | 61 | ||||
-rw-r--r-- | hw/arm_pic.c | 7 | ||||
-rw-r--r-- | hw/armv7m.c | 85 | ||||
-rw-r--r-- | hw/armv7m_nvic.c | 125 | ||||
-rw-r--r-- | hw/audiodev.h | 12 | ||||
-rw-r--r-- | hw/boards.h | 104 | ||||
-rw-r--r-- | hw/bt-hci-csr.c | 455 | ||||
-rw-r--r-- | hw/bt-hci.c | 2228 | ||||
-rw-r--r-- | hw/bt-hid.c | 571 | ||||
-rw-r--r-- | hw/bt-l2cap.c | 1364 | ||||
-rw-r--r-- | hw/bt-sdp.c | 968 | ||||
-rw-r--r-- | hw/bt.c | 122 | ||||
-rw-r--r-- | hw/bt.h | 2185 | ||||
-rw-r--r-- | hw/cdrom.c | 2 | ||||
-rw-r--r-- | hw/devices.h | 64 | ||||
-rw-r--r-- | hw/dma.c | 42 | ||||
-rw-r--r-- | hw/goldfish_audio.c | 268 | ||||
-rw-r--r-- | hw/goldfish_battery.c | 3 | ||||
-rw-r--r-- | hw/goldfish_device.c | 7 | ||||
-rw-r--r-- | hw/goldfish_events_device.c | 8 | ||||
-rw-r--r-- | hw/goldfish_fb.c | 6 | ||||
-rw-r--r-- | hw/goldfish_interrupt.c | 2 | ||||
-rw-r--r-- | hw/goldfish_memlog.c | 2 | ||||
-rw-r--r-- | hw/goldfish_mmc.c | 76 | ||||
-rw-r--r-- | hw/goldfish_nand.c | 14 | ||||
-rw-r--r-- | hw/goldfish_switch.c | 4 | ||||
-rw-r--r-- | hw/goldfish_timer.c | 4 | ||||
-rw-r--r-- | hw/goldfish_trace.c | 4 | ||||
-rw-r--r-- | hw/goldfish_trace.h | 3 | ||||
-rw-r--r-- | hw/goldfish_tty.c | 21 | ||||
-rw-r--r-- | hw/hw.h | 175 | ||||
-rw-r--r-- | hw/irq.c | 6 | ||||
-rw-r--r-- | hw/irq.h | 1 | ||||
-rw-r--r-- | hw/isa.h | 1 | ||||
-rw-r--r-- | hw/msmouse.c | 78 | ||||
-rw-r--r-- | hw/msmouse.h | 2 | ||||
-rw-r--r-- | hw/pc.h | 66 | ||||
-rw-r--r-- | hw/pci.c | 386 | ||||
-rw-r--r-- | hw/pci.h | 179 | ||||
-rw-r--r-- | hw/pci_host.h | 29 | ||||
-rw-r--r-- | hw/pci_ids.h | 97 | ||||
-rw-r--r-- | hw/pcmcia.h | 22 | ||||
-rw-r--r-- | hw/pxa.h | 93 | ||||
-rw-r--r-- | hw/qdev.c | 409 | ||||
-rw-r--r-- | hw/qdev.h | 129 | ||||
-rw-r--r-- | hw/scsi-disk.c | 367 | ||||
-rw-r--r-- | hw/smbios.h | 162 | ||||
-rw-r--r-- | hw/smc91c111.c | 86 | ||||
-rw-r--r-- | hw/sysbus.c | 165 | ||||
-rw-r--r-- | hw/sysbus.h | 62 | ||||
-rw-r--r-- | hw/usb-hid.c | 52 | ||||
-rw-r--r-- | hw/usb-hub.c | 2 | ||||
-rw-r--r-- | hw/usb-msd.c | 18 | ||||
-rw-r--r-- | hw/usb-ohci.c | 362 | ||||
-rw-r--r-- | hw/usb.h | 25 | ||||
-rw-r--r-- | hw/watchdog.c | 138 | ||||
-rw-r--r-- | hw/watchdog.h | 65 | ||||
-rw-r--r-- | hw/xen.h | 21 |
61 files changed, 11189 insertions, 1028 deletions
diff --git a/hw/android_arm.c b/hw/android_arm.c index b178dad..32f6925 100644 --- a/hw/android_arm.c +++ b/hw/android_arm.c @@ -19,6 +19,7 @@ #include "android/globals.h" #include "audio/audio.h" #include "arm-misc.h" +#include "console.h" #define ARM_CPU_SAVE_VERSION 1 @@ -49,9 +50,8 @@ uint32_t switch_test_write(void *opaque, uint32_t state) return state; } #endif - -static void android_arm_init(ram_addr_t ram_size, int vga_ram_size, - const char *boot_device, DisplayState *ds, +static void android_arm_init_(ram_addr_t ram_size, + const char *boot_device, const char *kernel_filename, const char *kernel_cmdline, const char *initrd_filename, @@ -62,15 +62,17 @@ static void android_arm_init(ram_addr_t ram_size, int vga_ram_size, qemu_irq *goldfish_pic; int i; struct arm_boot_info info; + ram_addr_t ram_offset; + DisplayState* ds = get_displaystate(); 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); + ram_offset = qemu_ram_alloc(ram_size); + cpu_register_physical_memory(0, ram_size, ram_offset | 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]); @@ -109,7 +111,6 @@ static void android_arm_init(ram_addr_t ram_size, int vga_ram_size, goldfish_fb_init(ds, 0); #ifdef HAS_AUDIO - AUD_init(); goldfish_audio_init(0xff004000, 0, audio_input_source); #endif { @@ -160,8 +161,16 @@ static void android_arm_init(ram_addr_t ram_size, int vga_ram_size, QEMUMachine android_arm_machine = { "android_arm", "ARM Android Emulator", - android_arm_init, + android_arm_init_, 0, 0, + 1, NULL }; + +static void android_arm_init(void) +{ + qemu_register_machine(&android_arm_machine); +} + +machine_init(android_arm_init); diff --git a/hw/arm-misc.h b/hw/arm-misc.h index 707e699..7523d44 100644 --- a/hw/arm-misc.h +++ b/hw/arm-misc.h @@ -29,21 +29,15 @@ struct arm_boot_info { const char *kernel_cmdline; const char *initrd_filename; target_phys_addr_t loader_start; + target_phys_addr_t smp_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); +extern int system_clock_scale; #endif /* !ARM_MISC_H */ - diff --git a/hw/arm_boot.c b/hw/arm_boot.c index 5990961..acfa67e 100644 --- a/hw/arm_boot.c +++ b/hw/arm_boot.c @@ -53,124 +53,135 @@ static void main_cpu_reset(void *opaque) /* TODO: Reset secondary CPUs. */ } +#define WRITE_WORD(p, value) do { \ + stl_phys_notdirty(p, value); \ + p += 4; \ +} while (0) + static void set_kernel_args(struct arm_boot_info *info, - int initrd_size, void *base) + int initrd_size, target_phys_addr_t base) { - uint32_t *p; + target_phys_addr_t p; - p = (uint32_t *)(base + KERNEL_ARGS_ADDR); + p = 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); + WRITE_WORD(p, 5); + WRITE_WORD(p, 0x54410001); + WRITE_WORD(p, 1); + WRITE_WORD(p, 0x1000); + WRITE_WORD(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); + WRITE_WORD(p, 4); + WRITE_WORD(p, 0x54410002); + WRITE_WORD(p, info->ram_size); + WRITE_WORD(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); + WRITE_WORD(p, 4); + WRITE_WORD(p, 0x54420005); + WRITE_WORD(p, info->loader_start + INITRD_LOAD_ADDR); + WRITE_WORD(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); + cpu_physical_memory_write(p + 8, (void *)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; + WRITE_WORD(p, cmdline_size + 2); + WRITE_WORD(p, 0x54410009); + p += cmdline_size * 4; } if (info->atag_board) { /* ATAG_BOARD */ int atag_board_len; + uint8_t atag_board_buf[0x1000]; - atag_board_len = (info->atag_board(info, p + 2) + 3) >> 2; - stl_raw(p++, 2 + atag_board_len); - stl_raw(p++, 0x414f4d50); + atag_board_len = (info->atag_board(info, atag_board_buf) + 3) & ~3; + WRITE_WORD(p, (atag_board_len + 8) >> 2); + WRITE_WORD(p, 0x414f4d50); + cpu_physical_memory_write(p, atag_board_buf, atag_board_len); p += atag_board_len; } /* ATAG_END */ - stl_raw(p++, 0); - stl_raw(p++, 0); + WRITE_WORD(p, 0); + WRITE_WORD(p, 0); } static void set_kernel_args_old(struct arm_boot_info *info, - int initrd_size, void *base) + int initrd_size, target_phys_addr_t base) { - uint32_t *p; - unsigned char *s; + target_phys_addr_t p; + const char *s; + /* see linux/include/asm-arm/setup.h */ - p = (uint32_t *)(base + KERNEL_ARGS_ADDR); + p = base + KERNEL_ARGS_ADDR; /* page_size */ - stl_raw(p++, 4096); + WRITE_WORD(p, 4096); /* nr_pages */ - stl_raw(p++, info->ram_size / 4096); + WRITE_WORD(p, info->ram_size / 4096); /* ramdisk_size */ - stl_raw(p++, 0); + WRITE_WORD(p, 0); #define FLAG_READONLY 1 #define FLAG_RDLOAD 4 #define FLAG_RDPROMPT 8 /* flags */ - stl_raw(p++, FLAG_READONLY | FLAG_RDLOAD | FLAG_RDPROMPT); + WRITE_WORD(p, FLAG_READONLY | FLAG_RDLOAD | FLAG_RDPROMPT); /* rootdev */ - stl_raw(p++, (31 << 8) | 0); /* /dev/mtdblock0 */ + WRITE_WORD(p, (31 << 8) | 0); /* /dev/mtdblock0 */ /* video_num_cols */ - stl_raw(p++, 0); + WRITE_WORD(p, 0); /* video_num_rows */ - stl_raw(p++, 0); + WRITE_WORD(p, 0); /* video_x */ - stl_raw(p++, 0); + WRITE_WORD(p, 0); /* video_y */ - stl_raw(p++, 0); + WRITE_WORD(p, 0); /* memc_control_reg */ - stl_raw(p++, 0); + WRITE_WORD(p, 0); /* unsigned char sounddefault */ /* unsigned char adfsdrives */ /* unsigned char bytes_per_char_h */ /* unsigned char bytes_per_char_v */ - stl_raw(p++, 0); + WRITE_WORD(p, 0); /* pages_in_bank[4] */ - stl_raw(p++, 0); - stl_raw(p++, 0); - stl_raw(p++, 0); - stl_raw(p++, 0); + WRITE_WORD(p, 0); + WRITE_WORD(p, 0); + WRITE_WORD(p, 0); + WRITE_WORD(p, 0); /* pages_in_vram */ - stl_raw(p++, 0); + WRITE_WORD(p, 0); /* initrd_start */ if (initrd_size) - stl_raw(p++, info->loader_start + INITRD_LOAD_ADDR); + WRITE_WORD(p, info->loader_start + INITRD_LOAD_ADDR); else - stl_raw(p++, 0); + WRITE_WORD(p, 0); /* initrd_size */ - stl_raw(p++, initrd_size); + WRITE_WORD(p, initrd_size); /* rd_start */ - stl_raw(p++, 0); + WRITE_WORD(p, 0); /* system_rev */ - stl_raw(p++, 0); + WRITE_WORD(p, 0); /* system_serial_low */ - stl_raw(p++, 0); + WRITE_WORD(p, 0); /* system_serial_high */ - stl_raw(p++, 0); + WRITE_WORD(p, 0); /* mem_fclk_21285 */ - stl_raw(p++, 0); + WRITE_WORD(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); + while (p < base + KERNEL_ARGS_ADDR + 256 + 1024) { + WRITE_WORD(p, 0); + } + s = info->kernel_cmdline; + if (s) { + cpu_physical_memory_write(p, (void *)s, strlen(s) + 1); + } else { + WRITE_WORD(p, 0); + } } void arm_load_kernel(CPUState *env, struct arm_boot_info *info) @@ -181,8 +192,6 @@ void arm_load_kernel(CPUState *env, struct arm_boot_info *info) int is_linux = 0; uint64_t elf_entry; target_ulong entry; - uint32_t pd; - void *loader_phys; /* Load the kernel. */ if (!info->kernel_filename) { @@ -194,23 +203,20 @@ void arm_load_kernel(CPUState *env, struct arm_boot_info *info) if (info->nb_cpus == 0) info->nb_cpus = 1; env->boot_info = info; - qemu_register_reset(main_cpu_reset, env); + qemu_register_reset(main_cpu_reset, 0, 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); + kernel_size = load_uimage(info->kernel_filename, &entry, NULL, + &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; + kernel_size = load_image_targphys(info->kernel_filename, entry, + ram_size - KERNEL_LOAD_ADDR); is_linux = 1; } if (kernel_size < 0) { @@ -224,8 +230,10 @@ void arm_load_kernel(CPUState *env, struct arm_boot_info *info) env->thumb = entry & 1; } else { if (info->initrd_filename) { - initrd_size = load_image(info->initrd_filename, - loader_phys + INITRD_LOAD_ADDR); + initrd_size = load_image_targphys(info->initrd_filename, + info->loader_start + + INITRD_LOAD_ADDR, + ram_size - INITRD_LOAD_ADDR); if (initrd_size < 0) { fprintf(stderr, "qemu: could not load initrd '%s'\n", info->initrd_filename); @@ -238,14 +246,17 @@ void arm_load_kernel(CPUState *env, struct arm_boot_info *info) 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]); + for (n = 0; n < sizeof(bootloader) / 4; n++) { + stl_phys_notdirty(info->loader_start + (n * 4), bootloader[n]); + } + if (info->nb_cpus > 1) { + for (n = 0; n < sizeof(smpboot) / 4; n++) { + stl_phys_notdirty(info->smp_loader_start + (n * 4), smpboot[n]); + } + } if (old_param) - set_kernel_args_old(info, initrd_size, loader_phys); + set_kernel_args_old(info, initrd_size, info->loader_start); else - set_kernel_args(info, initrd_size, loader_phys); + set_kernel_args(info, initrd_size, info->loader_start); } } diff --git a/hw/arm_gic.c b/hw/arm_gic.c index 54e99f4..563397d 100644 --- a/hw/arm_gic.c +++ b/hw/arm_gic.c @@ -14,26 +14,27 @@ //#define DEBUG_GIC #ifdef DEBUG_GIC -#define DPRINTF(fmt, args...) \ -do { printf("arm_gic: " fmt , ##args); } while (0) +#define DPRINTF(fmt, ...) \ +do { printf("arm_gic: " fmt , ## __VA_ARGS__); } while (0) #else -#define DPRINTF(fmt, args...) do {} while(0) +#define DPRINTF(fmt, ...) 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 +#define FROM_SYSBUSGIC(type, dev) \ + DO_UPCAST(type, gic, FROM_SYSBUS(gic_state, dev)) + typedef struct gic_irq_state { /* ??? The documentation seems to imply the enable bits are global, even @@ -41,7 +42,7 @@ typedef struct gic_irq_state unsigned enabled:1; unsigned pending:NCPU; unsigned active:NCPU; - unsigned level:1; + unsigned level:NCPU; unsigned model:1; /* 0 = N:N, 1 = 1:N */ unsigned trigger:1; /* nonzero = edge triggered. */ } gic_irq_state; @@ -76,7 +77,7 @@ typedef struct gic_irq_state typedef struct gic_state { - uint32_t base; + SysBusDevice busdev; qemu_irq parent_irq[NCPU]; int enabled; int cpu_enabled[NCPU]; @@ -94,10 +95,7 @@ typedef struct gic_state int running_priority[NCPU]; int current_pending[NCPU]; - qemu_irq *in; -#ifdef NVIC - void *nvic; -#endif + int iomemtype; } gic_state; /* TODO: Many places that call this routine could be optimized. */ @@ -252,7 +250,6 @@ static uint32_t gic_dist_readb(void *opaque, target_phys_addr_t offset) cpu = gic_get_current_cpu(); cm = 1 << cpu; - offset -= s->base + GIC_DIST_OFFSET; if (offset < 0x100) { #ifndef NVIC if (offset == 0) @@ -347,7 +344,7 @@ static uint32_t gic_dist_readb(void *opaque, target_phys_addr_t offset) } return res; bad_reg: - cpu_abort(cpu_single_env, "gic_dist_readb: Bad offset %x\n", (int)offset); + hw_error("gic_dist_readb: Bad offset %x\n", (int)offset); return 0; } @@ -365,9 +362,9 @@ static uint32_t gic_dist_readl(void *opaque, target_phys_addr_t offset) #ifdef NVIC gic_state *s = (gic_state *)opaque; uint32_t addr; - addr = offset - s->base; + addr = offset; if (addr < 0x100 || addr > 0xd00) - return nvic_readl(s->nvic, addr); + return nvic_readl(s, addr); #endif val = gic_dist_readw(opaque, offset); val |= gic_dist_readw(opaque, offset + 2) << 16; @@ -383,7 +380,6 @@ static void gic_dist_writeb(void *opaque, target_phys_addr_t offset, int cpu; cpu = gic_get_current_cpu(); - offset -= s->base + GIC_DIST_OFFSET; if (offset < 0x100) { #ifdef NVIC goto bad_reg; @@ -510,7 +506,7 @@ static void gic_dist_writeb(void *opaque, target_phys_addr_t offset, gic_update(s); return; bad_reg: - cpu_abort(cpu_single_env, "gic_dist_writeb: Bad offset %x\n", (int)offset); + hw_error("gic_dist_writeb: Bad offset %x\n", (int)offset); } static void gic_dist_writew(void *opaque, target_phys_addr_t offset, @@ -526,13 +522,13 @@ static void gic_dist_writel(void *opaque, target_phys_addr_t offset, gic_state *s = (gic_state *)opaque; #ifdef NVIC uint32_t addr; - addr = offset - s->base; + addr = offset; if (addr < 0x100 || (addr > 0xd00 && addr != 0xf00)) { - nvic_writel(s->nvic, addr, value); + nvic_writel(s, addr, value); return; } #endif - if (offset - s->base == GIC_DIST_OFFSET + 0xf00) { + if (offset == 0xf00) { int cpu; int irq; int mask; @@ -592,8 +588,7 @@ static uint32_t gic_cpu_read(gic_state *s, int cpu, int offset) 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); + hw_error("gic_cpu_read: Bad offset %x\n", (int)offset); return 0; } } @@ -614,8 +609,7 @@ static void gic_cpu_write(gic_state *s, int cpu, int offset, uint32_t value) 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); + hw_error("gic_cpu_write: Bad offset %x\n", (int)offset); return; } gic_update(s); @@ -723,25 +717,16 @@ static int gic_load(QEMUFile *f, void *opaque, int version_id) return 0; } -static gic_state *gic_init(uint32_t base, qemu_irq *parent_irq) +static void gic_init(gic_state *s) { - 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); + qdev_init_gpio_in(&s->busdev.qdev, gic_set_irq, GIC_NIRQ - 32); for (i = 0; i < NCPU; i++) { - s->parent_irq[i] = parent_irq[i]; + sysbus_init_irq(&s->busdev, &s->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; + s->iomemtype = cpu_register_io_memory(gic_dist_readfn, + gic_dist_writefn, s); 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 index 1fe55b7..f44568c 100644 --- a/hw/arm_pic.c +++ b/hw/arm_pic.c @@ -8,14 +8,15 @@ */ #include "hw.h" +#include "pc.h" #include "arm-misc.h" /* Stub functions for hardware that doesn't exist. */ -void pic_info(void) +void pic_info(Monitor *mon) { } -void irq_info(void) +void irq_info(Monitor *mon) { } @@ -38,7 +39,7 @@ static void arm_pic_cpu_handler(void *opaque, int irq, int level) cpu_reset_interrupt(env, CPU_INTERRUPT_FIQ); break; default: - cpu_abort(env, "arm_pic_cpu_handler: Bad interrput line %d\n", irq); + hw_error("arm_pic_cpu_handler: Bad interrput line %d\n", irq); } } diff --git a/hw/armv7m.c b/hw/armv7m.c index b2bad3c..297a3e1 100644 --- a/hw/armv7m.c +++ b/hw/armv7m.c @@ -7,18 +7,18 @@ * This code is licenced under the GPL. */ -#include "hw.h" +#include "sysbus.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) +static inline uint32_t bitband_addr(void * opaque, uint32_t addr) { uint32_t res; - res = addr & 0xe0000000; + res = *(uint32_t *)opaque; res |= (addr & 0x1ffffff) >> 5; return res; @@ -27,7 +27,7 @@ static inline uint32_t bitband_addr(uint32_t addr) static uint32_t bitband_readb(void *opaque, target_phys_addr_t offset) { uint8_t v; - cpu_physical_memory_read(bitband_addr(offset), &v, 1); + cpu_physical_memory_read(bitband_addr(opaque, offset), &v, 1); return (v & (1 << ((offset >> 2) & 7))) != 0; } @@ -37,7 +37,7 @@ static void bitband_writeb(void *opaque, target_phys_addr_t offset, uint32_t addr; uint8_t mask; uint8_t v; - addr = bitband_addr(offset); + addr = bitband_addr(opaque, offset); mask = (1 << ((offset >> 2) & 7)); cpu_physical_memory_read(addr, &v, 1); if (value & 1) @@ -52,7 +52,7 @@ 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; + addr = bitband_addr(opaque, offset) & ~1; mask = (1 << ((offset >> 2) & 15)); mask = tswap16(mask); cpu_physical_memory_read(addr, (uint8_t *)&v, 2); @@ -65,7 +65,7 @@ static void bitband_writew(void *opaque, target_phys_addr_t offset, uint32_t addr; uint16_t mask; uint16_t v; - addr = bitband_addr(offset) & ~1; + addr = bitband_addr(opaque, offset) & ~1; mask = (1 << ((offset >> 2) & 15)); mask = tswap16(mask); cpu_physical_memory_read(addr, (uint8_t *)&v, 2); @@ -81,7 +81,7 @@ 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; + addr = bitband_addr(opaque, offset) & ~3; mask = (1 << ((offset >> 2) & 31)); mask = tswap32(mask); cpu_physical_memory_read(addr, (uint8_t *)&v, 4); @@ -94,7 +94,7 @@ static void bitband_writel(void *opaque, target_phys_addr_t offset, uint32_t addr; uint32_t mask; uint32_t v; - addr = bitband_addr(offset) & ~3; + addr = bitband_addr(opaque, offset) & ~3; mask = (1 << ((offset >> 2) & 31)); mask = tswap32(mask); cpu_physical_memory_read(addr, (uint8_t *)&v, 4); @@ -117,14 +117,35 @@ static CPUWriteMemoryFunc *bitband_writefn[] = { bitband_writel }; -static void armv7m_bitband_init(void) +typedef struct { + SysBusDevice busdev; + uint32_t base; +} BitBandState; + +static void bitband_init(SysBusDevice *dev) { + BitBandState *s = FROM_SYSBUS(BitBandState, dev); 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); + s->base = qdev_get_prop_int(&dev->qdev, "base", 0); + iomemtype = cpu_register_io_memory(bitband_readfn, bitband_writefn, + &s->base); + sysbus_init_mmio(dev, 0x02000000, iomemtype); +} + +static void armv7m_bitband_init(void) +{ + DeviceState *dev; + + dev = qdev_create(NULL, "ARM,bitband-memory"); + qdev_set_prop_int(dev, "base", 0x20000000); + qdev_init(dev); + sysbus_mmio_map(sysbus_from_qdev(dev), 0, 0x22000000); + + dev = qdev_create(NULL, "ARM,bitband-memory"); + qdev_set_prop_int(dev, "base", 0x40000000); + qdev_init(dev); + sysbus_mmio_map(sysbus_from_qdev(dev), 0, 0x42000000); } /* Board init. */ @@ -136,11 +157,15 @@ qemu_irq *armv7m_init(int flash_size, int sram_size, const char *kernel_filename, const char *cpu_model) { CPUState *env; - qemu_irq *pic; + DeviceState *nvic; + /* FIXME: make this local state. */ + static qemu_irq pic[64]; + qemu_irq *cpu_pic; uint32_t pc; int image_size; uint64_t entry; uint64_t lowaddr; + int i; flash_size *= 1024; sram_size *= 1024; @@ -166,16 +191,24 @@ qemu_irq *armv7m_init(int flash_size, int 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(0, flash_size, + qemu_ram_alloc(flash_size) | IO_MEM_ROM); cpu_register_physical_memory(0x20000000, sram_size, - flash_size + IO_MEM_RAM); + qemu_ram_alloc(sram_size) | IO_MEM_RAM); armv7m_bitband_init(); - pic = armv7m_nvic_init(env); + nvic = qdev_create(NULL, "armv7m_nvic"); + env->v7m.nvic = nvic; + qdev_init(nvic); + cpu_pic = arm_pic_init_cpu(env); + sysbus_connect_irq(sysbus_from_qdev(nvic), 0, cpu_pic[ARM_PIC_CPU_IRQ]); + for (i = 0; i < 64; i++) { + pic[i] = qdev_get_gpio_in(nvic, i); + } image_size = load_elf(kernel_filename, 0, &entry, &lowaddr, NULL); if (image_size < 0) { - image_size = load_image(kernel_filename, phys_ram_base); + image_size = load_image_targphys(kernel_filename, 0, flash_size); lowaddr = 0; } if (image_size < 0) { @@ -188,8 +221,8 @@ qemu_irq *armv7m_init(int flash_size, int sram_size, 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)); + env->regs[13] = ldl_phys(0); + pc = ldl_phys(4); } else { pc = entry; } @@ -199,8 +232,16 @@ qemu_irq *armv7m_init(int flash_size, int sram_size, /* 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); + cpu_register_physical_memory(0xfffff000, 0x1000, + qemu_ram_alloc(0x1000) | IO_MEM_RAM); return pic; } +static void armv7m_register_devices(void) +{ + sysbus_register_dev("ARM,bitband-memory", sizeof(BitBandState), + bitband_init); +} + +device_init(armv7m_register_devices) diff --git a/hw/armv7m_nvic.c b/hw/armv7m_nvic.c index c55c958..f789c78 100644 --- a/hw/armv7m_nvic.c +++ b/hw/armv7m_nvic.c @@ -10,7 +10,7 @@ * NVIC. Much of that is also implemented here. */ -#include "hw.h" +#include "sysbus.h" #include "qemu-timer.h" #include "arm-misc.h" @@ -33,13 +33,13 @@ static void nvic_writel(void *opaque, uint32_t offset, uint32_t value); #include "arm_gic.c" typedef struct { + gic_state gic; 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. */ @@ -50,6 +50,8 @@ typedef struct { #define SYSTICK_CLKSOURCE (1 << 2) #define SYSTICK_COUNTFLAG (1 << 16) +int system_clock_scale; + /* Conversion factor from qemu timer to SysTick frequencies. */ static inline int64_t systick_scale(nvic_state *s) { @@ -89,7 +91,7 @@ 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); + gic_set_pending_private(&s->gic, 0, irq); } /* Make pending IRQ active. */ @@ -98,9 +100,9 @@ int armv7m_nvic_acknowledge_irq(void *opaque) nvic_state *s = (nvic_state *)opaque; uint32_t irq; - irq = gic_acknowledge_irq(s->gic, 0); + irq = gic_acknowledge_irq(&s->gic, 0); if (irq == 1023) - cpu_abort(cpu_single_env, "Interrupt but no vector\n"); + hw_error("Interrupt but no vector\n"); if (irq >= 32) irq -= 16; return irq; @@ -111,7 +113,7 @@ 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); + gic_complete_irq(&s->gic, 0, irq); } static uint32_t nvic_readl(void *opaque, uint32_t offset) @@ -151,35 +153,35 @@ static uint32_t nvic_readl(void *opaque, uint32_t offset) return cpu_single_env->cp15.c0_cpuid; case 0xd04: /* Interrypt Control State. */ /* VECTACTIVE */ - val = s->gic->running_irq[0]; + 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) { + 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); + 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) { + if (s->gic.irq_state[irq].pending) { val |= (1 << 22); break; } } /* PENDSTSET */ - if (s->gic->irq_state[ARMV7M_EXCP_SYSTICK].pending) + if (s->gic.irq_state[ARMV7M_EXCP_SYSTICK].pending) val |= (1 << 26); /* PENDSVSET */ - if (s->gic->irq_state[ARMV7M_EXCP_PENDSV].pending) + if (s->gic.irq_state[ARMV7M_EXCP_PENDSV].pending) val |= (1 << 28); /* NMIPENDSET */ - if (s->gic->irq_state[ARMV7M_EXCP_NMI].pending) + if (s->gic.irq_state[ARMV7M_EXCP_NMI].pending) val |= (1 << 31); return val; case 0xd08: /* Vector Table Offset. */ @@ -195,32 +197,31 @@ static uint32_t nvic_readl(void *opaque, uint32_t offset) 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; + 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); + 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."); + hw_error("Not implemented: Configurable Fault Status."); return 0; case 0xd2c: /* Hard Fault Status. */ case 0xd30: /* Debug Fault Status. */ @@ -258,7 +259,7 @@ static uint32_t nvic_readl(void *opaque, uint32_t offset) /* TODO: Implement debug registers. */ default: bad_reg: - cpu_abort(cpu_single_env, "NVIC: Bad read offset 0x%x\n", offset); + hw_error("NVIC: Bad read offset 0x%x\n", offset); } } @@ -306,14 +307,14 @@ static void nvic_writel(void *opaque, uint32_t offset, uint32_t value) 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); + 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); + s->gic.irq_state[ARMV7M_EXCP_SYSTICK].pending = 0; + gic_update(&s->gic); } break; case 0xd08: /* Vector Table Offset. */ @@ -322,10 +323,10 @@ static void nvic_writel(void *opaque, uint32_t offset, uint32_t value) case 0xd0c: /* Application Interrupt/Reset Control. */ if ((value >> 16) == 0x05fa) { if (value & 2) { - cpu_abort(cpu_single_env, "VECTCLRACTIVE not implemented"); + hw_error("VECTCLRACTIVE not implemented"); } if (value & 5) { - cpu_abort(cpu_single_env, "System reset"); + hw_error("System reset"); } } break; @@ -337,19 +338,19 @@ static void nvic_writel(void *opaque, uint32_t offset, uint32_t value) { 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); + 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; + 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. */ @@ -360,7 +361,7 @@ static void nvic_writel(void *opaque, uint32_t offset, uint32_t value) goto bad_reg; default: bad_reg: - cpu_abort(cpu_single_env, "NVIC: Bad write offset 0x%x\n", offset); + hw_error("NVIC: Bad write offset 0x%x\n", offset); } } @@ -389,19 +390,19 @@ static int nvic_load(QEMUFile *f, void *opaque, int version_id) return 0; } -qemu_irq *armv7m_nvic_init(CPUState *env) +static void armv7m_nvic_init(SysBusDevice *dev) { - nvic_state *s; - qemu_irq *parent; + nvic_state *s= FROM_SYSBUSGIC(nvic_state, dev); - 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; + gic_init(&s->gic); + cpu_register_physical_memory(0xe000e000, 0x1000, s->gic.iomemtype); 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; } + +static void armv7m_nvic_register_devices(void) +{ + sysbus_register_dev("armv7m_nvic", sizeof(nvic_state), armv7m_nvic_init); +} + +device_init(armv7m_nvic_register_devices) diff --git a/hw/audiodev.h b/hw/audiodev.h index 5f4a211..39a729b 100644 --- a/hw/audiodev.h +++ b/hw/audiodev.h @@ -1,17 +1,17 @@ /* es1370.c */ -int es1370_init (PCIBus *bus, AudioState *s); +int es1370_init(PCIBus *bus); /* sb16.c */ -int SB16_init (AudioState *s, qemu_irq *pic); +int SB16_init(qemu_irq *pic); /* adlib.c */ -int Adlib_init (AudioState *s, qemu_irq *pic); +int Adlib_init(qemu_irq *pic); /* gus.c */ -int GUS_init (AudioState *s, qemu_irq *pic); +int GUS_init(qemu_irq *pic); /* ac97.c */ -int ac97_init (PCIBus *buf, AudioState *s); +int ac97_init(PCIBus *buf); /* cs4231a.c */ -int cs4231a_init (AudioState *s, qemu_irq *pic); +int cs4231a_init(qemu_irq *pic); diff --git a/hw/boards.h b/hw/boards.h index cfb7c42..4a71e56 100644 --- a/hw/boards.h +++ b/hw/boards.h @@ -3,8 +3,8 @@ #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, +typedef void QEMUMachineInitFunc(ram_addr_t ram_size, + const char *boot_device, const char *kernel_filename, const char *kernel_cmdline, const char *initrd_filename, @@ -14,107 +14,15 @@ typedef struct QEMUMachine { const char *name; const char *desc; QEMUMachineInitFunc *init; -#define RAMSIZE_FIXED (1 << 0) - ram_addr_t ram_require; - int nodisk_ok; + int use_scsi; + int max_cpus; + int is_default; 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; +extern QEMUMachine *current_machine; /* android_arm.c */ extern QEMUMachine android_arm_machine; diff --git a/hw/bt-hci-csr.c b/hw/bt-hci-csr.c new file mode 100644 index 0000000..183441b --- /dev/null +++ b/hw/bt-hci-csr.c @@ -0,0 +1,455 @@ +/* + * Bluetooth serial HCI transport. + * CSR41814 HCI with H4p vendor extensions. + * + * Copyright (C) 2008 Andrzej Zaborowski <balrog@zabor.org> + * + * 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 or + * (at your option) version 3 of the License. + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "qemu-common.h" +#include "qemu-char.h" +#include "qemu-timer.h" +#include "irq.h" +#include "sysemu.h" +#include "net.h" +#include "bt.h" + +struct csrhci_s { + int enable; + qemu_irq *pins; + int pin_state; + int modem_state; + CharDriverState chr; +#define FIFO_LEN 4096 + int out_start; + int out_len; + int out_size; + uint8_t outfifo[FIFO_LEN * 2]; + uint8_t inpkt[FIFO_LEN]; + int in_len; + int in_hdr; + int in_data; + QEMUTimer *out_tm; + int64_t baud_delay; + + bdaddr_t bd_addr; + struct HCIInfo *hci; +}; + +/* H4+ packet types */ +enum { + H4_CMD_PKT = 1, + H4_ACL_PKT = 2, + H4_SCO_PKT = 3, + H4_EVT_PKT = 4, + H4_NEG_PKT = 6, + H4_ALIVE_PKT = 7, +}; + +/* CSR41814 negotiation start magic packet */ +static const uint8_t csrhci_neg_packet[] = { + H4_NEG_PKT, 10, + 0x00, 0xa0, 0x01, 0x00, 0x00, + 0x4c, 0x00, 0x96, 0x00, 0x00, +}; + +/* CSR41814 vendor-specific command OCFs */ +enum { + OCF_CSR_SEND_FIRMWARE = 0x000, +}; + +static inline void csrhci_fifo_wake(struct csrhci_s *s) +{ + if (!s->enable || !s->out_len) + return; + + /* XXX: Should wait for s->modem_state & CHR_TIOCM_RTS? */ + if (s->chr.chr_can_read && s->chr.chr_can_read(s->chr.handler_opaque) && + s->chr.chr_read) { + s->chr.chr_read(s->chr.handler_opaque, + s->outfifo + s->out_start ++, 1); + s->out_len --; + if (s->out_start >= s->out_size) { + s->out_start = 0; + s->out_size = FIFO_LEN; + } + } + + if (s->out_len) + qemu_mod_timer(s->out_tm, qemu_get_clock(vm_clock) + s->baud_delay); +} + +#define csrhci_out_packetz(s, len) memset(csrhci_out_packet(s, len), 0, len) +static uint8_t *csrhci_out_packet(struct csrhci_s *s, int len) +{ + int off = s->out_start + s->out_len; + + /* TODO: do the padding here, i.e. align len */ + s->out_len += len; + + if (off < FIFO_LEN) { + if (off + len > FIFO_LEN && (s->out_size = off + len) > FIFO_LEN * 2) { + fprintf(stderr, "%s: can't alloc %i bytes\n", __FUNCTION__, len); + exit(-1); + } + return s->outfifo + off; + } + + if (s->out_len > s->out_size) { + fprintf(stderr, "%s: can't alloc %i bytes\n", __FUNCTION__, len); + exit(-1); + } + + return s->outfifo + off - s->out_size; +} + +static inline uint8_t *csrhci_out_packet_csr(struct csrhci_s *s, + int type, int len) +{ + uint8_t *ret = csrhci_out_packetz(s, len + 2); + + *ret ++ = type; + *ret ++ = len; + + return ret; +} + +static inline uint8_t *csrhci_out_packet_event(struct csrhci_s *s, + int evt, int len) +{ + uint8_t *ret = csrhci_out_packetz(s, + len + 1 + sizeof(struct hci_event_hdr)); + + *ret ++ = H4_EVT_PKT; + ((struct hci_event_hdr *) ret)->evt = evt; + ((struct hci_event_hdr *) ret)->plen = len; + + return ret + sizeof(struct hci_event_hdr); +} + +static void csrhci_in_packet_vendor(struct csrhci_s *s, int ocf, + uint8_t *data, int len) +{ + int offset; + uint8_t *rpkt; + + switch (ocf) { + case OCF_CSR_SEND_FIRMWARE: + /* Check if this is the bd_address packet */ + if (len >= 18 + 8 && data[12] == 0x01 && data[13] == 0x00) { + offset = 18; + s->bd_addr.b[0] = data[offset + 7]; /* Beyond cmd packet end(!?) */ + s->bd_addr.b[1] = data[offset + 6]; + s->bd_addr.b[2] = data[offset + 4]; + s->bd_addr.b[3] = data[offset + 0]; + s->bd_addr.b[4] = data[offset + 3]; + s->bd_addr.b[5] = data[offset + 2]; + + s->hci->bdaddr_set(s->hci, s->bd_addr.b); + fprintf(stderr, "%s: bd_address loaded from firmware: " + "%02x:%02x:%02x:%02x:%02x:%02x\n", __FUNCTION__, + s->bd_addr.b[0], s->bd_addr.b[1], s->bd_addr.b[2], + s->bd_addr.b[3], s->bd_addr.b[4], s->bd_addr.b[5]); + } + + rpkt = csrhci_out_packet_event(s, EVT_VENDOR, 11); + /* Status bytes: no error */ + rpkt[9] = 0x00; + rpkt[10] = 0x00; + break; + + default: + fprintf(stderr, "%s: got a bad CMD packet\n", __FUNCTION__); + return; + } + + csrhci_fifo_wake(s); +} + +static void csrhci_in_packet(struct csrhci_s *s, uint8_t *pkt) +{ + uint8_t *rpkt; + int opc; + + switch (*pkt ++) { + case H4_CMD_PKT: + opc = le16_to_cpu(((struct hci_command_hdr *) pkt)->opcode); + if (cmd_opcode_ogf(opc) == OGF_VENDOR_CMD) { + csrhci_in_packet_vendor(s, cmd_opcode_ocf(opc), + pkt + sizeof(struct hci_command_hdr), + s->in_len - sizeof(struct hci_command_hdr) - 1); + return; + } + + /* TODO: if the command is OCF_READ_LOCAL_COMMANDS or the likes, + * we need to send it to the HCI layer and then add our supported + * commands to the returned mask (such as OGF_VENDOR_CMD). With + * bt-hci.c we could just have hooks for this kind of commands but + * we can't with bt-host.c. */ + + s->hci->cmd_send(s->hci, pkt, s->in_len - 1); + break; + + case H4_EVT_PKT: + goto bad_pkt; + + case H4_ACL_PKT: + s->hci->acl_send(s->hci, pkt, s->in_len - 1); + break; + + case H4_SCO_PKT: + s->hci->sco_send(s->hci, pkt, s->in_len - 1); + break; + + case H4_NEG_PKT: + if (s->in_hdr != sizeof(csrhci_neg_packet) || + memcmp(pkt - 1, csrhci_neg_packet, s->in_hdr)) { + fprintf(stderr, "%s: got a bad NEG packet\n", __FUNCTION__); + return; + } + pkt += 2; + + rpkt = csrhci_out_packet_csr(s, H4_NEG_PKT, 10); + + *rpkt ++ = 0x20; /* Operational settings negotation Ok */ + memcpy(rpkt, pkt, 7); rpkt += 7; + *rpkt ++ = 0xff; + *rpkt ++ = 0xff; + break; + + case H4_ALIVE_PKT: + if (s->in_hdr != 4 || pkt[1] != 0x55 || pkt[2] != 0x00) { + fprintf(stderr, "%s: got a bad ALIVE packet\n", __FUNCTION__); + return; + } + + rpkt = csrhci_out_packet_csr(s, H4_ALIVE_PKT, 2); + + *rpkt ++ = 0xcc; + *rpkt ++ = 0x00; + break; + + default: + bad_pkt: + /* TODO: error out */ + fprintf(stderr, "%s: got a bad packet\n", __FUNCTION__); + break; + } + + csrhci_fifo_wake(s); +} + +static int csrhci_header_len(const uint8_t *pkt) +{ + switch (pkt[0]) { + case H4_CMD_PKT: + return HCI_COMMAND_HDR_SIZE; + case H4_EVT_PKT: + return HCI_EVENT_HDR_SIZE; + case H4_ACL_PKT: + return HCI_ACL_HDR_SIZE; + case H4_SCO_PKT: + return HCI_SCO_HDR_SIZE; + case H4_NEG_PKT: + return pkt[1] + 1; + case H4_ALIVE_PKT: + return 3; + } + + exit(-1); +} + +static int csrhci_data_len(const uint8_t *pkt) +{ + switch (*pkt ++) { + case H4_CMD_PKT: + /* It seems that vendor-specific command packets for H4+ are all + * one byte longer than indicated in the standard header. */ + if (le16_to_cpu(((struct hci_command_hdr *) pkt)->opcode) == 0xfc00) + return (((struct hci_command_hdr *) pkt)->plen + 1) & ~1; + + return ((struct hci_command_hdr *) pkt)->plen; + case H4_EVT_PKT: + return ((struct hci_event_hdr *) pkt)->plen; + case H4_ACL_PKT: + return le16_to_cpu(((struct hci_acl_hdr *) pkt)->dlen); + case H4_SCO_PKT: + return ((struct hci_sco_hdr *) pkt)->dlen; + case H4_NEG_PKT: + case H4_ALIVE_PKT: + return 0; + } + + exit(-1); +} + +static int csrhci_write(struct CharDriverState *chr, + const uint8_t *buf, int len) +{ + struct csrhci_s *s = (struct csrhci_s *) chr->opaque; + int plen = s->in_len; + + if (!s->enable) + return 0; + + s->in_len += len; + memcpy(s->inpkt + plen, buf, len); + + while (1) { + if (s->in_len >= 2 && plen < 2) + s->in_hdr = csrhci_header_len(s->inpkt) + 1; + + if (s->in_len >= s->in_hdr && plen < s->in_hdr) + s->in_data = csrhci_data_len(s->inpkt) + s->in_hdr; + + if (s->in_len >= s->in_data) { + csrhci_in_packet(s, s->inpkt); + + memmove(s->inpkt, s->inpkt + s->in_len, s->in_len - s->in_data); + s->in_len -= s->in_data; + s->in_hdr = INT_MAX; + s->in_data = INT_MAX; + plen = 0; + } else + break; + } + + return len; +} + +static void csrhci_out_hci_packet_event(void *opaque, + const uint8_t *data, int len) +{ + struct csrhci_s *s = (struct csrhci_s *) opaque; + uint8_t *pkt = csrhci_out_packet(s, (len + 2) & ~1); /* Align */ + + *pkt ++ = H4_EVT_PKT; + memcpy(pkt, data, len); + + csrhci_fifo_wake(s); +} + +static void csrhci_out_hci_packet_acl(void *opaque, + const uint8_t *data, int len) +{ + struct csrhci_s *s = (struct csrhci_s *) opaque; + uint8_t *pkt = csrhci_out_packet(s, (len + 2) & ~1); /* Align */ + + *pkt ++ = H4_ACL_PKT; + pkt[len & ~1] = 0; + memcpy(pkt, data, len); + + csrhci_fifo_wake(s); +} + +static int csrhci_ioctl(struct CharDriverState *chr, int cmd, void *arg) +{ + QEMUSerialSetParams *ssp; + struct csrhci_s *s = (struct csrhci_s *) chr->opaque; + int prev_state = s->modem_state; + + switch (cmd) { + case CHR_IOCTL_SERIAL_SET_PARAMS: + ssp = (QEMUSerialSetParams *) arg; + s->baud_delay = ticks_per_sec / ssp->speed; + /* Moments later... (but shorter than 100ms) */ + s->modem_state |= CHR_TIOCM_CTS; + break; + + case CHR_IOCTL_SERIAL_GET_TIOCM: + *(int *) arg = s->modem_state; + break; + + case CHR_IOCTL_SERIAL_SET_TIOCM: + s->modem_state = *(int *) arg; + if (~s->modem_state & prev_state & CHR_TIOCM_RTS) + s->modem_state &= ~CHR_TIOCM_CTS; + break; + + default: + return -ENOTSUP; + } + return 0; +} + +static void csrhci_reset(struct csrhci_s *s) +{ + s->out_len = 0; + s->out_size = FIFO_LEN; + s->in_len = 0; + s->baud_delay = ticks_per_sec; + s->enable = 0; + s->in_hdr = INT_MAX; + s->in_data = INT_MAX; + + s->modem_state = 0; + /* After a while... (but sooner than 10ms) */ + s->modem_state |= CHR_TIOCM_CTS; + + memset(&s->bd_addr, 0, sizeof(bdaddr_t)); +} + +static void csrhci_out_tick(void *opaque) +{ + csrhci_fifo_wake((struct csrhci_s *) opaque); +} + +static void csrhci_pins(void *opaque, int line, int level) +{ + struct csrhci_s *s = (struct csrhci_s *) opaque; + int state = s->pin_state; + + s->pin_state &= ~(1 << line); + s->pin_state |= (!!level) << line; + + if ((state & ~s->pin_state) & (1 << csrhci_pin_reset)) { + /* TODO: Disappear from lower layers */ + csrhci_reset(s); + } + + if (s->pin_state == 3 && state != 3) { + s->enable = 1; + /* TODO: Wake lower layers up */ + } +} + +qemu_irq *csrhci_pins_get(CharDriverState *chr) +{ + struct csrhci_s *s = (struct csrhci_s *) chr->opaque; + + return s->pins; +} + +CharDriverState *uart_hci_init(qemu_irq wakeup) +{ + struct csrhci_s *s = (struct csrhci_s *) + qemu_mallocz(sizeof(struct csrhci_s)); + + s->chr.opaque = s; + s->chr.chr_write = csrhci_write; + s->chr.chr_ioctl = csrhci_ioctl; + + s->hci = qemu_next_hci(); + s->hci->opaque = s; + s->hci->evt_recv = csrhci_out_hci_packet_event; + s->hci->acl_recv = csrhci_out_hci_packet_acl; + + s->out_tm = qemu_new_timer(vm_clock, csrhci_out_tick, s); + s->pins = qemu_allocate_irqs(csrhci_pins, s, __csrhci_pins); + csrhci_reset(s); + + return &s->chr; +} diff --git a/hw/bt-hci.c b/hw/bt-hci.c new file mode 100644 index 0000000..a5902b0 --- /dev/null +++ b/hw/bt-hci.c @@ -0,0 +1,2228 @@ +/* + * QEMU Bluetooth HCI logic. + * + * Copyright (C) 2007 OpenMoko, Inc. + * Copyright (C) 2008 Andrzej Zaborowski <balrog@zabor.org> + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "qemu-common.h" +#include "qemu-timer.h" +#include "usb.h" +#include "net.h" +#include "bt.h" + +struct bt_hci_s { + uint8_t *(*evt_packet)(void *opaque); + void (*evt_submit)(void *opaque, int len); + void *opaque; + uint8_t evt_buf[256]; + + uint8_t acl_buf[4096]; + int acl_len; + + uint16_t asb_handle; + uint16_t psb_handle; + + int last_cmd; /* Note: Always little-endian */ + + struct bt_device_s *conn_req_host; + + struct { + int inquire; + int periodic; + int responses_left; + int responses; + QEMUTimer *inquiry_done; + QEMUTimer *inquiry_next; + int inquiry_length; + int inquiry_period; + int inquiry_mode; + +#define HCI_HANDLE_OFFSET 0x20 +#define HCI_HANDLES_MAX 0x10 + struct bt_hci_master_link_s { + struct bt_link_s *link; + void (*lmp_acl_data)(struct bt_link_s *link, + const uint8_t *data, int start, int len); + QEMUTimer *acl_mode_timer; + } handle[HCI_HANDLES_MAX]; + uint32_t role_bmp; + int last_handle; + int connecting; + bdaddr_t awaiting_bdaddr[HCI_HANDLES_MAX]; + } lm; + + uint8_t event_mask[8]; + uint16_t voice_setting; /* Notw: Always little-endian */ + uint16_t conn_accept_tout; + QEMUTimer *conn_accept_timer; + + struct HCIInfo info; + struct bt_device_s device; +}; + +#define DEFAULT_RSSI_DBM 20 + +#define hci_from_info(ptr) container_of((ptr), struct bt_hci_s, info) +#define hci_from_device(ptr) container_of((ptr), struct bt_hci_s, device) + +struct bt_hci_link_s { + struct bt_link_s btlink; + uint16_t handle; /* Local */ +}; + +/* LMP layer emulation */ +#if 0 +static void bt_submit_lmp(struct bt_device_s *bt, int length, uint8_t *data) +{ + int resp, resplen, error, op, tr; + uint8_t respdata[17]; + + if (length < 1) + return; + + tr = *data & 1; + op = *(data ++) >> 1; + resp = LMP_ACCEPTED; + resplen = 2; + respdata[1] = op; + error = 0; + length --; + + if (op >= 0x7c) { /* Extended opcode */ + op |= *(data ++) << 8; + resp = LMP_ACCEPTED_EXT; + resplen = 4; + respdata[0] = op >> 8; + respdata[1] = op & 0xff; + length --; + } + + switch (op) { + case LMP_ACCEPTED: + /* data[0] Op code + */ + if (length < 1) { + error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE; + goto not_accepted; + } + resp = 0; + break; + + case LMP_ACCEPTED_EXT: + /* data[0] Escape op code + * data[1] Extended op code + */ + if (length < 2) { + error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE; + goto not_accepted; + } + resp = 0; + break; + + case LMP_NOT_ACCEPTED: + /* data[0] Op code + * data[1] Error code + */ + if (length < 2) { + error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE; + goto not_accepted; + } + resp = 0; + break; + + case LMP_NOT_ACCEPTED_EXT: + /* data[0] Op code + * data[1] Extended op code + * data[2] Error code + */ + if (length < 3) { + error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE; + goto not_accepted; + } + resp = 0; + break; + + case LMP_HOST_CONNECTION_REQ: + break; + + case LMP_SETUP_COMPLETE: + resp = LMP_SETUP_COMPLETE; + resplen = 1; + bt->setup = 1; + break; + + case LMP_DETACH: + /* data[0] Error code + */ + if (length < 1) { + error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE; + goto not_accepted; + } + bt->setup = 0; + resp = 0; + break; + + case LMP_SUPERVISION_TIMEOUT: + /* data[0,1] Supervision timeout + */ + if (length < 2) { + error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE; + goto not_accepted; + } + resp = 0; + break; + + case LMP_QUALITY_OF_SERVICE: + resp = 0; + /* Fall through */ + case LMP_QOS_REQ: + /* data[0,1] Poll interval + * data[2] N(BC) + */ + if (length < 3) { + error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE; + goto not_accepted; + } + break; + + case LMP_MAX_SLOT: + resp = 0; + /* Fall through */ + case LMP_MAX_SLOT_REQ: + /* data[0] Max slots + */ + if (length < 1) { + error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE; + goto not_accepted; + } + break; + + case LMP_AU_RAND: + case LMP_IN_RAND: + case LMP_COMB_KEY: + /* data[0-15] Random number + */ + if (length < 16) { + error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE; + goto not_accepted; + } + if (op == LMP_AU_RAND) { + if (bt->key_present) { + resp = LMP_SRES; + resplen = 5; + /* XXX: [Part H] Section 6.1 on page 801 */ + } else { + error = HCI_PIN_OR_KEY_MISSING; + goto not_accepted; + } + } else if (op == LMP_IN_RAND) { + error = HCI_PAIRING_NOT_ALLOWED; + goto not_accepted; + } else { + /* XXX: [Part H] Section 3.2 on page 779 */ + resp = LMP_UNIT_KEY; + resplen = 17; + memcpy(respdata + 1, bt->key, 16); + + error = HCI_UNIT_LINK_KEY_USED; + goto not_accepted; + } + break; + + case LMP_UNIT_KEY: + /* data[0-15] Key + */ + if (length < 16) { + error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE; + goto not_accepted; + } + memcpy(bt->key, data, 16); + bt->key_present = 1; + break; + + case LMP_SRES: + /* data[0-3] Authentication response + */ + if (length < 4) { + error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE; + goto not_accepted; + } + break; + + case LMP_CLKOFFSET_REQ: + resp = LMP_CLKOFFSET_RES; + resplen = 3; + respdata[1] = 0x33; + respdata[2] = 0x33; + break; + + case LMP_CLKOFFSET_RES: + /* data[0,1] Clock offset + * (Slave to master only) + */ + if (length < 2) { + error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE; + goto not_accepted; + } + break; + + case LMP_VERSION_REQ: + case LMP_VERSION_RES: + /* data[0] VersNr + * data[1,2] CompId + * data[3,4] SubVersNr + */ + if (length < 5) { + error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE; + goto not_accepted; + } + if (op == LMP_VERSION_REQ) { + resp = LMP_VERSION_RES; + resplen = 6; + respdata[1] = 0x20; + respdata[2] = 0xff; + respdata[3] = 0xff; + respdata[4] = 0xff; + respdata[5] = 0xff; + } else + resp = 0; + break; + + case LMP_FEATURES_REQ: + case LMP_FEATURES_RES: + /* data[0-7] Features + */ + if (length < 8) { + error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE; + goto not_accepted; + } + if (op == LMP_FEATURES_REQ) { + resp = LMP_FEATURES_RES; + resplen = 9; + respdata[1] = (bt->lmp_caps >> 0) & 0xff; + respdata[2] = (bt->lmp_caps >> 8) & 0xff; + respdata[3] = (bt->lmp_caps >> 16) & 0xff; + respdata[4] = (bt->lmp_caps >> 24) & 0xff; + respdata[5] = (bt->lmp_caps >> 32) & 0xff; + respdata[6] = (bt->lmp_caps >> 40) & 0xff; + respdata[7] = (bt->lmp_caps >> 48) & 0xff; + respdata[8] = (bt->lmp_caps >> 56) & 0xff; + } else + resp = 0; + break; + + case LMP_NAME_REQ: + /* data[0] Name offset + */ + if (length < 1) { + error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE; + goto not_accepted; + } + resp = LMP_NAME_RES; + resplen = 17; + respdata[1] = data[0]; + respdata[2] = strlen(bt->lmp_name); + memset(respdata + 3, 0x00, 14); + if (respdata[2] > respdata[1]) + memcpy(respdata + 3, bt->lmp_name + respdata[1], + respdata[2] - respdata[1]); + break; + + case LMP_NAME_RES: + /* data[0] Name offset + * data[1] Name length + * data[2-15] Name fragment + */ + if (length < 16) { + error = HCI_UNSUPPORTED_LMP_PARAMETER_VALUE; + goto not_accepted; + } + resp = 0; + break; + + default: + error = HCI_UNKNOWN_LMP_PDU; + /* Fall through */ + not_accepted: + if (op >> 8) { + resp = LMP_NOT_ACCEPTED_EXT; + resplen = 5; + respdata[0] = op >> 8; + respdata[1] = op & 0xff; + respdata[2] = error; + } else { + resp = LMP_NOT_ACCEPTED; + resplen = 3; + respdata[0] = op & 0xff; + respdata[1] = error; + } + } + + if (resp == 0) + return; + + if (resp >> 8) { + respdata[0] = resp >> 8; + respdata[1] = resp & 0xff; + } else + respdata[0] = resp & 0xff; + + respdata[0] <<= 1; + respdata[0] |= tr; +} + +static void bt_submit_raw_acl(struct bt_piconet_s *net, int length, uint8_t *data) +{ + struct bt_device_s *slave; + if (length < 1) + return; + + slave = 0; +#if 0 + slave = net->slave; +#endif + + switch (data[0] & 3) { + case LLID_ACLC: + bt_submit_lmp(slave, length - 1, data + 1); + break; + case LLID_ACLU_START: +#if 0 + bt_sumbit_l2cap(slave, length - 1, data + 1, (data[0] >> 2) & 1); + breka; +#endif + default: + case LLID_ACLU_CONT: + break; + } +} +#endif + +/* HCI layer emulation */ + +/* Note: we could ignore endiannes because unswapped handles will still + * be valid as connection identifiers for the guest - they don't have to + * be continuously allocated. We do it though, to preserve similar + * behaviour between hosts. Some things, like the BD_ADDR cannot be + * preserved though (for example if a real hci is used). */ +#ifdef WORDS_BIGENDIAN +# define HNDL(raw) bswap16(raw) +#else +# define HNDL(raw) (raw) +#endif + +static const uint8_t bt_event_reserved_mask[8] = { + 0xff, 0x9f, 0xfb, 0xff, 0x07, 0x18, 0x00, 0x00, +}; + +static inline uint8_t *bt_hci_event_start(struct bt_hci_s *hci, + int evt, int len) +{ + uint8_t *packet, mask; + int mask_byte; + + if (len > 255) { + fprintf(stderr, "%s: HCI event params too long (%ib)\n", + __FUNCTION__, len); + exit(-1); + } + + mask_byte = (evt - 1) >> 3; + mask = 1 << ((evt - 1) & 3); + if (mask & bt_event_reserved_mask[mask_byte] & ~hci->event_mask[mask_byte]) + return NULL; + + packet = hci->evt_packet(hci->opaque); + packet[0] = evt; + packet[1] = len; + + return &packet[2]; +} + +static inline void bt_hci_event(struct bt_hci_s *hci, int evt, + void *params, int len) +{ + uint8_t *packet = bt_hci_event_start(hci, evt, len); + + if (!packet) + return; + + if (len) + memcpy(packet, params, len); + + hci->evt_submit(hci->opaque, len + 2); +} + +static inline void bt_hci_event_status(struct bt_hci_s *hci, int status) +{ + evt_cmd_status params = { + .status = status, + .ncmd = 1, + .opcode = hci->last_cmd, + }; + + bt_hci_event(hci, EVT_CMD_STATUS, ¶ms, EVT_CMD_STATUS_SIZE); +} + +static inline void bt_hci_event_complete(struct bt_hci_s *hci, + void *ret, int len) +{ + uint8_t *packet = bt_hci_event_start(hci, EVT_CMD_COMPLETE, + len + EVT_CMD_COMPLETE_SIZE); + evt_cmd_complete *params = (evt_cmd_complete *) packet; + + if (!packet) + return; + + params->ncmd = 1; + params->opcode = hci->last_cmd; + if (len) + memcpy(&packet[EVT_CMD_COMPLETE_SIZE], ret, len); + + hci->evt_submit(hci->opaque, len + EVT_CMD_COMPLETE_SIZE + 2); +} + +static void bt_hci_inquiry_done(void *opaque) +{ + struct bt_hci_s *hci = (struct bt_hci_s *) opaque; + uint8_t status = HCI_SUCCESS; + + if (!hci->lm.periodic) + hci->lm.inquire = 0; + + /* The specification is inconsistent about this one. Page 565 reads + * "The event parameters of Inquiry Complete event will have a summary + * of the result from the Inquiry process, which reports the number of + * nearby Bluetooth devices that responded [so hci->responses].", but + * Event Parameters (see page 729) has only Status. */ + bt_hci_event(hci, EVT_INQUIRY_COMPLETE, &status, 1); +} + +static void bt_hci_inquiry_result_standard(struct bt_hci_s *hci, + struct bt_device_s *slave) +{ + inquiry_info params = { + .num_responses = 1, + .bdaddr = BAINIT(&slave->bd_addr), + .pscan_rep_mode = 0x00, /* R0 */ + .pscan_period_mode = 0x00, /* P0 - deprecated */ + .pscan_mode = 0x00, /* Standard scan - deprecated */ + .dev_class[0] = slave->class[0], + .dev_class[1] = slave->class[1], + .dev_class[2] = slave->class[2], + /* TODO: return the clkoff *differenece* */ + .clock_offset = slave->clkoff, /* Note: no swapping */ + }; + + bt_hci_event(hci, EVT_INQUIRY_RESULT, ¶ms, INQUIRY_INFO_SIZE); +} + +static void bt_hci_inquiry_result_with_rssi(struct bt_hci_s *hci, + struct bt_device_s *slave) +{ + inquiry_info_with_rssi params = { + .num_responses = 1, + .bdaddr = BAINIT(&slave->bd_addr), + .pscan_rep_mode = 0x00, /* R0 */ + .pscan_period_mode = 0x00, /* P0 - deprecated */ + .dev_class[0] = slave->class[0], + .dev_class[1] = slave->class[1], + .dev_class[2] = slave->class[2], + /* TODO: return the clkoff *differenece* */ + .clock_offset = slave->clkoff, /* Note: no swapping */ + .rssi = DEFAULT_RSSI_DBM, + }; + + bt_hci_event(hci, EVT_INQUIRY_RESULT_WITH_RSSI, + ¶ms, INQUIRY_INFO_WITH_RSSI_SIZE); +} + +static void bt_hci_inquiry_result(struct bt_hci_s *hci, + struct bt_device_s *slave) +{ + if (!slave->inquiry_scan || !hci->lm.responses_left) + return; + + hci->lm.responses_left --; + hci->lm.responses ++; + + switch (hci->lm.inquiry_mode) { + case 0x00: + bt_hci_inquiry_result_standard(hci, slave); + return; + case 0x01: + bt_hci_inquiry_result_with_rssi(hci, slave); + return; + default: + fprintf(stderr, "%s: bad inquiry mode %02x\n", __FUNCTION__, + hci->lm.inquiry_mode); + exit(-1); + } +} + +static void bt_hci_mod_timer_1280ms(QEMUTimer *timer, int period) +{ + qemu_mod_timer(timer, qemu_get_clock(vm_clock) + + muldiv64(period << 7, ticks_per_sec, 100)); +} + +static void bt_hci_inquiry_start(struct bt_hci_s *hci, int length) +{ + struct bt_device_s *slave; + + hci->lm.inquiry_length = length; + for (slave = hci->device.net->slave; slave; slave = slave->next) + /* Don't uncover ourselves. */ + if (slave != &hci->device) + bt_hci_inquiry_result(hci, slave); + + /* TODO: register for a callback on a new device's addition to the + * scatternet so that if it's added before inquiry_length expires, + * an Inquiry Result is generated immediately. Alternatively re-loop + * through the devices on the inquiry_length expiration and report + * devices not seen before. */ + if (hci->lm.responses_left) + bt_hci_mod_timer_1280ms(hci->lm.inquiry_done, hci->lm.inquiry_length); + else + bt_hci_inquiry_done(hci); + + if (hci->lm.periodic) + bt_hci_mod_timer_1280ms(hci->lm.inquiry_next, hci->lm.inquiry_period); +} + +static void bt_hci_inquiry_next(void *opaque) +{ + struct bt_hci_s *hci = (struct bt_hci_s *) opaque; + + hci->lm.responses_left += hci->lm.responses; + hci->lm.responses = 0; + bt_hci_inquiry_start(hci, hci->lm.inquiry_length); +} + +static inline int bt_hci_handle_bad(struct bt_hci_s *hci, uint16_t handle) +{ + return !(handle & HCI_HANDLE_OFFSET) || + handle >= (HCI_HANDLE_OFFSET | HCI_HANDLES_MAX) || + !hci->lm.handle[handle & ~HCI_HANDLE_OFFSET].link; +} + +static inline int bt_hci_role_master(struct bt_hci_s *hci, uint16_t handle) +{ + return !!(hci->lm.role_bmp & (1 << (handle & ~HCI_HANDLE_OFFSET))); +} + +static inline struct bt_device_s *bt_hci_remote_dev(struct bt_hci_s *hci, + uint16_t handle) +{ + struct bt_link_s *link = hci->lm.handle[handle & ~HCI_HANDLE_OFFSET].link; + + return bt_hci_role_master(hci, handle) ? link->slave : link->host; +} + +static void bt_hci_mode_tick(void *opaque); +static void bt_hci_lmp_link_establish(struct bt_hci_s *hci, + struct bt_link_s *link, int master) +{ + hci->lm.handle[hci->lm.last_handle].link = link; + + if (master) { + /* We are the master side of an ACL link */ + hci->lm.role_bmp |= 1 << hci->lm.last_handle; + + hci->lm.handle[hci->lm.last_handle].lmp_acl_data = + link->slave->lmp_acl_data; + } else { + /* We are the slave side of an ACL link */ + hci->lm.role_bmp &= ~(1 << hci->lm.last_handle); + + hci->lm.handle[hci->lm.last_handle].lmp_acl_data = + link->host->lmp_acl_resp; + } + + /* Mode */ + if (master) { + link->acl_mode = acl_active; + hci->lm.handle[hci->lm.last_handle].acl_mode_timer = + qemu_new_timer(vm_clock, bt_hci_mode_tick, link); + } +} + +static void bt_hci_lmp_link_teardown(struct bt_hci_s *hci, uint16_t handle) +{ + handle &= ~HCI_HANDLE_OFFSET; + hci->lm.handle[handle].link = NULL; + + if (bt_hci_role_master(hci, handle)) { + qemu_del_timer(hci->lm.handle[handle].acl_mode_timer); + qemu_free_timer(hci->lm.handle[handle].acl_mode_timer); + } +} + +static int bt_hci_connect(struct bt_hci_s *hci, bdaddr_t *bdaddr) +{ + struct bt_device_s *slave; + struct bt_link_s link; + + for (slave = hci->device.net->slave; slave; slave = slave->next) + if (slave->page_scan && !bacmp(&slave->bd_addr, bdaddr)) + break; + if (!slave || slave == &hci->device) + return -ENODEV; + + bacpy(&hci->lm.awaiting_bdaddr[hci->lm.connecting ++], &slave->bd_addr); + + link.slave = slave; + link.host = &hci->device; + link.slave->lmp_connection_request(&link); /* Always last */ + + return 0; +} + +static void bt_hci_connection_reject(struct bt_hci_s *hci, + struct bt_device_s *host, uint8_t because) +{ + struct bt_link_s link = { + .slave = &hci->device, + .host = host, + /* Rest uninitialised */ + }; + + host->reject_reason = because; + host->lmp_connection_complete(&link); +} + +static void bt_hci_connection_reject_event(struct bt_hci_s *hci, + bdaddr_t *bdaddr) +{ + evt_conn_complete params; + + params.status = HCI_NO_CONNECTION; + params.handle = 0; + bacpy(¶ms.bdaddr, bdaddr); + params.link_type = ACL_LINK; + params.encr_mode = 0x00; /* Encryption not required */ + bt_hci_event(hci, EVT_CONN_COMPLETE, ¶ms, EVT_CONN_COMPLETE_SIZE); +} + +static void bt_hci_connection_accept(struct bt_hci_s *hci, + struct bt_device_s *host) +{ + struct bt_hci_link_s *link = qemu_mallocz(sizeof(struct bt_hci_link_s)); + evt_conn_complete params; + uint16_t handle; + uint8_t status = HCI_SUCCESS; + int tries = HCI_HANDLES_MAX; + + /* Make a connection handle */ + do { + while (hci->lm.handle[++ hci->lm.last_handle].link && -- tries) + hci->lm.last_handle &= HCI_HANDLES_MAX - 1; + handle = hci->lm.last_handle | HCI_HANDLE_OFFSET; + } while ((handle == hci->asb_handle || handle == hci->psb_handle) && + tries); + + if (!tries) { + qemu_free(link); + bt_hci_connection_reject(hci, host, HCI_REJECTED_LIMITED_RESOURCES); + status = HCI_NO_CONNECTION; + goto complete; + } + + link->btlink.slave = &hci->device; + link->btlink.host = host; + link->handle = handle; + + /* Link established */ + bt_hci_lmp_link_establish(hci, &link->btlink, 0); + +complete: + params.status = status; + params.handle = HNDL(handle); + bacpy(¶ms.bdaddr, &host->bd_addr); + params.link_type = ACL_LINK; + params.encr_mode = 0x00; /* Encryption not required */ + bt_hci_event(hci, EVT_CONN_COMPLETE, ¶ms, EVT_CONN_COMPLETE_SIZE); + + /* Neets to be done at the very end because it can trigger a (nested) + * disconnected, in case the other and had cancelled the request + * locally. */ + if (status == HCI_SUCCESS) { + host->reject_reason = 0; + host->lmp_connection_complete(&link->btlink); + } +} + +static void bt_hci_lmp_connection_request(struct bt_link_s *link) +{ + struct bt_hci_s *hci = hci_from_device(link->slave); + evt_conn_request params; + + if (hci->conn_req_host) { + bt_hci_connection_reject(hci, link->host, + HCI_REJECTED_LIMITED_RESOURCES); + return; + } + hci->conn_req_host = link->host; + /* TODO: if masked and auto-accept, then auto-accept, + * if masked and not auto-accept, then auto-reject */ + /* TODO: kick the hci->conn_accept_timer, timeout after + * hci->conn_accept_tout * 0.625 msec */ + + bacpy(¶ms.bdaddr, &link->host->bd_addr); + memcpy(¶ms.dev_class, &link->host->class, sizeof(params.dev_class)); + params.link_type = ACL_LINK; + bt_hci_event(hci, EVT_CONN_REQUEST, ¶ms, EVT_CONN_REQUEST_SIZE); + return; +} + +static void bt_hci_conn_accept_timeout(void *opaque) +{ + struct bt_hci_s *hci = (struct bt_hci_s *) opaque; + + if (!hci->conn_req_host) + /* Already accepted or rejected. If the other end cancelled the + * connection request then we still have to reject or accept it + * and then we'll get a disconnect. */ + return; + + /* TODO */ +} + +/* Remove from the list of devices which we wanted to connect to and + * are awaiting a response from. If the callback sees a response from + * a device which is not on the list it will assume it's a connection + * that's been cancelled by the host in the meantime and immediately + * try to detach the link and send a Connection Complete. */ +static int bt_hci_lmp_connection_ready(struct bt_hci_s *hci, + bdaddr_t *bdaddr) +{ + int i; + + for (i = 0; i < hci->lm.connecting; i ++) + if (!bacmp(&hci->lm.awaiting_bdaddr[i], bdaddr)) { + if (i < -- hci->lm.connecting) + bacpy(&hci->lm.awaiting_bdaddr[i], + &hci->lm.awaiting_bdaddr[hci->lm.connecting]); + return 0; + } + + return 1; +} + +static void bt_hci_lmp_connection_complete(struct bt_link_s *link) +{ + struct bt_hci_s *hci = hci_from_device(link->host); + evt_conn_complete params; + uint16_t handle; + uint8_t status = HCI_SUCCESS; + int tries = HCI_HANDLES_MAX; + + if (bt_hci_lmp_connection_ready(hci, &link->slave->bd_addr)) { + if (!hci->device.reject_reason) + link->slave->lmp_disconnect_slave(link); + handle = 0; + status = HCI_NO_CONNECTION; + goto complete; + } + + if (hci->device.reject_reason) { + handle = 0; + status = hci->device.reject_reason; + goto complete; + } + + /* Make a connection handle */ + do { + while (hci->lm.handle[++ hci->lm.last_handle].link && -- tries) + hci->lm.last_handle &= HCI_HANDLES_MAX - 1; + handle = hci->lm.last_handle | HCI_HANDLE_OFFSET; + } while ((handle == hci->asb_handle || handle == hci->psb_handle) && + tries); + + if (!tries) { + link->slave->lmp_disconnect_slave(link); + status = HCI_NO_CONNECTION; + goto complete; + } + + /* Link established */ + link->handle = handle; + bt_hci_lmp_link_establish(hci, link, 1); + +complete: + params.status = status; + params.handle = HNDL(handle); + params.link_type = ACL_LINK; + bacpy(¶ms.bdaddr, &link->slave->bd_addr); + params.encr_mode = 0x00; /* Encryption not required */ + bt_hci_event(hci, EVT_CONN_COMPLETE, ¶ms, EVT_CONN_COMPLETE_SIZE); +} + +static void bt_hci_disconnect(struct bt_hci_s *hci, + uint16_t handle, int reason) +{ + struct bt_link_s *btlink = + hci->lm.handle[handle & ~HCI_HANDLE_OFFSET].link; + struct bt_hci_link_s *link; + evt_disconn_complete params; + + if (bt_hci_role_master(hci, handle)) { + btlink->slave->reject_reason = reason; + btlink->slave->lmp_disconnect_slave(btlink); + /* The link pointer is invalid from now on */ + + goto complete; + } + + btlink->host->reject_reason = reason; + btlink->host->lmp_disconnect_master(btlink); + + /* We are the slave, we get to clean this burden */ + link = (struct bt_hci_link_s *) btlink; + qemu_free(link); + +complete: + bt_hci_lmp_link_teardown(hci, handle); + + params.status = HCI_SUCCESS; + params.handle = HNDL(handle); + params.reason = HCI_CONNECTION_TERMINATED; + bt_hci_event(hci, EVT_DISCONN_COMPLETE, + ¶ms, EVT_DISCONN_COMPLETE_SIZE); +} + +/* TODO: use only one function */ +static void bt_hci_lmp_disconnect_host(struct bt_link_s *link) +{ + struct bt_hci_s *hci = hci_from_device(link->host); + uint16_t handle = link->handle; + evt_disconn_complete params; + + bt_hci_lmp_link_teardown(hci, handle); + + params.status = HCI_SUCCESS; + params.handle = HNDL(handle); + params.reason = hci->device.reject_reason; + bt_hci_event(hci, EVT_DISCONN_COMPLETE, + ¶ms, EVT_DISCONN_COMPLETE_SIZE); +} + +static void bt_hci_lmp_disconnect_slave(struct bt_link_s *btlink) +{ + struct bt_hci_link_s *link = (struct bt_hci_link_s *) btlink; + struct bt_hci_s *hci = hci_from_device(btlink->slave); + uint16_t handle = link->handle; + evt_disconn_complete params; + + qemu_free(link); + + bt_hci_lmp_link_teardown(hci, handle); + + params.status = HCI_SUCCESS; + params.handle = HNDL(handle); + params.reason = hci->device.reject_reason; + bt_hci_event(hci, EVT_DISCONN_COMPLETE, + ¶ms, EVT_DISCONN_COMPLETE_SIZE); +} + +static int bt_hci_name_req(struct bt_hci_s *hci, bdaddr_t *bdaddr) +{ + struct bt_device_s *slave; + evt_remote_name_req_complete params; + int len; + + for (slave = hci->device.net->slave; slave; slave = slave->next) + if (slave->page_scan && !bacmp(&slave->bd_addr, bdaddr)) + break; + if (!slave) + return -ENODEV; + + bt_hci_event_status(hci, HCI_SUCCESS); + + params.status = HCI_SUCCESS; + bacpy(¶ms.bdaddr, &slave->bd_addr); + len = snprintf(params.name, sizeof(params.name), + "%s", slave->lmp_name ?: ""); + memset(params.name + len, 0, sizeof(params.name) - len); + bt_hci_event(hci, EVT_REMOTE_NAME_REQ_COMPLETE, + ¶ms, EVT_REMOTE_NAME_REQ_COMPLETE_SIZE); + + return 0; +} + +static int bt_hci_features_req(struct bt_hci_s *hci, uint16_t handle) +{ + struct bt_device_s *slave; + evt_read_remote_features_complete params; + + if (bt_hci_handle_bad(hci, handle)) + return -ENODEV; + + slave = bt_hci_remote_dev(hci, handle); + + bt_hci_event_status(hci, HCI_SUCCESS); + + params.status = HCI_SUCCESS; + params.handle = HNDL(handle); + params.features[0] = (slave->lmp_caps >> 0) & 0xff; + params.features[1] = (slave->lmp_caps >> 8) & 0xff; + params.features[2] = (slave->lmp_caps >> 16) & 0xff; + params.features[3] = (slave->lmp_caps >> 24) & 0xff; + params.features[4] = (slave->lmp_caps >> 32) & 0xff; + params.features[5] = (slave->lmp_caps >> 40) & 0xff; + params.features[6] = (slave->lmp_caps >> 48) & 0xff; + params.features[7] = (slave->lmp_caps >> 56) & 0xff; + bt_hci_event(hci, EVT_READ_REMOTE_FEATURES_COMPLETE, + ¶ms, EVT_READ_REMOTE_FEATURES_COMPLETE_SIZE); + + return 0; +} + +static int bt_hci_version_req(struct bt_hci_s *hci, uint16_t handle) +{ + struct bt_device_s *slave; + evt_read_remote_version_complete params; + + if (bt_hci_handle_bad(hci, handle)) + return -ENODEV; + + slave = bt_hci_remote_dev(hci, handle); + + bt_hci_event_status(hci, HCI_SUCCESS); + + params.status = HCI_SUCCESS; + params.handle = HNDL(handle); + params.lmp_ver = 0x03; + params.manufacturer = cpu_to_le16(0xa000); + params.lmp_subver = cpu_to_le16(0xa607); + bt_hci_event(hci, EVT_READ_REMOTE_VERSION_COMPLETE, + ¶ms, EVT_READ_REMOTE_VERSION_COMPLETE_SIZE); + + return 0; +} + +static int bt_hci_clkoffset_req(struct bt_hci_s *hci, uint16_t handle) +{ + struct bt_device_s *slave; + evt_read_clock_offset_complete params; + + if (bt_hci_handle_bad(hci, handle)) + return -ENODEV; + + slave = bt_hci_remote_dev(hci, handle); + + bt_hci_event_status(hci, HCI_SUCCESS); + + params.status = HCI_SUCCESS; + params.handle = HNDL(handle); + /* TODO: return the clkoff *differenece* */ + params.clock_offset = slave->clkoff; /* Note: no swapping */ + bt_hci_event(hci, EVT_READ_CLOCK_OFFSET_COMPLETE, + ¶ms, EVT_READ_CLOCK_OFFSET_COMPLETE_SIZE); + + return 0; +} + +static void bt_hci_event_mode(struct bt_hci_s *hci, struct bt_link_s *link, + uint16_t handle) +{ + evt_mode_change params = { + .status = HCI_SUCCESS, + .handle = HNDL(handle), + .mode = link->acl_mode, + .interval = cpu_to_le16(link->acl_interval), + }; + + bt_hci_event(hci, EVT_MODE_CHANGE, ¶ms, EVT_MODE_CHANGE_SIZE); +} + +static void bt_hci_lmp_mode_change_master(struct bt_hci_s *hci, + struct bt_link_s *link, int mode, uint16_t interval) +{ + link->acl_mode = mode; + link->acl_interval = interval; + + bt_hci_event_mode(hci, link, link->handle); + + link->slave->lmp_mode_change(link); +} + +static void bt_hci_lmp_mode_change_slave(struct bt_link_s *btlink) +{ + struct bt_hci_link_s *link = (struct bt_hci_link_s *) btlink; + struct bt_hci_s *hci = hci_from_device(btlink->slave); + + bt_hci_event_mode(hci, btlink, link->handle); +} + +static int bt_hci_mode_change(struct bt_hci_s *hci, uint16_t handle, + int interval, int mode) +{ + struct bt_hci_master_link_s *link; + + if (bt_hci_handle_bad(hci, handle) || !bt_hci_role_master(hci, handle)) + return -ENODEV; + + link = &hci->lm.handle[handle & ~HCI_HANDLE_OFFSET]; + if (link->link->acl_mode != acl_active) { + bt_hci_event_status(hci, HCI_COMMAND_DISALLOWED); + return 0; + } + + bt_hci_event_status(hci, HCI_SUCCESS); + + qemu_mod_timer(link->acl_mode_timer, qemu_get_clock(vm_clock) + + muldiv64(interval * 625, ticks_per_sec, 1000000)); + bt_hci_lmp_mode_change_master(hci, link->link, mode, interval); + + return 0; +} + +static int bt_hci_mode_cancel(struct bt_hci_s *hci, uint16_t handle, int mode) +{ + struct bt_hci_master_link_s *link; + + if (bt_hci_handle_bad(hci, handle) || !bt_hci_role_master(hci, handle)) + return -ENODEV; + + link = &hci->lm.handle[handle & ~HCI_HANDLE_OFFSET]; + if (link->link->acl_mode != mode) { + bt_hci_event_status(hci, HCI_COMMAND_DISALLOWED); + + return 0; + } + + bt_hci_event_status(hci, HCI_SUCCESS); + + qemu_del_timer(link->acl_mode_timer); + bt_hci_lmp_mode_change_master(hci, link->link, acl_active, 0); + + return 0; +} + +static void bt_hci_mode_tick(void *opaque) +{ + struct bt_link_s *link = opaque; + struct bt_hci_s *hci = hci_from_device(link->host); + + bt_hci_lmp_mode_change_master(hci, link, acl_active, 0); +} + +static void bt_hci_reset(struct bt_hci_s *hci) +{ + hci->acl_len = 0; + hci->last_cmd = 0; + hci->lm.connecting = 0; + + hci->event_mask[0] = 0xff; + hci->event_mask[1] = 0xff; + hci->event_mask[2] = 0xff; + hci->event_mask[3] = 0xff; + hci->event_mask[4] = 0xff; + hci->event_mask[5] = 0x1f; + hci->event_mask[6] = 0x00; + hci->event_mask[7] = 0x00; + hci->device.inquiry_scan = 0; + hci->device.page_scan = 0; + if (hci->device.lmp_name) + qemu_free((void *) hci->device.lmp_name); + hci->device.lmp_name = NULL; + hci->device.class[0] = 0x00; + hci->device.class[1] = 0x00; + hci->device.class[2] = 0x00; + hci->voice_setting = 0x0000; + hci->conn_accept_tout = 0x1f40; + hci->lm.inquiry_mode = 0x00; + + hci->psb_handle = 0x000; + hci->asb_handle = 0x000; + + /* XXX: qemu_del_timer(sl->acl_mode_timer); for all links */ + qemu_del_timer(hci->lm.inquiry_done); + qemu_del_timer(hci->lm.inquiry_next); + qemu_del_timer(hci->conn_accept_timer); +} + +static void bt_hci_read_local_version_rp(struct bt_hci_s *hci) +{ + read_local_version_rp lv = { + .status = HCI_SUCCESS, + .hci_ver = 0x03, + .hci_rev = cpu_to_le16(0xa607), + .lmp_ver = 0x03, + .manufacturer = cpu_to_le16(0xa000), + .lmp_subver = cpu_to_le16(0xa607), + }; + + bt_hci_event_complete(hci, &lv, READ_LOCAL_VERSION_RP_SIZE); +} + +static void bt_hci_read_local_commands_rp(struct bt_hci_s *hci) +{ + read_local_commands_rp lc = { + .status = HCI_SUCCESS, + .commands = { + /* Keep updated! */ + /* Also, keep in sync with hci->device.lmp_caps in bt_new_hci */ + 0xbf, 0x80, 0xf9, 0x03, 0xb2, 0xc0, 0x03, 0xc3, + 0x00, 0x0f, 0x80, 0x00, 0xc0, 0x00, 0xe8, 0x13, + 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, 0x00, 0x00, 0x00, 0x00, + }, + }; + + bt_hci_event_complete(hci, &lc, READ_LOCAL_COMMANDS_RP_SIZE); +} + +static void bt_hci_read_local_features_rp(struct bt_hci_s *hci) +{ + read_local_features_rp lf = { + .status = HCI_SUCCESS, + .features = { + (hci->device.lmp_caps >> 0) & 0xff, + (hci->device.lmp_caps >> 8) & 0xff, + (hci->device.lmp_caps >> 16) & 0xff, + (hci->device.lmp_caps >> 24) & 0xff, + (hci->device.lmp_caps >> 32) & 0xff, + (hci->device.lmp_caps >> 40) & 0xff, + (hci->device.lmp_caps >> 48) & 0xff, + (hci->device.lmp_caps >> 56) & 0xff, + }, + }; + + bt_hci_event_complete(hci, &lf, READ_LOCAL_FEATURES_RP_SIZE); +} + +static void bt_hci_read_local_ext_features_rp(struct bt_hci_s *hci, int page) +{ + read_local_ext_features_rp lef = { + .status = HCI_SUCCESS, + .page_num = page, + .max_page_num = 0x00, + .features = { + /* Keep updated! */ + 0x5f, 0x35, 0x85, 0x7e, 0x9b, 0x19, 0x00, 0x80, + }, + }; + if (page) + memset(lef.features, 0, sizeof(lef.features)); + + bt_hci_event_complete(hci, &lef, READ_LOCAL_EXT_FEATURES_RP_SIZE); +} + +static void bt_hci_read_buffer_size_rp(struct bt_hci_s *hci) +{ + read_buffer_size_rp bs = { + /* This can be made configurable, for one standard USB dongle HCI + * the four values are cpu_to_le16(0x0180), 0x40, + * cpu_to_le16(0x0008), cpu_to_le16(0x0008). */ + .status = HCI_SUCCESS, + .acl_mtu = cpu_to_le16(0x0200), + .sco_mtu = 0, + .acl_max_pkt = cpu_to_le16(0x0001), + .sco_max_pkt = cpu_to_le16(0x0000), + }; + + bt_hci_event_complete(hci, &bs, READ_BUFFER_SIZE_RP_SIZE); +} + +/* Deprecated in V2.0 (page 661) */ +static void bt_hci_read_country_code_rp(struct bt_hci_s *hci) +{ + read_country_code_rp cc ={ + .status = HCI_SUCCESS, + .country_code = 0x00, /* North America & Europe^1 and Japan */ + }; + + bt_hci_event_complete(hci, &cc, READ_COUNTRY_CODE_RP_SIZE); + + /* ^1. Except France, sorry */ +} + +static void bt_hci_read_bd_addr_rp(struct bt_hci_s *hci) +{ + read_bd_addr_rp ba = { + .status = HCI_SUCCESS, + .bdaddr = BAINIT(&hci->device.bd_addr), + }; + + bt_hci_event_complete(hci, &ba, READ_BD_ADDR_RP_SIZE); +} + +static int bt_hci_link_quality_rp(struct bt_hci_s *hci, uint16_t handle) +{ + read_link_quality_rp lq = { + .status = HCI_SUCCESS, + .handle = HNDL(handle), + .link_quality = 0xff, + }; + + if (bt_hci_handle_bad(hci, handle)) + lq.status = HCI_NO_CONNECTION; + + bt_hci_event_complete(hci, &lq, READ_LINK_QUALITY_RP_SIZE); + return 0; +} + +/* Generate a Command Complete event with only the Status parameter */ +static inline void bt_hci_event_complete_status(struct bt_hci_s *hci, + uint8_t status) +{ + bt_hci_event_complete(hci, &status, 1); +} + +static inline void bt_hci_event_complete_conn_cancel(struct bt_hci_s *hci, + uint8_t status, bdaddr_t *bd_addr) +{ + create_conn_cancel_rp params = { + .status = status, + .bdaddr = BAINIT(bd_addr), + }; + + bt_hci_event_complete(hci, ¶ms, CREATE_CONN_CANCEL_RP_SIZE); +} + +static inline void bt_hci_event_auth_complete(struct bt_hci_s *hci, + uint16_t handle) +{ + evt_auth_complete params = { + .status = HCI_SUCCESS, + .handle = HNDL(handle), + }; + + bt_hci_event(hci, EVT_AUTH_COMPLETE, ¶ms, EVT_AUTH_COMPLETE_SIZE); +} + +static inline void bt_hci_event_encrypt_change(struct bt_hci_s *hci, + uint16_t handle, uint8_t mode) +{ + evt_encrypt_change params = { + .status = HCI_SUCCESS, + .handle = HNDL(handle), + .encrypt = mode, + }; + + bt_hci_event(hci, EVT_ENCRYPT_CHANGE, ¶ms, EVT_ENCRYPT_CHANGE_SIZE); +} + +static inline void bt_hci_event_complete_name_cancel(struct bt_hci_s *hci, + bdaddr_t *bd_addr) +{ + remote_name_req_cancel_rp params = { + .status = HCI_INVALID_PARAMETERS, + .bdaddr = BAINIT(bd_addr), + }; + + bt_hci_event_complete(hci, ¶ms, REMOTE_NAME_REQ_CANCEL_RP_SIZE); +} + +static inline void bt_hci_event_read_remote_ext_features(struct bt_hci_s *hci, + uint16_t handle) +{ + evt_read_remote_ext_features_complete params = { + .status = HCI_UNSUPPORTED_FEATURE, + .handle = HNDL(handle), + /* Rest uninitialised */ + }; + + bt_hci_event(hci, EVT_READ_REMOTE_EXT_FEATURES_COMPLETE, + ¶ms, EVT_READ_REMOTE_EXT_FEATURES_COMPLETE_SIZE); +} + +static inline void bt_hci_event_complete_lmp_handle(struct bt_hci_s *hci, + uint16_t handle) +{ + read_lmp_handle_rp params = { + .status = HCI_NO_CONNECTION, + .handle = HNDL(handle), + .reserved = 0, + /* Rest uninitialised */ + }; + + bt_hci_event_complete(hci, ¶ms, READ_LMP_HANDLE_RP_SIZE); +} + +static inline void bt_hci_event_complete_role_discovery(struct bt_hci_s *hci, + int status, uint16_t handle, int master) +{ + role_discovery_rp params = { + .status = status, + .handle = HNDL(handle), + .role = master ? 0x00 : 0x01, + }; + + bt_hci_event_complete(hci, ¶ms, ROLE_DISCOVERY_RP_SIZE); +} + +static inline void bt_hci_event_complete_flush(struct bt_hci_s *hci, + int status, uint16_t handle) +{ + flush_rp params = { + .status = status, + .handle = HNDL(handle), + }; + + bt_hci_event_complete(hci, ¶ms, FLUSH_RP_SIZE); +} + +static inline void bt_hci_event_complete_read_local_name(struct bt_hci_s *hci) +{ + read_local_name_rp params; + params.status = HCI_SUCCESS; + memset(params.name, 0, sizeof(params.name)); + if (hci->device.lmp_name) + strncpy(params.name, hci->device.lmp_name, sizeof(params.name)); + + bt_hci_event_complete(hci, ¶ms, READ_LOCAL_NAME_RP_SIZE); +} + +static inline void bt_hci_event_complete_read_conn_accept_timeout( + struct bt_hci_s *hci) +{ + read_conn_accept_timeout_rp params = { + .status = HCI_SUCCESS, + .timeout = cpu_to_le16(hci->conn_accept_tout), + }; + + bt_hci_event_complete(hci, ¶ms, READ_CONN_ACCEPT_TIMEOUT_RP_SIZE); +} + +static inline void bt_hci_event_complete_read_scan_enable(struct bt_hci_s *hci) +{ + read_scan_enable_rp params = { + .status = HCI_SUCCESS, + .enable = + (hci->device.inquiry_scan ? SCAN_INQUIRY : 0) | + (hci->device.page_scan ? SCAN_PAGE : 0), + }; + + bt_hci_event_complete(hci, ¶ms, READ_SCAN_ENABLE_RP_SIZE); +} + +static inline void bt_hci_event_complete_read_local_class(struct bt_hci_s *hci) +{ + read_class_of_dev_rp params; + + params.status = HCI_SUCCESS; + memcpy(params.dev_class, hci->device.class, sizeof(params.dev_class)); + + bt_hci_event_complete(hci, ¶ms, READ_CLASS_OF_DEV_RP_SIZE); +} + +static inline void bt_hci_event_complete_voice_setting(struct bt_hci_s *hci) +{ + read_voice_setting_rp params = { + .status = HCI_SUCCESS, + .voice_setting = hci->voice_setting, /* Note: no swapping */ + }; + + bt_hci_event_complete(hci, ¶ms, READ_VOICE_SETTING_RP_SIZE); +} + +static inline void bt_hci_event_complete_read_inquiry_mode( + struct bt_hci_s *hci) +{ + read_inquiry_mode_rp params = { + .status = HCI_SUCCESS, + .mode = hci->lm.inquiry_mode, + }; + + bt_hci_event_complete(hci, ¶ms, READ_INQUIRY_MODE_RP_SIZE); +} + +static inline void bt_hci_event_num_comp_pkts(struct bt_hci_s *hci, + uint16_t handle, int packets) +{ + uint16_t buf[EVT_NUM_COMP_PKTS_SIZE(1) / 2 + 1]; + evt_num_comp_pkts *params = (void *) ((uint8_t *) buf + 1); + + params->num_hndl = 1; + params->connection->handle = HNDL(handle); + params->connection->num_packets = cpu_to_le16(packets); + + bt_hci_event(hci, EVT_NUM_COMP_PKTS, params, EVT_NUM_COMP_PKTS_SIZE(1)); +} + +static void bt_submit_hci(struct HCIInfo *info, + const uint8_t *data, int length) +{ + struct bt_hci_s *hci = hci_from_info(info); + uint16_t cmd; + int paramlen, i; + + if (length < HCI_COMMAND_HDR_SIZE) + goto short_hci; + + memcpy(&hci->last_cmd, data, 2); + + cmd = (data[1] << 8) | data[0]; + paramlen = data[2]; + if (cmd_opcode_ogf(cmd) == 0 || cmd_opcode_ocf(cmd) == 0) /* NOP */ + return; + + data += HCI_COMMAND_HDR_SIZE; + length -= HCI_COMMAND_HDR_SIZE; + + if (paramlen > length) + return; + +#define PARAM(cmd, param) (((cmd##_cp *) data)->param) +#define PARAM16(cmd, param) le16_to_cpup(&PARAM(cmd, param)) +#define PARAMHANDLE(cmd) HNDL(PARAM(cmd, handle)) +#define LENGTH_CHECK(cmd) if (length < sizeof(cmd##_cp)) goto short_hci + /* Note: the supported commands bitmask in bt_hci_read_local_commands_rp + * needs to be updated every time a command is implemented here! */ + switch (cmd) { + case cmd_opcode_pack(OGF_LINK_CTL, OCF_INQUIRY): + LENGTH_CHECK(inquiry); + + if (PARAM(inquiry, length) < 1) { + bt_hci_event_complete_status(hci, HCI_INVALID_PARAMETERS); + break; + } + + hci->lm.inquire = 1; + hci->lm.periodic = 0; + hci->lm.responses_left = PARAM(inquiry, num_rsp) ?: INT_MAX; + hci->lm.responses = 0; + bt_hci_event_status(hci, HCI_SUCCESS); + bt_hci_inquiry_start(hci, PARAM(inquiry, length)); + break; + + case cmd_opcode_pack(OGF_LINK_CTL, OCF_INQUIRY_CANCEL): + if (!hci->lm.inquire || hci->lm.periodic) { + fprintf(stderr, "%s: Inquiry Cancel should only be issued after " + "the Inquiry command has been issued, a Command " + "Status event has been received for the Inquiry " + "command, and before the Inquiry Complete event " + "occurs", __FUNCTION__); + bt_hci_event_complete_status(hci, HCI_COMMAND_DISALLOWED); + break; + } + + hci->lm.inquire = 0; + qemu_del_timer(hci->lm.inquiry_done); + bt_hci_event_complete_status(hci, HCI_SUCCESS); + break; + + case cmd_opcode_pack(OGF_LINK_CTL, OCF_PERIODIC_INQUIRY): + LENGTH_CHECK(periodic_inquiry); + + if (!(PARAM(periodic_inquiry, length) < + PARAM16(periodic_inquiry, min_period) && + PARAM16(periodic_inquiry, min_period) < + PARAM16(periodic_inquiry, max_period)) || + PARAM(periodic_inquiry, length) < 1 || + PARAM16(periodic_inquiry, min_period) < 2 || + PARAM16(periodic_inquiry, max_period) < 3) { + bt_hci_event_complete_status(hci, HCI_INVALID_PARAMETERS); + break; + } + + hci->lm.inquire = 1; + hci->lm.periodic = 1; + hci->lm.responses_left = PARAM(periodic_inquiry, num_rsp); + hci->lm.responses = 0; + hci->lm.inquiry_period = PARAM16(periodic_inquiry, max_period); + bt_hci_event_complete_status(hci, HCI_SUCCESS); + bt_hci_inquiry_start(hci, PARAM(periodic_inquiry, length)); + break; + + case cmd_opcode_pack(OGF_LINK_CTL, OCF_EXIT_PERIODIC_INQUIRY): + if (!hci->lm.inquire || !hci->lm.periodic) { + fprintf(stderr, "%s: Inquiry Cancel should only be issued after " + "the Inquiry command has been issued, a Command " + "Status event has been received for the Inquiry " + "command, and before the Inquiry Complete event " + "occurs", __FUNCTION__); + bt_hci_event_complete_status(hci, HCI_COMMAND_DISALLOWED); + break; + } + hci->lm.inquire = 0; + qemu_del_timer(hci->lm.inquiry_done); + qemu_del_timer(hci->lm.inquiry_next); + bt_hci_event_complete_status(hci, HCI_SUCCESS); + break; + + case cmd_opcode_pack(OGF_LINK_CTL, OCF_CREATE_CONN): + LENGTH_CHECK(create_conn); + + if (hci->lm.connecting >= HCI_HANDLES_MAX) { + bt_hci_event_status(hci, HCI_REJECTED_LIMITED_RESOURCES); + break; + } + bt_hci_event_status(hci, HCI_SUCCESS); + + if (bt_hci_connect(hci, &PARAM(create_conn, bdaddr))) + bt_hci_connection_reject_event(hci, &PARAM(create_conn, bdaddr)); + break; + + case cmd_opcode_pack(OGF_LINK_CTL, OCF_DISCONNECT): + LENGTH_CHECK(disconnect); + + if (bt_hci_handle_bad(hci, PARAMHANDLE(disconnect))) { + bt_hci_event_status(hci, HCI_NO_CONNECTION); + break; + } + + bt_hci_event_status(hci, HCI_SUCCESS); + bt_hci_disconnect(hci, PARAMHANDLE(disconnect), + PARAM(disconnect, reason)); + break; + + case cmd_opcode_pack(OGF_LINK_CTL, OCF_CREATE_CONN_CANCEL): + LENGTH_CHECK(create_conn_cancel); + + if (bt_hci_lmp_connection_ready(hci, + &PARAM(create_conn_cancel, bdaddr))) { + for (i = 0; i < HCI_HANDLES_MAX; i ++) + if (bt_hci_role_master(hci, i) && hci->lm.handle[i].link && + !bacmp(&hci->lm.handle[i].link->slave->bd_addr, + &PARAM(create_conn_cancel, bdaddr))) + break; + + bt_hci_event_complete_conn_cancel(hci, i < HCI_HANDLES_MAX ? + HCI_ACL_CONNECTION_EXISTS : HCI_NO_CONNECTION, + &PARAM(create_conn_cancel, bdaddr)); + } else + bt_hci_event_complete_conn_cancel(hci, HCI_SUCCESS, + &PARAM(create_conn_cancel, bdaddr)); + break; + + case cmd_opcode_pack(OGF_LINK_CTL, OCF_ACCEPT_CONN_REQ): + LENGTH_CHECK(accept_conn_req); + + if (!hci->conn_req_host || + bacmp(&PARAM(accept_conn_req, bdaddr), + &hci->conn_req_host->bd_addr)) { + bt_hci_event_status(hci, HCI_INVALID_PARAMETERS); + break; + } + + bt_hci_event_status(hci, HCI_SUCCESS); + bt_hci_connection_accept(hci, hci->conn_req_host); + hci->conn_req_host = NULL; + break; + + case cmd_opcode_pack(OGF_LINK_CTL, OCF_REJECT_CONN_REQ): + LENGTH_CHECK(reject_conn_req); + + if (!hci->conn_req_host || + bacmp(&PARAM(reject_conn_req, bdaddr), + &hci->conn_req_host->bd_addr)) { + bt_hci_event_status(hci, HCI_INVALID_PARAMETERS); + break; + } + + bt_hci_event_status(hci, HCI_SUCCESS); + bt_hci_connection_reject(hci, hci->conn_req_host, + PARAM(reject_conn_req, reason)); + bt_hci_connection_reject_event(hci, &hci->conn_req_host->bd_addr); + hci->conn_req_host = NULL; + break; + + case cmd_opcode_pack(OGF_LINK_CTL, OCF_AUTH_REQUESTED): + LENGTH_CHECK(auth_requested); + + if (bt_hci_handle_bad(hci, PARAMHANDLE(auth_requested))) + bt_hci_event_status(hci, HCI_NO_CONNECTION); + else { + bt_hci_event_status(hci, HCI_SUCCESS); + bt_hci_event_auth_complete(hci, PARAMHANDLE(auth_requested)); + } + break; + + case cmd_opcode_pack(OGF_LINK_CTL, OCF_SET_CONN_ENCRYPT): + LENGTH_CHECK(set_conn_encrypt); + + if (bt_hci_handle_bad(hci, PARAMHANDLE(set_conn_encrypt))) + bt_hci_event_status(hci, HCI_NO_CONNECTION); + else { + bt_hci_event_status(hci, HCI_SUCCESS); + bt_hci_event_encrypt_change(hci, + PARAMHANDLE(set_conn_encrypt), + PARAM(set_conn_encrypt, encrypt)); + } + break; + + case cmd_opcode_pack(OGF_LINK_CTL, OCF_REMOTE_NAME_REQ): + LENGTH_CHECK(remote_name_req); + + if (bt_hci_name_req(hci, &PARAM(remote_name_req, bdaddr))) + bt_hci_event_status(hci, HCI_NO_CONNECTION); + break; + + case cmd_opcode_pack(OGF_LINK_CTL, OCF_REMOTE_NAME_REQ_CANCEL): + LENGTH_CHECK(remote_name_req_cancel); + + bt_hci_event_complete_name_cancel(hci, + &PARAM(remote_name_req_cancel, bdaddr)); + break; + + case cmd_opcode_pack(OGF_LINK_CTL, OCF_READ_REMOTE_FEATURES): + LENGTH_CHECK(read_remote_features); + + if (bt_hci_features_req(hci, PARAMHANDLE(read_remote_features))) + bt_hci_event_status(hci, HCI_NO_CONNECTION); + break; + + case cmd_opcode_pack(OGF_LINK_CTL, OCF_READ_REMOTE_EXT_FEATURES): + LENGTH_CHECK(read_remote_ext_features); + + if (bt_hci_handle_bad(hci, PARAMHANDLE(read_remote_ext_features))) + bt_hci_event_status(hci, HCI_NO_CONNECTION); + else { + bt_hci_event_status(hci, HCI_SUCCESS); + bt_hci_event_read_remote_ext_features(hci, + PARAMHANDLE(read_remote_ext_features)); + } + break; + + case cmd_opcode_pack(OGF_LINK_CTL, OCF_READ_REMOTE_VERSION): + LENGTH_CHECK(read_remote_version); + + if (bt_hci_version_req(hci, PARAMHANDLE(read_remote_version))) + bt_hci_event_status(hci, HCI_NO_CONNECTION); + break; + + case cmd_opcode_pack(OGF_LINK_CTL, OCF_READ_CLOCK_OFFSET): + LENGTH_CHECK(read_clock_offset); + + if (bt_hci_clkoffset_req(hci, PARAMHANDLE(read_clock_offset))) + bt_hci_event_status(hci, HCI_NO_CONNECTION); + break; + + case cmd_opcode_pack(OGF_LINK_CTL, OCF_READ_LMP_HANDLE): + LENGTH_CHECK(read_lmp_handle); + + /* TODO: */ + bt_hci_event_complete_lmp_handle(hci, PARAMHANDLE(read_lmp_handle)); + break; + + case cmd_opcode_pack(OGF_LINK_POLICY, OCF_HOLD_MODE): + LENGTH_CHECK(hold_mode); + + if (PARAM16(hold_mode, min_interval) > + PARAM16(hold_mode, max_interval) || + PARAM16(hold_mode, min_interval) < 0x0002 || + PARAM16(hold_mode, max_interval) > 0xff00 || + (PARAM16(hold_mode, min_interval) & 1) || + (PARAM16(hold_mode, max_interval) & 1)) { + bt_hci_event_status(hci, HCI_INVALID_PARAMETERS); + break; + } + + if (bt_hci_mode_change(hci, PARAMHANDLE(hold_mode), + PARAM16(hold_mode, max_interval), + acl_hold)) + bt_hci_event_status(hci, HCI_NO_CONNECTION); + break; + + case cmd_opcode_pack(OGF_LINK_POLICY, OCF_PARK_MODE): + LENGTH_CHECK(park_mode); + + if (PARAM16(park_mode, min_interval) > + PARAM16(park_mode, max_interval) || + PARAM16(park_mode, min_interval) < 0x000e || + (PARAM16(park_mode, min_interval) & 1) || + (PARAM16(park_mode, max_interval) & 1)) { + bt_hci_event_status(hci, HCI_INVALID_PARAMETERS); + break; + } + + if (bt_hci_mode_change(hci, PARAMHANDLE(park_mode), + PARAM16(park_mode, max_interval), + acl_parked)) + bt_hci_event_status(hci, HCI_NO_CONNECTION); + break; + + case cmd_opcode_pack(OGF_LINK_POLICY, OCF_EXIT_PARK_MODE): + LENGTH_CHECK(exit_park_mode); + + if (bt_hci_mode_cancel(hci, PARAMHANDLE(exit_park_mode), + acl_parked)) + bt_hci_event_status(hci, HCI_NO_CONNECTION); + break; + + case cmd_opcode_pack(OGF_LINK_POLICY, OCF_ROLE_DISCOVERY): + LENGTH_CHECK(role_discovery); + + if (bt_hci_handle_bad(hci, PARAMHANDLE(role_discovery))) + bt_hci_event_complete_role_discovery(hci, + HCI_NO_CONNECTION, PARAMHANDLE(role_discovery), 0); + else + bt_hci_event_complete_role_discovery(hci, + HCI_SUCCESS, PARAMHANDLE(role_discovery), + bt_hci_role_master(hci, + PARAMHANDLE(role_discovery))); + break; + + case cmd_opcode_pack(OGF_HOST_CTL, OCF_SET_EVENT_MASK): + LENGTH_CHECK(set_event_mask); + + memcpy(hci->event_mask, PARAM(set_event_mask, mask), 8); + bt_hci_event_complete_status(hci, HCI_SUCCESS); + break; + + case cmd_opcode_pack(OGF_HOST_CTL, OCF_RESET): + bt_hci_reset(hci); + bt_hci_event_status(hci, HCI_SUCCESS); + break; + + case cmd_opcode_pack(OGF_HOST_CTL, OCF_SET_EVENT_FLT): + if (length >= 1 && PARAM(set_event_flt, flt_type) == FLT_CLEAR_ALL) + /* No length check */; + else + LENGTH_CHECK(set_event_flt); + + /* Filters are not implemented */ + bt_hci_event_complete_status(hci, HCI_SUCCESS); + break; + + case cmd_opcode_pack(OGF_HOST_CTL, OCF_FLUSH): + LENGTH_CHECK(flush); + + if (bt_hci_handle_bad(hci, PARAMHANDLE(flush))) + bt_hci_event_complete_flush(hci, + HCI_NO_CONNECTION, PARAMHANDLE(flush)); + else { + /* TODO: ordering? */ + bt_hci_event(hci, EVT_FLUSH_OCCURRED, + &PARAM(flush, handle), + EVT_FLUSH_OCCURRED_SIZE); + bt_hci_event_complete_flush(hci, + HCI_SUCCESS, PARAMHANDLE(flush)); + } + break; + + case cmd_opcode_pack(OGF_HOST_CTL, OCF_CHANGE_LOCAL_NAME): + LENGTH_CHECK(change_local_name); + + if (hci->device.lmp_name) + qemu_free((void *) hci->device.lmp_name); + hci->device.lmp_name = qemu_strndup(PARAM(change_local_name, name), + sizeof(PARAM(change_local_name, name))); + bt_hci_event_complete_status(hci, HCI_SUCCESS); + break; + + case cmd_opcode_pack(OGF_HOST_CTL, OCF_READ_LOCAL_NAME): + bt_hci_event_complete_read_local_name(hci); + break; + + case cmd_opcode_pack(OGF_HOST_CTL, OCF_READ_CONN_ACCEPT_TIMEOUT): + bt_hci_event_complete_read_conn_accept_timeout(hci); + break; + + case cmd_opcode_pack(OGF_HOST_CTL, OCF_WRITE_CONN_ACCEPT_TIMEOUT): + /* TODO */ + LENGTH_CHECK(write_conn_accept_timeout); + + if (PARAM16(write_conn_accept_timeout, timeout) < 0x0001 || + PARAM16(write_conn_accept_timeout, timeout) > 0xb540) { + bt_hci_event_complete_status(hci, HCI_INVALID_PARAMETERS); + break; + } + + hci->conn_accept_tout = PARAM16(write_conn_accept_timeout, timeout); + bt_hci_event_complete_status(hci, HCI_SUCCESS); + break; + + case cmd_opcode_pack(OGF_HOST_CTL, OCF_READ_SCAN_ENABLE): + bt_hci_event_complete_read_scan_enable(hci); + break; + + case cmd_opcode_pack(OGF_HOST_CTL, OCF_WRITE_SCAN_ENABLE): + LENGTH_CHECK(write_scan_enable); + + /* TODO: check that the remaining bits are all 0 */ + hci->device.inquiry_scan = + !!(PARAM(write_scan_enable, scan_enable) & SCAN_INQUIRY); + hci->device.page_scan = + !!(PARAM(write_scan_enable, scan_enable) & SCAN_PAGE); + bt_hci_event_complete_status(hci, HCI_SUCCESS); + break; + + case cmd_opcode_pack(OGF_HOST_CTL, OCF_READ_CLASS_OF_DEV): + bt_hci_event_complete_read_local_class(hci); + break; + + case cmd_opcode_pack(OGF_HOST_CTL, OCF_WRITE_CLASS_OF_DEV): + LENGTH_CHECK(write_class_of_dev); + + memcpy(hci->device.class, PARAM(write_class_of_dev, dev_class), + sizeof(PARAM(write_class_of_dev, dev_class))); + bt_hci_event_complete_status(hci, HCI_SUCCESS); + break; + + case cmd_opcode_pack(OGF_HOST_CTL, OCF_READ_VOICE_SETTING): + bt_hci_event_complete_voice_setting(hci); + break; + + case cmd_opcode_pack(OGF_HOST_CTL, OCF_WRITE_VOICE_SETTING): + LENGTH_CHECK(write_voice_setting); + + hci->voice_setting = PARAM(write_voice_setting, voice_setting); + bt_hci_event_complete_status(hci, HCI_SUCCESS); + break; + + case cmd_opcode_pack(OGF_HOST_CTL, OCF_HOST_NUMBER_OF_COMPLETED_PACKETS): + if (length < data[0] * 2 + 1) + goto short_hci; + + for (i = 0; i < data[0]; i ++) + if (bt_hci_handle_bad(hci, + data[i * 2 + 1] | (data[i * 2 + 2] << 8))) + bt_hci_event_complete_status(hci, HCI_INVALID_PARAMETERS); + break; + + case cmd_opcode_pack(OGF_HOST_CTL, OCF_READ_INQUIRY_MODE): + /* Only if (local_features[3] & 0x40) && (local_commands[12] & 0x40) + * else + * goto unknown_command */ + bt_hci_event_complete_read_inquiry_mode(hci); + break; + + case cmd_opcode_pack(OGF_HOST_CTL, OCF_WRITE_INQUIRY_MODE): + /* Only if (local_features[3] & 0x40) && (local_commands[12] & 0x80) + * else + * goto unknown_command */ + LENGTH_CHECK(write_inquiry_mode); + + if (PARAM(write_inquiry_mode, mode) > 0x01) { + bt_hci_event_complete_status(hci, HCI_INVALID_PARAMETERS); + break; + } + + hci->lm.inquiry_mode = PARAM(write_inquiry_mode, mode); + bt_hci_event_complete_status(hci, HCI_SUCCESS); + break; + + case cmd_opcode_pack(OGF_INFO_PARAM, OCF_READ_LOCAL_VERSION): + bt_hci_read_local_version_rp(hci); + break; + + case cmd_opcode_pack(OGF_INFO_PARAM, OCF_READ_LOCAL_COMMANDS): + bt_hci_read_local_commands_rp(hci); + break; + + case cmd_opcode_pack(OGF_INFO_PARAM, OCF_READ_LOCAL_FEATURES): + bt_hci_read_local_features_rp(hci); + break; + + case cmd_opcode_pack(OGF_INFO_PARAM, OCF_READ_LOCAL_EXT_FEATURES): + LENGTH_CHECK(read_local_ext_features); + + bt_hci_read_local_ext_features_rp(hci, + PARAM(read_local_ext_features, page_num)); + break; + + case cmd_opcode_pack(OGF_INFO_PARAM, OCF_READ_BUFFER_SIZE): + bt_hci_read_buffer_size_rp(hci); + break; + + case cmd_opcode_pack(OGF_INFO_PARAM, OCF_READ_COUNTRY_CODE): + bt_hci_read_country_code_rp(hci); + break; + + case cmd_opcode_pack(OGF_INFO_PARAM, OCF_READ_BD_ADDR): + bt_hci_read_bd_addr_rp(hci); + break; + + case cmd_opcode_pack(OGF_STATUS_PARAM, OCF_READ_LINK_QUALITY): + LENGTH_CHECK(read_link_quality); + + bt_hci_link_quality_rp(hci, PARAMHANDLE(read_link_quality)); + break; + + default: + bt_hci_event_status(hci, HCI_UNKNOWN_COMMAND); + break; + + short_hci: + fprintf(stderr, "%s: HCI packet too short (%iB)\n", + __FUNCTION__, length); + bt_hci_event_status(hci, HCI_INVALID_PARAMETERS); + break; + } +} + +/* We could perform fragmentation here, we can't do "recombination" because + * at this layer the length of the payload is not know ahead, so we only + * know that a packet contained the last fragment of the SDU when the next + * SDU starts. */ +static inline void bt_hci_lmp_acl_data(struct bt_hci_s *hci, uint16_t handle, + const uint8_t *data, int start, int len) +{ + struct hci_acl_hdr *pkt = (void *) hci->acl_buf; + + /* TODO: packet flags */ + /* TODO: avoid memcpy'ing */ + + if (len + HCI_ACL_HDR_SIZE > sizeof(hci->acl_buf)) { + fprintf(stderr, "%s: can't take ACL packets %i bytes long\n", + __FUNCTION__, len); + return; + } + memcpy(hci->acl_buf + HCI_ACL_HDR_SIZE, data, len); + + pkt->handle = cpu_to_le16( + acl_handle_pack(handle, start ? ACL_START : ACL_CONT)); + pkt->dlen = cpu_to_le16(len); + hci->info.acl_recv(hci->info.opaque, + hci->acl_buf, len + HCI_ACL_HDR_SIZE); +} + +static void bt_hci_lmp_acl_data_slave(struct bt_link_s *btlink, + const uint8_t *data, int start, int len) +{ + struct bt_hci_link_s *link = (struct bt_hci_link_s *) btlink; + + bt_hci_lmp_acl_data(hci_from_device(btlink->slave), + link->handle, data, start, len); +} + +static void bt_hci_lmp_acl_data_host(struct bt_link_s *link, + const uint8_t *data, int start, int len) +{ + bt_hci_lmp_acl_data(hci_from_device(link->host), + link->handle, data, start, len); +} + +static void bt_submit_acl(struct HCIInfo *info, + const uint8_t *data, int length) +{ + struct bt_hci_s *hci = hci_from_info(info); + uint16_t handle; + int datalen, flags; + struct bt_link_s *link; + + if (length < HCI_ACL_HDR_SIZE) { + fprintf(stderr, "%s: ACL packet too short (%iB)\n", + __FUNCTION__, length); + return; + } + + handle = acl_handle((data[1] << 8) | data[0]); + flags = acl_flags((data[1] << 8) | data[0]); + datalen = (data[3] << 8) | data[2]; + data += HCI_ACL_HDR_SIZE; + length -= HCI_ACL_HDR_SIZE; + + if (bt_hci_handle_bad(hci, handle)) { + fprintf(stderr, "%s: invalid ACL handle %03x\n", + __FUNCTION__, handle); + /* TODO: signal an error */ + return; + } + handle &= ~HCI_HANDLE_OFFSET; + + if (datalen > length) { + fprintf(stderr, "%s: ACL packet too short (%iB < %iB)\n", + __FUNCTION__, length, datalen); + return; + } + + link = hci->lm.handle[handle].link; + + if ((flags & ~3) == ACL_ACTIVE_BCAST) { + if (!hci->asb_handle) + hci->asb_handle = handle; + else if (handle != hci->asb_handle) { + fprintf(stderr, "%s: Bad handle %03x in Active Slave Broadcast\n", + __FUNCTION__, handle); + /* TODO: signal an error */ + return; + } + + /* TODO */ + } + + if ((flags & ~3) == ACL_PICO_BCAST) { + if (!hci->psb_handle) + hci->psb_handle = handle; + else if (handle != hci->psb_handle) { + fprintf(stderr, "%s: Bad handle %03x in Parked Slave Broadcast\n", + __FUNCTION__, handle); + /* TODO: signal an error */ + return; + } + + /* TODO */ + } + + /* TODO: increase counter and send EVT_NUM_COMP_PKTS */ + bt_hci_event_num_comp_pkts(hci, handle | HCI_HANDLE_OFFSET, 1); + + /* Do this last as it can trigger further events even in this HCI */ + hci->lm.handle[handle].lmp_acl_data(link, data, + (flags & 3) == ACL_START, length); +} + +static void bt_submit_sco(struct HCIInfo *info, + const uint8_t *data, int length) +{ + struct bt_hci_s *hci = hci_from_info(info); + struct bt_link_s *link; + uint16_t handle; + int datalen; + + if (length < 3) + return; + + handle = acl_handle((data[1] << 8) | data[0]); + datalen = data[2]; + data += 3; + length -= 3; + + if (bt_hci_handle_bad(hci, handle)) { + fprintf(stderr, "%s: invalid SCO handle %03x\n", + __FUNCTION__, handle); + return; + } + handle &= ~HCI_HANDLE_OFFSET; + + if (datalen > length) { + fprintf(stderr, "%s: SCO packet too short (%iB < %iB)\n", + __FUNCTION__, length, datalen); + return; + } + + link = hci->lm.handle[handle].link; + /* TODO */ + + /* TODO: increase counter and send EVT_NUM_COMP_PKTS if synchronous + * Flow Control is enabled. + * (See Read/Write_Synchronous_Flow_Control_Enable on page 513 and + * page 514.) */ +} + +static uint8_t *bt_hci_evt_packet(void *opaque) +{ + /* TODO: allocate a packet from upper layer */ + struct bt_hci_s *s = opaque; + + return s->evt_buf; +} + +static void bt_hci_evt_submit(void *opaque, int len) +{ + /* TODO: notify upper layer */ + struct bt_hci_s *s = opaque; + + s->info.evt_recv(s->info.opaque, s->evt_buf, len); +} + +static int bt_hci_bdaddr_set(struct HCIInfo *info, const uint8_t *bd_addr) +{ + struct bt_hci_s *hci = hci_from_info(info); + + bacpy(&hci->device.bd_addr, (const bdaddr_t *) bd_addr); + return 0; +} + +static void bt_hci_done(struct HCIInfo *info); +static void bt_hci_destroy(struct bt_device_s *dev) +{ + struct bt_hci_s *hci = hci_from_device(dev); + + bt_hci_done(&hci->info); +} + +struct HCIInfo *bt_new_hci(struct bt_scatternet_s *net) +{ + struct bt_hci_s *s = qemu_mallocz(sizeof(struct bt_hci_s)); + + s->lm.inquiry_done = qemu_new_timer(vm_clock, bt_hci_inquiry_done, s); + s->lm.inquiry_next = qemu_new_timer(vm_clock, bt_hci_inquiry_next, s); + s->conn_accept_timer = + qemu_new_timer(vm_clock, bt_hci_conn_accept_timeout, s); + + s->evt_packet = bt_hci_evt_packet; + s->evt_submit = bt_hci_evt_submit; + s->opaque = s; + + bt_device_init(&s->device, net); + s->device.lmp_connection_request = bt_hci_lmp_connection_request; + s->device.lmp_connection_complete = bt_hci_lmp_connection_complete; + s->device.lmp_disconnect_master = bt_hci_lmp_disconnect_host; + s->device.lmp_disconnect_slave = bt_hci_lmp_disconnect_slave; + s->device.lmp_acl_data = bt_hci_lmp_acl_data_slave; + s->device.lmp_acl_resp = bt_hci_lmp_acl_data_host; + s->device.lmp_mode_change = bt_hci_lmp_mode_change_slave; + + /* Keep updated! */ + /* Also keep in sync with supported commands bitmask in + * bt_hci_read_local_commands_rp */ + s->device.lmp_caps = 0x8000199b7e85355fll; + + bt_hci_reset(s); + + s->info.cmd_send = bt_submit_hci; + s->info.sco_send = bt_submit_sco; + s->info.acl_send = bt_submit_acl; + s->info.bdaddr_set = bt_hci_bdaddr_set; + + s->device.handle_destroy = bt_hci_destroy; + + return &s->info; +} + +static void bt_hci_done(struct HCIInfo *info) +{ + struct bt_hci_s *hci = hci_from_info(info); + int handle; + + bt_device_done(&hci->device); + + if (hci->device.lmp_name) + qemu_free((void *) hci->device.lmp_name); + + /* Be gentle and send DISCONNECT to all connected peers and those + * currently waiting for us to accept or reject a connection request. + * This frees the links. */ + if (hci->conn_req_host) { + bt_hci_connection_reject(hci, + hci->conn_req_host, HCI_OE_POWER_OFF); + return; + } + + for (handle = HCI_HANDLE_OFFSET; + handle < (HCI_HANDLE_OFFSET | HCI_HANDLES_MAX); handle ++) + if (!bt_hci_handle_bad(hci, handle)) + bt_hci_disconnect(hci, handle, HCI_OE_POWER_OFF); + + /* TODO: this is not enough actually, there may be slaves from whom + * we have requested a connection who will soon (or not) respond with + * an accept or a reject, so we should also check if hci->lm.connecting + * is non-zero and if so, avoid freeing the hci but otherwise disappear + * from all qemu social life (e.g. stop scanning and request to be + * removed from s->device.net) and arrange for + * s->device.lmp_connection_complete to free the remaining bits once + * hci->lm.awaiting_bdaddr[] is empty. */ + + qemu_free_timer(hci->lm.inquiry_done); + qemu_free_timer(hci->lm.inquiry_next); + qemu_free_timer(hci->conn_accept_timer); + + qemu_free(hci); +} diff --git a/hw/bt-hid.c b/hw/bt-hid.c new file mode 100644 index 0000000..af0c3d5 --- /dev/null +++ b/hw/bt-hid.c @@ -0,0 +1,571 @@ +/* + * QEMU Bluetooth HID Profile wrapper for USB HID. + * + * Copyright (C) 2007-2008 OpenMoko, Inc. + * Written by Andrzej Zaborowski <andrew@openedhand.com> + * + * 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 or + * (at your option) version 3 of the License. + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "qemu-common.h" +#include "usb.h" +#include "bt.h" + +enum hid_transaction_req { + BT_HANDSHAKE = 0x0, + BT_HID_CONTROL = 0x1, + BT_GET_REPORT = 0x4, + BT_SET_REPORT = 0x5, + BT_GET_PROTOCOL = 0x6, + BT_SET_PROTOCOL = 0x7, + BT_GET_IDLE = 0x8, + BT_SET_IDLE = 0x9, + BT_DATA = 0xa, + BT_DATC = 0xb, +}; + +enum hid_transaction_handshake { + BT_HS_SUCCESSFUL = 0x0, + BT_HS_NOT_READY = 0x1, + BT_HS_ERR_INVALID_REPORT_ID = 0x2, + BT_HS_ERR_UNSUPPORTED_REQUEST = 0x3, + BT_HS_ERR_INVALID_PARAMETER = 0x4, + BT_HS_ERR_UNKNOWN = 0xe, + BT_HS_ERR_FATAL = 0xf, +}; + +enum hid_transaction_control { + BT_HC_NOP = 0x0, + BT_HC_HARD_RESET = 0x1, + BT_HC_SOFT_RESET = 0x2, + BT_HC_SUSPEND = 0x3, + BT_HC_EXIT_SUSPEND = 0x4, + BT_HC_VIRTUAL_CABLE_UNPLUG = 0x5, +}; + +enum hid_protocol { + BT_HID_PROTO_BOOT = 0, + BT_HID_PROTO_REPORT = 1, +}; + +enum hid_boot_reportid { + BT_HID_BOOT_INVALID = 0, + BT_HID_BOOT_KEYBOARD, + BT_HID_BOOT_MOUSE, +}; + +enum hid_data_pkt { + BT_DATA_OTHER = 0, + BT_DATA_INPUT, + BT_DATA_OUTPUT, + BT_DATA_FEATURE, +}; + +#define BT_HID_MTU 48 + +/* 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 + +struct bt_hid_device_s { + struct bt_l2cap_device_s btdev; + struct bt_l2cap_conn_params_s *control; + struct bt_l2cap_conn_params_s *interrupt; + USBDevice *usbdev; + + int proto; + int connected; + int data_type; + int intr_state; + struct { + int len; + uint8_t buffer[1024]; + } dataother, datain, dataout, feature, intrdataout; + enum { + bt_state_ready, + bt_state_transaction, + bt_state_suspend, + } state; +}; + +static void bt_hid_reset(struct bt_hid_device_s *s) +{ + struct bt_scatternet_s *net = s->btdev.device.net; + + /* Go as far as... */ + bt_l2cap_device_done(&s->btdev); + bt_l2cap_device_init(&s->btdev, net); + + s->usbdev->handle_reset(s->usbdev); + s->proto = BT_HID_PROTO_REPORT; + s->state = bt_state_ready; + s->dataother.len = 0; + s->datain.len = 0; + s->dataout.len = 0; + s->feature.len = 0; + s->intrdataout.len = 0; + s->intr_state = 0; +} + +static int bt_hid_out(struct bt_hid_device_s *s) +{ + USBPacket p; + + if (s->data_type == BT_DATA_OUTPUT) { + p.pid = USB_TOKEN_OUT; + p.devep = 1; + p.data = s->dataout.buffer; + p.len = s->dataout.len; + s->dataout.len = s->usbdev->handle_data(s->usbdev, &p); + + return s->dataout.len; + } + + if (s->data_type == BT_DATA_FEATURE) { + /* XXX: + * does this send a USB_REQ_CLEAR_FEATURE/USB_REQ_SET_FEATURE + * or a SET_REPORT? */ + p.devep = 0; + } + + return -1; +} + +static int bt_hid_in(struct bt_hid_device_s *s) +{ + USBPacket p; + + p.pid = USB_TOKEN_IN; + p.devep = 1; + p.data = s->datain.buffer; + p.len = sizeof(s->datain.buffer); + s->datain.len = s->usbdev->handle_data(s->usbdev, &p); + + return s->datain.len; +} + +static void bt_hid_send_handshake(struct bt_hid_device_s *s, int result) +{ + *s->control->sdu_out(s->control, 1) = + (BT_HANDSHAKE << 4) | result; + s->control->sdu_submit(s->control); +} + +static void bt_hid_send_control(struct bt_hid_device_s *s, int operation) +{ + *s->control->sdu_out(s->control, 1) = + (BT_HID_CONTROL << 4) | operation; + s->control->sdu_submit(s->control); +} + +static void bt_hid_disconnect(struct bt_hid_device_s *s) +{ + /* Disconnect s->control and s->interrupt */ +} + +static void bt_hid_send_data(struct bt_l2cap_conn_params_s *ch, int type, + const uint8_t *data, int len) +{ + uint8_t *pkt, hdr = (BT_DATA << 4) | type; + int plen; + + do { + plen = MIN(len, ch->remote_mtu - 1); + pkt = ch->sdu_out(ch, plen + 1); + + pkt[0] = hdr; + if (plen) + memcpy(pkt + 1, data, plen); + ch->sdu_submit(ch); + + len -= plen; + data += plen; + hdr = (BT_DATC << 4) | type; + } while (plen == ch->remote_mtu - 1); +} + +static void bt_hid_control_transaction(struct bt_hid_device_s *s, + const uint8_t *data, int len) +{ + uint8_t type, parameter; + int rlen, ret = -1; + if (len < 1) + return; + + type = data[0] >> 4; + parameter = data[0] & 0xf; + + switch (type) { + case BT_HANDSHAKE: + case BT_DATA: + switch (parameter) { + default: + /* These are not expected to be sent this direction. */ + ret = BT_HS_ERR_INVALID_PARAMETER; + } + break; + + case BT_HID_CONTROL: + if (len != 1 || (parameter != BT_HC_VIRTUAL_CABLE_UNPLUG && + s->state == bt_state_transaction)) { + ret = BT_HS_ERR_INVALID_PARAMETER; + break; + } + switch (parameter) { + case BT_HC_NOP: + break; + case BT_HC_HARD_RESET: + case BT_HC_SOFT_RESET: + bt_hid_reset(s); + break; + case BT_HC_SUSPEND: + if (s->state == bt_state_ready) + s->state = bt_state_suspend; + else + ret = BT_HS_ERR_INVALID_PARAMETER; + break; + case BT_HC_EXIT_SUSPEND: + if (s->state == bt_state_suspend) + s->state = bt_state_ready; + else + ret = BT_HS_ERR_INVALID_PARAMETER; + break; + case BT_HC_VIRTUAL_CABLE_UNPLUG: + bt_hid_disconnect(s); + break; + default: + ret = BT_HS_ERR_INVALID_PARAMETER; + } + break; + + case BT_GET_REPORT: + /* No ReportIDs declared. */ + if (((parameter & 8) && len != 3) || + (!(parameter & 8) && len != 1) || + s->state != bt_state_ready) { + ret = BT_HS_ERR_INVALID_PARAMETER; + break; + } + if (parameter & 8) + rlen = data[2] | (data[3] << 8); + else + rlen = INT_MAX; + switch (parameter & 3) { + case BT_DATA_OTHER: + ret = BT_HS_ERR_INVALID_PARAMETER; + break; + case BT_DATA_INPUT: + /* Here we can as well poll s->usbdev */ + bt_hid_send_data(s->control, BT_DATA_INPUT, + s->datain.buffer, MIN(rlen, s->datain.len)); + break; + case BT_DATA_OUTPUT: + bt_hid_send_data(s->control, BT_DATA_OUTPUT, + s->dataout.buffer, MIN(rlen, s->dataout.len)); + break; + case BT_DATA_FEATURE: + bt_hid_send_data(s->control, BT_DATA_FEATURE, + s->feature.buffer, MIN(rlen, s->feature.len)); + break; + } + break; + + case BT_SET_REPORT: + if (len < 2 || len > BT_HID_MTU || s->state != bt_state_ready || + (parameter & 3) == BT_DATA_OTHER || + (parameter & 3) == BT_DATA_INPUT) { + ret = BT_HS_ERR_INVALID_PARAMETER; + break; + } + s->data_type = parameter & 3; + if (s->data_type == BT_DATA_OUTPUT) { + s->dataout.len = len - 1; + memcpy(s->dataout.buffer, data + 1, s->dataout.len); + } else { + s->feature.len = len - 1; + memcpy(s->feature.buffer, data + 1, s->feature.len); + } + if (len == BT_HID_MTU) + s->state = bt_state_transaction; + else + bt_hid_out(s); + break; + + case BT_GET_PROTOCOL: + if (len != 1 || s->state == bt_state_transaction) { + ret = BT_HS_ERR_INVALID_PARAMETER; + break; + } + *s->control->sdu_out(s->control, 1) = s->proto; + s->control->sdu_submit(s->control); + break; + + case BT_SET_PROTOCOL: + if (len != 1 || s->state == bt_state_transaction || + (parameter != BT_HID_PROTO_BOOT && + parameter != BT_HID_PROTO_REPORT)) { + ret = BT_HS_ERR_INVALID_PARAMETER; + break; + } + s->proto = parameter; + s->usbdev->handle_control(s->usbdev, SET_PROTOCOL, s->proto, 0, 0, + NULL); + ret = BT_HS_SUCCESSFUL; + break; + + case BT_GET_IDLE: + if (len != 1 || s->state == bt_state_transaction) { + ret = BT_HS_ERR_INVALID_PARAMETER; + break; + } + s->usbdev->handle_control(s->usbdev, GET_IDLE, 0, 0, 1, + s->control->sdu_out(s->control, 1)); + s->control->sdu_submit(s->control); + break; + + case BT_SET_IDLE: + if (len != 2 || s->state == bt_state_transaction) { + ret = BT_HS_ERR_INVALID_PARAMETER; + break; + } + + /* We don't need to know about the Idle Rate here really, + * so just pass it on to the device. */ + ret = s->usbdev->handle_control(s->usbdev, + SET_IDLE, data[1], 0, 0, NULL) ? + BT_HS_SUCCESSFUL : BT_HS_ERR_INVALID_PARAMETER; + /* XXX: Does this generate a handshake? */ + break; + + case BT_DATC: + if (len > BT_HID_MTU || s->state != bt_state_transaction) { + ret = BT_HS_ERR_INVALID_PARAMETER; + break; + } + if (s->data_type == BT_DATA_OUTPUT) { + memcpy(s->dataout.buffer + s->dataout.len, data + 1, len - 1); + s->dataout.len += len - 1; + } else { + memcpy(s->feature.buffer + s->feature.len, data + 1, len - 1); + s->feature.len += len - 1; + } + if (len < BT_HID_MTU) { + bt_hid_out(s); + s->state = bt_state_ready; + } + break; + + default: + ret = BT_HS_ERR_UNSUPPORTED_REQUEST; + } + + if (ret != -1) + bt_hid_send_handshake(s, ret); +} + +static void bt_hid_control_sdu(void *opaque, const uint8_t *data, int len) +{ + struct bt_hid_device_s *hid = opaque; + + bt_hid_control_transaction(hid, data, len); +} + +static void bt_hid_datain(void *opaque) +{ + struct bt_hid_device_s *hid = opaque; + + /* If suspended, wake-up and send a wake-up event first. We might + * want to also inspect the input report and ignore event like + * mouse movements until a button event occurs. */ + if (hid->state == bt_state_suspend) { + hid->state = bt_state_ready; + } + + if (bt_hid_in(hid) > 0) + /* TODO: when in boot-mode precede any Input reports with the ReportID + * byte, here and in GetReport/SetReport on the Control channel. */ + bt_hid_send_data(hid->interrupt, BT_DATA_INPUT, + hid->datain.buffer, hid->datain.len); +} + +static void bt_hid_interrupt_sdu(void *opaque, const uint8_t *data, int len) +{ + struct bt_hid_device_s *hid = opaque; + + if (len > BT_HID_MTU || len < 1) + goto bad; + if ((data[0] & 3) != BT_DATA_OUTPUT) + goto bad; + if ((data[0] >> 4) == BT_DATA) { + if (hid->intr_state) + goto bad; + + hid->data_type = BT_DATA_OUTPUT; + hid->intrdataout.len = 0; + } else if ((data[0] >> 4) == BT_DATC) { + if (!hid->intr_state) + goto bad; + } else + goto bad; + + memcpy(hid->intrdataout.buffer + hid->intrdataout.len, data + 1, len - 1); + hid->intrdataout.len += len - 1; + hid->intr_state = (len == BT_HID_MTU); + if (!hid->intr_state) { + memcpy(hid->dataout.buffer, hid->intrdataout.buffer, + hid->dataout.len = hid->intrdataout.len); + bt_hid_out(hid); + } + + return; +bad: + fprintf(stderr, "%s: bad transaction on Interrupt channel.\n", + __FUNCTION__); +} + +/* "Virtual cable" plug/unplug event. */ +static void bt_hid_connected_update(struct bt_hid_device_s *hid) +{ + int prev = hid->connected; + + hid->connected = hid->control && hid->interrupt; + + /* Stop page-/inquiry-scanning when a host is connected. */ + hid->btdev.device.page_scan = !hid->connected; + hid->btdev.device.inquiry_scan = !hid->connected; + + if (hid->connected && !prev) { + hid->usbdev->handle_reset(hid->usbdev); + hid->proto = BT_HID_PROTO_REPORT; + } + + /* Should set HIDVirtualCable in SDP (possibly need to check that SDP + * isn't destroyed yet, in case we're being called from handle_destroy) */ +} + +static void bt_hid_close_control(void *opaque) +{ + struct bt_hid_device_s *hid = opaque; + + hid->control = NULL; + bt_hid_connected_update(hid); +} + +static void bt_hid_close_interrupt(void *opaque) +{ + struct bt_hid_device_s *hid = opaque; + + hid->interrupt = NULL; + bt_hid_connected_update(hid); +} + +static int bt_hid_new_control_ch(struct bt_l2cap_device_s *dev, + struct bt_l2cap_conn_params_s *params) +{ + struct bt_hid_device_s *hid = (struct bt_hid_device_s *) dev; + + if (hid->control) + return 1; + + hid->control = params; + hid->control->opaque = hid; + hid->control->close = bt_hid_close_control; + hid->control->sdu_in = bt_hid_control_sdu; + + bt_hid_connected_update(hid); + + return 0; +} + +static int bt_hid_new_interrupt_ch(struct bt_l2cap_device_s *dev, + struct bt_l2cap_conn_params_s *params) +{ + struct bt_hid_device_s *hid = (struct bt_hid_device_s *) dev; + + if (hid->interrupt) + return 1; + + hid->interrupt = params; + hid->interrupt->opaque = hid; + hid->interrupt->close = bt_hid_close_interrupt; + hid->interrupt->sdu_in = bt_hid_interrupt_sdu; + + bt_hid_connected_update(hid); + + return 0; +} + +static void bt_hid_destroy(struct bt_device_s *dev) +{ + struct bt_hid_device_s *hid = (struct bt_hid_device_s *) dev; + + if (hid->connected) + bt_hid_send_control(hid, BT_HC_VIRTUAL_CABLE_UNPLUG); + bt_l2cap_device_done(&hid->btdev); + + hid->usbdev->handle_destroy(hid->usbdev); + + qemu_free(hid); +} + +enum peripheral_minor_class { + class_other = 0 << 4, + class_keyboard = 1 << 4, + class_pointing = 2 << 4, + class_combo = 3 << 4, +}; + +static struct bt_device_s *bt_hid_init(struct bt_scatternet_s *net, + USBDevice *dev, enum peripheral_minor_class minor) +{ + struct bt_hid_device_s *s = qemu_mallocz(sizeof(*s)); + uint32_t class = + /* Format type */ + (0 << 0) | + /* Device class */ + (minor << 2) | + (5 << 8) | /* "Peripheral" */ + /* Service classes */ + (1 << 13) | /* Limited discoverable mode */ + (1 << 19); /* Capturing device (?) */ + + bt_l2cap_device_init(&s->btdev, net); + bt_l2cap_sdp_init(&s->btdev); + bt_l2cap_psm_register(&s->btdev, BT_PSM_HID_CTRL, + BT_HID_MTU, bt_hid_new_control_ch); + bt_l2cap_psm_register(&s->btdev, BT_PSM_HID_INTR, + BT_HID_MTU, bt_hid_new_interrupt_ch); + + s->usbdev = dev; + s->btdev.device.lmp_name = s->usbdev->devname; + usb_hid_datain_cb(s->usbdev, s, bt_hid_datain); + + s->btdev.device.handle_destroy = bt_hid_destroy; + + s->btdev.device.class[0] = (class >> 0) & 0xff; + s->btdev.device.class[1] = (class >> 8) & 0xff; + s->btdev.device.class[2] = (class >> 16) & 0xff; + + return &s->btdev.device; +} + +struct bt_device_s *bt_keyboard_init(struct bt_scatternet_s *net) +{ + return bt_hid_init(net, usb_keyboard_init(), class_keyboard); +} diff --git a/hw/bt-l2cap.c b/hw/bt-l2cap.c new file mode 100644 index 0000000..b22b761 --- /dev/null +++ b/hw/bt-l2cap.c @@ -0,0 +1,1364 @@ +/* + * QEMU Bluetooth L2CAP logic. + * + * Copyright (C) 2008 Andrzej Zaborowski <balrog@zabor.org> + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "qemu-common.h" +#include "qemu-timer.h" +#include "bt.h" + +#define L2CAP_CID_MAX 0x100 /* Between 0x40 and 0x10000 */ + +struct l2cap_instance_s { + struct bt_link_s *link; + struct bt_l2cap_device_s *dev; + int role; + + uint8_t frame_in[65535 + L2CAP_HDR_SIZE] __attribute__ ((aligned (4))); + int frame_in_len; + + uint8_t frame_out[65535 + L2CAP_HDR_SIZE] __attribute__ ((aligned (4))); + int frame_out_len; + + /* Signalling channel timers. They exist per-request but we can make + * sure we have no more than one outstanding request at any time. */ + QEMUTimer *rtx; + QEMUTimer *ertx; + + int last_id; + int next_id; + + struct l2cap_chan_s { + struct bt_l2cap_conn_params_s params; + + void (*frame_in)(struct l2cap_chan_s *chan, uint16_t cid, + const l2cap_hdr *hdr, int len); + int mps; + int min_mtu; + + struct l2cap_instance_s *l2cap; + + /* Only allocated channels */ + uint16_t remote_cid; +#define L2CAP_CFG_INIT 2 +#define L2CAP_CFG_ACC 1 + int config_req_id; /* TODO: handle outgoing requests generically */ + int config; + + /* Only connection-oriented channels. Note: if we allow the tx and + * rx traffic to be in different modes at any time, we need two. */ + int mode; + + /* Only flow-controlled, connection-oriented channels */ + uint8_t sdu[65536]; /* TODO: dynamically allocate */ + int len_cur, len_total; + int rexmit; + int monitor_timeout; + QEMUTimer *monitor_timer; + QEMUTimer *retransmission_timer; + } *cid[L2CAP_CID_MAX]; + /* The channel state machine states map as following: + * CLOSED -> !cid[N] + * WAIT_CONNECT -> never occurs + * WAIT_CONNECT_RSP -> never occurs + * CONFIG -> cid[N] && config < 3 + * WAIT_CONFIG -> never occurs, cid[N] && config == 0 && !config_r + * WAIT_SEND_CONFIG -> never occurs, cid[N] && config == 1 && !config_r + * WAIT_CONFIG_REQ_RSP -> cid[N] && config == 0 && config_req_id + * WAIT_CONFIG_RSP -> cid[N] && config == 1 && config_req_id + * WAIT_CONFIG_REQ -> cid[N] && config == 2 + * OPEN -> cid[N] && config == 3 + * WAIT_DISCONNECT -> never occurs + */ + + struct l2cap_chan_s signalling_ch; + struct l2cap_chan_s group_ch; +}; + +struct slave_l2cap_instance_s { + struct bt_link_s link; /* Underlying logical link (ACL) */ + struct l2cap_instance_s l2cap; +}; + +struct bt_l2cap_psm_s { + int psm; + int min_mtu; + int (*new_channel)(struct bt_l2cap_device_s *device, + struct bt_l2cap_conn_params_s *params); + struct bt_l2cap_psm_s *next; +}; + +static const uint16_t l2cap_fcs16_table[256] = { + 0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, + 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440, + 0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40, + 0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841, + 0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40, + 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41, + 0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641, + 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040, + 0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240, + 0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441, + 0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41, + 0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840, + 0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41, + 0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40, + 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640, + 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041, + 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240, + 0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441, + 0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41, + 0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840, + 0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41, + 0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40, + 0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640, + 0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041, + 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241, + 0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440, + 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40, + 0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841, + 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, + 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41, + 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, + 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040, +}; + +static uint16_t l2cap_fcs16(const uint8_t *message, int len) +{ + uint16_t fcs = 0x0000; + + while (len --) +#if 0 + { + int i; + + fcs ^= *message ++; + for (i = 8; i; -- i) + if (fcs & 1) + fcs = (fcs >> 1) ^ 0xa001; + else + fcs = (fcs >> 1); + } +#else + fcs = (fcs >> 8) ^ l2cap_fcs16_table[(fcs ^ *message ++) & 0xff]; +#endif + + return fcs; +} + +/* L2CAP layer logic (protocol) */ + +static void l2cap_retransmission_timer_update(struct l2cap_chan_s *ch) +{ +#if 0 + if (ch->mode != L2CAP_MODE_BASIC && ch->rexmit) + qemu_mod_timer(ch->retransmission_timer); + else + qemu_del_timer(ch->retransmission_timer); +#endif +} + +static void l2cap_monitor_timer_update(struct l2cap_chan_s *ch) +{ +#if 0 + if (ch->mode != L2CAP_MODE_BASIC && !ch->rexmit) + qemu_mod_timer(ch->monitor_timer); + else + qemu_del_timer(ch->monitor_timer); +#endif +} + +static void l2cap_command_reject(struct l2cap_instance_s *l2cap, int id, + uint16_t reason, const void *data, int plen) +{ + uint8_t *pkt; + l2cap_cmd_hdr *hdr; + l2cap_cmd_rej *params; + uint16_t len; + + reason = cpu_to_le16(reason); + len = cpu_to_le16(L2CAP_CMD_REJ_SIZE + plen); + + pkt = l2cap->signalling_ch.params.sdu_out(&l2cap->signalling_ch.params, + L2CAP_CMD_HDR_SIZE + L2CAP_CMD_REJ_SIZE + plen); + hdr = (void *) (pkt + 0); + params = (void *) (pkt + L2CAP_CMD_HDR_SIZE); + + hdr->code = L2CAP_COMMAND_REJ; + hdr->ident = id; + memcpy(&hdr->len, &len, sizeof(hdr->len)); + memcpy(¶ms->reason, &reason, sizeof(reason)); + if (plen) + memcpy(pkt + L2CAP_CMD_HDR_SIZE + L2CAP_CMD_REJ_SIZE, data, plen); + + l2cap->signalling_ch.params.sdu_submit(&l2cap->signalling_ch.params); +} + +static void l2cap_command_reject_cid(struct l2cap_instance_s *l2cap, int id, + uint16_t reason, uint16_t dcid, uint16_t scid) +{ + l2cap_cmd_rej_cid params = { + .dcid = dcid, + .scid = scid, + }; + + l2cap_command_reject(l2cap, id, reason, ¶ms, L2CAP_CMD_REJ_CID_SIZE); +} + +static void l2cap_connection_response(struct l2cap_instance_s *l2cap, + int dcid, int scid, int result, int status) +{ + uint8_t *pkt; + l2cap_cmd_hdr *hdr; + l2cap_conn_rsp *params; + + pkt = l2cap->signalling_ch.params.sdu_out(&l2cap->signalling_ch.params, + L2CAP_CMD_HDR_SIZE + L2CAP_CONN_RSP_SIZE); + hdr = (void *) (pkt + 0); + params = (void *) (pkt + L2CAP_CMD_HDR_SIZE); + + hdr->code = L2CAP_CONN_RSP; + hdr->ident = l2cap->last_id; + hdr->len = cpu_to_le16(L2CAP_CONN_RSP_SIZE); + + params->dcid = cpu_to_le16(dcid); + params->scid = cpu_to_le16(scid); + params->result = cpu_to_le16(result); + params->status = cpu_to_le16(status); + + l2cap->signalling_ch.params.sdu_submit(&l2cap->signalling_ch.params); +} + +static void l2cap_configuration_request(struct l2cap_instance_s *l2cap, + int dcid, int flag, const uint8_t *data, int len) +{ + uint8_t *pkt; + l2cap_cmd_hdr *hdr; + l2cap_conf_req *params; + + pkt = l2cap->signalling_ch.params.sdu_out(&l2cap->signalling_ch.params, + L2CAP_CMD_HDR_SIZE + L2CAP_CONF_REQ_SIZE(len)); + hdr = (void *) (pkt + 0); + params = (void *) (pkt + L2CAP_CMD_HDR_SIZE); + + /* TODO: unify the id sequencing */ + l2cap->last_id = l2cap->next_id; + l2cap->next_id = l2cap->next_id == 255 ? 1 : l2cap->next_id + 1; + + hdr->code = L2CAP_CONF_REQ; + hdr->ident = l2cap->last_id; + hdr->len = cpu_to_le16(L2CAP_CONF_REQ_SIZE(len)); + + params->dcid = cpu_to_le16(dcid); + params->flags = cpu_to_le16(flag); + if (len) + memcpy(params->data, data, len); + + l2cap->signalling_ch.params.sdu_submit(&l2cap->signalling_ch.params); +} + +static void l2cap_configuration_response(struct l2cap_instance_s *l2cap, + int scid, int flag, int result, const uint8_t *data, int len) +{ + uint8_t *pkt; + l2cap_cmd_hdr *hdr; + l2cap_conf_rsp *params; + + pkt = l2cap->signalling_ch.params.sdu_out(&l2cap->signalling_ch.params, + L2CAP_CMD_HDR_SIZE + L2CAP_CONF_RSP_SIZE(len)); + hdr = (void *) (pkt + 0); + params = (void *) (pkt + L2CAP_CMD_HDR_SIZE); + + hdr->code = L2CAP_CONF_RSP; + hdr->ident = l2cap->last_id; + hdr->len = cpu_to_le16(L2CAP_CONF_RSP_SIZE(len)); + + params->scid = cpu_to_le16(scid); + params->flags = cpu_to_le16(flag); + params->result = cpu_to_le16(result); + if (len) + memcpy(params->data, data, len); + + l2cap->signalling_ch.params.sdu_submit(&l2cap->signalling_ch.params); +} + +static void l2cap_disconnection_response(struct l2cap_instance_s *l2cap, + int dcid, int scid) +{ + uint8_t *pkt; + l2cap_cmd_hdr *hdr; + l2cap_disconn_rsp *params; + + pkt = l2cap->signalling_ch.params.sdu_out(&l2cap->signalling_ch.params, + L2CAP_CMD_HDR_SIZE + L2CAP_DISCONN_RSP_SIZE); + hdr = (void *) (pkt + 0); + params = (void *) (pkt + L2CAP_CMD_HDR_SIZE); + + hdr->code = L2CAP_DISCONN_RSP; + hdr->ident = l2cap->last_id; + hdr->len = cpu_to_le16(L2CAP_DISCONN_RSP_SIZE); + + params->dcid = cpu_to_le16(dcid); + params->scid = cpu_to_le16(scid); + + l2cap->signalling_ch.params.sdu_submit(&l2cap->signalling_ch.params); +} + +static void l2cap_echo_response(struct l2cap_instance_s *l2cap, + const uint8_t *data, int len) +{ + uint8_t *pkt; + l2cap_cmd_hdr *hdr; + uint8_t *params; + + pkt = l2cap->signalling_ch.params.sdu_out(&l2cap->signalling_ch.params, + L2CAP_CMD_HDR_SIZE + len); + hdr = (void *) (pkt + 0); + params = (void *) (pkt + L2CAP_CMD_HDR_SIZE); + + hdr->code = L2CAP_ECHO_RSP; + hdr->ident = l2cap->last_id; + hdr->len = cpu_to_le16(len); + + memcpy(params, data, len); + + l2cap->signalling_ch.params.sdu_submit(&l2cap->signalling_ch.params); +} + +static void l2cap_info_response(struct l2cap_instance_s *l2cap, int type, + int result, const uint8_t *data, int len) +{ + uint8_t *pkt; + l2cap_cmd_hdr *hdr; + l2cap_info_rsp *params; + + pkt = l2cap->signalling_ch.params.sdu_out(&l2cap->signalling_ch.params, + L2CAP_CMD_HDR_SIZE + L2CAP_INFO_RSP_SIZE + len); + hdr = (void *) (pkt + 0); + params = (void *) (pkt + L2CAP_CMD_HDR_SIZE); + + hdr->code = L2CAP_INFO_RSP; + hdr->ident = l2cap->last_id; + hdr->len = cpu_to_le16(L2CAP_INFO_RSP_SIZE + len); + + params->type = cpu_to_le16(type); + params->result = cpu_to_le16(result); + if (len) + memcpy(params->data, data, len); + + l2cap->signalling_ch.params.sdu_submit(&l2cap->signalling_ch.params); +} + +static uint8_t *l2cap_bframe_out(struct bt_l2cap_conn_params_s *parm, int len); +static void l2cap_bframe_submit(struct bt_l2cap_conn_params_s *parms); +#if 0 +static uint8_t *l2cap_iframe_out(struct bt_l2cap_conn_params_s *parm, int len); +static void l2cap_iframe_submit(struct bt_l2cap_conn_params_s *parm); +#endif +static void l2cap_bframe_in(struct l2cap_chan_s *ch, uint16_t cid, + const l2cap_hdr *hdr, int len); +static void l2cap_iframe_in(struct l2cap_chan_s *ch, uint16_t cid, + const l2cap_hdr *hdr, int len); + +static int l2cap_cid_new(struct l2cap_instance_s *l2cap) +{ + int i; + + for (i = L2CAP_CID_ALLOC; i < L2CAP_CID_MAX; i ++) + if (!l2cap->cid[i]) + return i; + + return L2CAP_CID_INVALID; +} + +static inline struct bt_l2cap_psm_s *l2cap_psm( + struct bt_l2cap_device_s *device, int psm) +{ + struct bt_l2cap_psm_s *ret = device->first_psm; + + while (ret && ret->psm != psm) + ret = ret->next; + + return ret; +} + +static struct l2cap_chan_s *l2cap_channel_open(struct l2cap_instance_s *l2cap, + int psm, int source_cid) +{ + struct l2cap_chan_s *ch = NULL; + struct bt_l2cap_psm_s *psm_info; + int result, status; + int cid = l2cap_cid_new(l2cap); + + if (cid) { + /* See what the channel is to be used for.. */ + psm_info = l2cap_psm(l2cap->dev, psm); + + if (psm_info) { + /* Device supports this use-case. */ + ch = qemu_mallocz(sizeof(*ch)); + ch->params.sdu_out = l2cap_bframe_out; + ch->params.sdu_submit = l2cap_bframe_submit; + ch->frame_in = l2cap_bframe_in; + ch->mps = 65536; + ch->min_mtu = MAX(48, psm_info->min_mtu); + ch->params.remote_mtu = MAX(672, ch->min_mtu); + ch->remote_cid = source_cid; + ch->mode = L2CAP_MODE_BASIC; + ch->l2cap = l2cap; + + /* Does it feel like opening yet another channel though? */ + if (!psm_info->new_channel(l2cap->dev, &ch->params)) { + l2cap->cid[cid] = ch; + + result = L2CAP_CR_SUCCESS; + status = L2CAP_CS_NO_INFO; + } else { + qemu_free(ch); + + result = L2CAP_CR_NO_MEM; + status = L2CAP_CS_NO_INFO; + } + } else { + result = L2CAP_CR_BAD_PSM; + status = L2CAP_CS_NO_INFO; + } + } else { + result = L2CAP_CR_NO_MEM; + status = L2CAP_CS_NO_INFO; + } + + l2cap_connection_response(l2cap, cid, source_cid, result, status); + + return ch; +} + +static void l2cap_channel_close(struct l2cap_instance_s *l2cap, + int cid, int source_cid) +{ + struct l2cap_chan_s *ch = NULL; + + /* According to Volume 3, section 6.1.1, pg 1048 of BT Core V2.0, a + * connection in CLOSED state still responds with a L2CAP_DisconnectRsp + * message on an L2CAP_DisconnectReq event. */ + if (unlikely(cid < L2CAP_CID_ALLOC)) { + l2cap_command_reject_cid(l2cap, l2cap->last_id, L2CAP_REJ_CID_INVAL, + cid, source_cid); + return; + } + if (likely(cid >= L2CAP_CID_ALLOC && cid < L2CAP_CID_MAX)) + ch = l2cap->cid[cid]; + + if (likely(ch)) { + if (ch->remote_cid != source_cid) { + fprintf(stderr, "%s: Ignoring a Disconnection Request with the " + "invalid SCID %04x.\n", __FUNCTION__, source_cid); + return; + } + + l2cap->cid[cid] = NULL; + + ch->params.close(ch->params.opaque); + qemu_free(ch); + } + + l2cap_disconnection_response(l2cap, cid, source_cid); +} + +static void l2cap_channel_config_null(struct l2cap_instance_s *l2cap, + struct l2cap_chan_s *ch) +{ + l2cap_configuration_request(l2cap, ch->remote_cid, 0, NULL, 0); + ch->config_req_id = l2cap->last_id; + ch->config &= ~L2CAP_CFG_INIT; +} + +static void l2cap_channel_config_req_event(struct l2cap_instance_s *l2cap, + struct l2cap_chan_s *ch) +{ + /* Use all default channel options and terminate negotiation. */ + l2cap_channel_config_null(l2cap, ch); +} + +static int l2cap_channel_config(struct l2cap_instance_s *l2cap, + struct l2cap_chan_s *ch, int flag, + const uint8_t *data, int len) +{ + l2cap_conf_opt *opt; + l2cap_conf_opt_qos *qos; + uint32_t val; + uint8_t rsp[len]; + int result = L2CAP_CONF_SUCCESS; + + data = memcpy(rsp, data, len); + while (len) { + opt = (void *) data; + + if (len < L2CAP_CONF_OPT_SIZE || + len < L2CAP_CONF_OPT_SIZE + opt->len) { + result = L2CAP_CONF_REJECT; + break; + } + data += L2CAP_CONF_OPT_SIZE + opt->len; + len -= L2CAP_CONF_OPT_SIZE + opt->len; + + switch (opt->type & 0x7f) { + case L2CAP_CONF_MTU: + if (opt->len != 2) { + result = L2CAP_CONF_REJECT; + break; + } + + /* MTU */ + val = le16_to_cpup((void *) opt->val); + if (val < ch->min_mtu) { + cpu_to_le16w((void *) opt->val, ch->min_mtu); + result = L2CAP_CONF_UNACCEPT; + break; + } + + ch->params.remote_mtu = val; + break; + + case L2CAP_CONF_FLUSH_TO: + if (opt->len != 2) { + result = L2CAP_CONF_REJECT; + break; + } + + /* Flush Timeout */ + val = le16_to_cpup((void *) opt->val); + if (val < 0x0001) { + opt->val[0] = 0xff; + opt->val[1] = 0xff; + result = L2CAP_CONF_UNACCEPT; + break; + } + break; + + case L2CAP_CONF_QOS: + if (opt->len != L2CAP_CONF_OPT_QOS_SIZE) { + result = L2CAP_CONF_REJECT; + break; + } + qos = (void *) opt->val; + + /* Flags */ + val = qos->flags; + if (val) { + qos->flags = 0; + result = L2CAP_CONF_UNACCEPT; + } + + /* Service type */ + val = qos->service_type; + if (val != L2CAP_CONF_QOS_BEST_EFFORT && + val != L2CAP_CONF_QOS_NO_TRAFFIC) { + qos->service_type = L2CAP_CONF_QOS_BEST_EFFORT; + result = L2CAP_CONF_UNACCEPT; + } + + if (val != L2CAP_CONF_QOS_NO_TRAFFIC) { + /* XXX: These values should possibly be calculated + * based on LM / baseband properties also. */ + + /* Token rate */ + val = le32_to_cpu(qos->token_rate); + if (val == L2CAP_CONF_QOS_WILDCARD) + qos->token_rate = cpu_to_le32(0x100000); + + /* Token bucket size */ + val = le32_to_cpu(qos->token_bucket_size); + if (val == L2CAP_CONF_QOS_WILDCARD) + qos->token_bucket_size = cpu_to_le32(65500); + + /* Any Peak bandwidth value is correct to return as-is */ + /* Any Access latency value is correct to return as-is */ + /* Any Delay variation value is correct to return as-is */ + } + break; + + case L2CAP_CONF_RFC: + if (opt->len != 9) { + result = L2CAP_CONF_REJECT; + break; + } + + /* Mode */ + val = opt->val[0]; + switch (val) { + case L2CAP_MODE_BASIC: + ch->mode = val; + ch->frame_in = l2cap_bframe_in; + + /* All other parameters shall be ignored */ + break; + + case L2CAP_MODE_RETRANS: + case L2CAP_MODE_FLOWCTL: + ch->mode = val; + ch->frame_in = l2cap_iframe_in; + /* Note: most of these parameters refer to incoming traffic + * so we don't need to save them as long as we can accept + * incoming PDUs at any values of the parameters. */ + + /* TxWindow size */ + val = opt->val[1]; + if (val < 1 || val > 32) { + opt->val[1] = 32; + result = L2CAP_CONF_UNACCEPT; + break; + } + + /* MaxTransmit */ + val = opt->val[2]; + if (val < 1) { + opt->val[2] = 1; + result = L2CAP_CONF_UNACCEPT; + break; + } + + /* Remote Retransmission time-out shouldn't affect local + * operation (?) */ + + /* The Monitor time-out drives the local Monitor timer (?), + * so save the value. */ + val = (opt->val[6] << 8) | opt->val[5]; + if (val < 30) { + opt->val[5] = 100 & 0xff; + opt->val[6] = 100 >> 8; + result = L2CAP_CONF_UNACCEPT; + break; + } + ch->monitor_timeout = val; + l2cap_monitor_timer_update(ch); + + /* MPS */ + val = (opt->val[8] << 8) | opt->val[7]; + if (val < ch->min_mtu) { + opt->val[7] = ch->min_mtu & 0xff; + opt->val[8] = ch->min_mtu >> 8; + result = L2CAP_CONF_UNACCEPT; + break; + } + ch->mps = val; + break; + + default: + result = L2CAP_CONF_UNACCEPT; + break; + } + break; + + default: + if (!(opt->type >> 7)) + result = L2CAP_CONF_UNKNOWN; + break; + } + + if (result != L2CAP_CONF_SUCCESS) + break; /* XXX: should continue? */ + } + + l2cap_configuration_response(l2cap, ch->remote_cid, + flag, result, rsp, len); + + return result == L2CAP_CONF_SUCCESS && !flag; +} + +static void l2cap_channel_config_req_msg(struct l2cap_instance_s *l2cap, + int flag, int cid, const uint8_t *data, int len) +{ + struct l2cap_chan_s *ch; + + if (unlikely(cid >= L2CAP_CID_MAX || !l2cap->cid[cid])) { + l2cap_command_reject_cid(l2cap, l2cap->last_id, L2CAP_REJ_CID_INVAL, + cid, 0x0000); + return; + } + ch = l2cap->cid[cid]; + + /* From OPEN go to WAIT_CONFIG_REQ and from WAIT_CONFIG_REQ_RSP to + * WAIT_CONFIG_REQ_RSP. This is assuming the transition chart for OPEN + * on pg 1053, section 6.1.5, volume 3 of BT Core V2.0 has a mistake + * and on options-acceptable we go back to OPEN and otherwise to + * WAIT_CONFIG_REQ and not the other way. */ + ch->config &= ~L2CAP_CFG_ACC; + + if (l2cap_channel_config(l2cap, ch, flag, data, len)) + /* Go to OPEN or WAIT_CONFIG_RSP */ + ch->config |= L2CAP_CFG_ACC; + + /* TODO: if the incoming traffic flow control or retransmission mode + * changed then we probably need to also generate the + * ConfigureChannel_Req event and set the outgoing traffic to the same + * mode. */ + if (!(ch->config & L2CAP_CFG_INIT) && (ch->config & L2CAP_CFG_ACC) && + !ch->config_req_id) + l2cap_channel_config_req_event(l2cap, ch); +} + +static int l2cap_channel_config_rsp_msg(struct l2cap_instance_s *l2cap, + int result, int flag, int cid, const uint8_t *data, int len) +{ + struct l2cap_chan_s *ch; + + if (unlikely(cid >= L2CAP_CID_MAX || !l2cap->cid[cid])) { + l2cap_command_reject_cid(l2cap, l2cap->last_id, L2CAP_REJ_CID_INVAL, + cid, 0x0000); + return 0; + } + ch = l2cap->cid[cid]; + + if (ch->config_req_id != l2cap->last_id) + return 1; + ch->config_req_id = 0; + + if (result == L2CAP_CONF_SUCCESS) { + if (!flag) + ch->config |= L2CAP_CFG_INIT; + else + l2cap_channel_config_null(l2cap, ch); + } else + /* Retry until we succeed */ + l2cap_channel_config_req_event(l2cap, ch); + + return 0; +} + +static void l2cap_channel_open_req_msg(struct l2cap_instance_s *l2cap, + int psm, int source_cid) +{ + struct l2cap_chan_s *ch = l2cap_channel_open(l2cap, psm, source_cid); + + if (!ch) + return; + + /* Optional */ + if (!(ch->config & L2CAP_CFG_INIT) && !ch->config_req_id) + l2cap_channel_config_req_event(l2cap, ch); +} + +static void l2cap_info(struct l2cap_instance_s *l2cap, int type) +{ + uint8_t data[4]; + int len = 0; + int result = L2CAP_IR_SUCCESS; + + switch (type) { + case L2CAP_IT_CL_MTU: + data[len ++] = l2cap->group_ch.mps & 0xff; + data[len ++] = l2cap->group_ch.mps >> 8; + break; + + case L2CAP_IT_FEAT_MASK: + /* (Prematurely) report Flow control and Retransmission modes. */ + data[len ++] = 0x03; + data[len ++] = 0x00; + data[len ++] = 0x00; + data[len ++] = 0x00; + break; + + default: + result = L2CAP_IR_NOTSUPP; + } + + l2cap_info_response(l2cap, type, result, data, len); +} + +static void l2cap_command(struct l2cap_instance_s *l2cap, int code, int id, + const uint8_t *params, int len) +{ + int err; + +#if 0 + /* TODO: do the IDs really have to be in sequence? */ + if (!id || (id != l2cap->last_id && id != l2cap->next_id)) { + fprintf(stderr, "%s: out of sequence command packet ignored.\n", + __FUNCTION__); + return; + } +#else + l2cap->next_id = id; +#endif + if (id == l2cap->next_id) { + l2cap->last_id = l2cap->next_id; + l2cap->next_id = l2cap->next_id == 255 ? 1 : l2cap->next_id + 1; + } else { + /* TODO: Need to re-send the same response, without re-executing + * the corresponding command! */ + } + + switch (code) { + case L2CAP_COMMAND_REJ: + if (unlikely(len != 2 && len != 4 && len != 6)) { + err = L2CAP_REJ_CMD_NOT_UNDERSTOOD; + goto reject; + } + + /* We never issue commands other than Command Reject currently. */ + fprintf(stderr, "%s: stray Command Reject (%02x, %04x) " + "packet, ignoring.\n", __FUNCTION__, id, + le16_to_cpu(((l2cap_cmd_rej *) params)->reason)); + break; + + case L2CAP_CONN_REQ: + if (unlikely(len != L2CAP_CONN_REQ_SIZE)) { + err = L2CAP_REJ_CMD_NOT_UNDERSTOOD; + goto reject; + } + + l2cap_channel_open_req_msg(l2cap, + le16_to_cpu(((l2cap_conn_req *) params)->psm), + le16_to_cpu(((l2cap_conn_req *) params)->scid)); + break; + + case L2CAP_CONN_RSP: + if (unlikely(len != L2CAP_CONN_RSP_SIZE)) { + err = L2CAP_REJ_CMD_NOT_UNDERSTOOD; + goto reject; + } + + /* We never issue Connection Requests currently. TODO */ + fprintf(stderr, "%s: unexpected Connection Response (%02x) " + "packet, ignoring.\n", __FUNCTION__, id); + break; + + case L2CAP_CONF_REQ: + if (unlikely(len < L2CAP_CONF_REQ_SIZE(0))) { + err = L2CAP_REJ_CMD_NOT_UNDERSTOOD; + goto reject; + } + + l2cap_channel_config_req_msg(l2cap, + le16_to_cpu(((l2cap_conf_req *) params)->flags) & 1, + le16_to_cpu(((l2cap_conf_req *) params)->dcid), + ((l2cap_conf_req *) params)->data, + len - L2CAP_CONF_REQ_SIZE(0)); + break; + + case L2CAP_CONF_RSP: + if (unlikely(len < L2CAP_CONF_RSP_SIZE(0))) { + err = L2CAP_REJ_CMD_NOT_UNDERSTOOD; + goto reject; + } + + if (l2cap_channel_config_rsp_msg(l2cap, + le16_to_cpu(((l2cap_conf_rsp *) params)->result), + le16_to_cpu(((l2cap_conf_rsp *) params)->flags) & 1, + le16_to_cpu(((l2cap_conf_rsp *) params)->scid), + ((l2cap_conf_rsp *) params)->data, + len - L2CAP_CONF_RSP_SIZE(0))) + fprintf(stderr, "%s: unexpected Configure Response (%02x) " + "packet, ignoring.\n", __FUNCTION__, id); + break; + + case L2CAP_DISCONN_REQ: + if (unlikely(len != L2CAP_DISCONN_REQ_SIZE)) { + err = L2CAP_REJ_CMD_NOT_UNDERSTOOD; + goto reject; + } + + l2cap_channel_close(l2cap, + le16_to_cpu(((l2cap_disconn_req *) params)->dcid), + le16_to_cpu(((l2cap_disconn_req *) params)->scid)); + break; + + case L2CAP_DISCONN_RSP: + if (unlikely(len != L2CAP_DISCONN_RSP_SIZE)) { + err = L2CAP_REJ_CMD_NOT_UNDERSTOOD; + goto reject; + } + + /* We never issue Disconnection Requests currently. TODO */ + fprintf(stderr, "%s: unexpected Disconnection Response (%02x) " + "packet, ignoring.\n", __FUNCTION__, id); + break; + + case L2CAP_ECHO_REQ: + l2cap_echo_response(l2cap, params, len); + break; + + case L2CAP_ECHO_RSP: + /* We never issue Echo Requests currently. TODO */ + fprintf(stderr, "%s: unexpected Echo Response (%02x) " + "packet, ignoring.\n", __FUNCTION__, id); + break; + + case L2CAP_INFO_REQ: + if (unlikely(len != L2CAP_INFO_REQ_SIZE)) { + err = L2CAP_REJ_CMD_NOT_UNDERSTOOD; + goto reject; + } + + l2cap_info(l2cap, le16_to_cpu(((l2cap_info_req *) params)->type)); + break; + + case L2CAP_INFO_RSP: + if (unlikely(len != L2CAP_INFO_RSP_SIZE)) { + err = L2CAP_REJ_CMD_NOT_UNDERSTOOD; + goto reject; + } + + /* We never issue Information Requests currently. TODO */ + fprintf(stderr, "%s: unexpected Information Response (%02x) " + "packet, ignoring.\n", __FUNCTION__, id); + break; + + default: + err = L2CAP_REJ_CMD_NOT_UNDERSTOOD; + reject: + l2cap_command_reject(l2cap, id, err, 0, 0); + break; + } +} + +static void l2cap_rexmit_enable(struct l2cap_chan_s *ch, int enable) +{ + ch->rexmit = enable; + + l2cap_retransmission_timer_update(ch); + l2cap_monitor_timer_update(ch); +} + +/* Command frame SDU */ +static void l2cap_cframe_in(void *opaque, const uint8_t *data, int len) +{ + struct l2cap_instance_s *l2cap = opaque; + const l2cap_cmd_hdr *hdr; + int clen; + + while (len) { + hdr = (void *) data; + if (len < L2CAP_CMD_HDR_SIZE) + /* TODO: signal an error */ + return; + len -= L2CAP_CMD_HDR_SIZE; + data += L2CAP_CMD_HDR_SIZE; + + clen = le16_to_cpu(hdr->len); + if (len < clen) { + l2cap_command_reject(l2cap, hdr->ident, + L2CAP_REJ_CMD_NOT_UNDERSTOOD, 0, 0); + break; + } + + l2cap_command(l2cap, hdr->code, hdr->ident, data, clen); + len -= clen; + data += clen; + } +} + +/* Group frame SDU */ +static void l2cap_gframe_in(void *opaque, const uint8_t *data, int len) +{ +} + +/* Supervisory frame */ +static void l2cap_sframe_in(struct l2cap_chan_s *ch, uint16_t ctrl) +{ +} + +/* Basic L2CAP mode Information frame */ +static void l2cap_bframe_in(struct l2cap_chan_s *ch, uint16_t cid, + const l2cap_hdr *hdr, int len) +{ + /* We have a full SDU, no further processing */ + ch->params.sdu_in(ch->params.opaque, hdr->data, len); +} + +/* Flow Control and Retransmission mode frame */ +static void l2cap_iframe_in(struct l2cap_chan_s *ch, uint16_t cid, + const l2cap_hdr *hdr, int len) +{ + uint16_t fcs = le16_to_cpup((void *) (hdr->data + len - 2)); + + if (len < 4) + goto len_error; + if (l2cap_fcs16((const uint8_t *) hdr, L2CAP_HDR_SIZE + len - 2) != fcs) + goto fcs_error; + + if ((hdr->data[0] >> 7) == ch->rexmit) + l2cap_rexmit_enable(ch, !(hdr->data[0] >> 7)); + + if (hdr->data[0] & 1) { + if (len != 4) + /* TODO: Signal an error? */; + return; + + return l2cap_sframe_in(ch, le16_to_cpup((void *) hdr->data)); + } + + switch (hdr->data[1] >> 6) { /* SAR */ + case L2CAP_SAR_NO_SEG: + if (ch->len_total) + goto seg_error; + if (len - 4 > ch->mps) + goto len_error; + + return ch->params.sdu_in(ch->params.opaque, hdr->data + 2, len - 4); + + case L2CAP_SAR_START: + if (ch->len_total || len < 6) + goto seg_error; + if (len - 6 > ch->mps) + goto len_error; + + ch->len_total = le16_to_cpup((void *) (hdr->data + 2)); + if (len >= 6 + ch->len_total) + goto seg_error; + + ch->len_cur = len - 6; + memcpy(ch->sdu, hdr->data + 4, ch->len_cur); + break; + + case L2CAP_SAR_END: + if (!ch->len_total || ch->len_cur + len - 4 < ch->len_total) + goto seg_error; + if (len - 4 > ch->mps) + goto len_error; + + memcpy(ch->sdu + ch->len_cur, hdr->data + 2, len - 4); + return ch->params.sdu_in(ch->params.opaque, ch->sdu, ch->len_total); + + case L2CAP_SAR_CONT: + if (!ch->len_total || ch->len_cur + len - 4 >= ch->len_total) + goto seg_error; + if (len - 4 > ch->mps) + goto len_error; + + memcpy(ch->sdu + ch->len_cur, hdr->data + 2, len - 4); + ch->len_cur += len - 4; + break; + + seg_error: + len_error: /* TODO */ + fcs_error: /* TODO */ + ch->len_cur = 0; + ch->len_total = 0; + break; + } +} + +static void l2cap_frame_in(struct l2cap_instance_s *l2cap, + const l2cap_hdr *frame) +{ + uint16_t cid = le16_to_cpu(frame->cid); + uint16_t len = le16_to_cpu(frame->len); + + if (unlikely(cid >= L2CAP_CID_MAX || !l2cap->cid[cid])) { + fprintf(stderr, "%s: frame addressed to a non-existent L2CAP " + "channel %04x received.\n", __FUNCTION__, cid); + return; + } + + l2cap->cid[cid]->frame_in(l2cap->cid[cid], cid, frame, len); +} + +/* "Recombination" */ +static void l2cap_pdu_in(struct l2cap_instance_s *l2cap, + const uint8_t *data, int len) +{ + const l2cap_hdr *hdr = (void *) l2cap->frame_in; + + if (unlikely(len + l2cap->frame_in_len > sizeof(l2cap->frame_in))) { + if (l2cap->frame_in_len < sizeof(l2cap->frame_in)) { + memcpy(l2cap->frame_in + l2cap->frame_in_len, data, + sizeof(l2cap->frame_in) - l2cap->frame_in_len); + l2cap->frame_in_len = sizeof(l2cap->frame_in); + /* TODO: truncate */ + l2cap_frame_in(l2cap, hdr); + } + + return; + } + + memcpy(l2cap->frame_in + l2cap->frame_in_len, data, len); + l2cap->frame_in_len += len; + + if (len >= L2CAP_HDR_SIZE) + if (len >= L2CAP_HDR_SIZE + le16_to_cpu(hdr->len)) + l2cap_frame_in(l2cap, hdr); + /* There is never a start of a new PDU in the same ACL packet, so + * no need to memmove the remaining payload and loop. */ +} + +static inline uint8_t *l2cap_pdu_out(struct l2cap_instance_s *l2cap, + uint16_t cid, uint16_t len) +{ + l2cap_hdr *hdr = (void *) l2cap->frame_out; + + l2cap->frame_out_len = len + L2CAP_HDR_SIZE; + + hdr->cid = cpu_to_le16(cid); + hdr->len = cpu_to_le16(len); + + return l2cap->frame_out + L2CAP_HDR_SIZE; +} + +static inline void l2cap_pdu_submit(struct l2cap_instance_s *l2cap) +{ + /* TODO: Fragmentation */ + (l2cap->role ? + l2cap->link->slave->lmp_acl_data : l2cap->link->host->lmp_acl_resp) + (l2cap->link, l2cap->frame_out, 1, l2cap->frame_out_len); +} + +static uint8_t *l2cap_bframe_out(struct bt_l2cap_conn_params_s *parm, int len) +{ + struct l2cap_chan_s *chan = (struct l2cap_chan_s *) parm; + + if (len > chan->params.remote_mtu) { + fprintf(stderr, "%s: B-Frame for CID %04x longer than %i octets.\n", + __FUNCTION__, + chan->remote_cid, chan->params.remote_mtu); + exit(-1); + } + + return l2cap_pdu_out(chan->l2cap, chan->remote_cid, len); +} + +static void l2cap_bframe_submit(struct bt_l2cap_conn_params_s *parms) +{ + struct l2cap_chan_s *chan = (struct l2cap_chan_s *) parms; + + return l2cap_pdu_submit(chan->l2cap); +} + +#if 0 +/* Stub: Only used if an emulated device requests outgoing flow control */ +static uint8_t *l2cap_iframe_out(struct bt_l2cap_conn_params_s *parm, int len) +{ + struct l2cap_chan_s *chan = (struct l2cap_chan_s *) parm; + + if (len > chan->params.remote_mtu) { + /* TODO: slice into segments and queue each segment as a separate + * I-Frame in a FIFO of I-Frames, local to the CID. */ + } else { + /* TODO: add to the FIFO of I-Frames, local to the CID. */ + /* Possibly we need to return a pointer to a contiguous buffer + * for now and then memcpy from it into FIFOs in l2cap_iframe_submit + * while segmenting at the same time. */ + } + return 0; +} + +static void l2cap_iframe_submit(struct bt_l2cap_conn_params_s *parm) +{ + /* TODO: If flow control indicates clear to send, start submitting the + * invidual I-Frames from the FIFO, but don't remove them from there. + * Kick the appropriate timer until we get an S-Frame, and only then + * remove from FIFO or resubmit and re-kick the timer if the timer + * expired. */ +} +#endif + +static void l2cap_init(struct l2cap_instance_s *l2cap, + struct bt_link_s *link, int role) +{ + l2cap->link = link; + l2cap->role = role; + l2cap->dev = (struct bt_l2cap_device_s *) + (role ? link->host : link->slave); + + l2cap->next_id = 1; + + /* Establish the signalling channel */ + l2cap->signalling_ch.params.sdu_in = l2cap_cframe_in; + l2cap->signalling_ch.params.sdu_out = l2cap_bframe_out; + l2cap->signalling_ch.params.sdu_submit = l2cap_bframe_submit; + l2cap->signalling_ch.params.opaque = l2cap; + l2cap->signalling_ch.params.remote_mtu = 48; + l2cap->signalling_ch.remote_cid = L2CAP_CID_SIGNALLING; + l2cap->signalling_ch.frame_in = l2cap_bframe_in; + l2cap->signalling_ch.mps = 65536; + l2cap->signalling_ch.min_mtu = 48; + l2cap->signalling_ch.mode = L2CAP_MODE_BASIC; + l2cap->signalling_ch.l2cap = l2cap; + l2cap->cid[L2CAP_CID_SIGNALLING] = &l2cap->signalling_ch; + + /* Establish the connection-less data channel */ + l2cap->group_ch.params.sdu_in = l2cap_gframe_in; + l2cap->group_ch.params.opaque = l2cap; + l2cap->group_ch.frame_in = l2cap_bframe_in; + l2cap->group_ch.mps = 65533; + l2cap->group_ch.l2cap = l2cap; + l2cap->group_ch.remote_cid = L2CAP_CID_INVALID; + l2cap->cid[L2CAP_CID_GROUP] = &l2cap->group_ch; +} + +static void l2cap_teardown(struct l2cap_instance_s *l2cap, int send_disconnect) +{ + int cid; + + /* Don't send DISCONNECT if we are currently handling a DISCONNECT + * sent from the other side. */ + if (send_disconnect) { + if (l2cap->role) + l2cap->dev->device.lmp_disconnect_slave(l2cap->link); + /* l2cap->link is invalid from now on. */ + else + l2cap->dev->device.lmp_disconnect_master(l2cap->link); + } + + for (cid = L2CAP_CID_ALLOC; cid < L2CAP_CID_MAX; cid ++) + if (l2cap->cid[cid]) { + l2cap->cid[cid]->params.close(l2cap->cid[cid]->params.opaque); + free(l2cap->cid[cid]); + } + + if (l2cap->role) + qemu_free(l2cap); + else + qemu_free(l2cap->link); +} + +/* L2CAP glue to lower layers in bluetooth stack (LMP) */ + +static void l2cap_lmp_connection_request(struct bt_link_s *link) +{ + struct bt_l2cap_device_s *dev = (struct bt_l2cap_device_s *) link->slave; + struct slave_l2cap_instance_s *l2cap; + + /* Always accept - we only get called if (dev->device->page_scan). */ + + l2cap = qemu_mallocz(sizeof(struct slave_l2cap_instance_s)); + l2cap->link.slave = &dev->device; + l2cap->link.host = link->host; + l2cap_init(&l2cap->l2cap, &l2cap->link, 0); + + /* Always at the end */ + link->host->reject_reason = 0; + link->host->lmp_connection_complete(&l2cap->link); +} + +/* Stub */ +static void l2cap_lmp_connection_complete(struct bt_link_s *link) +{ + struct bt_l2cap_device_s *dev = (struct bt_l2cap_device_s *) link->host; + struct l2cap_instance_s *l2cap; + + if (dev->device.reject_reason) { + /* Signal to upper layer */ + return; + } + + l2cap = qemu_mallocz(sizeof(struct l2cap_instance_s)); + l2cap_init(l2cap, link, 1); + + link->acl_mode = acl_active; + + /* Signal to upper layer */ +} + +/* Stub */ +static void l2cap_lmp_disconnect_host(struct bt_link_s *link) +{ + struct bt_l2cap_device_s *dev = (struct bt_l2cap_device_s *) link->host; + struct l2cap_instance_s *l2cap = + /* TODO: Retrieve from upper layer */ (void *) dev; + + /* Signal to upper layer */ + + l2cap_teardown(l2cap, 0); +} + +static void l2cap_lmp_disconnect_slave(struct bt_link_s *link) +{ + struct slave_l2cap_instance_s *l2cap = + (struct slave_l2cap_instance_s *) link; + + l2cap_teardown(&l2cap->l2cap, 0); +} + +static void l2cap_lmp_acl_data_slave(struct bt_link_s *link, + const uint8_t *data, int start, int len) +{ + struct slave_l2cap_instance_s *l2cap = + (struct slave_l2cap_instance_s *) link; + + if (start) + l2cap->l2cap.frame_in_len = 0; + + l2cap_pdu_in(&l2cap->l2cap, data, len); +} + +/* Stub */ +static void l2cap_lmp_acl_data_host(struct bt_link_s *link, + const uint8_t *data, int start, int len) +{ + struct bt_l2cap_device_s *dev = (struct bt_l2cap_device_s *) link->host; + struct l2cap_instance_s *l2cap = + /* TODO: Retrieve from upper layer */ (void *) dev; + + if (start) + l2cap->frame_in_len = 0; + + l2cap_pdu_in(l2cap, data, len); +} + +static void l2cap_dummy_destroy(struct bt_device_s *dev) +{ + struct bt_l2cap_device_s *l2cap_dev = (struct bt_l2cap_device_s *) dev; + + bt_l2cap_device_done(l2cap_dev); +} + +void bt_l2cap_device_init(struct bt_l2cap_device_s *dev, + struct bt_scatternet_s *net) +{ + bt_device_init(&dev->device, net); + + dev->device.lmp_connection_request = l2cap_lmp_connection_request; + dev->device.lmp_connection_complete = l2cap_lmp_connection_complete; + dev->device.lmp_disconnect_master = l2cap_lmp_disconnect_host; + dev->device.lmp_disconnect_slave = l2cap_lmp_disconnect_slave; + dev->device.lmp_acl_data = l2cap_lmp_acl_data_slave; + dev->device.lmp_acl_resp = l2cap_lmp_acl_data_host; + + dev->device.handle_destroy = l2cap_dummy_destroy; +} + +void bt_l2cap_device_done(struct bt_l2cap_device_s *dev) +{ + bt_device_done(&dev->device); + + /* Should keep a list of all instances and go through it and + * invoke l2cap_teardown() for each. */ +} + +void bt_l2cap_psm_register(struct bt_l2cap_device_s *dev, int psm, int min_mtu, + int (*new_channel)(struct bt_l2cap_device_s *dev, + struct bt_l2cap_conn_params_s *params)) +{ + struct bt_l2cap_psm_s *new_psm = l2cap_psm(dev, psm); + + if (new_psm) { + fprintf(stderr, "%s: PSM %04x already registered for device `%s'.\n", + __FUNCTION__, psm, dev->device.lmp_name); + exit(-1); + } + + new_psm = qemu_mallocz(sizeof(*new_psm)); + new_psm->psm = psm; + new_psm->min_mtu = min_mtu; + new_psm->new_channel = new_channel; + new_psm->next = dev->first_psm; + dev->first_psm = new_psm; +} diff --git a/hw/bt-sdp.c b/hw/bt-sdp.c new file mode 100644 index 0000000..992de0e --- /dev/null +++ b/hw/bt-sdp.c @@ -0,0 +1,968 @@ +/* + * Service Discover Protocol server for QEMU L2CAP devices + * + * Copyright (C) 2008 Andrzej Zaborowski <balrog@zabor.org> + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "qemu-common.h" +#include "bt.h" + +struct bt_l2cap_sdp_state_s { + struct bt_l2cap_conn_params_s *channel; + + struct sdp_service_record_s { + int match; + + int *uuid; + int uuids; + struct sdp_service_attribute_s { + int match; + + int attribute_id; + int len; + void *pair; + } *attribute_list; + int attributes; + } *service_list; + int services; +}; + +static ssize_t sdp_datalen(const uint8_t **element, ssize_t *left) +{ + size_t len = *(*element) ++ & SDP_DSIZE_MASK; + + if (!*left) + return -1; + (*left) --; + + if (len < SDP_DSIZE_NEXT1) + return 1 << len; + else if (len == SDP_DSIZE_NEXT1) { + if (*left < 1) + return -1; + (*left) --; + + return *(*element) ++; + } else if (len == SDP_DSIZE_NEXT2) { + if (*left < 2) + return -1; + (*left) -= 2; + + len = (*(*element) ++) << 8; + return len | (*(*element) ++); + } else { + if (*left < 4) + return -1; + (*left) -= 4; + + len = (*(*element) ++) << 24; + len |= (*(*element) ++) << 16; + len |= (*(*element) ++) << 8; + return len | (*(*element) ++); + } +} + +static const uint8_t bt_base_uuid[12] = { + 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb, +}; + +static int sdp_uuid_match(struct sdp_service_record_s *record, + const uint8_t *uuid, ssize_t datalen) +{ + int *lo, hi, val; + + if (datalen == 16 || datalen == 4) { + if (datalen == 16 && memcmp(uuid + 4, bt_base_uuid, 12)) + return 0; + + if (uuid[0] | uuid[1]) + return 0; + uuid += 2; + } + + val = (uuid[0] << 8) | uuid[1]; + lo = record->uuid; + hi = record->uuids; + while (hi >>= 1) + if (lo[hi] <= val) + lo += hi; + + return *lo == val; +} + +#define CONTINUATION_PARAM_SIZE (1 + sizeof(int)) +#define MAX_PDU_OUT_SIZE 96 /* Arbitrary */ +#define PDU_HEADER_SIZE 5 +#define MAX_RSP_PARAM_SIZE (MAX_PDU_OUT_SIZE - PDU_HEADER_SIZE - \ + CONTINUATION_PARAM_SIZE) + +static int sdp_svc_match(struct bt_l2cap_sdp_state_s *sdp, + const uint8_t **req, ssize_t *len) +{ + size_t datalen; + int i; + + if ((**req & ~SDP_DSIZE_MASK) != SDP_DTYPE_UUID) + return 1; + + datalen = sdp_datalen(req, len); + if (datalen != 2 && datalen != 4 && datalen != 16) + return 1; + + for (i = 0; i < sdp->services; i ++) + if (sdp_uuid_match(&sdp->service_list[i], *req, datalen)) + sdp->service_list[i].match = 1; + + (*req) += datalen; + (*len) -= datalen; + + return 0; +} + +static ssize_t sdp_svc_search(struct bt_l2cap_sdp_state_s *sdp, + uint8_t *rsp, const uint8_t *req, ssize_t len) +{ + ssize_t seqlen; + int i, count, start, end, max; + int32_t handle; + + /* Perform the search */ + for (i = 0; i < sdp->services; i ++) + sdp->service_list[i].match = 0; + + if (len < 1) + return -SDP_INVALID_SYNTAX; + if ((*req & ~SDP_DSIZE_MASK) == SDP_DTYPE_SEQ) { + seqlen = sdp_datalen(&req, &len); + if (seqlen < 3 || len < seqlen) + return -SDP_INVALID_SYNTAX; + len -= seqlen; + + while (seqlen) + if (sdp_svc_match(sdp, &req, &seqlen)) + return -SDP_INVALID_SYNTAX; + } else if (sdp_svc_match(sdp, &req, &seqlen)) + return -SDP_INVALID_SYNTAX; + + if (len < 3) + return -SDP_INVALID_SYNTAX; + end = (req[0] << 8) | req[1]; + req += 2; + len -= 2; + + if (*req) { + if (len <= sizeof(int)) + return -SDP_INVALID_SYNTAX; + len -= sizeof(int); + memcpy(&start, req + 1, sizeof(int)); + } else + start = 0; + + if (len > 1); + return -SDP_INVALID_SYNTAX; + + /* Output the results */ + len = 4; + count = 0; + end = start; + for (i = 0; i < sdp->services; i ++) + if (sdp->service_list[i].match) { + if (count >= start && count < max && len + 4 < MAX_RSP_PARAM_SIZE) { + handle = i; + memcpy(rsp + len, &handle, 4); + len += 4; + end = count + 1; + } + + count ++; + } + + rsp[0] = count >> 8; + rsp[1] = count & 0xff; + rsp[2] = (end - start) >> 8; + rsp[3] = (end - start) & 0xff; + + if (end < count) { + rsp[len ++] = sizeof(int); + memcpy(rsp + len, &end, sizeof(int)); + len += 4; + } else + rsp[len ++] = 0; + + return len; +} + +static int sdp_attr_match(struct sdp_service_record_s *record, + const uint8_t **req, ssize_t *len) +{ + int i, start, end; + + if (**req == (SDP_DTYPE_UINT | SDP_DSIZE_2)) { + (*req) ++; + if (*len < 3) + return 1; + + start = (*(*req) ++) << 8; + start |= *(*req) ++; + end = start; + *len -= 3; + } else if (**req == (SDP_DTYPE_UINT | SDP_DSIZE_4)) { + (*req) ++; + if (*len < 5) + return 1; + + start = (*(*req) ++) << 8; + start |= *(*req) ++; + end = (*(*req) ++) << 8; + end |= *(*req) ++; + *len -= 5; + } else + return 1; + + for (i = 0; i < record->attributes; i ++) + if (record->attribute_list[i].attribute_id >= start && + record->attribute_list[i].attribute_id <= end) + record->attribute_list[i].match = 1; + + return 0; +} + +static ssize_t sdp_attr_get(struct bt_l2cap_sdp_state_s *sdp, + uint8_t *rsp, const uint8_t *req, ssize_t len) +{ + ssize_t seqlen; + int i, start, end, max; + int32_t handle; + struct sdp_service_record_s *record; + uint8_t *lst; + + /* Perform the search */ + if (len < 7) + return -SDP_INVALID_SYNTAX; + memcpy(&handle, req, 4); + req += 4; + len -= 4; + + if (handle < 0 || handle > sdp->services) + return -SDP_INVALID_RECORD_HANDLE; + record = &sdp->service_list[handle]; + + for (i = 0; i < record->attributes; i ++) + record->attribute_list[i].match = 0; + + max = (req[0] << 8) | req[1]; + req += 2; + len -= 2; + if (max < 0x0007) + return -SDP_INVALID_SYNTAX; + + if ((*req & ~SDP_DSIZE_MASK) == SDP_DTYPE_SEQ) { + seqlen = sdp_datalen(&req, &len); + if (seqlen < 3 || len < seqlen) + return -SDP_INVALID_SYNTAX; + len -= seqlen; + + while (seqlen) + if (sdp_attr_match(record, &req, &seqlen)) + return -SDP_INVALID_SYNTAX; + } else if (sdp_attr_match(record, &req, &seqlen)) + return -SDP_INVALID_SYNTAX; + + if (len < 1) + return -SDP_INVALID_SYNTAX; + + if (*req) { + if (len <= sizeof(int)) + return -SDP_INVALID_SYNTAX; + len -= sizeof(int); + memcpy(&start, req + 1, sizeof(int)); + } else + start = 0; + + if (len > 1) + return -SDP_INVALID_SYNTAX; + + /* Output the results */ + lst = rsp + 2; + max = MIN(max, MAX_RSP_PARAM_SIZE); + len = 3 - start; + end = 0; + for (i = 0; i < record->attributes; i ++) + if (record->attribute_list[i].match) { + if (len >= 0 && len + record->attribute_list[i].len < max) { + memcpy(lst + len, record->attribute_list[i].pair, + record->attribute_list[i].len); + end = len + record->attribute_list[i].len; + } + len += record->attribute_list[i].len; + } + if (0 >= start) { + lst[0] = SDP_DTYPE_SEQ | SDP_DSIZE_NEXT2; + lst[1] = (len + start - 3) >> 8; + lst[2] = (len + start - 3) & 0xff; + } + + rsp[0] = end >> 8; + rsp[1] = end & 0xff; + + if (end < len) { + len = end + start; + lst[end ++] = sizeof(int); + memcpy(lst + end, &len, sizeof(int)); + end += sizeof(int); + } else + lst[end ++] = 0; + + return end + 2; +} + +static int sdp_svc_attr_match(struct bt_l2cap_sdp_state_s *sdp, + const uint8_t **req, ssize_t *len) +{ + int i, j, start, end; + struct sdp_service_record_s *record; + + if (**req == (SDP_DTYPE_UINT | SDP_DSIZE_2)) { + (*req) ++; + if (*len < 3) + return 1; + + start = (*(*req) ++) << 8; + start |= *(*req) ++; + end = start; + *len -= 3; + } else if (**req == (SDP_DTYPE_UINT | SDP_DSIZE_4)) { + (*req) ++; + if (*len < 5) + return 1; + + start = (*(*req) ++) << 8; + start |= *(*req) ++; + end = (*(*req) ++) << 8; + end |= *(*req) ++; + *len -= 5; + } else + return 1; + + for (i = 0; i < sdp->services; i ++) + if ((record = &sdp->service_list[i])->match) + for (j = 0; j < record->attributes; j ++) + if (record->attribute_list[j].attribute_id >= start && + record->attribute_list[j].attribute_id <= end) + record->attribute_list[j].match = 1; + + return 0; +} + +static ssize_t sdp_svc_search_attr_get(struct bt_l2cap_sdp_state_s *sdp, + uint8_t *rsp, const uint8_t *req, ssize_t len) +{ + ssize_t seqlen; + int i, j, start, end, max; + struct sdp_service_record_s *record; + uint8_t *lst; + + /* Perform the search */ + for (i = 0; i < sdp->services; i ++) { + sdp->service_list[i].match = 0; + for (j = 0; j < sdp->service_list[i].attributes; j ++) + sdp->service_list[i].attribute_list[j].match = 0; + } + + if (len < 1) + return -SDP_INVALID_SYNTAX; + if ((*req & ~SDP_DSIZE_MASK) == SDP_DTYPE_SEQ) { + seqlen = sdp_datalen(&req, &len); + if (seqlen < 3 || len < seqlen) + return -SDP_INVALID_SYNTAX; + len -= seqlen; + + while (seqlen) + if (sdp_svc_match(sdp, &req, &seqlen)) + return -SDP_INVALID_SYNTAX; + } else if (sdp_svc_match(sdp, &req, &seqlen)) + return -SDP_INVALID_SYNTAX; + + if (len < 3) + return -SDP_INVALID_SYNTAX; + max = (req[0] << 8) | req[1]; + req += 2; + len -= 2; + if (max < 0x0007) + return -SDP_INVALID_SYNTAX; + + if ((*req & ~SDP_DSIZE_MASK) == SDP_DTYPE_SEQ) { + seqlen = sdp_datalen(&req, &len); + if (seqlen < 3 || len < seqlen) + return -SDP_INVALID_SYNTAX; + len -= seqlen; + + while (seqlen) + if (sdp_svc_attr_match(sdp, &req, &seqlen)) + return -SDP_INVALID_SYNTAX; + } else if (sdp_svc_attr_match(sdp, &req, &seqlen)) + return -SDP_INVALID_SYNTAX; + + if (len < 1) + return -SDP_INVALID_SYNTAX; + + if (*req) { + if (len <= sizeof(int)) + return -SDP_INVALID_SYNTAX; + len -= sizeof(int); + memcpy(&start, req + 1, sizeof(int)); + } else + start = 0; + + if (len > 1) + return -SDP_INVALID_SYNTAX; + + /* Output the results */ + /* This assumes empty attribute lists are never to be returned even + * for matching Service Records. In practice this shouldn't happen + * as the requestor will usually include the always present + * ServiceRecordHandle AttributeID in AttributeIDList. */ + lst = rsp + 2; + max = MIN(max, MAX_RSP_PARAM_SIZE); + len = 3 - start; + end = 0; + for (i = 0; i < sdp->services; i ++) + if ((record = &sdp->service_list[i])->match) { + len += 3; + seqlen = len; + for (j = 0; j < record->attributes; j ++) + if (record->attribute_list[j].match) { + if (len >= 0) + if (len + record->attribute_list[j].len < max) { + memcpy(lst + len, record->attribute_list[j].pair, + record->attribute_list[j].len); + end = len + record->attribute_list[j].len; + } + len += record->attribute_list[j].len; + } + if (seqlen == len) + len -= 3; + else if (seqlen >= 3 && seqlen < max) { + lst[seqlen - 3] = SDP_DTYPE_SEQ | SDP_DSIZE_NEXT2; + lst[seqlen - 2] = (len - seqlen) >> 8; + lst[seqlen - 1] = (len - seqlen) & 0xff; + } + } + if (len == 3 - start) + len -= 3; + else if (0 >= start) { + lst[0] = SDP_DTYPE_SEQ | SDP_DSIZE_NEXT2; + lst[1] = (len + start - 3) >> 8; + lst[2] = (len + start - 3) & 0xff; + } + + rsp[0] = end >> 8; + rsp[1] = end & 0xff; + + if (end < len) { + len = end + start; + lst[end ++] = sizeof(int); + memcpy(lst + end, &len, sizeof(int)); + end += sizeof(int); + } else + lst[end ++] = 0; + + return end + 2; +} + +static void bt_l2cap_sdp_sdu_in(void *opaque, const uint8_t *data, int len) +{ + struct bt_l2cap_sdp_state_s *sdp = opaque; + enum bt_sdp_cmd pdu_id; + uint8_t rsp[MAX_PDU_OUT_SIZE - PDU_HEADER_SIZE], *sdu_out; + int transaction_id, plen; + int err = 0; + int rsp_len = 0; + + if (len < 5) { + fprintf(stderr, "%s: short SDP PDU (%iB).\n", __FUNCTION__, len); + return; + } + + pdu_id = *data ++; + transaction_id = (data[0] << 8) | data[1]; + plen = (data[2] << 8) | data[3]; + data += 4; + len -= 5; + + if (len != plen) { + fprintf(stderr, "%s: wrong SDP PDU length (%iB != %iB).\n", + __FUNCTION__, plen, len); + err = SDP_INVALID_PDU_SIZE; + goto respond; + } + + switch (pdu_id) { + case SDP_SVC_SEARCH_REQ: + rsp_len = sdp_svc_search(sdp, rsp, data, len); + pdu_id = SDP_SVC_SEARCH_RSP; + break; + + case SDP_SVC_ATTR_REQ: + rsp_len = sdp_attr_get(sdp, rsp, data, len); + pdu_id = SDP_SVC_ATTR_RSP; + break; + + case SDP_SVC_SEARCH_ATTR_REQ: + rsp_len = sdp_svc_search_attr_get(sdp, rsp, data, len); + pdu_id = SDP_SVC_SEARCH_ATTR_RSP; + break; + + case SDP_ERROR_RSP: + case SDP_SVC_ATTR_RSP: + case SDP_SVC_SEARCH_RSP: + case SDP_SVC_SEARCH_ATTR_RSP: + default: + fprintf(stderr, "%s: unexpected SDP PDU ID %02x.\n", + __FUNCTION__, pdu_id); + err = SDP_INVALID_SYNTAX; + break; + } + + if (rsp_len < 0) { + err = -rsp_len; + rsp_len = 0; + } + +respond: + if (err) { + pdu_id = SDP_ERROR_RSP; + rsp[rsp_len ++] = err >> 8; + rsp[rsp_len ++] = err & 0xff; + } + + sdu_out = sdp->channel->sdu_out(sdp->channel, rsp_len + PDU_HEADER_SIZE); + + sdu_out[0] = pdu_id; + sdu_out[1] = transaction_id >> 8; + sdu_out[2] = transaction_id & 0xff; + sdu_out[3] = rsp_len >> 8; + sdu_out[4] = rsp_len & 0xff; + memcpy(sdu_out + PDU_HEADER_SIZE, rsp, rsp_len); + + sdp->channel->sdu_submit(sdp->channel); +} + +static void bt_l2cap_sdp_close_ch(void *opaque) +{ + struct bt_l2cap_sdp_state_s *sdp = opaque; + int i; + + for (i = 0; i < sdp->services; i ++) { + qemu_free(sdp->service_list[i].attribute_list->pair); + qemu_free(sdp->service_list[i].attribute_list); + qemu_free(sdp->service_list[i].uuid); + } + qemu_free(sdp->service_list); + qemu_free(sdp); +} + +struct sdp_def_service_s { + uint16_t class_uuid; + struct sdp_def_attribute_s { + uint16_t id; + struct sdp_def_data_element_s { + uint8_t type; + union { + uint32_t uint; + const char *str; + struct sdp_def_data_element_s *list; + } value; + } data; + } attributes[]; +}; + +/* Calculate a safe byte count to allocate that will store the given + * element, at the same time count elements of a UUID type. */ +static int sdp_attr_max_size(struct sdp_def_data_element_s *element, + int *uuids) +{ + int type = element->type & ~SDP_DSIZE_MASK; + int len; + + if (type == SDP_DTYPE_UINT || type == SDP_DTYPE_UUID || + type == SDP_DTYPE_BOOL) { + if (type == SDP_DTYPE_UUID) + (*uuids) ++; + return 1 + (1 << (element->type & SDP_DSIZE_MASK)); + } + + if (type == SDP_DTYPE_STRING || type == SDP_DTYPE_URL) { + if (element->type & SDP_DSIZE_MASK) { + for (len = 0; element->value.str[len] | + element->value.str[len + 1]; len ++); + return len; + } else + return 2 + strlen(element->value.str); + } + + if (type != SDP_DTYPE_SEQ) + exit(-1); + len = 2; + element = element->value.list; + while (element->type) + len += sdp_attr_max_size(element ++, uuids); + if (len > 255) + exit (-1); + + return len; +} + +static int sdp_attr_write(uint8_t *data, + struct sdp_def_data_element_s *element, int **uuid) +{ + int type = element->type & ~SDP_DSIZE_MASK; + int len = 0; + + if (type == SDP_DTYPE_UINT || type == SDP_DTYPE_BOOL) { + data[len ++] = element->type; + if ((element->type & SDP_DSIZE_MASK) == SDP_DSIZE_1) + data[len ++] = (element->value.uint >> 0) & 0xff; + else if ((element->type & SDP_DSIZE_MASK) == SDP_DSIZE_2) { + data[len ++] = (element->value.uint >> 8) & 0xff; + data[len ++] = (element->value.uint >> 0) & 0xff; + } else if ((element->type & SDP_DSIZE_MASK) == SDP_DSIZE_4) { + data[len ++] = (element->value.uint >> 24) & 0xff; + data[len ++] = (element->value.uint >> 16) & 0xff; + data[len ++] = (element->value.uint >> 8) & 0xff; + data[len ++] = (element->value.uint >> 0) & 0xff; + } + + return len; + } + + if (type == SDP_DTYPE_UUID) { + *(*uuid) ++ = element->value.uint; + + data[len ++] = element->type; + data[len ++] = (element->value.uint >> 24) & 0xff; + data[len ++] = (element->value.uint >> 16) & 0xff; + data[len ++] = (element->value.uint >> 8) & 0xff; + data[len ++] = (element->value.uint >> 0) & 0xff; + memcpy(data + len, bt_base_uuid, 12); + + return len + 12; + } + + data[0] = type | SDP_DSIZE_NEXT1; + if (type == SDP_DTYPE_STRING || type == SDP_DTYPE_URL) { + if (element->type & SDP_DSIZE_MASK) + for (len = 0; element->value.str[len] | + element->value.str[len + 1]; len ++); + else + len = strlen(element->value.str); + memcpy(data + 2, element->value.str, data[1] = len); + + return len + 2; + } + + len = 2; + element = element->value.list; + while (element->type) + len += sdp_attr_write(data + len, element ++, uuid); + data[1] = len - 2; + + return len; +} + +static int sdp_attributeid_compare(const struct sdp_service_attribute_s *a, + const struct sdp_service_attribute_s *b) +{ + return (int) b->attribute_id - a->attribute_id; +} + +static int sdp_uuid_compare(const int *a, const int *b) +{ + return *a - *b; +} + +static void sdp_service_record_build(struct sdp_service_record_s *record, + struct sdp_def_service_s *def, int handle) +{ + int len = 0; + uint8_t *data; + int *uuid; + + record->uuids = 0; + while (def->attributes[record->attributes].data.type) { + len += 3; + len += sdp_attr_max_size(&def->attributes[record->attributes ++].data, + &record->uuids); + } + record->uuids = 1 << ffs(record->uuids - 1); + record->attribute_list = + qemu_mallocz(record->attributes * sizeof(*record->attribute_list)); + record->uuid = + qemu_mallocz(record->uuids * sizeof(*record->uuid)); + data = qemu_malloc(len); + + record->attributes = 0; + uuid = record->uuid; + while (def->attributes[record->attributes].data.type) { + record->attribute_list[record->attributes].pair = data; + + len = 0; + data[len ++] = SDP_DTYPE_UINT | SDP_DSIZE_2; + data[len ++] = def->attributes[record->attributes].id >> 8; + data[len ++] = def->attributes[record->attributes].id & 0xff; + len += sdp_attr_write(data + len, + &def->attributes[record->attributes].data, &uuid); + + /* Special case: assign a ServiceRecordHandle in sequence */ + if (def->attributes[record->attributes].id == SDP_ATTR_RECORD_HANDLE) + def->attributes[record->attributes].data.value.uint = handle; + /* Note: we could also assign a ServiceDescription based on + * sdp->device.device->lmp_name. */ + + record->attribute_list[record->attributes ++].len = len; + data += len; + } + + /* Sort the attribute list by the AttributeID */ + qsort(record->attribute_list, record->attributes, + sizeof(*record->attribute_list), + (void *) sdp_attributeid_compare); + /* Sort the searchable UUIDs list for bisection */ + qsort(record->uuid, record->uuids, + sizeof(*record->uuid), + (void *) sdp_uuid_compare); +} + +static void sdp_service_db_build(struct bt_l2cap_sdp_state_s *sdp, + struct sdp_def_service_s **service) +{ + sdp->services = 0; + while (service[sdp->services]) + sdp->services ++; + sdp->service_list = + qemu_mallocz(sdp->services * sizeof(*sdp->service_list)); + + sdp->services = 0; + while (*service) { + sdp_service_record_build(&sdp->service_list[sdp->services], + *service, sdp->services); + service ++; + sdp->services ++; + } +} + +#define LAST { .type = 0 } +#define SERVICE(name, attrs) \ + static struct sdp_def_service_s glue(glue(sdp_service_, name), _s) = { \ + .attributes = { attrs { .data = LAST } }, \ + }; +#define ATTRIBUTE(attrid, val) { .id = glue(SDP_ATTR_, attrid), .data = val }, +#define UINT8(val) { \ + .type = SDP_DTYPE_UINT | SDP_DSIZE_1, \ + .value.uint = val, \ + }, +#define UINT16(val) { \ + .type = SDP_DTYPE_UINT | SDP_DSIZE_2, \ + .value.uint = val, \ + }, +#define UINT32(val) { \ + .type = SDP_DTYPE_UINT | SDP_DSIZE_4, \ + .value.uint = val, \ + }, +#define UUID128(val) { \ + .type = SDP_DTYPE_UUID | SDP_DSIZE_16, \ + .value.uint = val, \ + }, +#define TRUE { \ + .type = SDP_DTYPE_BOOL | SDP_DSIZE_1, \ + .value.uint = 1, \ + }, +#define FALSE { \ + .type = SDP_DTYPE_BOOL | SDP_DSIZE_1, \ + .value.uint = 0, \ + }, +#define STRING(val) { \ + .type = SDP_DTYPE_STRING, \ + .value.str = val, \ + }, +#define ARRAY(...) { \ + .type = SDP_DTYPE_STRING | SDP_DSIZE_2, \ + .value.str = (char []) { __VA_ARGS__, 0, 0 }, \ + }, +#define URL(val) { \ + .type = SDP_DTYPE_URL, \ + .value.str = val, \ + }, +#if 1 +#define LIST(val) { \ + .type = SDP_DTYPE_SEQ, \ + .value.list = (struct sdp_def_data_element_s []) { val LAST }, \ + }, +#endif + +/* Try to keep each single attribute below MAX_PDU_OUT_SIZE bytes + * in resulting SDP data representation size. */ + +SERVICE(hid, + ATTRIBUTE(RECORD_HANDLE, UINT32(0)) /* Filled in later */ + ATTRIBUTE(SVCLASS_ID_LIST, LIST(UUID128(HID_SVCLASS_ID))) + ATTRIBUTE(RECORD_STATE, UINT32(1)) + ATTRIBUTE(PROTO_DESC_LIST, LIST( + LIST(UUID128(L2CAP_UUID) UINT16(BT_PSM_HID_CTRL)) + LIST(UUID128(HIDP_UUID)) + )) + ATTRIBUTE(BROWSE_GRP_LIST, LIST(UUID128(0x1002))) + ATTRIBUTE(LANG_BASE_ATTR_ID_LIST, LIST( + UINT16(0x656e) UINT16(0x006a) UINT16(0x0100) + )) + ATTRIBUTE(PFILE_DESC_LIST, LIST( + LIST(UUID128(HID_PROFILE_ID) UINT16(0x0100)) + )) + ATTRIBUTE(DOC_URL, URL("http://bellard.org/qemu/user-doc.html")) + ATTRIBUTE(SVCNAME_PRIMARY, STRING("QEMU Bluetooth HID")) + ATTRIBUTE(SVCDESC_PRIMARY, STRING("QEMU Keyboard/Mouse")) + ATTRIBUTE(SVCPROV_PRIMARY, STRING("QEMU " QEMU_VERSION)) + + /* Profile specific */ + ATTRIBUTE(DEVICE_RELEASE_NUMBER, UINT16(0x0091)) /* Deprecated, remove */ + ATTRIBUTE(PARSER_VERSION, UINT16(0x0111)) + /* TODO: extract from l2cap_device->device.class[0] */ + ATTRIBUTE(DEVICE_SUBCLASS, UINT8(0x40)) + ATTRIBUTE(COUNTRY_CODE, UINT8(0x15)) + ATTRIBUTE(VIRTUAL_CABLE, TRUE) + ATTRIBUTE(RECONNECT_INITIATE, FALSE) + /* TODO: extract from hid->usbdev->report_desc */ + ATTRIBUTE(DESCRIPTOR_LIST, LIST( + LIST(UINT8(0x22) ARRAY( + 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 */ + )))) + ATTRIBUTE(LANG_ID_BASE_LIST, LIST( + LIST(UINT16(0x0409) UINT16(0x0100)) + )) + ATTRIBUTE(SDP_DISABLE, FALSE) + ATTRIBUTE(BATTERY_POWER, TRUE) + ATTRIBUTE(REMOTE_WAKEUP, TRUE) + ATTRIBUTE(BOOT_DEVICE, TRUE) /* XXX: untested */ + ATTRIBUTE(SUPERVISION_TIMEOUT, UINT16(0x0c80)) + ATTRIBUTE(NORMALLY_CONNECTABLE, TRUE) + ATTRIBUTE(PROFILE_VERSION, UINT16(0x0100)) +) + +SERVICE(sdp, + ATTRIBUTE(RECORD_HANDLE, UINT32(0)) /* Filled in later */ + ATTRIBUTE(SVCLASS_ID_LIST, LIST(UUID128(SDP_SERVER_SVCLASS_ID))) + ATTRIBUTE(RECORD_STATE, UINT32(1)) + ATTRIBUTE(PROTO_DESC_LIST, LIST( + LIST(UUID128(L2CAP_UUID) UINT16(BT_PSM_SDP)) + LIST(UUID128(SDP_UUID)) + )) + ATTRIBUTE(BROWSE_GRP_LIST, LIST(UUID128(0x1002))) + ATTRIBUTE(LANG_BASE_ATTR_ID_LIST, LIST( + UINT16(0x656e) UINT16(0x006a) UINT16(0x0100) + )) + ATTRIBUTE(PFILE_DESC_LIST, LIST( + LIST(UUID128(SDP_SERVER_PROFILE_ID) UINT16(0x0100)) + )) + ATTRIBUTE(DOC_URL, URL("http://bellard.org/qemu/user-doc.html")) + ATTRIBUTE(SVCPROV_PRIMARY, STRING("QEMU " QEMU_VERSION)) + + /* Profile specific */ + ATTRIBUTE(VERSION_NUM_LIST, LIST(UINT16(0x0100))) + ATTRIBUTE(SVCDB_STATE , UINT32(1)) +) + +SERVICE(pnp, + ATTRIBUTE(RECORD_HANDLE, UINT32(0)) /* Filled in later */ + ATTRIBUTE(SVCLASS_ID_LIST, LIST(UUID128(PNP_INFO_SVCLASS_ID))) + ATTRIBUTE(RECORD_STATE, UINT32(1)) + ATTRIBUTE(PROTO_DESC_LIST, LIST( + LIST(UUID128(L2CAP_UUID) UINT16(BT_PSM_SDP)) + LIST(UUID128(SDP_UUID)) + )) + ATTRIBUTE(BROWSE_GRP_LIST, LIST(UUID128(0x1002))) + ATTRIBUTE(LANG_BASE_ATTR_ID_LIST, LIST( + UINT16(0x656e) UINT16(0x006a) UINT16(0x0100) + )) + ATTRIBUTE(PFILE_DESC_LIST, LIST( + LIST(UUID128(PNP_INFO_PROFILE_ID) UINT16(0x0100)) + )) + ATTRIBUTE(DOC_URL, URL("http://bellard.org/qemu/user-doc.html")) + ATTRIBUTE(SVCPROV_PRIMARY, STRING("QEMU " QEMU_VERSION)) + + /* Profile specific */ + ATTRIBUTE(SPECIFICATION_ID, UINT16(0x0100)) + ATTRIBUTE(VERSION, UINT16(0x0100)) + ATTRIBUTE(PRIMARY_RECORD, TRUE) +) + +static int bt_l2cap_sdp_new_ch(struct bt_l2cap_device_s *dev, + struct bt_l2cap_conn_params_s *params) +{ + struct bt_l2cap_sdp_state_s *sdp = qemu_mallocz(sizeof(*sdp)); + struct sdp_def_service_s *services[] = { + &sdp_service_sdp_s, + &sdp_service_hid_s, + &sdp_service_pnp_s, + NULL, + }; + + sdp->channel = params; + sdp->channel->opaque = sdp; + sdp->channel->close = bt_l2cap_sdp_close_ch; + sdp->channel->sdu_in = bt_l2cap_sdp_sdu_in; + + sdp_service_db_build(sdp, services); + + return 0; +} + +void bt_l2cap_sdp_init(struct bt_l2cap_device_s *dev) +{ + bt_l2cap_psm_register(dev, BT_PSM_SDP, + MAX_PDU_OUT_SIZE, bt_l2cap_sdp_new_ch); +} @@ -0,0 +1,122 @@ +/* + * Convenience functions for bluetooth. + * + * Copyright (C) 2008 Andrzej Zaborowski <balrog@zabor.org> + * + * 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 or + * (at your option) version 3 of the License. + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "qemu-common.h" +#include "net.h" +#include "bt.h" + +/* Slave implementations can ignore this */ +static void bt_dummy_lmp_mode_change(struct bt_link_s *link) +{ +} + +/* Slaves should never receive these PDUs */ +static void bt_dummy_lmp_connection_complete(struct bt_link_s *link) +{ + if (link->slave->reject_reason) + fprintf(stderr, "%s: stray LMP_not_accepted received, fixme\n", + __FUNCTION__); + else + fprintf(stderr, "%s: stray LMP_accepted received, fixme\n", + __FUNCTION__); + exit(-1); +} + +static void bt_dummy_lmp_disconnect_master(struct bt_link_s *link) +{ + fprintf(stderr, "%s: stray LMP_detach received, fixme\n", __FUNCTION__); + exit(-1); +} + +static void bt_dummy_lmp_acl_resp(struct bt_link_s *link, + const uint8_t *data, int start, int len) +{ + fprintf(stderr, "%s: stray ACL response PDU, fixme\n", __FUNCTION__); + exit(-1); +} + +/* Slaves that don't hold any additional per link state can use these */ +static void bt_dummy_lmp_connection_request(struct bt_link_s *req) +{ + struct bt_link_s *link = qemu_mallocz(sizeof(struct bt_link_s)); + + link->slave = req->slave; + link->host = req->host; + + req->host->reject_reason = 0; + req->host->lmp_connection_complete(link); +} + +static void bt_dummy_lmp_disconnect_slave(struct bt_link_s *link) +{ + qemu_free(link); +} + +static void bt_dummy_destroy(struct bt_device_s *device) +{ + bt_device_done(device); + qemu_free(device); +} + +static int bt_dev_idx = 0; + +void bt_device_init(struct bt_device_s *dev, struct bt_scatternet_s *net) +{ + memset(dev, 0, sizeof(*dev)); + dev->inquiry_scan = 1; + dev->page_scan = 1; + + dev->bd_addr.b[0] = bt_dev_idx & 0xff; + dev->bd_addr.b[1] = bt_dev_idx >> 8; + dev->bd_addr.b[2] = 0xd0; + dev->bd_addr.b[3] = 0xba; + dev->bd_addr.b[4] = 0xbe; + dev->bd_addr.b[5] = 0xba; + bt_dev_idx ++; + + /* Simple slave-only devices need to implement only .lmp_acl_data */ + dev->lmp_connection_complete = bt_dummy_lmp_connection_complete; + dev->lmp_disconnect_master = bt_dummy_lmp_disconnect_master; + dev->lmp_acl_resp = bt_dummy_lmp_acl_resp; + dev->lmp_mode_change = bt_dummy_lmp_mode_change; + dev->lmp_connection_request = bt_dummy_lmp_connection_request; + dev->lmp_disconnect_slave = bt_dummy_lmp_disconnect_slave; + + dev->handle_destroy = bt_dummy_destroy; + + dev->net = net; + dev->next = net->slave; + net->slave = dev; +} + +void bt_device_done(struct bt_device_s *dev) +{ + struct bt_device_s **p = &dev->net->slave; + + while (*p && *p != dev) + p = &(*p)->next; + if (*p != dev) { + fprintf(stderr, "%s: bad bt device \"%s\"\n", __FUNCTION__, + dev->lmp_name ?: "(null)"); + exit(-1); + } + + *p = dev->next; +} @@ -0,0 +1,2185 @@ +/* + * QEMU Bluetooth HCI helpers. + * + * Copyright (C) 2007 OpenMoko, Inc. + * Written by Andrzej Zaborowski <andrew@openedhand.com> + * + * Useful definitions taken from BlueZ project's headers. + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2002-2006 Marcel Holtmann <marcel@holtmann.org> + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +/* BD Address */ +typedef struct { + uint8_t b[6]; +} __attribute__((packed)) bdaddr_t; + +#define BDADDR_ANY (&(bdaddr_t) {{0, 0, 0, 0, 0, 0}}) +#define BDADDR_ALL (&(bdaddr_t) {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}) +#define BDADDR_LOCAL (&(bdaddr_t) {{0, 0, 0, 0xff, 0xff, 0xff}}) + +/* Copy, swap, convert BD Address */ +static inline int bacmp(const bdaddr_t *ba1, const bdaddr_t *ba2) +{ + return memcmp(ba1, ba2, sizeof(bdaddr_t)); +} +static inline void bacpy(bdaddr_t *dst, const bdaddr_t *src) +{ + memcpy(dst, src, sizeof(bdaddr_t)); +} + +#define BAINIT(orig) { .b = { \ + (orig)->b[0], (orig)->b[1], (orig)->b[2], \ + (orig)->b[3], (orig)->b[4], (orig)->b[5], \ +}, } + +/* The twisted structures of a bluetooth environment */ +struct bt_device_s; +struct bt_scatternet_s; +struct bt_piconet_s; +struct bt_link_s; + +struct bt_scatternet_s { + struct bt_device_s *slave; +}; + +struct bt_link_s { + struct bt_device_s *slave, *host; + uint16_t handle; /* Master (host) side handle */ + uint16_t acl_interval; + enum { + acl_active, + acl_hold, + acl_sniff, + acl_parked, + } acl_mode; +}; + +struct bt_device_s { + int lt_addr; + bdaddr_t bd_addr; + int mtu; + int setup; + struct bt_scatternet_s *net; + + uint8_t key[16]; + int key_present; + uint8_t class[3]; + + uint8_t reject_reason; + + uint64_t lmp_caps; + const char *lmp_name; + void (*lmp_connection_request)(struct bt_link_s *link); + void (*lmp_connection_complete)(struct bt_link_s *link); + void (*lmp_disconnect_master)(struct bt_link_s *link); + void (*lmp_disconnect_slave)(struct bt_link_s *link); + void (*lmp_acl_data)(struct bt_link_s *link, const uint8_t *data, + int start, int len); + void (*lmp_acl_resp)(struct bt_link_s *link, const uint8_t *data, + int start, int len); + void (*lmp_mode_change)(struct bt_link_s *link); + + void (*handle_destroy)(struct bt_device_s *device); + struct bt_device_s *next; /* Next in the piconet/scatternet */ + + int inquiry_scan; + int page_scan; + + uint16_t clkoff; /* Note: Always little-endian */ +}; + +/* bt.c */ +void bt_device_init(struct bt_device_s *dev, struct bt_scatternet_s *net); +void bt_device_done(struct bt_device_s *dev); + +/* bt-hci.c */ +struct HCIInfo *bt_new_hci(struct bt_scatternet_s *net); + +/* bt-vhci.c */ +void bt_vhci_init(struct HCIInfo *info); + +/* bt-hci-csr.c */ +enum { + csrhci_pin_reset, + csrhci_pin_wakeup, + __csrhci_pins, +}; +qemu_irq *csrhci_pins_get(CharDriverState *chr); +CharDriverState *uart_hci_init(qemu_irq wakeup); + +/* bt-l2cap.c */ +struct bt_l2cap_device_s; +struct bt_l2cap_conn_params_s; +struct bt_l2cap_psm_s; +void bt_l2cap_device_init(struct bt_l2cap_device_s *dev, + struct bt_scatternet_s *net); +void bt_l2cap_device_done(struct bt_l2cap_device_s *dev); +void bt_l2cap_psm_register(struct bt_l2cap_device_s *dev, int psm, + int min_mtu, int (*new_channel)(struct bt_l2cap_device_s *dev, + struct bt_l2cap_conn_params_s *params)); + +struct bt_l2cap_device_s { + struct bt_device_s device; + struct bt_l2cap_psm_s *first_psm; +}; + +struct bt_l2cap_conn_params_s { + /* Input */ + uint8_t *(*sdu_out)(struct bt_l2cap_conn_params_s *chan, int len); + void (*sdu_submit)(struct bt_l2cap_conn_params_s *chan); + int remote_mtu; + /* Output */ + void *opaque; + void (*sdu_in)(void *opaque, const uint8_t *data, int len); + void (*close)(void *opaque); +}; + +enum bt_l2cap_psm_predef { + BT_PSM_SDP = 0x0001, + BT_PSM_RFCOMM = 0x0003, + BT_PSM_TELEPHONY = 0x0005, + BT_PSM_TCS = 0x0007, + BT_PSM_BNEP = 0x000f, + BT_PSM_HID_CTRL = 0x0011, + BT_PSM_HID_INTR = 0x0013, + BT_PSM_UPNP = 0x0015, + BT_PSM_AVCTP = 0x0017, + BT_PSM_AVDTP = 0x0019, +}; + +/* bt-sdp.c */ +void bt_l2cap_sdp_init(struct bt_l2cap_device_s *dev); + +/* bt-hid.c */ +struct bt_device_s *bt_mouse_init(struct bt_scatternet_s *net); +struct bt_device_s *bt_tablet_init(struct bt_scatternet_s *net); +struct bt_device_s *bt_keyboard_init(struct bt_scatternet_s *net); + +/* Link Management Protocol layer defines */ + +#define LLID_ACLU_CONT 0x1 +#define LLID_ACLU_START 0x2 +#define LLID_ACLC 0x3 + +enum lmp_pdu_type { + LMP_NAME_REQ = 0x0001, + LMP_NAME_RES = 0x0002, + LMP_ACCEPTED = 0x0003, + LMP_NOT_ACCEPTED = 0x0004, + LMP_CLKOFFSET_REQ = 0x0005, + LMP_CLKOFFSET_RES = 0x0006, + LMP_DETACH = 0x0007, + LMP_IN_RAND = 0x0008, + LMP_COMB_KEY = 0x0009, + LMP_UNIT_KEY = 0x000a, + LMP_AU_RAND = 0x000b, + LMP_SRES = 0x000c, + LMP_TEMP_RAND = 0x000d, + LMP_TEMP_KEY = 0x000e, + LMP_CRYPT_MODE_REQ = 0x000f, + LMP_CRYPT_KEY_SIZE_REQ = 0x0010, + LMP_START_ENCRYPT_REQ = 0x0011, + LMP_STOP_ENCRYPT_REQ = 0x0012, + LMP_SWITCH_REQ = 0x0013, + LMP_HOLD = 0x0014, + LMP_HOLD_REQ = 0x0015, + LMP_SNIFF_REQ = 0x0017, + LMP_UNSNIFF_REQ = 0x0018, + LMP_LMP_PARK_REQ = 0x0019, + LMP_SET_BCAST_SCAN_WND = 0x001b, + LMP_MODIFY_BEACON = 0x001c, + LMP_UNPARK_BD_ADDR_REQ = 0x001d, + LMP_UNPARK_PM_ADDR_REQ = 0x001e, + LMP_INCR_POWER_REQ = 0x001f, + LMP_DECR_POWER_REQ = 0x0020, + LMP_MAX_POWER = 0x0021, + LMP_MIN_POWER = 0x0022, + LMP_AUTO_RATE = 0x0023, + LMP_PREFERRED_RATE = 0x0024, + LMP_VERSION_REQ = 0x0025, + LMP_VERSION_RES = 0x0026, + LMP_FEATURES_REQ = 0x0027, + LMP_FEATURES_RES = 0x0028, + LMP_QUALITY_OF_SERVICE = 0x0029, + LMP_QOS_REQ = 0x002a, + LMP_RM_SCO_LINK_REQ = 0x002b, + LMP_SCO_LINK_REQ = 0x002c, + LMP_MAX_SLOT = 0x002d, + LMP_MAX_SLOT_REQ = 0x002e, + LMP_TIMING_ACCURACY_REQ = 0x002f, + LMP_TIMING_ACCURACY_RES = 0x0030, + LMP_SETUP_COMPLETE = 0x0031, + LMP_USE_SEMIPERM_KEY = 0x0032, + LMP_HOST_CONNECTION_REQ = 0x0033, + LMP_SLOT_OFFSET = 0x0034, + LMP_PAGE_MODE_REQ = 0x0035, + LMP_PAGE_SCAN_MODE_REQ = 0x0036, + LMP_SUPERVISION_TIMEOUT = 0x0037, + LMP_TEST_ACTIVATE = 0x0038, + LMP_TEST_CONTROL = 0x0039, + LMP_CRYPT_KEY_MASK_REQ = 0x003a, + LMP_CRYPT_KEY_MASK_RES = 0x003b, + LMP_SET_AFH = 0x003c, + LMP_ACCEPTED_EXT = 0x7f01, + LMP_NOT_ACCEPTED_EXT = 0x7f02, + LMP_FEATURES_REQ_EXT = 0x7f03, + LMP_FEATURES_RES_EXT = 0x7f04, + LMP_PACKET_TYPE_TBL_REQ = 0x7f0b, + LMP_ESCO_LINK_REQ = 0x7f0c, + LMP_RM_ESCO_LINK_REQ = 0x7f0d, + LMP_CHANNEL_CLASS_REQ = 0x7f10, + LMP_CHANNEL_CLASS = 0x7f11, +}; + +/* Host Controller Interface layer defines */ + +enum hci_packet_type { + HCI_COMMAND_PKT = 0x01, + HCI_ACLDATA_PKT = 0x02, + HCI_SCODATA_PKT = 0x03, + HCI_EVENT_PKT = 0x04, + HCI_VENDOR_PKT = 0xff, +}; + +enum bt_packet_type { + HCI_2DH1 = 1 << 1, + HCI_3DH1 = 1 << 2, + HCI_DM1 = 1 << 3, + HCI_DH1 = 1 << 4, + HCI_2DH3 = 1 << 8, + HCI_3DH3 = 1 << 9, + HCI_DM3 = 1 << 10, + HCI_DH3 = 1 << 11, + HCI_2DH5 = 1 << 12, + HCI_3DH5 = 1 << 13, + HCI_DM5 = 1 << 14, + HCI_DH5 = 1 << 15, +}; + +enum sco_packet_type { + HCI_HV1 = 1 << 5, + HCI_HV2 = 1 << 6, + HCI_HV3 = 1 << 7, +}; + +enum ev_packet_type { + HCI_EV3 = 1 << 3, + HCI_EV4 = 1 << 4, + HCI_EV5 = 1 << 5, + HCI_2EV3 = 1 << 6, + HCI_3EV3 = 1 << 7, + HCI_2EV5 = 1 << 8, + HCI_3EV5 = 1 << 9, +}; + +enum hci_error_code { + HCI_SUCCESS = 0x00, + HCI_UNKNOWN_COMMAND = 0x01, + HCI_NO_CONNECTION = 0x02, + HCI_HARDWARE_FAILURE = 0x03, + HCI_PAGE_TIMEOUT = 0x04, + HCI_AUTHENTICATION_FAILURE = 0x05, + HCI_PIN_OR_KEY_MISSING = 0x06, + HCI_MEMORY_FULL = 0x07, + HCI_CONNECTION_TIMEOUT = 0x08, + HCI_MAX_NUMBER_OF_CONNECTIONS = 0x09, + HCI_MAX_NUMBER_OF_SCO_CONNECTIONS = 0x0a, + HCI_ACL_CONNECTION_EXISTS = 0x0b, + HCI_COMMAND_DISALLOWED = 0x0c, + HCI_REJECTED_LIMITED_RESOURCES = 0x0d, + HCI_REJECTED_SECURITY = 0x0e, + HCI_REJECTED_PERSONAL = 0x0f, + HCI_HOST_TIMEOUT = 0x10, + HCI_UNSUPPORTED_FEATURE = 0x11, + HCI_INVALID_PARAMETERS = 0x12, + HCI_OE_USER_ENDED_CONNECTION = 0x13, + HCI_OE_LOW_RESOURCES = 0x14, + HCI_OE_POWER_OFF = 0x15, + HCI_CONNECTION_TERMINATED = 0x16, + HCI_REPEATED_ATTEMPTS = 0x17, + HCI_PAIRING_NOT_ALLOWED = 0x18, + HCI_UNKNOWN_LMP_PDU = 0x19, + HCI_UNSUPPORTED_REMOTE_FEATURE = 0x1a, + HCI_SCO_OFFSET_REJECTED = 0x1b, + HCI_SCO_INTERVAL_REJECTED = 0x1c, + HCI_AIR_MODE_REJECTED = 0x1d, + HCI_INVALID_LMP_PARAMETERS = 0x1e, + HCI_UNSPECIFIED_ERROR = 0x1f, + HCI_UNSUPPORTED_LMP_PARAMETER_VALUE = 0x20, + HCI_ROLE_CHANGE_NOT_ALLOWED = 0x21, + HCI_LMP_RESPONSE_TIMEOUT = 0x22, + HCI_LMP_ERROR_TRANSACTION_COLLISION = 0x23, + HCI_LMP_PDU_NOT_ALLOWED = 0x24, + HCI_ENCRYPTION_MODE_NOT_ACCEPTED = 0x25, + HCI_UNIT_LINK_KEY_USED = 0x26, + HCI_QOS_NOT_SUPPORTED = 0x27, + HCI_INSTANT_PASSED = 0x28, + HCI_PAIRING_NOT_SUPPORTED = 0x29, + HCI_TRANSACTION_COLLISION = 0x2a, + HCI_QOS_UNACCEPTABLE_PARAMETER = 0x2c, + HCI_QOS_REJECTED = 0x2d, + HCI_CLASSIFICATION_NOT_SUPPORTED = 0x2e, + HCI_INSUFFICIENT_SECURITY = 0x2f, + HCI_PARAMETER_OUT_OF_RANGE = 0x30, + HCI_ROLE_SWITCH_PENDING = 0x32, + HCI_SLOT_VIOLATION = 0x34, + HCI_ROLE_SWITCH_FAILED = 0x35, +}; + +enum acl_flag_bits { + ACL_CONT = 1 << 0, + ACL_START = 1 << 1, + ACL_ACTIVE_BCAST = 1 << 2, + ACL_PICO_BCAST = 1 << 3, +}; + +enum baseband_link_type { + SCO_LINK = 0x00, + ACL_LINK = 0x01, +}; + +enum lmp_feature_bits0 { + LMP_3SLOT = 1 << 0, + LMP_5SLOT = 1 << 1, + LMP_ENCRYPT = 1 << 2, + LMP_SOFFSET = 1 << 3, + LMP_TACCURACY = 1 << 4, + LMP_RSWITCH = 1 << 5, + LMP_HOLD_MODE = 1 << 6, + LMP_SNIFF_MODE = 1 << 7, +}; + +enum lmp_feature_bits1 { + LMP_PARK = 1 << 0, + LMP_RSSI = 1 << 1, + LMP_QUALITY = 1 << 2, + LMP_SCO = 1 << 3, + LMP_HV2 = 1 << 4, + LMP_HV3 = 1 << 5, + LMP_ULAW = 1 << 6, + LMP_ALAW = 1 << 7, +}; + +enum lmp_feature_bits2 { + LMP_CVSD = 1 << 0, + LMP_PSCHEME = 1 << 1, + LMP_PCONTROL = 1 << 2, + LMP_TRSP_SCO = 1 << 3, + LMP_BCAST_ENC = 1 << 7, +}; + +enum lmp_feature_bits3 { + LMP_EDR_ACL_2M = 1 << 1, + LMP_EDR_ACL_3M = 1 << 2, + LMP_ENH_ISCAN = 1 << 3, + LMP_ILACE_ISCAN = 1 << 4, + LMP_ILACE_PSCAN = 1 << 5, + LMP_RSSI_INQ = 1 << 6, + LMP_ESCO = 1 << 7, +}; + +enum lmp_feature_bits4 { + LMP_EV4 = 1 << 0, + LMP_EV5 = 1 << 1, + LMP_AFH_CAP_SLV = 1 << 3, + LMP_AFH_CLS_SLV = 1 << 4, + LMP_EDR_3SLOT = 1 << 7, +}; + +enum lmp_feature_bits5 { + LMP_EDR_5SLOT = 1 << 0, + LMP_SNIFF_SUBR = 1 << 1, + LMP_AFH_CAP_MST = 1 << 3, + LMP_AFH_CLS_MST = 1 << 4, + LMP_EDR_ESCO_2M = 1 << 5, + LMP_EDR_ESCO_3M = 1 << 6, + LMP_EDR_3S_ESCO = 1 << 7, +}; + +enum lmp_feature_bits6 { + LMP_EXT_INQ = 1 << 0, +}; + +enum lmp_feature_bits7 { + LMP_EXT_FEAT = 1 << 7, +}; + +enum hci_link_policy { + HCI_LP_RSWITCH = 1 << 0, + HCI_LP_HOLD = 1 << 1, + HCI_LP_SNIFF = 1 << 2, + HCI_LP_PARK = 1 << 3, +}; + +enum hci_link_mode { + HCI_LM_ACCEPT = 1 << 15, + HCI_LM_MASTER = 1 << 0, + HCI_LM_AUTH = 1 << 1, + HCI_LM_ENCRYPT = 1 << 2, + HCI_LM_TRUSTED = 1 << 3, + HCI_LM_RELIABLE = 1 << 4, + HCI_LM_SECURE = 1 << 5, +}; + +/* HCI Commands */ + +/* Link Control */ +#define OGF_LINK_CTL 0x01 + +#define OCF_INQUIRY 0x0001 +typedef struct { + uint8_t lap[3]; + uint8_t length; /* 1.28s units */ + uint8_t num_rsp; +} __attribute__ ((packed)) inquiry_cp; +#define INQUIRY_CP_SIZE 5 + +typedef struct { + uint8_t status; + bdaddr_t bdaddr; +} __attribute__ ((packed)) status_bdaddr_rp; +#define STATUS_BDADDR_RP_SIZE 7 + +#define OCF_INQUIRY_CANCEL 0x0002 + +#define OCF_PERIODIC_INQUIRY 0x0003 +typedef struct { + uint16_t max_period; /* 1.28s units */ + uint16_t min_period; /* 1.28s units */ + uint8_t lap[3]; + uint8_t length; /* 1.28s units */ + uint8_t num_rsp; +} __attribute__ ((packed)) periodic_inquiry_cp; +#define PERIODIC_INQUIRY_CP_SIZE 9 + +#define OCF_EXIT_PERIODIC_INQUIRY 0x0004 + +#define OCF_CREATE_CONN 0x0005 +typedef struct { + bdaddr_t bdaddr; + uint16_t pkt_type; + uint8_t pscan_rep_mode; + uint8_t pscan_mode; + uint16_t clock_offset; + uint8_t role_switch; +} __attribute__ ((packed)) create_conn_cp; +#define CREATE_CONN_CP_SIZE 13 + +#define OCF_DISCONNECT 0x0006 +typedef struct { + uint16_t handle; + uint8_t reason; +} __attribute__ ((packed)) disconnect_cp; +#define DISCONNECT_CP_SIZE 3 + +#define OCF_ADD_SCO 0x0007 +typedef struct { + uint16_t handle; + uint16_t pkt_type; +} __attribute__ ((packed)) add_sco_cp; +#define ADD_SCO_CP_SIZE 4 + +#define OCF_CREATE_CONN_CANCEL 0x0008 +typedef struct { + uint8_t status; + bdaddr_t bdaddr; +} __attribute__ ((packed)) create_conn_cancel_cp; +#define CREATE_CONN_CANCEL_CP_SIZE 6 + +typedef struct { + uint8_t status; + bdaddr_t bdaddr; +} __attribute__ ((packed)) create_conn_cancel_rp; +#define CREATE_CONN_CANCEL_RP_SIZE 7 + +#define OCF_ACCEPT_CONN_REQ 0x0009 +typedef struct { + bdaddr_t bdaddr; + uint8_t role; +} __attribute__ ((packed)) accept_conn_req_cp; +#define ACCEPT_CONN_REQ_CP_SIZE 7 + +#define OCF_REJECT_CONN_REQ 0x000A +typedef struct { + bdaddr_t bdaddr; + uint8_t reason; +} __attribute__ ((packed)) reject_conn_req_cp; +#define REJECT_CONN_REQ_CP_SIZE 7 + +#define OCF_LINK_KEY_REPLY 0x000B +typedef struct { + bdaddr_t bdaddr; + uint8_t link_key[16]; +} __attribute__ ((packed)) link_key_reply_cp; +#define LINK_KEY_REPLY_CP_SIZE 22 + +#define OCF_LINK_KEY_NEG_REPLY 0x000C + +#define OCF_PIN_CODE_REPLY 0x000D +typedef struct { + bdaddr_t bdaddr; + uint8_t pin_len; + uint8_t pin_code[16]; +} __attribute__ ((packed)) pin_code_reply_cp; +#define PIN_CODE_REPLY_CP_SIZE 23 + +#define OCF_PIN_CODE_NEG_REPLY 0x000E + +#define OCF_SET_CONN_PTYPE 0x000F +typedef struct { + uint16_t handle; + uint16_t pkt_type; +} __attribute__ ((packed)) set_conn_ptype_cp; +#define SET_CONN_PTYPE_CP_SIZE 4 + +#define OCF_AUTH_REQUESTED 0x0011 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) auth_requested_cp; +#define AUTH_REQUESTED_CP_SIZE 2 + +#define OCF_SET_CONN_ENCRYPT 0x0013 +typedef struct { + uint16_t handle; + uint8_t encrypt; +} __attribute__ ((packed)) set_conn_encrypt_cp; +#define SET_CONN_ENCRYPT_CP_SIZE 3 + +#define OCF_CHANGE_CONN_LINK_KEY 0x0015 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) change_conn_link_key_cp; +#define CHANGE_CONN_LINK_KEY_CP_SIZE 2 + +#define OCF_MASTER_LINK_KEY 0x0017 +typedef struct { + uint8_t key_flag; +} __attribute__ ((packed)) master_link_key_cp; +#define MASTER_LINK_KEY_CP_SIZE 1 + +#define OCF_REMOTE_NAME_REQ 0x0019 +typedef struct { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_mode; + uint16_t clock_offset; +} __attribute__ ((packed)) remote_name_req_cp; +#define REMOTE_NAME_REQ_CP_SIZE 10 + +#define OCF_REMOTE_NAME_REQ_CANCEL 0x001A +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) remote_name_req_cancel_cp; +#define REMOTE_NAME_REQ_CANCEL_CP_SIZE 6 + +typedef struct { + uint8_t status; + bdaddr_t bdaddr; +} __attribute__ ((packed)) remote_name_req_cancel_rp; +#define REMOTE_NAME_REQ_CANCEL_RP_SIZE 7 + +#define OCF_READ_REMOTE_FEATURES 0x001B +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) read_remote_features_cp; +#define READ_REMOTE_FEATURES_CP_SIZE 2 + +#define OCF_READ_REMOTE_EXT_FEATURES 0x001C +typedef struct { + uint16_t handle; + uint8_t page_num; +} __attribute__ ((packed)) read_remote_ext_features_cp; +#define READ_REMOTE_EXT_FEATURES_CP_SIZE 3 + +#define OCF_READ_REMOTE_VERSION 0x001D +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) read_remote_version_cp; +#define READ_REMOTE_VERSION_CP_SIZE 2 + +#define OCF_READ_CLOCK_OFFSET 0x001F +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) read_clock_offset_cp; +#define READ_CLOCK_OFFSET_CP_SIZE 2 + +#define OCF_READ_LMP_HANDLE 0x0020 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) read_lmp_handle_cp; +#define READ_LMP_HANDLE_CP_SIZE 2 + +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t lmp_handle; + uint32_t reserved; +} __attribute__ ((packed)) read_lmp_handle_rp; +#define READ_LMP_HANDLE_RP_SIZE 8 + +#define OCF_SETUP_SYNC_CONN 0x0028 +typedef struct { + uint16_t handle; + uint32_t tx_bandwith; + uint32_t rx_bandwith; + uint16_t max_latency; + uint16_t voice_setting; + uint8_t retrans_effort; + uint16_t pkt_type; +} __attribute__ ((packed)) setup_sync_conn_cp; +#define SETUP_SYNC_CONN_CP_SIZE 17 + +#define OCF_ACCEPT_SYNC_CONN_REQ 0x0029 +typedef struct { + bdaddr_t bdaddr; + uint32_t tx_bandwith; + uint32_t rx_bandwith; + uint16_t max_latency; + uint16_t voice_setting; + uint8_t retrans_effort; + uint16_t pkt_type; +} __attribute__ ((packed)) accept_sync_conn_req_cp; +#define ACCEPT_SYNC_CONN_REQ_CP_SIZE 21 + +#define OCF_REJECT_SYNC_CONN_REQ 0x002A +typedef struct { + bdaddr_t bdaddr; + uint8_t reason; +} __attribute__ ((packed)) reject_sync_conn_req_cp; +#define REJECT_SYNC_CONN_REQ_CP_SIZE 7 + +/* Link Policy */ +#define OGF_LINK_POLICY 0x02 + +#define OCF_HOLD_MODE 0x0001 +typedef struct { + uint16_t handle; + uint16_t max_interval; + uint16_t min_interval; +} __attribute__ ((packed)) hold_mode_cp; +#define HOLD_MODE_CP_SIZE 6 + +#define OCF_SNIFF_MODE 0x0003 +typedef struct { + uint16_t handle; + uint16_t max_interval; + uint16_t min_interval; + uint16_t attempt; + uint16_t timeout; +} __attribute__ ((packed)) sniff_mode_cp; +#define SNIFF_MODE_CP_SIZE 10 + +#define OCF_EXIT_SNIFF_MODE 0x0004 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) exit_sniff_mode_cp; +#define EXIT_SNIFF_MODE_CP_SIZE 2 + +#define OCF_PARK_MODE 0x0005 +typedef struct { + uint16_t handle; + uint16_t max_interval; + uint16_t min_interval; +} __attribute__ ((packed)) park_mode_cp; +#define PARK_MODE_CP_SIZE 6 + +#define OCF_EXIT_PARK_MODE 0x0006 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) exit_park_mode_cp; +#define EXIT_PARK_MODE_CP_SIZE 2 + +#define OCF_QOS_SETUP 0x0007 +typedef struct { + uint8_t service_type; /* 1 = best effort */ + uint32_t token_rate; /* Byte per seconds */ + uint32_t peak_bandwidth; /* Byte per seconds */ + uint32_t latency; /* Microseconds */ + uint32_t delay_variation; /* Microseconds */ +} __attribute__ ((packed)) hci_qos; +#define HCI_QOS_CP_SIZE 17 +typedef struct { + uint16_t handle; + uint8_t flags; /* Reserved */ + hci_qos qos; +} __attribute__ ((packed)) qos_setup_cp; +#define QOS_SETUP_CP_SIZE (3 + HCI_QOS_CP_SIZE) + +#define OCF_ROLE_DISCOVERY 0x0009 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) role_discovery_cp; +#define ROLE_DISCOVERY_CP_SIZE 2 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t role; +} __attribute__ ((packed)) role_discovery_rp; +#define ROLE_DISCOVERY_RP_SIZE 4 + +#define OCF_SWITCH_ROLE 0x000B +typedef struct { + bdaddr_t bdaddr; + uint8_t role; +} __attribute__ ((packed)) switch_role_cp; +#define SWITCH_ROLE_CP_SIZE 7 + +#define OCF_READ_LINK_POLICY 0x000C +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) read_link_policy_cp; +#define READ_LINK_POLICY_CP_SIZE 2 +typedef struct { + uint8_t status; + uint16_t handle; + uint16_t policy; +} __attribute__ ((packed)) read_link_policy_rp; +#define READ_LINK_POLICY_RP_SIZE 5 + +#define OCF_WRITE_LINK_POLICY 0x000D +typedef struct { + uint16_t handle; + uint16_t policy; +} __attribute__ ((packed)) write_link_policy_cp; +#define WRITE_LINK_POLICY_CP_SIZE 4 +typedef struct { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)) write_link_policy_rp; +#define WRITE_LINK_POLICY_RP_SIZE 3 + +#define OCF_READ_DEFAULT_LINK_POLICY 0x000E + +#define OCF_WRITE_DEFAULT_LINK_POLICY 0x000F + +#define OCF_FLOW_SPECIFICATION 0x0010 + +#define OCF_SNIFF_SUBRATE 0x0011 +typedef struct { + uint16_t handle; + uint16_t max_remote_latency; + uint16_t max_local_latency; + uint16_t min_remote_timeout; + uint16_t min_local_timeout; +} __attribute__ ((packed)) sniff_subrate_cp; +#define SNIFF_SUBRATE_CP_SIZE 10 + +/* Host Controller and Baseband */ +#define OGF_HOST_CTL 0x03 + +#define OCF_SET_EVENT_MASK 0x0001 +typedef struct { + uint8_t mask[8]; +} __attribute__ ((packed)) set_event_mask_cp; +#define SET_EVENT_MASK_CP_SIZE 8 + +#define OCF_RESET 0x0003 + +#define OCF_SET_EVENT_FLT 0x0005 +typedef struct { + uint8_t flt_type; + uint8_t cond_type; + uint8_t condition[0]; +} __attribute__ ((packed)) set_event_flt_cp; +#define SET_EVENT_FLT_CP_SIZE 2 + +enum bt_filter_type { + FLT_CLEAR_ALL = 0x00, + FLT_INQ_RESULT = 0x01, + FLT_CONN_SETUP = 0x02, +}; +enum inq_result_cond_type { + INQ_RESULT_RETURN_ALL = 0x00, + INQ_RESULT_RETURN_CLASS = 0x01, + INQ_RESULT_RETURN_BDADDR = 0x02, +}; +enum conn_setup_cond_type { + CONN_SETUP_ALLOW_ALL = 0x00, + CONN_SETUP_ALLOW_CLASS = 0x01, + CONN_SETUP_ALLOW_BDADDR = 0x02, +}; +enum conn_setup_cond { + CONN_SETUP_AUTO_OFF = 0x01, + CONN_SETUP_AUTO_ON = 0x02, +}; + +#define OCF_FLUSH 0x0008 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) flush_cp; +#define FLUSH_CP_SIZE 2 + +typedef struct { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)) flush_rp; +#define FLUSH_RP_SIZE 3 + +#define OCF_READ_PIN_TYPE 0x0009 +typedef struct { + uint8_t status; + uint8_t pin_type; +} __attribute__ ((packed)) read_pin_type_rp; +#define READ_PIN_TYPE_RP_SIZE 2 + +#define OCF_WRITE_PIN_TYPE 0x000A +typedef struct { + uint8_t pin_type; +} __attribute__ ((packed)) write_pin_type_cp; +#define WRITE_PIN_TYPE_CP_SIZE 1 + +#define OCF_CREATE_NEW_UNIT_KEY 0x000B + +#define OCF_READ_STORED_LINK_KEY 0x000D +typedef struct { + bdaddr_t bdaddr; + uint8_t read_all; +} __attribute__ ((packed)) read_stored_link_key_cp; +#define READ_STORED_LINK_KEY_CP_SIZE 7 +typedef struct { + uint8_t status; + uint16_t max_keys; + uint16_t num_keys; +} __attribute__ ((packed)) read_stored_link_key_rp; +#define READ_STORED_LINK_KEY_RP_SIZE 5 + +#define OCF_WRITE_STORED_LINK_KEY 0x0011 +typedef struct { + uint8_t num_keys; + /* variable length part */ +} __attribute__ ((packed)) write_stored_link_key_cp; +#define WRITE_STORED_LINK_KEY_CP_SIZE 1 +typedef struct { + uint8_t status; + uint8_t num_keys; +} __attribute__ ((packed)) write_stored_link_key_rp; +#define READ_WRITE_LINK_KEY_RP_SIZE 2 + +#define OCF_DELETE_STORED_LINK_KEY 0x0012 +typedef struct { + bdaddr_t bdaddr; + uint8_t delete_all; +} __attribute__ ((packed)) delete_stored_link_key_cp; +#define DELETE_STORED_LINK_KEY_CP_SIZE 7 +typedef struct { + uint8_t status; + uint16_t num_keys; +} __attribute__ ((packed)) delete_stored_link_key_rp; +#define DELETE_STORED_LINK_KEY_RP_SIZE 3 + +#define OCF_CHANGE_LOCAL_NAME 0x0013 +typedef struct { + char name[248]; +} __attribute__ ((packed)) change_local_name_cp; +#define CHANGE_LOCAL_NAME_CP_SIZE 248 + +#define OCF_READ_LOCAL_NAME 0x0014 +typedef struct { + uint8_t status; + char name[248]; +} __attribute__ ((packed)) read_local_name_rp; +#define READ_LOCAL_NAME_RP_SIZE 249 + +#define OCF_READ_CONN_ACCEPT_TIMEOUT 0x0015 +typedef struct { + uint8_t status; + uint16_t timeout; +} __attribute__ ((packed)) read_conn_accept_timeout_rp; +#define READ_CONN_ACCEPT_TIMEOUT_RP_SIZE 3 + +#define OCF_WRITE_CONN_ACCEPT_TIMEOUT 0x0016 +typedef struct { + uint16_t timeout; +} __attribute__ ((packed)) write_conn_accept_timeout_cp; +#define WRITE_CONN_ACCEPT_TIMEOUT_CP_SIZE 2 + +#define OCF_READ_PAGE_TIMEOUT 0x0017 +typedef struct { + uint8_t status; + uint16_t timeout; +} __attribute__ ((packed)) read_page_timeout_rp; +#define READ_PAGE_TIMEOUT_RP_SIZE 3 + +#define OCF_WRITE_PAGE_TIMEOUT 0x0018 +typedef struct { + uint16_t timeout; +} __attribute__ ((packed)) write_page_timeout_cp; +#define WRITE_PAGE_TIMEOUT_CP_SIZE 2 + +#define OCF_READ_SCAN_ENABLE 0x0019 +typedef struct { + uint8_t status; + uint8_t enable; +} __attribute__ ((packed)) read_scan_enable_rp; +#define READ_SCAN_ENABLE_RP_SIZE 2 + +#define OCF_WRITE_SCAN_ENABLE 0x001A +typedef struct { + uint8_t scan_enable; +} __attribute__ ((packed)) write_scan_enable_cp; +#define WRITE_SCAN_ENABLE_CP_SIZE 1 + +enum scan_enable_bits { + SCAN_DISABLED = 0, + SCAN_INQUIRY = 1 << 0, + SCAN_PAGE = 1 << 1, +}; + +#define OCF_READ_PAGE_ACTIVITY 0x001B +typedef struct { + uint8_t status; + uint16_t interval; + uint16_t window; +} __attribute__ ((packed)) read_page_activity_rp; +#define READ_PAGE_ACTIVITY_RP_SIZE 5 + +#define OCF_WRITE_PAGE_ACTIVITY 0x001C +typedef struct { + uint16_t interval; + uint16_t window; +} __attribute__ ((packed)) write_page_activity_cp; +#define WRITE_PAGE_ACTIVITY_CP_SIZE 4 + +#define OCF_READ_INQ_ACTIVITY 0x001D +typedef struct { + uint8_t status; + uint16_t interval; + uint16_t window; +} __attribute__ ((packed)) read_inq_activity_rp; +#define READ_INQ_ACTIVITY_RP_SIZE 5 + +#define OCF_WRITE_INQ_ACTIVITY 0x001E +typedef struct { + uint16_t interval; + uint16_t window; +} __attribute__ ((packed)) write_inq_activity_cp; +#define WRITE_INQ_ACTIVITY_CP_SIZE 4 + +#define OCF_READ_AUTH_ENABLE 0x001F + +#define OCF_WRITE_AUTH_ENABLE 0x0020 + +#define AUTH_DISABLED 0x00 +#define AUTH_ENABLED 0x01 + +#define OCF_READ_ENCRYPT_MODE 0x0021 + +#define OCF_WRITE_ENCRYPT_MODE 0x0022 + +#define ENCRYPT_DISABLED 0x00 +#define ENCRYPT_P2P 0x01 +#define ENCRYPT_BOTH 0x02 + +#define OCF_READ_CLASS_OF_DEV 0x0023 +typedef struct { + uint8_t status; + uint8_t dev_class[3]; +} __attribute__ ((packed)) read_class_of_dev_rp; +#define READ_CLASS_OF_DEV_RP_SIZE 4 + +#define OCF_WRITE_CLASS_OF_DEV 0x0024 +typedef struct { + uint8_t dev_class[3]; +} __attribute__ ((packed)) write_class_of_dev_cp; +#define WRITE_CLASS_OF_DEV_CP_SIZE 3 + +#define OCF_READ_VOICE_SETTING 0x0025 +typedef struct { + uint8_t status; + uint16_t voice_setting; +} __attribute__ ((packed)) read_voice_setting_rp; +#define READ_VOICE_SETTING_RP_SIZE 3 + +#define OCF_WRITE_VOICE_SETTING 0x0026 +typedef struct { + uint16_t voice_setting; +} __attribute__ ((packed)) write_voice_setting_cp; +#define WRITE_VOICE_SETTING_CP_SIZE 2 + +#define OCF_READ_AUTOMATIC_FLUSH_TIMEOUT 0x0027 + +#define OCF_WRITE_AUTOMATIC_FLUSH_TIMEOUT 0x0028 + +#define OCF_READ_NUM_BROADCAST_RETRANS 0x0029 + +#define OCF_WRITE_NUM_BROADCAST_RETRANS 0x002A + +#define OCF_READ_HOLD_MODE_ACTIVITY 0x002B + +#define OCF_WRITE_HOLD_MODE_ACTIVITY 0x002C + +#define OCF_READ_TRANSMIT_POWER_LEVEL 0x002D +typedef struct { + uint16_t handle; + uint8_t type; +} __attribute__ ((packed)) read_transmit_power_level_cp; +#define READ_TRANSMIT_POWER_LEVEL_CP_SIZE 3 +typedef struct { + uint8_t status; + uint16_t handle; + int8_t level; +} __attribute__ ((packed)) read_transmit_power_level_rp; +#define READ_TRANSMIT_POWER_LEVEL_RP_SIZE 4 + +#define OCF_HOST_BUFFER_SIZE 0x0033 +typedef struct { + uint16_t acl_mtu; + uint8_t sco_mtu; + uint16_t acl_max_pkt; + uint16_t sco_max_pkt; +} __attribute__ ((packed)) host_buffer_size_cp; +#define HOST_BUFFER_SIZE_CP_SIZE 7 + +#define OCF_HOST_NUMBER_OF_COMPLETED_PACKETS 0x0035 + +#define OCF_READ_LINK_SUPERVISION_TIMEOUT 0x0036 +typedef struct { + uint8_t status; + uint16_t handle; + uint16_t link_sup_to; +} __attribute__ ((packed)) read_link_supervision_timeout_rp; +#define READ_LINK_SUPERVISION_TIMEOUT_RP_SIZE 5 + +#define OCF_WRITE_LINK_SUPERVISION_TIMEOUT 0x0037 +typedef struct { + uint16_t handle; + uint16_t link_sup_to; +} __attribute__ ((packed)) write_link_supervision_timeout_cp; +#define WRITE_LINK_SUPERVISION_TIMEOUT_CP_SIZE 4 +typedef struct { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)) write_link_supervision_timeout_rp; +#define WRITE_LINK_SUPERVISION_TIMEOUT_RP_SIZE 3 + +#define OCF_READ_NUM_SUPPORTED_IAC 0x0038 + +#define MAX_IAC_LAP 0x40 +#define OCF_READ_CURRENT_IAC_LAP 0x0039 +typedef struct { + uint8_t status; + uint8_t num_current_iac; + uint8_t lap[MAX_IAC_LAP][3]; +} __attribute__ ((packed)) read_current_iac_lap_rp; +#define READ_CURRENT_IAC_LAP_RP_SIZE 2+3*MAX_IAC_LAP + +#define OCF_WRITE_CURRENT_IAC_LAP 0x003A +typedef struct { + uint8_t num_current_iac; + uint8_t lap[MAX_IAC_LAP][3]; +} __attribute__ ((packed)) write_current_iac_lap_cp; +#define WRITE_CURRENT_IAC_LAP_CP_SIZE 1+3*MAX_IAC_LAP + +#define OCF_READ_PAGE_SCAN_PERIOD_MODE 0x003B + +#define OCF_WRITE_PAGE_SCAN_PERIOD_MODE 0x003C + +#define OCF_READ_PAGE_SCAN_MODE 0x003D + +#define OCF_WRITE_PAGE_SCAN_MODE 0x003E + +#define OCF_SET_AFH_CLASSIFICATION 0x003F +typedef struct { + uint8_t map[10]; +} __attribute__ ((packed)) set_afh_classification_cp; +#define SET_AFH_CLASSIFICATION_CP_SIZE 10 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) set_afh_classification_rp; +#define SET_AFH_CLASSIFICATION_RP_SIZE 1 + +#define OCF_READ_INQUIRY_SCAN_TYPE 0x0042 +typedef struct { + uint8_t status; + uint8_t type; +} __attribute__ ((packed)) read_inquiry_scan_type_rp; +#define READ_INQUIRY_SCAN_TYPE_RP_SIZE 2 + +#define OCF_WRITE_INQUIRY_SCAN_TYPE 0x0043 +typedef struct { + uint8_t type; +} __attribute__ ((packed)) write_inquiry_scan_type_cp; +#define WRITE_INQUIRY_SCAN_TYPE_CP_SIZE 1 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) write_inquiry_scan_type_rp; +#define WRITE_INQUIRY_SCAN_TYPE_RP_SIZE 1 + +#define OCF_READ_INQUIRY_MODE 0x0044 +typedef struct { + uint8_t status; + uint8_t mode; +} __attribute__ ((packed)) read_inquiry_mode_rp; +#define READ_INQUIRY_MODE_RP_SIZE 2 + +#define OCF_WRITE_INQUIRY_MODE 0x0045 +typedef struct { + uint8_t mode; +} __attribute__ ((packed)) write_inquiry_mode_cp; +#define WRITE_INQUIRY_MODE_CP_SIZE 1 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) write_inquiry_mode_rp; +#define WRITE_INQUIRY_MODE_RP_SIZE 1 + +#define OCF_READ_PAGE_SCAN_TYPE 0x0046 + +#define OCF_WRITE_PAGE_SCAN_TYPE 0x0047 + +#define OCF_READ_AFH_MODE 0x0048 +typedef struct { + uint8_t status; + uint8_t mode; +} __attribute__ ((packed)) read_afh_mode_rp; +#define READ_AFH_MODE_RP_SIZE 2 + +#define OCF_WRITE_AFH_MODE 0x0049 +typedef struct { + uint8_t mode; +} __attribute__ ((packed)) write_afh_mode_cp; +#define WRITE_AFH_MODE_CP_SIZE 1 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) write_afh_mode_rp; +#define WRITE_AFH_MODE_RP_SIZE 1 + +#define OCF_READ_EXT_INQUIRY_RESPONSE 0x0051 +typedef struct { + uint8_t status; + uint8_t fec; + uint8_t data[240]; +} __attribute__ ((packed)) read_ext_inquiry_response_rp; +#define READ_EXT_INQUIRY_RESPONSE_RP_SIZE 242 + +#define OCF_WRITE_EXT_INQUIRY_RESPONSE 0x0052 +typedef struct { + uint8_t fec; + uint8_t data[240]; +} __attribute__ ((packed)) write_ext_inquiry_response_cp; +#define WRITE_EXT_INQUIRY_RESPONSE_CP_SIZE 241 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) write_ext_inquiry_response_rp; +#define WRITE_EXT_INQUIRY_RESPONSE_RP_SIZE 1 + +/* Informational Parameters */ +#define OGF_INFO_PARAM 0x04 + +#define OCF_READ_LOCAL_VERSION 0x0001 +typedef struct { + uint8_t status; + uint8_t hci_ver; + uint16_t hci_rev; + uint8_t lmp_ver; + uint16_t manufacturer; + uint16_t lmp_subver; +} __attribute__ ((packed)) read_local_version_rp; +#define READ_LOCAL_VERSION_RP_SIZE 9 + +#define OCF_READ_LOCAL_COMMANDS 0x0002 +typedef struct { + uint8_t status; + uint8_t commands[64]; +} __attribute__ ((packed)) read_local_commands_rp; +#define READ_LOCAL_COMMANDS_RP_SIZE 65 + +#define OCF_READ_LOCAL_FEATURES 0x0003 +typedef struct { + uint8_t status; + uint8_t features[8]; +} __attribute__ ((packed)) read_local_features_rp; +#define READ_LOCAL_FEATURES_RP_SIZE 9 + +#define OCF_READ_LOCAL_EXT_FEATURES 0x0004 +typedef struct { + uint8_t page_num; +} __attribute__ ((packed)) read_local_ext_features_cp; +#define READ_LOCAL_EXT_FEATURES_CP_SIZE 1 +typedef struct { + uint8_t status; + uint8_t page_num; + uint8_t max_page_num; + uint8_t features[8]; +} __attribute__ ((packed)) read_local_ext_features_rp; +#define READ_LOCAL_EXT_FEATURES_RP_SIZE 11 + +#define OCF_READ_BUFFER_SIZE 0x0005 +typedef struct { + uint8_t status; + uint16_t acl_mtu; + uint8_t sco_mtu; + uint16_t acl_max_pkt; + uint16_t sco_max_pkt; +} __attribute__ ((packed)) read_buffer_size_rp; +#define READ_BUFFER_SIZE_RP_SIZE 8 + +#define OCF_READ_COUNTRY_CODE 0x0007 +typedef struct { + uint8_t status; + uint8_t country_code; +} __attribute__ ((packed)) read_country_code_rp; +#define READ_COUNTRY_CODE_RP_SIZE 2 + +#define OCF_READ_BD_ADDR 0x0009 +typedef struct { + uint8_t status; + bdaddr_t bdaddr; +} __attribute__ ((packed)) read_bd_addr_rp; +#define READ_BD_ADDR_RP_SIZE 7 + +/* Status params */ +#define OGF_STATUS_PARAM 0x05 + +#define OCF_READ_FAILED_CONTACT_COUNTER 0x0001 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t counter; +} __attribute__ ((packed)) read_failed_contact_counter_rp; +#define READ_FAILED_CONTACT_COUNTER_RP_SIZE 4 + +#define OCF_RESET_FAILED_CONTACT_COUNTER 0x0002 +typedef struct { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)) reset_failed_contact_counter_rp; +#define RESET_FAILED_CONTACT_COUNTER_RP_SIZE 4 + +#define OCF_READ_LINK_QUALITY 0x0003 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) read_link_quality_cp; +#define READ_LINK_QUALITY_CP_SIZE 4 + +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t link_quality; +} __attribute__ ((packed)) read_link_quality_rp; +#define READ_LINK_QUALITY_RP_SIZE 4 + +#define OCF_READ_RSSI 0x0005 +typedef struct { + uint8_t status; + uint16_t handle; + int8_t rssi; +} __attribute__ ((packed)) read_rssi_rp; +#define READ_RSSI_RP_SIZE 4 + +#define OCF_READ_AFH_MAP 0x0006 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t mode; + uint8_t map[10]; +} __attribute__ ((packed)) read_afh_map_rp; +#define READ_AFH_MAP_RP_SIZE 14 + +#define OCF_READ_CLOCK 0x0007 +typedef struct { + uint16_t handle; + uint8_t which_clock; +} __attribute__ ((packed)) read_clock_cp; +#define READ_CLOCK_CP_SIZE 3 +typedef struct { + uint8_t status; + uint16_t handle; + uint32_t clock; + uint16_t accuracy; +} __attribute__ ((packed)) read_clock_rp; +#define READ_CLOCK_RP_SIZE 9 + +/* Testing commands */ +#define OGF_TESTING_CMD 0x3e + +/* Vendor specific commands */ +#define OGF_VENDOR_CMD 0x3f + +/* HCI Events */ + +#define EVT_INQUIRY_COMPLETE 0x01 + +#define EVT_INQUIRY_RESULT 0x02 +typedef struct { + uint8_t num_responses; + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t pscan_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; +} __attribute__ ((packed)) inquiry_info; +#define INQUIRY_INFO_SIZE 14 + +#define EVT_CONN_COMPLETE 0x03 +typedef struct { + uint8_t status; + uint16_t handle; + bdaddr_t bdaddr; + uint8_t link_type; + uint8_t encr_mode; +} __attribute__ ((packed)) evt_conn_complete; +#define EVT_CONN_COMPLETE_SIZE 11 + +#define EVT_CONN_REQUEST 0x04 +typedef struct { + bdaddr_t bdaddr; + uint8_t dev_class[3]; + uint8_t link_type; +} __attribute__ ((packed)) evt_conn_request; +#define EVT_CONN_REQUEST_SIZE 10 + +#define EVT_DISCONN_COMPLETE 0x05 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t reason; +} __attribute__ ((packed)) evt_disconn_complete; +#define EVT_DISCONN_COMPLETE_SIZE 4 + +#define EVT_AUTH_COMPLETE 0x06 +typedef struct { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)) evt_auth_complete; +#define EVT_AUTH_COMPLETE_SIZE 3 + +#define EVT_REMOTE_NAME_REQ_COMPLETE 0x07 +typedef struct { + uint8_t status; + bdaddr_t bdaddr; + char name[248]; +} __attribute__ ((packed)) evt_remote_name_req_complete; +#define EVT_REMOTE_NAME_REQ_COMPLETE_SIZE 255 + +#define EVT_ENCRYPT_CHANGE 0x08 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t encrypt; +} __attribute__ ((packed)) evt_encrypt_change; +#define EVT_ENCRYPT_CHANGE_SIZE 5 + +#define EVT_CHANGE_CONN_LINK_KEY_COMPLETE 0x09 +typedef struct { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)) evt_change_conn_link_key_complete; +#define EVT_CHANGE_CONN_LINK_KEY_COMPLETE_SIZE 3 + +#define EVT_MASTER_LINK_KEY_COMPLETE 0x0A +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t key_flag; +} __attribute__ ((packed)) evt_master_link_key_complete; +#define EVT_MASTER_LINK_KEY_COMPLETE_SIZE 4 + +#define EVT_READ_REMOTE_FEATURES_COMPLETE 0x0B +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t features[8]; +} __attribute__ ((packed)) evt_read_remote_features_complete; +#define EVT_READ_REMOTE_FEATURES_COMPLETE_SIZE 11 + +#define EVT_READ_REMOTE_VERSION_COMPLETE 0x0C +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t lmp_ver; + uint16_t manufacturer; + uint16_t lmp_subver; +} __attribute__ ((packed)) evt_read_remote_version_complete; +#define EVT_READ_REMOTE_VERSION_COMPLETE_SIZE 8 + +#define EVT_QOS_SETUP_COMPLETE 0x0D +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t flags; /* Reserved */ + hci_qos qos; +} __attribute__ ((packed)) evt_qos_setup_complete; +#define EVT_QOS_SETUP_COMPLETE_SIZE (4 + HCI_QOS_CP_SIZE) + +#define EVT_CMD_COMPLETE 0x0E +typedef struct { + uint8_t ncmd; + uint16_t opcode; +} __attribute__ ((packed)) evt_cmd_complete; +#define EVT_CMD_COMPLETE_SIZE 3 + +#define EVT_CMD_STATUS 0x0F +typedef struct { + uint8_t status; + uint8_t ncmd; + uint16_t opcode; +} __attribute__ ((packed)) evt_cmd_status; +#define EVT_CMD_STATUS_SIZE 4 + +#define EVT_HARDWARE_ERROR 0x10 +typedef struct { + uint8_t code; +} __attribute__ ((packed)) evt_hardware_error; +#define EVT_HARDWARE_ERROR_SIZE 1 + +#define EVT_FLUSH_OCCURRED 0x11 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) evt_flush_occured; +#define EVT_FLUSH_OCCURRED_SIZE 2 + +#define EVT_ROLE_CHANGE 0x12 +typedef struct { + uint8_t status; + bdaddr_t bdaddr; + uint8_t role; +} __attribute__ ((packed)) evt_role_change; +#define EVT_ROLE_CHANGE_SIZE 8 + +#define EVT_NUM_COMP_PKTS 0x13 +typedef struct { + uint8_t num_hndl; + struct { + uint16_t handle; + uint16_t num_packets; + } connection[0]; +} __attribute__ ((packed)) evt_num_comp_pkts; +#define EVT_NUM_COMP_PKTS_SIZE(num_hndl) (1 + 4 * (num_hndl)) + +#define EVT_MODE_CHANGE 0x14 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t mode; + uint16_t interval; +} __attribute__ ((packed)) evt_mode_change; +#define EVT_MODE_CHANGE_SIZE 6 + +#define EVT_RETURN_LINK_KEYS 0x15 +typedef struct { + uint8_t num_keys; + /* variable length part */ +} __attribute__ ((packed)) evt_return_link_keys; +#define EVT_RETURN_LINK_KEYS_SIZE 1 + +#define EVT_PIN_CODE_REQ 0x16 +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) evt_pin_code_req; +#define EVT_PIN_CODE_REQ_SIZE 6 + +#define EVT_LINK_KEY_REQ 0x17 +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) evt_link_key_req; +#define EVT_LINK_KEY_REQ_SIZE 6 + +#define EVT_LINK_KEY_NOTIFY 0x18 +typedef struct { + bdaddr_t bdaddr; + uint8_t link_key[16]; + uint8_t key_type; +} __attribute__ ((packed)) evt_link_key_notify; +#define EVT_LINK_KEY_NOTIFY_SIZE 23 + +#define EVT_LOOPBACK_COMMAND 0x19 + +#define EVT_DATA_BUFFER_OVERFLOW 0x1A +typedef struct { + uint8_t link_type; +} __attribute__ ((packed)) evt_data_buffer_overflow; +#define EVT_DATA_BUFFER_OVERFLOW_SIZE 1 + +#define EVT_MAX_SLOTS_CHANGE 0x1B +typedef struct { + uint16_t handle; + uint8_t max_slots; +} __attribute__ ((packed)) evt_max_slots_change; +#define EVT_MAX_SLOTS_CHANGE_SIZE 3 + +#define EVT_READ_CLOCK_OFFSET_COMPLETE 0x1C +typedef struct { + uint8_t status; + uint16_t handle; + uint16_t clock_offset; +} __attribute__ ((packed)) evt_read_clock_offset_complete; +#define EVT_READ_CLOCK_OFFSET_COMPLETE_SIZE 5 + +#define EVT_CONN_PTYPE_CHANGED 0x1D +typedef struct { + uint8_t status; + uint16_t handle; + uint16_t ptype; +} __attribute__ ((packed)) evt_conn_ptype_changed; +#define EVT_CONN_PTYPE_CHANGED_SIZE 5 + +#define EVT_QOS_VIOLATION 0x1E +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) evt_qos_violation; +#define EVT_QOS_VIOLATION_SIZE 2 + +#define EVT_PSCAN_REP_MODE_CHANGE 0x20 +typedef struct { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; +} __attribute__ ((packed)) evt_pscan_rep_mode_change; +#define EVT_PSCAN_REP_MODE_CHANGE_SIZE 7 + +#define EVT_FLOW_SPEC_COMPLETE 0x21 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t flags; + uint8_t direction; + hci_qos qos; +} __attribute__ ((packed)) evt_flow_spec_complete; +#define EVT_FLOW_SPEC_COMPLETE_SIZE (5 + HCI_QOS_CP_SIZE) + +#define EVT_INQUIRY_RESULT_WITH_RSSI 0x22 +typedef struct { + uint8_t num_responses; + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; + int8_t rssi; +} __attribute__ ((packed)) inquiry_info_with_rssi; +#define INQUIRY_INFO_WITH_RSSI_SIZE 15 +typedef struct { + uint8_t num_responses; + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t pscan_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; + int8_t rssi; +} __attribute__ ((packed)) inquiry_info_with_rssi_and_pscan_mode; +#define INQUIRY_INFO_WITH_RSSI_AND_PSCAN_MODE_SIZE 16 + +#define EVT_READ_REMOTE_EXT_FEATURES_COMPLETE 0x23 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t page_num; + uint8_t max_page_num; + uint8_t features[8]; +} __attribute__ ((packed)) evt_read_remote_ext_features_complete; +#define EVT_READ_REMOTE_EXT_FEATURES_COMPLETE_SIZE 13 + +#define EVT_SYNC_CONN_COMPLETE 0x2C +typedef struct { + uint8_t status; + uint16_t handle; + bdaddr_t bdaddr; + uint8_t link_type; + uint8_t trans_interval; + uint8_t retrans_window; + uint16_t rx_pkt_len; + uint16_t tx_pkt_len; + uint8_t air_mode; +} __attribute__ ((packed)) evt_sync_conn_complete; +#define EVT_SYNC_CONN_COMPLETE_SIZE 17 + +#define EVT_SYNC_CONN_CHANGED 0x2D +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t trans_interval; + uint8_t retrans_window; + uint16_t rx_pkt_len; + uint16_t tx_pkt_len; +} __attribute__ ((packed)) evt_sync_conn_changed; +#define EVT_SYNC_CONN_CHANGED_SIZE 9 + +#define EVT_SNIFF_SUBRATE 0x2E +typedef struct { + uint8_t status; + uint16_t handle; + uint16_t max_remote_latency; + uint16_t max_local_latency; + uint16_t min_remote_timeout; + uint16_t min_local_timeout; +} __attribute__ ((packed)) evt_sniff_subrate; +#define EVT_SNIFF_SUBRATE_SIZE 11 + +#define EVT_EXTENDED_INQUIRY_RESULT 0x2F +typedef struct { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; + int8_t rssi; + uint8_t data[240]; +} __attribute__ ((packed)) extended_inquiry_info; +#define EXTENDED_INQUIRY_INFO_SIZE 254 + +#define EVT_TESTING 0xFE + +#define EVT_VENDOR 0xFF + +/* Command opcode pack/unpack */ +#define cmd_opcode_pack(ogf, ocf) (uint16_t)((ocf & 0x03ff)|(ogf << 10)) +#define cmd_opcode_ogf(op) (op >> 10) +#define cmd_opcode_ocf(op) (op & 0x03ff) + +/* ACL handle and flags pack/unpack */ +#define acl_handle_pack(h, f) (uint16_t)(((h) & 0x0fff)|((f) << 12)) +#define acl_handle(h) ((h) & 0x0fff) +#define acl_flags(h) ((h) >> 12) + +/* HCI Packet structures */ +#define HCI_COMMAND_HDR_SIZE 3 +#define HCI_EVENT_HDR_SIZE 2 +#define HCI_ACL_HDR_SIZE 4 +#define HCI_SCO_HDR_SIZE 3 + +struct hci_command_hdr { + uint16_t opcode; /* OCF & OGF */ + uint8_t plen; +} __attribute__ ((packed)); + +struct hci_event_hdr { + uint8_t evt; + uint8_t plen; +} __attribute__ ((packed)); + +struct hci_acl_hdr { + uint16_t handle; /* Handle & Flags(PB, BC) */ + uint16_t dlen; +} __attribute__ ((packed)); + +struct hci_sco_hdr { + uint16_t handle; + uint8_t dlen; +} __attribute__ ((packed)); + +/* L2CAP layer defines */ + +enum bt_l2cap_lm_bits { + L2CAP_LM_MASTER = 1 << 0, + L2CAP_LM_AUTH = 1 << 1, + L2CAP_LM_ENCRYPT = 1 << 2, + L2CAP_LM_TRUSTED = 1 << 3, + L2CAP_LM_RELIABLE = 1 << 4, + L2CAP_LM_SECURE = 1 << 5, +}; + +enum bt_l2cap_cid_predef { + L2CAP_CID_INVALID = 0x0000, + L2CAP_CID_SIGNALLING= 0x0001, + L2CAP_CID_GROUP = 0x0002, + L2CAP_CID_ALLOC = 0x0040, +}; + +/* L2CAP command codes */ +enum bt_l2cap_cmd { + L2CAP_COMMAND_REJ = 1, + L2CAP_CONN_REQ, + L2CAP_CONN_RSP, + L2CAP_CONF_REQ, + L2CAP_CONF_RSP, + L2CAP_DISCONN_REQ, + L2CAP_DISCONN_RSP, + L2CAP_ECHO_REQ, + L2CAP_ECHO_RSP, + L2CAP_INFO_REQ, + L2CAP_INFO_RSP, +}; + +enum bt_l2cap_sar_bits { + L2CAP_SAR_NO_SEG = 0, + L2CAP_SAR_START, + L2CAP_SAR_END, + L2CAP_SAR_CONT, +}; + +/* L2CAP structures */ +typedef struct { + uint16_t len; + uint16_t cid; + uint8_t data[0]; +} __attribute__ ((packed)) l2cap_hdr; +#define L2CAP_HDR_SIZE 4 + +typedef struct { + uint8_t code; + uint8_t ident; + uint16_t len; +} __attribute__ ((packed)) l2cap_cmd_hdr; +#define L2CAP_CMD_HDR_SIZE 4 + +typedef struct { + uint16_t reason; +} __attribute__ ((packed)) l2cap_cmd_rej; +#define L2CAP_CMD_REJ_SIZE 2 + +typedef struct { + uint16_t dcid; + uint16_t scid; +} __attribute__ ((packed)) l2cap_cmd_rej_cid; +#define L2CAP_CMD_REJ_CID_SIZE 4 + +/* reject reason */ +enum bt_l2cap_rej_reason { + L2CAP_REJ_CMD_NOT_UNDERSTOOD = 0, + L2CAP_REJ_SIG_TOOBIG, + L2CAP_REJ_CID_INVAL, +}; + +typedef struct { + uint16_t psm; + uint16_t scid; +} __attribute__ ((packed)) l2cap_conn_req; +#define L2CAP_CONN_REQ_SIZE 4 + +typedef struct { + uint16_t dcid; + uint16_t scid; + uint16_t result; + uint16_t status; +} __attribute__ ((packed)) l2cap_conn_rsp; +#define L2CAP_CONN_RSP_SIZE 8 + +/* connect result */ +enum bt_l2cap_conn_res { + L2CAP_CR_SUCCESS = 0, + L2CAP_CR_PEND, + L2CAP_CR_BAD_PSM, + L2CAP_CR_SEC_BLOCK, + L2CAP_CR_NO_MEM, +}; + +/* connect status */ +enum bt_l2cap_conn_stat { + L2CAP_CS_NO_INFO = 0, + L2CAP_CS_AUTHEN_PEND, + L2CAP_CS_AUTHOR_PEND, +}; + +typedef struct { + uint16_t dcid; + uint16_t flags; + uint8_t data[0]; +} __attribute__ ((packed)) l2cap_conf_req; +#define L2CAP_CONF_REQ_SIZE(datalen) (4 + (datalen)) + +typedef struct { + uint16_t scid; + uint16_t flags; + uint16_t result; + uint8_t data[0]; +} __attribute__ ((packed)) l2cap_conf_rsp; +#define L2CAP_CONF_RSP_SIZE(datalen) (6 + datalen) + +enum bt_l2cap_conf_res { + L2CAP_CONF_SUCCESS = 0, + L2CAP_CONF_UNACCEPT, + L2CAP_CONF_REJECT, + L2CAP_CONF_UNKNOWN, +}; + +typedef struct { + uint8_t type; + uint8_t len; + uint8_t val[0]; +} __attribute__ ((packed)) l2cap_conf_opt; +#define L2CAP_CONF_OPT_SIZE 2 + +enum bt_l2cap_conf_val { + L2CAP_CONF_MTU = 1, + L2CAP_CONF_FLUSH_TO, + L2CAP_CONF_QOS, + L2CAP_CONF_RFC, + L2CAP_CONF_RFC_MODE = L2CAP_CONF_RFC, +}; + +typedef struct { + uint8_t flags; + uint8_t service_type; + uint32_t token_rate; + uint32_t token_bucket_size; + uint32_t peak_bandwidth; + uint32_t latency; + uint32_t delay_variation; +} __attribute__ ((packed)) l2cap_conf_opt_qos; +#define L2CAP_CONF_OPT_QOS_SIZE 22 + +enum bt_l2cap_conf_opt_qos_st { + L2CAP_CONF_QOS_NO_TRAFFIC = 0x00, + L2CAP_CONF_QOS_BEST_EFFORT, + L2CAP_CONF_QOS_GUARANTEED, +}; + +#define L2CAP_CONF_QOS_WILDCARD 0xffffffff + +enum bt_l2cap_mode { + L2CAP_MODE_BASIC = 0, + L2CAP_MODE_RETRANS = 1, + L2CAP_MODE_FLOWCTL = 2, +}; + +typedef struct { + uint16_t dcid; + uint16_t scid; +} __attribute__ ((packed)) l2cap_disconn_req; +#define L2CAP_DISCONN_REQ_SIZE 4 + +typedef struct { + uint16_t dcid; + uint16_t scid; +} __attribute__ ((packed)) l2cap_disconn_rsp; +#define L2CAP_DISCONN_RSP_SIZE 4 + +typedef struct { + uint16_t type; +} __attribute__ ((packed)) l2cap_info_req; +#define L2CAP_INFO_REQ_SIZE 2 + +typedef struct { + uint16_t type; + uint16_t result; + uint8_t data[0]; +} __attribute__ ((packed)) l2cap_info_rsp; +#define L2CAP_INFO_RSP_SIZE 4 + +/* info type */ +enum bt_l2cap_info_type { + L2CAP_IT_CL_MTU = 1, + L2CAP_IT_FEAT_MASK, +}; + +/* info result */ +enum bt_l2cap_info_result { + L2CAP_IR_SUCCESS = 0, + L2CAP_IR_NOTSUPP, +}; + +/* Service Discovery Protocol defines */ +/* Note that all multibyte values in lower layer protocols (above in this file) + * are little-endian while SDP is big-endian. */ + +/* Protocol UUIDs */ +enum sdp_proto_uuid { + SDP_UUID = 0x0001, + UDP_UUID = 0x0002, + RFCOMM_UUID = 0x0003, + TCP_UUID = 0x0004, + TCS_BIN_UUID = 0x0005, + TCS_AT_UUID = 0x0006, + OBEX_UUID = 0x0008, + IP_UUID = 0x0009, + FTP_UUID = 0x000a, + HTTP_UUID = 0x000c, + WSP_UUID = 0x000e, + BNEP_UUID = 0x000f, + UPNP_UUID = 0x0010, + HIDP_UUID = 0x0011, + HCRP_CTRL_UUID = 0x0012, + HCRP_DATA_UUID = 0x0014, + HCRP_NOTE_UUID = 0x0016, + AVCTP_UUID = 0x0017, + AVDTP_UUID = 0x0019, + CMTP_UUID = 0x001b, + UDI_UUID = 0x001d, + MCAP_CTRL_UUID = 0x001e, + MCAP_DATA_UUID = 0x001f, + L2CAP_UUID = 0x0100, +}; + +/* + * Service class identifiers of standard services and service groups + */ +enum service_class_id { + SDP_SERVER_SVCLASS_ID = 0x1000, + BROWSE_GRP_DESC_SVCLASS_ID = 0x1001, + PUBLIC_BROWSE_GROUP = 0x1002, + SERIAL_PORT_SVCLASS_ID = 0x1101, + LAN_ACCESS_SVCLASS_ID = 0x1102, + DIALUP_NET_SVCLASS_ID = 0x1103, + IRMC_SYNC_SVCLASS_ID = 0x1104, + OBEX_OBJPUSH_SVCLASS_ID = 0x1105, + OBEX_FILETRANS_SVCLASS_ID = 0x1106, + IRMC_SYNC_CMD_SVCLASS_ID = 0x1107, + HEADSET_SVCLASS_ID = 0x1108, + CORDLESS_TELEPHONY_SVCLASS_ID = 0x1109, + AUDIO_SOURCE_SVCLASS_ID = 0x110a, + AUDIO_SINK_SVCLASS_ID = 0x110b, + AV_REMOTE_TARGET_SVCLASS_ID = 0x110c, + ADVANCED_AUDIO_SVCLASS_ID = 0x110d, + AV_REMOTE_SVCLASS_ID = 0x110e, + VIDEO_CONF_SVCLASS_ID = 0x110f, + INTERCOM_SVCLASS_ID = 0x1110, + FAX_SVCLASS_ID = 0x1111, + HEADSET_AGW_SVCLASS_ID = 0x1112, + WAP_SVCLASS_ID = 0x1113, + WAP_CLIENT_SVCLASS_ID = 0x1114, + PANU_SVCLASS_ID = 0x1115, + NAP_SVCLASS_ID = 0x1116, + GN_SVCLASS_ID = 0x1117, + DIRECT_PRINTING_SVCLASS_ID = 0x1118, + REFERENCE_PRINTING_SVCLASS_ID = 0x1119, + IMAGING_SVCLASS_ID = 0x111a, + IMAGING_RESPONDER_SVCLASS_ID = 0x111b, + IMAGING_ARCHIVE_SVCLASS_ID = 0x111c, + IMAGING_REFOBJS_SVCLASS_ID = 0x111d, + HANDSFREE_SVCLASS_ID = 0x111e, + HANDSFREE_AGW_SVCLASS_ID = 0x111f, + DIRECT_PRT_REFOBJS_SVCLASS_ID = 0x1120, + REFLECTED_UI_SVCLASS_ID = 0x1121, + BASIC_PRINTING_SVCLASS_ID = 0x1122, + PRINTING_STATUS_SVCLASS_ID = 0x1123, + HID_SVCLASS_ID = 0x1124, + HCR_SVCLASS_ID = 0x1125, + HCR_PRINT_SVCLASS_ID = 0x1126, + HCR_SCAN_SVCLASS_ID = 0x1127, + CIP_SVCLASS_ID = 0x1128, + VIDEO_CONF_GW_SVCLASS_ID = 0x1129, + UDI_MT_SVCLASS_ID = 0x112a, + UDI_TA_SVCLASS_ID = 0x112b, + AV_SVCLASS_ID = 0x112c, + SAP_SVCLASS_ID = 0x112d, + PBAP_PCE_SVCLASS_ID = 0x112e, + PBAP_PSE_SVCLASS_ID = 0x112f, + PBAP_SVCLASS_ID = 0x1130, + PNP_INFO_SVCLASS_ID = 0x1200, + GENERIC_NETWORKING_SVCLASS_ID = 0x1201, + GENERIC_FILETRANS_SVCLASS_ID = 0x1202, + GENERIC_AUDIO_SVCLASS_ID = 0x1203, + GENERIC_TELEPHONY_SVCLASS_ID = 0x1204, + UPNP_SVCLASS_ID = 0x1205, + UPNP_IP_SVCLASS_ID = 0x1206, + UPNP_PAN_SVCLASS_ID = 0x1300, + UPNP_LAP_SVCLASS_ID = 0x1301, + UPNP_L2CAP_SVCLASS_ID = 0x1302, + VIDEO_SOURCE_SVCLASS_ID = 0x1303, + VIDEO_SINK_SVCLASS_ID = 0x1304, + VIDEO_DISTRIBUTION_SVCLASS_ID = 0x1305, + MDP_SVCLASS_ID = 0x1400, + MDP_SOURCE_SVCLASS_ID = 0x1401, + MDP_SINK_SVCLASS_ID = 0x1402, + APPLE_AGENT_SVCLASS_ID = 0x2112, +}; + +/* + * Standard profile descriptor identifiers; note these + * may be identical to some of the service classes defined above + */ +#define SDP_SERVER_PROFILE_ID SDP_SERVER_SVCLASS_ID +#define BROWSE_GRP_DESC_PROFILE_ID BROWSE_GRP_DESC_SVCLASS_ID +#define SERIAL_PORT_PROFILE_ID SERIAL_PORT_SVCLASS_ID +#define LAN_ACCESS_PROFILE_ID LAN_ACCESS_SVCLASS_ID +#define DIALUP_NET_PROFILE_ID DIALUP_NET_SVCLASS_ID +#define IRMC_SYNC_PROFILE_ID IRMC_SYNC_SVCLASS_ID +#define OBEX_OBJPUSH_PROFILE_ID OBEX_OBJPUSH_SVCLASS_ID +#define OBEX_FILETRANS_PROFILE_ID OBEX_FILETRANS_SVCLASS_ID +#define IRMC_SYNC_CMD_PROFILE_ID IRMC_SYNC_CMD_SVCLASS_ID +#define HEADSET_PROFILE_ID HEADSET_SVCLASS_ID +#define CORDLESS_TELEPHONY_PROFILE_ID CORDLESS_TELEPHONY_SVCLASS_ID +#define AUDIO_SOURCE_PROFILE_ID AUDIO_SOURCE_SVCLASS_ID +#define AUDIO_SINK_PROFILE_ID AUDIO_SINK_SVCLASS_ID +#define AV_REMOTE_TARGET_PROFILE_ID AV_REMOTE_TARGET_SVCLASS_ID +#define ADVANCED_AUDIO_PROFILE_ID ADVANCED_AUDIO_SVCLASS_ID +#define AV_REMOTE_PROFILE_ID AV_REMOTE_SVCLASS_ID +#define VIDEO_CONF_PROFILE_ID VIDEO_CONF_SVCLASS_ID +#define INTERCOM_PROFILE_ID INTERCOM_SVCLASS_ID +#define FAX_PROFILE_ID FAX_SVCLASS_ID +#define HEADSET_AGW_PROFILE_ID HEADSET_AGW_SVCLASS_ID +#define WAP_PROFILE_ID WAP_SVCLASS_ID +#define WAP_CLIENT_PROFILE_ID WAP_CLIENT_SVCLASS_ID +#define PANU_PROFILE_ID PANU_SVCLASS_ID +#define NAP_PROFILE_ID NAP_SVCLASS_ID +#define GN_PROFILE_ID GN_SVCLASS_ID +#define DIRECT_PRINTING_PROFILE_ID DIRECT_PRINTING_SVCLASS_ID +#define REFERENCE_PRINTING_PROFILE_ID REFERENCE_PRINTING_SVCLASS_ID +#define IMAGING_PROFILE_ID IMAGING_SVCLASS_ID +#define IMAGING_RESPONDER_PROFILE_ID IMAGING_RESPONDER_SVCLASS_ID +#define IMAGING_ARCHIVE_PROFILE_ID IMAGING_ARCHIVE_SVCLASS_ID +#define IMAGING_REFOBJS_PROFILE_ID IMAGING_REFOBJS_SVCLASS_ID +#define HANDSFREE_PROFILE_ID HANDSFREE_SVCLASS_ID +#define HANDSFREE_AGW_PROFILE_ID HANDSFREE_AGW_SVCLASS_ID +#define DIRECT_PRT_REFOBJS_PROFILE_ID DIRECT_PRT_REFOBJS_SVCLASS_ID +#define REFLECTED_UI_PROFILE_ID REFLECTED_UI_SVCLASS_ID +#define BASIC_PRINTING_PROFILE_ID BASIC_PRINTING_SVCLASS_ID +#define PRINTING_STATUS_PROFILE_ID PRINTING_STATUS_SVCLASS_ID +#define HID_PROFILE_ID HID_SVCLASS_ID +#define HCR_PROFILE_ID HCR_SCAN_SVCLASS_ID +#define HCR_PRINT_PROFILE_ID HCR_PRINT_SVCLASS_ID +#define HCR_SCAN_PROFILE_ID HCR_SCAN_SVCLASS_ID +#define CIP_PROFILE_ID CIP_SVCLASS_ID +#define VIDEO_CONF_GW_PROFILE_ID VIDEO_CONF_GW_SVCLASS_ID +#define UDI_MT_PROFILE_ID UDI_MT_SVCLASS_ID +#define UDI_TA_PROFILE_ID UDI_TA_SVCLASS_ID +#define AV_PROFILE_ID AV_SVCLASS_ID +#define SAP_PROFILE_ID SAP_SVCLASS_ID +#define PBAP_PCE_PROFILE_ID PBAP_PCE_SVCLASS_ID +#define PBAP_PSE_PROFILE_ID PBAP_PSE_SVCLASS_ID +#define PBAP_PROFILE_ID PBAP_SVCLASS_ID +#define PNP_INFO_PROFILE_ID PNP_INFO_SVCLASS_ID +#define GENERIC_NETWORKING_PROFILE_ID GENERIC_NETWORKING_SVCLASS_ID +#define GENERIC_FILETRANS_PROFILE_ID GENERIC_FILETRANS_SVCLASS_ID +#define GENERIC_AUDIO_PROFILE_ID GENERIC_AUDIO_SVCLASS_ID +#define GENERIC_TELEPHONY_PROFILE_ID GENERIC_TELEPHONY_SVCLASS_ID +#define UPNP_PROFILE_ID UPNP_SVCLASS_ID +#define UPNP_IP_PROFILE_ID UPNP_IP_SVCLASS_ID +#define UPNP_PAN_PROFILE_ID UPNP_PAN_SVCLASS_ID +#define UPNP_LAP_PROFILE_ID UPNP_LAP_SVCLASS_ID +#define UPNP_L2CAP_PROFILE_ID UPNP_L2CAP_SVCLASS_ID +#define VIDEO_SOURCE_PROFILE_ID VIDEO_SOURCE_SVCLASS_ID +#define VIDEO_SINK_PROFILE_ID VIDEO_SINK_SVCLASS_ID +#define VIDEO_DISTRIBUTION_PROFILE_ID VIDEO_DISTRIBUTION_SVCLASS_ID +#define MDP_PROFILE_ID MDP_SVCLASS_ID +#define MDP_SOURCE_PROFILE_ID MDP_SROUCE_SVCLASS_ID +#define MDP_SINK_PROFILE_ID MDP_SINK_SVCLASS_ID +#define APPLE_AGENT_PROFILE_ID APPLE_AGENT_SVCLASS_ID + +/* Data Representation */ +enum bt_sdp_data_type { + SDP_DTYPE_NIL = 0 << 3, + SDP_DTYPE_UINT = 1 << 3, + SDP_DTYPE_SINT = 2 << 3, + SDP_DTYPE_UUID = 3 << 3, + SDP_DTYPE_STRING = 4 << 3, + SDP_DTYPE_BOOL = 5 << 3, + SDP_DTYPE_SEQ = 6 << 3, + SDP_DTYPE_ALT = 7 << 3, + SDP_DTYPE_URL = 8 << 3, +}; + +enum bt_sdp_data_size { + SDP_DSIZE_1 = 0, + SDP_DSIZE_2, + SDP_DSIZE_4, + SDP_DSIZE_8, + SDP_DSIZE_16, + SDP_DSIZE_NEXT1, + SDP_DSIZE_NEXT2, + SDP_DSIZE_NEXT4, + SDP_DSIZE_MASK = SDP_DSIZE_NEXT4, +}; + +enum bt_sdp_cmd { + SDP_ERROR_RSP = 0x01, + SDP_SVC_SEARCH_REQ = 0x02, + SDP_SVC_SEARCH_RSP = 0x03, + SDP_SVC_ATTR_REQ = 0x04, + SDP_SVC_ATTR_RSP = 0x05, + SDP_SVC_SEARCH_ATTR_REQ = 0x06, + SDP_SVC_SEARCH_ATTR_RSP = 0x07, +}; + +enum bt_sdp_errorcode { + SDP_INVALID_VERSION = 0x0001, + SDP_INVALID_RECORD_HANDLE = 0x0002, + SDP_INVALID_SYNTAX = 0x0003, + SDP_INVALID_PDU_SIZE = 0x0004, + SDP_INVALID_CSTATE = 0x0005, +}; + +/* + * String identifiers are based on the SDP spec stating that + * "base attribute id of the primary (universal) language must be 0x0100" + * + * Other languages should have their own offset; e.g.: + * #define XXXLangBase yyyy + * #define AttrServiceName_XXX 0x0000+XXXLangBase + */ +#define SDP_PRIMARY_LANG_BASE 0x0100 + +enum bt_sdp_attribute_id { + SDP_ATTR_RECORD_HANDLE = 0x0000, + SDP_ATTR_SVCLASS_ID_LIST = 0x0001, + SDP_ATTR_RECORD_STATE = 0x0002, + SDP_ATTR_SERVICE_ID = 0x0003, + SDP_ATTR_PROTO_DESC_LIST = 0x0004, + SDP_ATTR_BROWSE_GRP_LIST = 0x0005, + SDP_ATTR_LANG_BASE_ATTR_ID_LIST = 0x0006, + SDP_ATTR_SVCINFO_TTL = 0x0007, + SDP_ATTR_SERVICE_AVAILABILITY = 0x0008, + SDP_ATTR_PFILE_DESC_LIST = 0x0009, + SDP_ATTR_DOC_URL = 0x000a, + SDP_ATTR_CLNT_EXEC_URL = 0x000b, + SDP_ATTR_ICON_URL = 0x000c, + SDP_ATTR_ADD_PROTO_DESC_LIST = 0x000d, + + SDP_ATTR_SVCNAME_PRIMARY = SDP_PRIMARY_LANG_BASE + 0, + SDP_ATTR_SVCDESC_PRIMARY = SDP_PRIMARY_LANG_BASE + 1, + SDP_ATTR_SVCPROV_PRIMARY = SDP_PRIMARY_LANG_BASE + 2, + + SDP_ATTR_GROUP_ID = 0x0200, + SDP_ATTR_IP_SUBNET = 0x0200, + + /* SDP */ + SDP_ATTR_VERSION_NUM_LIST = 0x0200, + SDP_ATTR_SVCDB_STATE = 0x0201, + + SDP_ATTR_SERVICE_VERSION = 0x0300, + SDP_ATTR_EXTERNAL_NETWORK = 0x0301, + SDP_ATTR_SUPPORTED_DATA_STORES_LIST = 0x0301, + SDP_ATTR_FAX_CLASS1_SUPPORT = 0x0302, + SDP_ATTR_REMOTE_AUDIO_VOLUME_CONTROL = 0x0302, + SDP_ATTR_FAX_CLASS20_SUPPORT = 0x0303, + SDP_ATTR_SUPPORTED_FORMATS_LIST = 0x0303, + SDP_ATTR_FAX_CLASS2_SUPPORT = 0x0304, + SDP_ATTR_AUDIO_FEEDBACK_SUPPORT = 0x0305, + SDP_ATTR_NETWORK_ADDRESS = 0x0306, + SDP_ATTR_WAP_GATEWAY = 0x0307, + SDP_ATTR_HOMEPAGE_URL = 0x0308, + SDP_ATTR_WAP_STACK_TYPE = 0x0309, + SDP_ATTR_SECURITY_DESC = 0x030a, + SDP_ATTR_NET_ACCESS_TYPE = 0x030b, + SDP_ATTR_MAX_NET_ACCESSRATE = 0x030c, + SDP_ATTR_IP4_SUBNET = 0x030d, + SDP_ATTR_IP6_SUBNET = 0x030e, + SDP_ATTR_SUPPORTED_CAPABILITIES = 0x0310, + SDP_ATTR_SUPPORTED_FEATURES = 0x0311, + SDP_ATTR_SUPPORTED_FUNCTIONS = 0x0312, + SDP_ATTR_TOTAL_IMAGING_DATA_CAPACITY = 0x0313, + SDP_ATTR_SUPPORTED_REPOSITORIES = 0x0314, + + /* PnP Information */ + SDP_ATTR_SPECIFICATION_ID = 0x0200, + SDP_ATTR_VENDOR_ID = 0x0201, + SDP_ATTR_PRODUCT_ID = 0x0202, + SDP_ATTR_VERSION = 0x0203, + SDP_ATTR_PRIMARY_RECORD = 0x0204, + SDP_ATTR_VENDOR_ID_SOURCE = 0x0205, + + /* BT HID */ + SDP_ATTR_DEVICE_RELEASE_NUMBER = 0x0200, + SDP_ATTR_PARSER_VERSION = 0x0201, + SDP_ATTR_DEVICE_SUBCLASS = 0x0202, + SDP_ATTR_COUNTRY_CODE = 0x0203, + SDP_ATTR_VIRTUAL_CABLE = 0x0204, + SDP_ATTR_RECONNECT_INITIATE = 0x0205, + SDP_ATTR_DESCRIPTOR_LIST = 0x0206, + SDP_ATTR_LANG_ID_BASE_LIST = 0x0207, + SDP_ATTR_SDP_DISABLE = 0x0208, + SDP_ATTR_BATTERY_POWER = 0x0209, + SDP_ATTR_REMOTE_WAKEUP = 0x020a, + SDP_ATTR_PROFILE_VERSION = 0x020b, + SDP_ATTR_SUPERVISION_TIMEOUT = 0x020c, + SDP_ATTR_NORMALLY_CONNECTABLE = 0x020d, + SDP_ATTR_BOOT_DEVICE = 0x020e, +}; @@ -153,5 +153,3 @@ int cdrom_read_toc_raw(int nb_sectors, uint8_t *buf, int msf, int session_num) cpu_to_be16wu((uint16_t *)buf, len - 2); return len; } - - diff --git a/hw/devices.h b/hw/devices.h index 45fead9..156bde2 100644 --- a/hw/devices.h +++ b/hw/devices.h @@ -6,51 +6,38 @@ /* 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); +uWireSlave *tsc2102_init(qemu_irq pint); +uWireSlave *tsc2301_init(qemu_irq penirq, qemu_irq kbirq, qemu_irq dav); +I2SCodec *tsc210x_codec(uWireSlave *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); +void tsc210x_set_transform(uWireSlave *chip, + MouseTransformInfo *info); +void tsc210x_key_event(uWireSlave *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); +void tsc2005_set_transform(void *opaque, MouseTransformInfo *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_init(qemu_irq gpio_int); 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 { +typedef struct { 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); +} CBus; +CBus *cbus_init(qemu_irq dat_out); +void cbus_attach(CBus *bus, void *slave_opaque); void *retu_init(qemu_irq irq, int vilma); void *tahvo_init(qemu_irq irq, int betty); @@ -58,17 +45,26 @@ 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); +typedef struct TUSBState TUSBState; +TUSBState *tusb6010_init(qemu_irq intr); +int tusb6010_sync_io(TUSBState *s); +int tusb6010_async_io(TUSBState *s); +void tusb6010_power(TUSBState *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, +typedef struct TC6393xbState TC6393xbState; +#define TC6393XB_RAM 0x110000 /* amount of ram for Video and USB */ +TC6393xbState *tc6393xb_init(uint32_t base, qemu_irq irq); +void tc6393xb_gpio_out_set(TC6393xbState *s, int line, qemu_irq handler); -qemu_irq *tc6393xb_gpio_in_get(struct tc6393xb_s *s); +qemu_irq *tc6393xb_gpio_in_get(TC6393xbState *s); +qemu_irq tc6393xb_l3v_get(TC6393xbState *s); + +/* sm501.c */ +void sm501_init(uint32_t base, uint32_t local_mem_bytes, qemu_irq irq, + CharDriverState *chr); +/* usb-ohci.c */ +void usb_ohci_init_sm501(uint32_t mmio_base, uint32_t localmem_base, + int num_ports, int devfn, qemu_irq irq); #endif @@ -28,17 +28,13 @@ #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]; @@ -78,6 +74,8 @@ enum { }; +static void DMA_run (void); + static int channels[8] = {-1, 2, 3, 1, -1, -1, -1, 0}; static void write_page (void *opaque, uint32_t nport, uint32_t data) @@ -214,6 +212,7 @@ static void write_cont (void *opaque, uint32_t nport, uint32_t data) d->status &= ~(1 << (ichan + 4)); } d->status &= ~(1 << ichan); + DMA_run(); break; case 0x0a: /* single mask */ @@ -221,6 +220,7 @@ static void write_cont (void *opaque, uint32_t nport, uint32_t data) d->mask |= 1 << (data & 3); else d->mask &= ~(1 << (data & 3)); + DMA_run(); break; case 0x0b: /* mode */ @@ -255,10 +255,12 @@ static void write_cont (void *opaque, uint32_t nport, uint32_t data) case 0x0e: /* clear mask for all channels */ d->mask = 0; + DMA_run(); break; case 0x0f: /* write mask for all channels */ d->mask = data; + DMA_run(); break; default: @@ -310,6 +312,7 @@ void DMA_hold_DREQ (int nchan) ichan = nchan & 3; linfo ("held cont=%d chan=%d\n", ncont, ichan); dma_controllers[ncont].status |= 1 << (ichan + 4); + DMA_run(); } void DMA_release_DREQ (int nchan) @@ -320,6 +323,7 @@ void DMA_release_DREQ (int nchan) ichan = nchan & 3; linfo ("released cont=%d chan=%d\n", ncont, ichan); dma_controllers[ncont].status &= ~(1 << (ichan + 4)); + DMA_run(); } static void channel_run (int ncont, int ichan) @@ -347,10 +351,13 @@ static void channel_run (int ncont, int ichan) ldebug ("dma_pos %d size %d\n", n, (r->base[COUNT] + 1) << ncont); } -void DMA_run (void) +static QEMUBH *dma_bh; + +static void DMA_run (void) { struct dma_cont *d; int icont, ichan; + int rearm = 0; d = dma_controllers; @@ -360,10 +367,20 @@ void DMA_run (void) mask = 1 << ichan; - if ((0 == (d->mask & mask)) && (0 != (d->status & (mask << 4)))) + if ((0 == (d->mask & mask)) && (0 != (d->status & (mask << 4)))) { channel_run (icont, ichan); + rearm = 1; + } } } + + if (rearm) + qemu_bh_schedule_idle(dma_bh); +} + +static void DMA_run_bh(void *unused) +{ + DMA_run(); } void DMA_register_channel (int nchan, @@ -430,7 +447,7 @@ void DMA_schedule(int nchan) { CPUState *env = cpu_single_env; if (env) - cpu_interrupt(env, CPU_INTERRUPT_EXIT); + cpu_exit(env); } static void dma_reset(void *opaque) @@ -458,7 +475,7 @@ static void dma_init2(struct dma_cont *d, int base, int dshift, 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++) { + for (i = 0; i < ARRAY_SIZE (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, @@ -476,9 +493,9 @@ static void dma_init2(struct dma_cont *d, int base, int dshift, register_ioport_read (base + ((i + 8) << dshift), 1, 1, read_cont, d); } - qemu_register_reset(dma_reset, d); + qemu_register_reset(dma_reset, 0, d); dma_reset(d); - for (i = 0; i < LENOFA (d->regs); ++i) { + for (i = 0; i < ARRAY_SIZE (d->regs); ++i) { d->regs[i].transfer_handler = dma_phony_handler; } } @@ -534,6 +551,9 @@ static int dma_load (QEMUFile *f, void *opaque, int version_id) qemu_get_8s (f, &r->dack); qemu_get_8s (f, &r->eop); } + + DMA_run(); + return 0; } @@ -545,4 +565,6 @@ void DMA_init (int high_page_enable) 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]); + + dma_bh = qemu_bh_new(DMA_run_bh, NULL); } diff --git a/hw/goldfish_audio.c b/hw/goldfish_audio.c index d0a44b5..c8a6712 100644 --- a/hw/goldfish_audio.c +++ b/hw/goldfish_audio.c @@ -62,21 +62,25 @@ enum { AUDIO_INT_READ_BUFFER_FULL = 1U << 2, }; +struct goldfish_audio_buff { + uint32_t address; + uint32_t length; + uint8* data; + uint32_t capacity; + uint32_t offset; +}; + 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 +#ifndef USE_QEMU_AUDIO_IN + // address of the read buffer + uint32_t read_buffer; // path to file or device to use for input const char* input_source; // true if input is a wav file @@ -94,11 +98,9 @@ struct goldfish_audio_state { int current_buffer; // current data to write - uint8* data_1; - uint32_t data_1_length; - uint8* data_2; - uint32_t data_2_length; - + struct goldfish_audio_buff out_buff1[1]; + struct goldfish_audio_buff out_buff2[1]; + struct goldfish_audio_buff in_buff[1]; // for QEMU sound output QEMUSoundCard card; @@ -108,24 +110,132 @@ struct goldfish_audio_state { #endif }; +static void +goldfish_audio_buff_init( struct goldfish_audio_buff* b ) +{ + b->address = 0; + b->length = 0; + b->data = NULL; + b->capacity = 0; + b->offset = 0; +} + +static void +goldfish_audio_buff_reset( struct goldfish_audio_buff* b ) +{ + b->offset = 0; + b->length = 0; +} + +static uint32_t +goldfish_audio_buff_length( struct goldfish_audio_buff* b ) +{ + return b->length; +} + +static void +goldfish_audio_buff_ensure( struct goldfish_audio_buff* b, uint32_t size ) +{ + if (b->capacity < size) { + b->data = qemu_realloc(b->data, size); + b->capacity = size; + } +} + +static void +goldfish_audio_buff_set_address( struct goldfish_audio_buff* b, uint32_t addr ) +{ + b->address = addr; +} + +static void +goldfish_audio_buff_set_length( struct goldfish_audio_buff* b, uint32_t len ) +{ + b->length = len; + b->offset = 0; + goldfish_audio_buff_ensure(b, len); +} + +static void +goldfish_audio_buff_read( struct goldfish_audio_buff* b ) +{ + cpu_physical_memory_read(b->address, b->data, b->length); +} + +static void +goldfish_audio_buff_write( struct goldfish_audio_buff* b ) +{ + cpu_physical_memory_write(b->address, b->data, b->length); +} + +static int +goldfish_audio_buff_send( struct goldfish_audio_buff* b, int free, struct goldfish_audio_state* s ) +{ + int ret, write = b->length; + + if (write > free) + write = free; + + ret = AUD_write(s->voice, b->data + b->offset, write); + b->offset += ret; + b->length -= ret; + return ret; +} + +static int +goldfish_audio_buff_available( struct goldfish_audio_buff* b ) +{ + return b->length - b->offset; +} + +static int +goldfish_audio_buff_recv( struct goldfish_audio_buff* b, int avail, struct goldfish_audio_state* s ) +{ + int missing = b->length - b->offset; + int avail2 = (avail > missing) ? missing : avail; + int read; + + read = AUD_read(s->voicein, b->data + b->offset, avail2 ); + if (read == 0) + return 0; + + if (avail2 > 0) + D("%s: AUD_read(%d) returned %d", __FUNCTION__, avail2, read); + + cpu_physical_memory_write( b->address + b->offset, b->data, read ); + b->offset += read; + + return read; +} + +static void +goldfish_audio_buff_put( struct goldfish_audio_buff* b, QEMUFile* f ) +{ + qemu_put_be32(f, b->address ); + qemu_put_be32(f, b->length ); + qemu_put_be32(f, b->offset ); + qemu_put_buffer(f, b->data, b->length ); +} + +static int +goldfish_audio_buff_get( struct goldfish_audio_buff* b, QEMUFile* f ) +{ + b->address = qemu_get_be32(f); + b->length = qemu_get_be32(f); + b->offset = qemu_get_be32(f); + goldfish_audio_buff_ensure(b, b->length); + qemu_get_buffer(f, b->data, b->length); +} + /* update this whenever you change the goldfish_audio_state structure */ -#define AUDIO_STATE_SAVE_VERSION 1 +#define AUDIO_STATE_SAVE_VERSION 2 #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 ) @@ -134,9 +244,9 @@ static void audio_state_save( QEMUFile* f, void* 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 ); + goldfish_audio_buff_put (s->out_buff1, f); + goldfish_audio_buff_put (s->out_buff2, f); + goldfish_audio_buff_put (s->in_buff, f); } static int audio_state_load( QEMUFile* f, void* opaque, int version_id ) @@ -149,8 +259,9 @@ static int audio_state_load( QEMUFile* f, void* opaque, int version_id ) 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; + goldfish_audio_buff_get( s->out_buff1, f ); + goldfish_audio_buff_get( s->out_buff2, f ); + goldfish_audio_buff_get (s->in_buff, f); } return -1; } @@ -158,25 +269,25 @@ static int audio_state_load( QEMUFile* f, void* opaque, int version_id ) static void enable_audio(struct goldfish_audio_state *s, int enable) { // enable or disable the output voice - if (s->voice != NULL) + if (s->voice != NULL) { AUD_set_active_out(s->voice, (enable & (AUDIO_INT_WRITE_BUFFER_1_EMPTY | AUDIO_INT_WRITE_BUFFER_2_EMPTY)) != 0); + goldfish_audio_buff_reset( s->out_buff1 ); + goldfish_audio_buff_reset( s->out_buff2 ); + } - if (s->voicein) + 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; + goldfish_audio_buff_reset( s->in_buff ); + } 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; + goldfish_audio_buff_set_length( s->in_buff, count ); + s->read_buffer_available = count; } #else static void start_read(struct goldfish_audio_state *s, uint32_t count) @@ -255,7 +366,6 @@ 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 @@ -277,6 +387,7 @@ static uint32_t goldfish_audio_read(void *opaque, target_phys_addr_t offset) case AUDIO_READ_BUFFER_AVAILABLE: D("%s: AUDIO_READ_BUFFER_AVAILABLE returns %d", __FUNCTION__, s->read_buffer_available); + goldfish_audio_buff_write( s->in_buff ); return s->read_buffer_available; default: @@ -288,7 +399,6 @@ static uint32_t goldfish_audio_read(void *opaque, target_phys_addr_t offset) 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: @@ -301,30 +411,34 @@ static void goldfish_audio_write(void *opaque, target_phys_addr_t offset, uint32 break; case AUDIO_SET_WRITE_BUFFER_1: /* save pointer to buffer 1 */ - s->buffer_1 = val; + D( "%s: AUDIO_SET_WRITE_BUFFER_1 %08x", __FUNCTION__, val); + goldfish_audio_buff_set_address( s->out_buff1, val ); break; case AUDIO_SET_WRITE_BUFFER_2: /* save pointer to buffer 2 */ - s->buffer_2 = val; + D( "%s: AUDIO_SET_WRITE_BUFFER_2 %08x", __FUNCTION__, val); + goldfish_audio_buff_set_address( s->out_buff2, val ); break; case AUDIO_WRITE_BUFFER_1: /* record that data in buffer 1 is ready to write */ + //D( "%s: AUDIO_WRITE_BUFFER_1 %08x", __FUNCTION__, val); if (s->current_buffer == 0) s->current_buffer = 1; - s->data_1 = phys_ram_base + s->buffer_1; - s->data_1_length = val; + goldfish_audio_buff_set_length( s->out_buff1, val ); + goldfish_audio_buff_read( s->out_buff1 ); 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 */ + //D( "%s: AUDIO_WRITE_BUFFER_2 %08x", __FUNCTION__, val); if (s->current_buffer == 0) s->current_buffer = 2; - s->data_2 = phys_ram_base + s->buffer_2; - s->data_2_length = val; + goldfish_audio_buff_set_length( s->out_buff2, val ); + goldfish_audio_buff_read( s->out_buff2 ); s->int_status &= ~AUDIO_INT_WRITE_BUFFER_2_EMPTY; break; - case AUDIO_SET_READ_BUFFER: + case AUDIO_SET_READ_BUFFER: /* save pointer to the read buffer */ - s->read_buffer = val; + goldfish_audio_buff_set_address( s->in_buff, val ); D( "%s: AUDIO_SET_READ_BUFFER %p", __FUNCTION__, (void*)val ); break; @@ -350,19 +464,14 @@ static void goldfish_audio_callback(void *opaque, int free) /* 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); + int written = goldfish_audio_buff_send( s->out_buff1, free, s ); if (written) { - D("%s: sent %d bytes to audio output", __FUNCTION__, write); - s->data_1 += written; - s->data_1_length -= written; + D("%s: sent %5d bytes to audio output (buffer 1)", __FUNCTION__, written); free -= written; - if (s->data_1_length == 0) { + if (goldfish_audio_buff_length( s->out_buff1 ) == 0) { new_status |= AUDIO_INT_WRITE_BUFFER_1_EMPTY; - s->current_buffer = (s->data_2_length ? 2 : 0); + s->current_buffer = (goldfish_audio_buff_length( s->out_buff2 ) ? 2 : 0); } } else { break; @@ -371,19 +480,14 @@ static void goldfish_audio_callback(void *opaque, int free) /* 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); + int written = goldfish_audio_buff_send( s->out_buff2, free, s ); if (written) { - D("%s: sent %d bytes to audio output", __FUNCTION__, write); - s->data_2 += written; - s->data_2_length -= written; + D("%s: sent %5d bytes to audio output (buffer 2)", __FUNCTION__, written); free -= written; - if (s->data_2_length == 0) { + if (goldfish_audio_buff_length( s->out_buff2 ) == 0) { new_status |= AUDIO_INT_WRITE_BUFFER_2_EMPTY; - s->current_buffer = (s->data_1_length ? 1 : 0); + s->current_buffer = (goldfish_audio_buff_length( s->out_buff1 ) ? 1 : 0); } } else { break; @@ -404,36 +508,22 @@ 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) + if (goldfish_audio_buff_available( s->in_buff ) == 0 ) 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); + int read = goldfish_audio_buff_recv( s->in_buff, avail, s ); 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) { + + if (goldfish_audio_buff_available( s->in_buff) == 0) { new_status |= AUDIO_INT_READ_BUFFER_FULL; - D("%s: AUDIO_INT_READ_BUFFER_FULL available=%d", __FUNCTION__, s->read_buffer_available); + D("%s: AUDIO_INT_READ_BUFFER_FULL available=%d", + __FUNCTION__, goldfish_audio_buff_length( s->in_buff )); + break; } - s->read_pos = pos; } if (new_status && new_status != s->int_status) { @@ -458,7 +548,7 @@ static CPUWriteMemoryFunc *goldfish_audio_writefn[] = { void goldfish_audio_init(uint32_t base, int id, const char* input_source) { struct goldfish_audio_state *s; - audsettings_t as; + struct audsettings as; /* nothing to do if no audio input and output */ if (!android_hw->hw_audioOutput && !android_hw->hw_audioInput) @@ -482,7 +572,7 @@ void goldfish_audio_init(uint32_t base, int id, const char* input_source) } #endif - AUD_register_card( &glob_audio_state, "goldfish_audio", &s->card); + AUD_register_card( "goldfish_audio", &s->card); as.freq = 44100; as.nchannels = 2; @@ -525,6 +615,10 @@ void goldfish_audio_init(uint32_t base, int id, const char* input_source) } #endif + goldfish_audio_buff_init( s->out_buff1 ); + goldfish_audio_buff_init( s->out_buff2 ); + goldfish_audio_buff_init( s->in_buff ); + goldfish_device_add(&s->dev, goldfish_audio_readfn, goldfish_audio_writefn, s); register_savevm( "audio_state", 0, AUDIO_STATE_SAVE_VERSION, diff --git a/hw/goldfish_battery.c b/hw/goldfish_battery.c index d9ef785..c5eef9c 100644 --- a/hw/goldfish_battery.c +++ b/hw/goldfish_battery.c @@ -83,7 +83,7 @@ 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 @@ -116,7 +116,6 @@ static uint32_t goldfish_battery_read(void *opaque, target_phys_addr_t offset) 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: diff --git a/hw/goldfish_device.c b/hw/goldfish_device.c index 2c9dd6e..3ced4ce 100644 --- a/hw/goldfish_device.c +++ b/hw/goldfish_device.c @@ -77,8 +77,7 @@ int goldfish_device_add(struct goldfish_device *dev, { int iomemtype; goldfish_add_device_no_io(dev); - iomemtype = cpu_register_io_memory(0, mem_read, - mem_write, opaque); + iomemtype = cpu_register_io_memory(mem_read, mem_write, opaque); cpu_register_physical_memory(dev->base, dev->size, iomemtype); return 0; } @@ -86,7 +85,6 @@ int goldfish_device_add(struct goldfish_device *dev, 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: @@ -138,7 +136,6 @@ static void goldfish_bus_op_init(struct bus_state *s) 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: @@ -152,7 +149,7 @@ static void goldfish_bus_write(void *opaque, target_phys_addr_t offset, uint32_t break; case PDEV_BUS_GET_NAME: if(s->current) - pmemcpy(value, s->current->name, strlen(s->current->name)); + cpu_memory_rw_debug(cpu_single_env, value, (void*)s->current->name, strlen(s->current->name), 1); break; default: cpu_abort (cpu_single_env, "goldfish_bus_write: Bad offset %x\n", offset); diff --git a/hw/goldfish_events_device.c b/hw/goldfish_events_device.c index 6f7e41b..d8e265c 100644 --- a/hw/goldfish_events_device.c +++ b/hw/goldfish_events_device.c @@ -164,7 +164,7 @@ static int get_page_data(events_state *s, int offset) static uint32_t events_read(void *x, target_phys_addr_t off) { events_state *s = (events_state *) x; - int offset = off - s->base; + int offset = off; // - s->base; if (offset == REG_READ) return dequeue_event(s); else if (offset == REG_LEN) @@ -177,7 +177,7 @@ static uint32_t events_read(void *x, target_phys_addr_t off) 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; + int offset = off; // - s->base; if (offset == REG_SET_PAGE) s->page = val; } @@ -379,12 +379,12 @@ void events_dev_init(uint32_t base, qemu_irq irq) events_set_bit(s, EV_SW, 0); } - iomemtype = cpu_register_io_memory(0, events_readfn, events_writefn, s); + iomemtype = cpu_register_io_memory(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_mouse_event_handler(events_put_mouse, s, 1, "goldfish-events"); qemu_add_generic_event_handler(events_put_generic, s); s->base = base; diff --git a/hw/goldfish_fb.c b/hw/goldfish_fb.c index 71cede2..4a0e335 100644 --- a/hw/goldfish_fb.c +++ b/hw/goldfish_fb.c @@ -155,7 +155,7 @@ static void goldfish_fb_update_display(void *opaque) s->need_update = 0; } - src_line = phys_ram_base + base; + src_line = qemu_get_ram_ptr( base ); dst_line = s->qfbuff->pixels; pitch = s->qfbuff->pitch; width = s->qfbuff->width; @@ -292,7 +292,7 @@ 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; @@ -332,7 +332,7 @@ 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; diff --git a/hw/goldfish_interrupt.c b/hw/goldfish_interrupt.c index 2cba649..c620664 100644 --- a/hw/goldfish_interrupt.c +++ b/hw/goldfish_interrupt.c @@ -95,7 +95,6 @@ static void goldfish_int_set_irq(void *opaque, int irq, int level) 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 */ @@ -119,7 +118,6 @@ static void goldfish_int_write(void *opaque, target_phys_addr_t offset, uint32_t { 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: diff --git a/hw/goldfish_memlog.c b/hw/goldfish_memlog.c index 98fcffc..f4be28a 100644 --- a/hw/goldfish_memlog.c +++ b/hw/goldfish_memlog.c @@ -24,7 +24,6 @@ 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; } @@ -35,7 +34,6 @@ 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; diff --git a/hw/goldfish_mmc.c b/hw/goldfish_mmc.c index 272f403..2295d2d 100644 --- a/hw/goldfish_mmc.c +++ b/hw/goldfish_mmc.c @@ -63,7 +63,7 @@ struct goldfish_mmc_state { struct goldfish_device dev; BlockDriverState *bs; // pointer to our buffer - uint8_t* buffer; + uint32_t buffer_address; // offsets for read and write operations uint32_t read_offset, write_offset; // buffer status flags @@ -78,11 +78,14 @@ struct goldfish_mmc_state { uint32_t block_length; uint32_t block_count; int is_SDHC; + + uint8_t* buf; }; -#define GOLDFISH_MMC_SAVE_VERSION 1 +#define GOLDFISH_MMC_SAVE_VERSION 2 #define QFIELD_STRUCT struct goldfish_mmc_state QFIELD_BEGIN(goldfish_mmc_fields) + QFIELD_INT32(buffer_address), QFIELD_INT32(read_offset), QFIELD_INT32(write_offset), QFIELD_INT32(int_status), @@ -101,7 +104,6 @@ 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); } @@ -112,7 +114,6 @@ static int goldfish_mmc_load(QEMUFile* f, void* opaque, int version_id) 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); } @@ -169,6 +170,48 @@ static const char* get_command_name(int command) } #endif +static int goldfish_mmc_bdrv_read(struct goldfish_mmc_state *s, + int64_t sector_number, + target_phys_addr_t dst_address, + int num_sectors) +{ + int ret; + + while (num_sectors > 0) { + ret = bdrv_read(s->bs, sector_number, s->buf, 1); + if (ret < 0) + return ret; + + cpu_physical_memory_write(dst_address, s->buf, 512); + dst_address += 512; + num_sectors -= 1; + sector_number += 1; + } + return 0; +} + +static int goldfish_mmc_bdrv_write(struct goldfish_mmc_state *s, + int64_t sector_number, + target_phys_addr_t dst_address, + int num_sectors) +{ + int ret; + + while (num_sectors > 0) { + cpu_physical_memory_read(dst_address, s->buf, 512); + + ret = bdrv_write(s->bs, sector_number, s->buf, 1); + if (ret < 0) + return ret; + + dst_address += 512; + num_sectors -= 1; + sector_number += 1; + } + return 0; +} + + static void goldfish_mmc_do_command(struct goldfish_mmc_state *s, uint32_t cmd, uint32_t arg) { int result; @@ -269,9 +312,15 @@ static void goldfish_mmc_do_command(struct goldfish_mmc_state *s, uint32_t cmd, case SD_APP_SEND_SCR: { - uint32_t* scr = (uint32_t*)s->buffer; +#if 1 /* this code is actually endian-safe */ + const uint8_t scr[8] = "\x02\x25\x00\x00\x00\x00\x00\x00"; +#else /* this original code wasn't */ + uint32_t scr[2]; scr[0] = 0x00002502; scr[1] = 0x00000000; +#endif + cpu_physical_memory_write(s->buffer_address, (uint8_t*)scr, 8); + s->resp[0] = SET_R1_CURRENT_STATE(4) | R1_READY_FOR_DATA | R1_APP_CMD; //2336 new_status |= MMC_STAT_END_OF_DATA; break; @@ -293,9 +342,10 @@ static void goldfish_mmc_do_command(struct goldfish_mmc_state *s, uint32_t cmd, case MMC_SWITCH: if (arg == 0x00FFFFF1 || arg == 0x80FFFFF1) { - uint8_t* switchbuf = s->buffer; - memset(switchbuf, 0, 64); - switchbuf[13] = 2; + uint8_t buff0[64]; + memset(buff0, 0, sizeof buff0); + buff0[13] = 2; + cpu_physical_memory_write(s->buffer_address, buff0, sizeof buff0); new_status |= MMC_STAT_END_OF_DATA; } s->resp[0] = SET_R1_CURRENT_STATE(4) | R1_READY_FOR_DATA | R1_APP_CMD; //2336 @@ -317,7 +367,7 @@ static void goldfish_mmc_do_command(struct goldfish_mmc_state *s, uint32_t cmd, 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); + result = goldfish_mmc_bdrv_read(s, arg, s->buffer_address, s->block_count); new_status |= MMC_STAT_END_OF_DATA; s->resp[0] = SET_R1_CURRENT_STATE(4) | R1_READY_FOR_DATA; // 2304 break; @@ -335,7 +385,7 @@ static void goldfish_mmc_do_command(struct goldfish_mmc_state *s, uint32_t cmd, arg /= s->block_length; } // arg is byte offset - result = bdrv_write(s->bs, arg, s->buffer, s->block_count); + result = goldfish_mmc_bdrv_write(s, arg, s->buffer_address, 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 @@ -363,7 +413,6 @@ 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 @@ -394,8 +443,6 @@ static void goldfish_mmc_write(void *opaque, target_phys_addr_t offset, uint32_t struct goldfish_mmc_state *s = opaque; int status, old_status; - offset -= s->dev.base; - switch(offset) { case MMC_INT_STATUS: @@ -416,7 +463,7 @@ static void goldfish_mmc_write(void *opaque, target_phys_addr_t offset, uint32_t break; case MMC_SET_BUFFER: /* save pointer to buffer 1 */ - s->buffer = phys_ram_base + val; + s->buffer_address = val; break; case MMC_CMD: goldfish_mmc_do_command(s, val, s->arg); @@ -459,6 +506,7 @@ void goldfish_mmc_init(uint32_t base, int id, BlockDriverState* bs) s->dev.size = 0x1000; s->dev.irq_count = 1; s->bs = bs; + s->buf = qemu_memalign(512,512); goldfish_device_add(&s->dev, goldfish_mmc_readfn, goldfish_mmc_writefn, s); diff --git a/hw/goldfish_nand.c b/hw/goldfish_nand.c index 61b075e..e84a58b 100644 --- a/hw/goldfish_nand.c +++ b/hw/goldfish_nand.c @@ -193,7 +193,7 @@ static uint32_t nand_dev_read_file(nand_dev *dev, uint32_t data, uint64_t addr, if(!eof) { read_len = do_read(dev->fd, dev->data, read_len); } - pmemcpy(data, dev->data, read_len); + cpu_memory_rw_debug(cpu_single_env, data, dev->data, read_len, 1); data += read_len; len -= read_len; } @@ -212,7 +212,7 @@ static uint32_t nand_dev_write_file(nand_dev *dev, uint32_t data, uint64_t addr, while(len > 0) { if(len < write_len) write_len = len; - vmemcpy(data, dev->data, write_len); + cpu_memory_rw_debug(cpu_single_env, data, dev->data, write_len, 0); ret = do_write(dev->fd, dev->data, write_len); if(ret < write_len) { XLOG("nand_dev_write_file, write failed: %s\n", strerror(errno)); @@ -274,7 +274,7 @@ uint32_t nand_dev_do_cmd(nand_dev_state *s, uint32_t cmd) case NAND_CMD_GET_DEV_NAME: if(size > dev->devname_len) size = dev->devname_len; - pmemcpy(s->data, dev->devname, size); + cpu_memory_rw_debug(cpu_single_env, s->data, dev->devname, size, 1); return size; case NAND_CMD_READ: if(addr >= dev->size) @@ -283,7 +283,7 @@ uint32_t nand_dev_do_cmd(nand_dev_state *s, uint32_t cmd) 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); + cpu_memory_rw_debug(cpu_single_env,s->data, &dev->data[addr], size, 1); return size; case NAND_CMD_WRITE: if(dev->flags & NAND_DEV_FLAG_READ_ONLY) @@ -294,7 +294,7 @@ uint32_t nand_dev_do_cmd(nand_dev_state *s, uint32_t cmd) 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); + cpu_memory_rw_debug(cpu_single_env,s->data, &dev->data[addr], size, 0); return size; case NAND_CMD_ERASE: if(dev->flags & NAND_DEV_FLAG_READ_ONLY) @@ -324,7 +324,6 @@ static void nand_dev_write(void *opaque, target_phys_addr_t offset, uint32_t val { nand_dev_state *s = (nand_dev_state *)opaque; - offset -= s->base; switch (offset) { case NAND_DEV: s->dev = value; @@ -359,7 +358,6 @@ 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; @@ -422,7 +420,7 @@ void nand_dev_init(uint32_t base) 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); + iomemtype = cpu_register_io_memory(nand_dev_readfn, nand_dev_writefn, s); cpu_register_physical_memory(base, 0x00000fff, iomemtype); s->base = base; diff --git a/hw/goldfish_switch.c b/hw/goldfish_switch.c index 8a12d66..99a9379 100644 --- a/hw/goldfish_switch.c +++ b/hw/goldfish_switch.c @@ -62,7 +62,6 @@ static int goldfish_switch_load(QEMUFile* f, void* opaque, int version_id) 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); @@ -89,13 +88,12 @@ static uint32_t goldfish_switch_read(void *opaque, target_phys_addr_t offset) 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)); + cpu_memory_rw_debug(cpu_single_env, value, (void*)s->name, strlen(s->name), 1); break; case SW_STATE: diff --git a/hw/goldfish_timer.c b/hw/goldfish_timer.c index 73f1455..8a84895 100644 --- a/hw/goldfish_timer.c +++ b/hw/goldfish_timer.c @@ -74,7 +74,6 @@ static int goldfish_timer_load(QEMUFile* f, void* opaque, int version_id) 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); @@ -91,7 +90,6 @@ static void goldfish_timer_write(void *opaque, target_phys_addr_t offset, uint32 { 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; @@ -161,7 +159,6 @@ static int goldfish_rtc_load(QEMUFile* f, void* opaque, int version_id) 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; @@ -178,7 +175,6 @@ static void goldfish_rtc_write(void *opaque, target_phys_addr_t offset, uint32_t { struct rtc_state *s = (struct rtc_state *)opaque; int64_t alarm; - offset -= s->dev.base; switch(offset) { case 0x8: s->alarm_low = value; diff --git a/hw/goldfish_trace.c b/hw/goldfish_trace.c index f49b704..c4f2f92 100644 --- a/hw/goldfish_trace.c +++ b/hw/goldfish_trace.c @@ -42,7 +42,6 @@ static void trace_dev_write(void *opaque, target_phys_addr_t offset, uint32_t va { trace_dev_state *s = (trace_dev_state *)opaque; - offset -= s->dev.base; switch (offset >> 2) { case TRACE_DEV_REG_SWITCH: // context switch, switch to pid trace_switch(value); @@ -89,7 +88,7 @@ static void trace_dev_write(void *opaque, target_phys_addr_t offset, uint32_t va cmdlen = value; break; case TRACE_DEV_REG_CMDLINE: // execve, process cmdline - vmemcpy(value, arg, cmdlen); + cpu_memory_rw_debug(cpu_single_env, value, arg, cmdlen, 0); trace_execve(arg, cmdlen); #ifdef DEBUG { @@ -227,7 +226,6 @@ static uint32_t trace_dev_read(void *opaque, target_phys_addr_t offset) { trace_dev_state *s = (trace_dev_state *)opaque; - offset -= s->dev.base; switch (offset >> 2) { case TRACE_DEV_REG_ENABLE: // tracing enable return tracing; diff --git a/hw/goldfish_trace.h b/hw/goldfish_trace.h index 11ed906..76b61a8 100644 --- a/hw/goldfish_trace.h +++ b/hw/goldfish_trace.h @@ -59,9 +59,6 @@ typedef struct { * 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); /* diff --git a/hw/goldfish_tty.c b/hw/goldfish_tty.c index aa62d75..dd50efc 100644 --- a/hw/goldfish_tty.c +++ b/hw/goldfish_tty.c @@ -69,7 +69,6 @@ static int goldfish_tty_load(QEMUFile* f, void* opaque, int version_id) 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); @@ -85,7 +84,6 @@ static uint32_t goldfish_tty_read(void *opaque, target_phys_addr_t offset) 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); @@ -117,18 +115,19 @@ static void goldfish_tty_write(void *opaque, target_phys_addr_t offset, uint32_t case TTY_CMD_WRITE_BUFFER: if(s->cs) { int len; - target_ulong buf; + target_phys_addr_t 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); + while (len) { + char temp[64]; + int to_write = sizeof(temp); + if (to_write > len) + to_write = len; + + cpu_memory_rw_debug(cpu_single_env, buf, temp, to_write, 0); + qemu_chr_write(s->cs, temp, to_write); buf += to_write; len -= to_write; } @@ -139,7 +138,7 @@ static void goldfish_tty_write(void *opaque, target_phys_addr_t offset, uint32_t 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); + cpu_memory_rw_debug(cpu_single_env,s->ptr, s->data, s->ptr_len,1); //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); @@ -8,19 +8,81 @@ /* VM Load/Save */ +/* This function writes a chunk of data to a file at the given position. + * The pos argument can be ignored if the file is only being used for + * streaming. The handler should try to write all of the data it can. + */ +typedef int (QEMUFilePutBufferFunc)(void *opaque, const uint8_t *buf, + int64_t pos, int size); + +/* Read a chunk of data from a file at the given position. The pos argument + * can be ignored if the file is only be used for streaming. The number of + * bytes actually read should be returned. + */ +typedef int (QEMUFileGetBufferFunc)(void *opaque, uint8_t *buf, + int64_t pos, int size); + +/* Close a file and return an error code */ +typedef int (QEMUFileCloseFunc)(void *opaque); + +/* Called to determine if the file has exceeded it's bandwidth allocation. The + * bandwidth capping is a soft limit, not a hard limit. + */ +typedef int (QEMUFileRateLimit)(void *opaque); + +/* Called to change the current bandwidth allocation. This function must return + * the new actual bandwidth. It should be new_rate if everything goes ok, and + * the old rate otherwise + */ +typedef size_t (QEMUFileSetRateLimit)(void *opaque, size_t new_rate); + +QEMUFile *qemu_fopen_ops(void *opaque, QEMUFilePutBufferFunc *put_buffer, + QEMUFileGetBufferFunc *get_buffer, + QEMUFileCloseFunc *close, + QEMUFileRateLimit *rate_limit, + QEMUFileSetRateLimit *set_rate_limit); QEMUFile *qemu_fopen(const char *filename, const char *mode); +QEMUFile *qemu_fopen_socket(int fd); +QEMUFile *qemu_popen(FILE *popen_file, const char *mode); +QEMUFile *qemu_popen_cmd(const char *command, const char *mode); +int qemu_popen_fd(QEMUFile *f); void qemu_fflush(QEMUFile *f); -void qemu_fclose(QEMUFile *f); +int qemu_fclose(QEMUFile *f); void qemu_put_buffer(QEMUFile *f, const uint8_t *buf, int size); void qemu_put_byte(QEMUFile *f, int v); + +static inline void qemu_put_ubyte(QEMUFile *f, unsigned int v) +{ + qemu_put_byte(f, (int)v); +} + +#define qemu_put_sbyte qemu_put_byte + 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); + +static inline unsigned int qemu_get_ubyte(QEMUFile *f) +{ + return (unsigned int)qemu_get_byte(f); +} + +#define qemu_get_sbyte qemu_get_byte + unsigned int qemu_get_be16(QEMUFile *f); unsigned int qemu_get_be32(QEMUFile *f); uint64_t qemu_get_be64(QEMUFile *f); +int qemu_file_rate_limit(QEMUFile *f); +size_t qemu_file_set_rate_limit(QEMUFile *f, size_t new_rate); +int qemu_file_has_error(QEMUFile *f); +void qemu_file_set_error(QEMUFile *f); + +/* Try to send any outstanding data. This function is useful when output is + * halted due to rate limiting or EAGAIN errors occur as it can be used to + * resume output. */ +void qemu_file_put_notify(QEMUFile *f); static inline void qemu_put_be64s(QEMUFile *f, const uint64_t *pv) { @@ -62,17 +124,106 @@ static inline void qemu_get_8s(QEMUFile *f, uint8_t *pv) *pv = qemu_get_byte(f); } +// Signed versions for type safety +static inline void qemu_put_sbuffer(QEMUFile *f, const int8_t *buf, int size) +{ + qemu_put_buffer(f, (const uint8_t *)buf, size); +} + +static inline void qemu_put_sbe16(QEMUFile *f, int v) +{ + qemu_put_be16(f, (unsigned int)v); +} + +static inline void qemu_put_sbe32(QEMUFile *f, int v) +{ + qemu_put_be32(f, (unsigned int)v); +} + +static inline void qemu_put_sbe64(QEMUFile *f, int64_t v) +{ + qemu_put_be64(f, (uint64_t)v); +} + +static inline size_t qemu_get_sbuffer(QEMUFile *f, int8_t *buf, int size) +{ + return qemu_get_buffer(f, (uint8_t *)buf, size); +} + +static inline int qemu_get_sbe16(QEMUFile *f) +{ + return (int)qemu_get_be16(f); +} + +static inline int qemu_get_sbe32(QEMUFile *f) +{ + return (int)qemu_get_be32(f); +} + +static inline int64_t qemu_get_sbe64(QEMUFile *f) +{ + return (int64_t)qemu_get_be64(f); +} + +static inline void qemu_put_s8s(QEMUFile *f, const int8_t *pv) +{ + qemu_put_8s(f, (const uint8_t *)pv); +} + +static inline void qemu_put_sbe16s(QEMUFile *f, const int16_t *pv) +{ + qemu_put_be16s(f, (const uint16_t *)pv); +} + +static inline void qemu_put_sbe32s(QEMUFile *f, const int32_t *pv) +{ + qemu_put_be32s(f, (const uint32_t *)pv); +} + +static inline void qemu_put_sbe64s(QEMUFile *f, const int64_t *pv) +{ + qemu_put_be64s(f, (const uint64_t *)pv); +} + +static inline void qemu_get_s8s(QEMUFile *f, int8_t *pv) +{ + qemu_get_8s(f, (uint8_t *)pv); +} + +static inline void qemu_get_sbe16s(QEMUFile *f, int16_t *pv) +{ + qemu_get_be16s(f, (uint16_t *)pv); +} + +static inline void qemu_get_sbe32s(QEMUFile *f, int32_t *pv) +{ + qemu_get_be32s(f, (uint32_t *)pv); +} + +static inline void qemu_get_sbe64s(QEMUFile *f, int64_t *pv) +{ + qemu_get_be64s(f, (uint64_t *)pv); +} + #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 +#define qemu_put_sbetl qemu_put_sbe64 +#define qemu_get_sbetl qemu_get_sbe64 +#define qemu_put_sbetls qemu_put_sbe64s +#define qemu_get_sbetls qemu_get_sbe64s #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 +#define qemu_put_sbetl qemu_put_sbe32 +#define qemu_get_sbetl qemu_get_sbe32 +#define qemu_put_sbetls qemu_put_sbe32s +#define qemu_get_sbetls qemu_get_sbe32s #endif #endif @@ -80,6 +231,7 @@ 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 SaveLiveStateHandler(QEMUFile *f, int stage, void *opaque); typedef int LoadStateHandler(QEMUFile *f, void *opaque, int version_id); int register_savevm(const char *idstr, @@ -89,22 +241,27 @@ int register_savevm(const char *idstr, LoadStateHandler *load_state, void *opaque); +int register_savevm_live(const char *idstr, + int instance_id, + int version_id, + SaveLiveStateHandler *save_live_state, + SaveStateHandler *save_state, + LoadStateHandler *load_state, + void *opaque); + +void unregister_savevm(const char *idstr, void *opaque); + typedef void QEMUResetHandler(void *opaque); -void qemu_register_reset(QEMUResetHandler *func, void *opaque); +void qemu_register_reset(QEMUResetHandler *func, int order, 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); +typedef int QEMUBootSetHandler(void *opaque, const char *boot_device); +void qemu_register_boot_set(QEMUBootSetHandler *func, void *opaque); /* 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 @@ -56,6 +56,12 @@ qemu_irq *qemu_allocate_irqs(qemu_irq_handler handler, void *opaque, int n) return s; } +void qemu_free_irqs(qemu_irq *s) +{ + qemu_free(s[0]); + qemu_free(s); +} + static void qemu_notirq(void *opaque, int line, int level) { struct IRQState *irq = opaque; @@ -27,6 +27,7 @@ static inline void qemu_irq_pulse(qemu_irq irq) /* Returns an array of N IRQs. */ qemu_irq *qemu_allocate_irqs(qemu_irq_handler handler, void *opaque, int n); +void qemu_free_irqs(qemu_irq *s); /* Returns a new IRQ with opposite polarity. */ qemu_irq qemu_irq_invert(qemu_irq irq); @@ -19,7 +19,6 @@ 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, diff --git a/hw/msmouse.c b/hw/msmouse.c new file mode 100644 index 0000000..69356a5 --- /dev/null +++ b/hw/msmouse.c @@ -0,0 +1,78 @@ +/* + * QEMU Microsoft serial mouse emulation + * + * Copyright (c) 2008 Lubomir Rintel + * + * 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 <stdlib.h> +#include "../qemu-common.h" +#include "../qemu-char.h" +#include "../console.h" +#include "msmouse.h" + +#define MSMOUSE_LO6(n) ((n) & 0x3f) +#define MSMOUSE_HI2(n) (((n) & 0xc0) >> 6) + +static void msmouse_event(void *opaque, + int dx, int dy, int dz, int buttons_state) +{ + CharDriverState *chr = (CharDriverState *)opaque; + + unsigned char bytes[4] = { 0x40, 0x00, 0x00, 0x00 }; + + /* Movement deltas */ + bytes[0] |= (MSMOUSE_HI2(dy) << 2) | MSMOUSE_HI2(dx); + bytes[1] |= MSMOUSE_LO6(dx); + bytes[2] |= MSMOUSE_LO6(dy); + + /* Buttons */ + bytes[0] |= (buttons_state & 0x01 ? 0x20 : 0x00); + bytes[0] |= (buttons_state & 0x02 ? 0x10 : 0x00); + bytes[3] |= (buttons_state & 0x04 ? 0x20 : 0x00); + + /* We always send the packet of, so that we do not have to keep track + of previous state of the middle button. This can potentially confuse + some very old drivers for two button mice though. */ + qemu_chr_read(chr, bytes, 4); +} + +static int msmouse_chr_write (struct CharDriverState *s, const uint8_t *buf, int len) +{ + /* Ignore writes to mouse port */ + return len; +} + +static void msmouse_chr_close (struct CharDriverState *chr) +{ + qemu_free (chr); +} + +CharDriverState *qemu_chr_open_msmouse(void) +{ + CharDriverState *chr; + + chr = qemu_mallocz(sizeof(CharDriverState)); + chr->chr_write = msmouse_chr_write; + chr->chr_close = msmouse_chr_close; + + qemu_add_mouse_event_handler(msmouse_event, chr, 0, "QEMU Microsoft Mouse"); + + return chr; +} diff --git a/hw/msmouse.h b/hw/msmouse.h new file mode 100644 index 0000000..947afd9 --- /dev/null +++ b/hw/msmouse.h @@ -0,0 +1,2 @@ +/* msmouse.c */ +CharDriverState *qemu_chr_open_msmouse(void); @@ -1,5 +1,8 @@ #ifndef HW_PC_H #define HW_PC_H + +#include "qemu-common.h" + /* PC-style peripherals (also used by other machines). */ /* serial.c */ @@ -34,18 +37,23 @@ void pic_set_alt_irq_func(PicState2 *s, SetIRQFunc *alt_irq_func, 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); +void pic_info(Monitor *mon); +void irq_info(Monitor *mon); /* APIC */ typedef struct IOAPICState IOAPICState; - +void apic_deliver_irq(uint8_t dest, uint8_t dest_mode, + uint8_t delivery_mode, + uint8_t vector_num, uint8_t polarity, + uint8_t trigger_mode); 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); +void apic_reset_irq_delivered(void); +int apic_get_irq_delivered(void); /* i8254.c */ @@ -60,6 +68,9 @@ 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); +void hpet_pit_disable(void); +void hpet_pit_enable(void); + /* vmport.c */ void vmport_init(void); void vmport_register(unsigned char command, IOPortReadFunc *func, void *opaque); @@ -71,16 +82,20 @@ void *vmmouse_init(void *m); 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); + target_phys_addr_t base, ram_addr_t size, + target_phys_addr_t mask); /* 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); +RTCState *rtc_init(int base, qemu_irq irq, int base_year); +RTCState *rtc_init_sqw(int base, qemu_irq irq, qemu_irq sqw_irq, int base_year); +RTCState *rtc_mm_init(target_phys_addr_t base, int it_shift, qemu_irq irq, + int base_year); void rtc_set_memory(RTCState *s, int addr, int val); void rtc_set_date(RTCState *s, const struct tm *tm); +void cmos_set_s3_resume(void); /* pc.c */ extern int fd_bootchk; @@ -90,14 +105,21 @@ int ioport_get_a20(void); /* acpi.c */ extern int acpi_enabled; +extern char *acpi_tables; +extern size_t acpi_tables_len; + 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); +int acpi_table_add(const char *table_desc); + +/* hpet.c */ +extern int no_hpet; /* pcspk.c */ void pcspk_init(PITState *); -int pcspk_audio_init(AudioState *, qemu_irq *pic); +int pcspk_audio_init(qemu_irq *pic); /* piix_pci.c */ PCIBus *i440fx_init(PCIDevice **pi440fx_state, qemu_irq *pic); @@ -105,31 +127,26 @@ void i440fx_set_smm(PCIDevice *d, int val); int piix3_init(PCIBus *bus, int devfn); void i440fx_init_memory_mappings(PCIDevice *d); +extern PCIDevice *piix4_dev; int piix4_init(PCIBus *bus, int devfn); /* vga.c */ +enum vga_retrace_method { + VGA_RETRACE_DUMB, + VGA_RETRACE_PRECISE +}; -#ifndef TARGET_SPARC -#define VGA_RAM_SIZE (8192 * 1024) -#else -#define VGA_RAM_SIZE (9 * 1024 * 1024) -#endif +extern enum vga_retrace_method vga_retrace_method; -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, +int isa_vga_init(void); +int pci_vga_init(PCIBus *bus, 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); +int isa_vga_mm_init(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); +void pci_cirrus_vga_init(PCIBus *bus); +void isa_cirrus_vga_init(void); /* ide.c */ void isa_ide_init(int iobase, int iobase2, qemu_irq irq, @@ -145,4 +162,5 @@ void pci_piix4_ide_init(PCIBus *bus, BlockDriverState **hd_table, int devfn, void isa_ne2000_init(int base, qemu_irq irq, NICInfo *nd); +int cpu_is_bsp(CPUState *env); #endif @@ -23,12 +23,14 @@ */ #include "hw.h" #include "pci.h" -#include "console.h" +#include "monitor.h" #include "net.h" +#include "sysemu.h" //#define DEBUG_PCI struct PCIBus { + BusState qbus; int bus_num; int devfn_min; pci_set_irq_fn set_irq; @@ -50,7 +52,8 @@ 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 uint16_t pci_default_sub_vendor_id = PCI_SUBVENDOR_ID_REDHAT_QUMRANET; +static uint16_t pci_default_sub_device_id = PCI_SUBDEVICE_ID_QEMU; static PCIBus *first_bus; static void pcibus_save(QEMUFile *f, void *opaque) @@ -84,18 +87,22 @@ static int pcibus_load(QEMUFile *f, void *opaque, int version_id) return 0; } -PCIBus *pci_register_bus(pci_set_irq_fn set_irq, pci_map_irq_fn map_irq, +PCIBus *pci_register_bus(DeviceState *parent, const char *name, + 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 = FROM_QBUS(PCIBus, qbus_create(BUS_TYPE_PCI, + sizeof(PCIBus) + (nirq * sizeof(int)), + parent, name)); bus->set_irq = set_irq; bus->map_irq = map_irq; bus->irq_opaque = pic; bus->devfn_min = devfn_min; bus->nirq = nirq; + bus->next = first_bus; first_bus = bus; register_savevm("PCIBUS", nbus++, 1, pcibus_save, pcibus_load, bus); return bus; @@ -145,17 +152,98 @@ int pci_device_load(PCIDevice *s, QEMUFile *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) +static int pci_set_default_subsystem_id(PCIDevice *pci_dev) { - PCIDevice *pci_dev; + uint16_t *id; - if (pci_irq_index >= PCI_DEVICES_MAX) - return NULL; + id = (void*)(&pci_dev->config[PCI_SUBVENDOR_ID]); + id[0] = cpu_to_le16(pci_default_sub_vendor_id); + id[1] = cpu_to_le16(pci_default_sub_device_id); + return 0; +} + +/* + * Parse [[<domain>:]<bus>:]<slot>, return -1 on error + */ +static int pci_parse_devaddr(const char *addr, int *domp, int *busp, unsigned *slotp) +{ + const char *p; + char *e; + unsigned long val; + unsigned long dom = 0, bus = 0; + unsigned slot = 0; + + p = addr; + val = strtoul(p, &e, 16); + if (e == p) + return -1; + if (*e == ':') { + bus = val; + p = e + 1; + val = strtoul(p, &e, 16); + if (e == p) + return -1; + if (*e == ':') { + dom = bus; + bus = val; + p = e + 1; + val = strtoul(p, &e, 16); + if (e == p) + return -1; + } + } + + if (dom > 0xffff || bus > 0xff || val > 0x1f) + return -1; + + slot = val; + + if (*e) + return -1; + + /* Note: QEMU doesn't implement domains other than 0 */ + if (dom != 0 || pci_find_bus(bus) == NULL) + return -1; + + *domp = dom; + *busp = bus; + *slotp = slot; + return 0; +} + +int pci_read_devaddr(const char *addr, int *domp, int *busp, unsigned *slotp) +{ + char devaddr[32]; + + if (!get_param_value(devaddr, sizeof(devaddr), "pci_addr", addr)) + return -1; + + return pci_parse_devaddr(devaddr, domp, busp, slotp); +} + +int pci_assign_devaddr(const char *addr, int *domp, int *busp, unsigned *slotp) +{ + char devaddr[32]; + + if (!get_param_value(devaddr, sizeof(devaddr), "pci_addr", addr)) + return -1; + + if (!strcmp(devaddr, "auto")) { + *domp = *busp = 0; + *slotp = -1; + /* want to support dom/bus auto-assign at some point */ + return 0; + } + + return pci_parse_devaddr(devaddr, domp, busp, slotp); +} +/* -1 for devfn means auto assign */ +static PCIDevice *do_pci_register_device(PCIDevice *pci_dev, PCIBus *bus, + const char *name, int devfn, + PCIConfigReadFunc *config_read, + PCIConfigWriteFunc *config_write) +{ if (devfn < 0) { for(devfn = bus->devfn_min ; devfn < 256; devfn += 8) { if (!bus->devices[devfn]) @@ -164,13 +252,11 @@ PCIDevice *pci_register_device(PCIBus *bus, const char *name, 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)); + pci_set_default_subsystem_id(pci_dev); if (!config_read) config_read = pci_default_read_config; @@ -178,13 +264,65 @@ PCIDevice *pci_register_device(PCIBus *bus, const char *name, 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, +PCIDevice *pci_register_device(PCIBus *bus, const char *name, + int instance_size, int devfn, + PCIConfigReadFunc *config_read, + PCIConfigWriteFunc *config_write) +{ + PCIDevice *pci_dev; + + pci_dev = qemu_mallocz(instance_size); + pci_dev = do_pci_register_device(pci_dev, bus, name, devfn, + config_read, config_write); + return pci_dev; +} +static target_phys_addr_t pci_to_cpu_addr(target_phys_addr_t addr) +{ + return addr + pci_mem_base; +} + +static void pci_unregister_io_regions(PCIDevice *pci_dev) +{ + PCIIORegion *r; + int i; + + for(i = 0; i < PCI_NUM_REGIONS; i++) { + r = &pci_dev->io_regions[i]; + if (!r->size || r->addr == -1) + continue; + if (r->type == PCI_ADDRESS_SPACE_IO) { + isa_unassign_ioport(r->addr, r->size); + } else { + cpu_register_physical_memory(pci_to_cpu_addr(r->addr), + r->size, + IO_MEM_UNASSIGNED); + } + } +} + +int pci_unregister_device(PCIDevice *pci_dev) +{ + int ret = 0; + + if (pci_dev->unregister) + ret = pci_dev->unregister(pci_dev); + if (ret) + return ret; + + pci_unregister_io_regions(pci_dev); + + qemu_free_irqs(pci_dev->irq); + pci_dev->bus->devices[pci_dev->devfn] = NULL; + qdev_free(&pci_dev->qdev); + return 0; +} + +void pci_register_bar(PCIDevice *pci_dev, int region_num, uint32_t size, int type, PCIMapIORegionFunc *map_func) { @@ -193,6 +331,13 @@ void pci_register_io_region(PCIDevice *pci_dev, int region_num, if ((unsigned int)region_num >= PCI_NUM_REGIONS) return; + + if (size & (size-1)) { + fprintf(stderr, "ERROR: PCI region size must be pow2 " + "type=0x%x, size=0x%x\n", type, size); + exit(1); + } + r = &pci_dev->io_regions[region_num]; r->addr = -1; r->size = size; @@ -206,11 +351,6 @@ void pci_register_io_region(PCIDevice *pci_dev, int region_num, *(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; @@ -279,6 +419,7 @@ static void pci_update_mappings(PCIDevice *d) cpu_register_physical_memory(pci_to_cpu_addr(r->addr), r->size, IO_MEM_UNASSIGNED); + qemu_unregister_coalesced_mmio(r->addr, r->size); } } r->addr = new_addr; @@ -360,12 +501,15 @@ void pci_default_write_config(PCIDevice *d, case 0x01: case 0x02: case 0x03: + case 0x06: + case 0x07: case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0e: case 0x10 ... 0x27: /* base */ + case 0x2c ... 0x2f: /* read-only subsystem ID & vendor ID */ case 0x30 ... 0x33: /* rom */ case 0x3d: can_write = 0; @@ -382,11 +526,14 @@ void pci_default_write_config(PCIDevice *d, case 0x01: case 0x02: case 0x03: + case 0x06: + case 0x07: case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0e: + case 0x2c ... 0x2f: /* read-only subsystem ID & vendor ID */ case 0x38 ... 0x3b: /* rom */ case 0x3d: can_write = 0; @@ -398,6 +545,18 @@ void pci_default_write_config(PCIDevice *d, break; } if (can_write) { + /* Mask out writes to reserved bits in registers */ + switch (addr) { + case 0x05: + val &= ~PCI_COMMAND_RESERVED_MASK_HI; + break; + case 0x06: + val &= ~PCI_STATUS_RESERVED_MASK_LO; + break; + case 0x07: + val &= ~PCI_STATUS_RESERVED_MASK_HI; + break; + } d->config[addr] = val; } if (++addr > 0xff) @@ -515,7 +674,7 @@ typedef struct { const char *desc; } pci_class_desc; -static pci_class_desc pci_class_descriptions[] = +static const pci_class_desc pci_class_descriptions[] = { { 0x0100, "SCSI controller"}, { 0x0101, "IDE controller"}, @@ -557,42 +716,44 @@ static pci_class_desc pci_class_descriptions[] = static void pci_info_device(PCIDevice *d) { + Monitor *mon = cur_mon; int i, class; PCIIORegion *r; - pci_class_desc *desc; + const pci_class_desc *desc; - term_printf(" Bus %2d, device %3d, function %d:\n", - d->bus->bus_num, d->devfn >> 3, d->devfn & 7); + monitor_printf(mon, " 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(" "); + monitor_printf(mon, " "); desc = pci_class_descriptions; while (desc->desc && class != desc->class) desc++; if (desc->desc) { - term_printf("%s", desc->desc); + monitor_printf(mon, "%s", desc->desc); } else { - term_printf("Class %04x", class); + monitor_printf(mon, "Class %04x", class); } - term_printf(": PCI device %04x:%04x\n", + monitor_printf(mon, ": 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]); + monitor_printf(mon, " IRQ %d.\n", + d->config[PCI_INTERRUPT_LINE]); } if (class == 0x0604) { - term_printf(" BUS %d.\n", d->config[0x19]); + monitor_printf(mon, " 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); + monitor_printf(mon, " 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); + monitor_printf(mon, "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); + monitor_printf(mon, "32 bit memory at 0x%08x [0x%08x].\n", + r->addr, r->addr + r->size - 1); } } } @@ -618,38 +779,56 @@ void pci_for_each_device(int bus_num, void (*fn)(PCIDevice *d)) } } -void pci_info(void) +void pci_info(Monitor *mon) { pci_for_each_device(0, pci_info_device); } +static const char * const pci_nic_models[] = { + "ne2k_pci", + "i82551", + "i82557b", + "i82559er", + "rtl8139", + "e1000", + "pcnet", + "virtio", + NULL +}; + +static const char * const pci_nic_names[] = { + "ne2k_pci", + "i82551", + "i82557b", + "i82559er", + "rtl8139", + "e1000", + "pcnet", + "virtio-net-pci", + NULL +}; + /* 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); +PCIDevice *pci_nic_init(PCIBus *bus, NICInfo *nd, int devfn, + const char *default_model) +{ + DeviceState *dev; + int i; + + qemu_check_nic_model_list(nd, pci_nic_models, default_model); + + for (i = 0; pci_nic_models[i]; i++) { + if (strcmp(nd->model, pci_nic_models[i]) == 0) { + dev = qdev_create(&bus->qbus, pci_nic_names[i]); + qdev_set_prop_int(dev, "devfn", devfn); + qdev_set_netdev(dev, nd); + qdev_init(dev); + nd->private = dev; + return (PCIDevice *)dev; + } } -#endif + + return NULL; } typedef struct { @@ -674,28 +853,93 @@ static void pci_bridge_write_config(PCIDevice *d, pci_default_write_config(d, address, val, len); } -PCIBus *pci_bridge_init(PCIBus *bus, int devfn, uint32_t id, +PCIBus *pci_find_bus(int bus_num) +{ + PCIBus *bus = first_bus; + + while (bus && bus->bus_num != bus_num) + bus = bus->next; + + return bus; +} + +PCIDevice *pci_find_device(int bus_num, int slot, int function) +{ + PCIBus *bus = pci_find_bus(bus_num); + + if (!bus) + return NULL; + + return bus->devices[PCI_DEVFN(slot, function)]; +} + +PCIBus *pci_bridge_init(PCIBus *bus, int devfn, uint16_t vid, uint16_t did, 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; + + pci_config_set_vendor_id(s->dev.config, vid); + pci_config_set_device_id(s->dev.config, did); + 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 + pci_config_set_class(s->dev.config, PCI_CLASS_BRIDGE_PCI); s->dev.config[0x0D] = 0x10; // latency_timer - s->dev.config[0x0E] = 0x81; // header_type + s->dev.config[PCI_HEADER_TYPE] = + PCI_HEADER_TYPE_MULTI_FUNCTION | PCI_HEADER_TYPE_BRIDGE; // header_type s->dev.config[0x1E] = 0xa0; // secondary status s->bus = pci_register_secondary_bus(&s->dev, map_irq); return s->bus; } + +typedef struct { + DeviceInfo qdev; + pci_qdev_initfn init; +} PCIDeviceInfo; + +static void pci_qdev_init(DeviceState *qdev, DeviceInfo *base) +{ + PCIDevice *pci_dev = (PCIDevice *)qdev; + PCIDeviceInfo *info = container_of(base, PCIDeviceInfo, qdev); + PCIBus *bus; + int devfn; + + bus = FROM_QBUS(PCIBus, qdev_get_parent_bus(qdev)); + devfn = qdev_get_prop_int(qdev, "devfn", -1); + pci_dev = do_pci_register_device(pci_dev, bus, "FIXME", devfn, + NULL, NULL);//FIXME:config_read, config_write); + assert(pci_dev); + info->init(pci_dev); +} + +void pci_qdev_register(const char *name, int size, pci_qdev_initfn init) +{ + PCIDeviceInfo *info; + + info = qemu_mallocz(sizeof(*info)); + info->qdev.name = qemu_strdup(name); + info->qdev.size = size; + info->init = init; + info->qdev.init = pci_qdev_init; + info->qdev.bus_type = BUS_TYPE_PCI; + + qdev_register(&info->qdev); +} + +PCIDevice *pci_create_simple(PCIBus *bus, int devfn, const char *name) +{ + DeviceState *dev; + + dev = qdev_create(&bus->qbus, name); + qdev_set_prop_int(dev, "devfn", devfn); + qdev_init(dev); + + return (PCIDevice *)dev; +} @@ -1,6 +1,10 @@ #ifndef QEMU_PCI_H #define QEMU_PCI_H +#include "qemu-common.h" + +#include "qdev.h" + /* PCI includes legacy ISA access. */ #include "isa.h" @@ -8,12 +12,71 @@ extern target_phys_addr_t pci_mem_base; +#define PCI_DEVFN(slot, func) ((((slot) & 0x1f) << 3) | ((func) & 0x07)) +#define PCI_SLOT(devfn) (((devfn) >> 3) & 0x1f) +#define PCI_FUNC(devfn) ((devfn) & 0x07) + +/* Class, Vendor and Device IDs from Linux's pci_ids.h */ +#include "pci_ids.h" + +/* QEMU-specific Vendor and Device ID definitions */ + +/* IBM (0x1014) */ +#define PCI_DEVICE_ID_IBM_440GX 0x027f +#define PCI_DEVICE_ID_IBM_OPENPIC2 0xffff + +/* Hitachi (0x1054) */ +#define PCI_VENDOR_ID_HITACHI 0x1054 +#define PCI_DEVICE_ID_HITACHI_SH7751R 0x350e + +/* Apple (0x106b) */ +#define PCI_DEVICE_ID_APPLE_343S1201 0x0010 +#define PCI_DEVICE_ID_APPLE_UNI_N_I_PCI 0x001e +#define PCI_DEVICE_ID_APPLE_UNI_N_PCI 0x001f +#define PCI_DEVICE_ID_APPLE_UNI_N_KEYL 0x0022 +#define PCI_DEVICE_ID_APPLE_IPID_USB 0x003f + +/* Realtek (0x10ec) */ +#define PCI_DEVICE_ID_REALTEK_8029 0x8029 + +/* Xilinx (0x10ee) */ +#define PCI_DEVICE_ID_XILINX_XC2VP30 0x0300 + +/* Marvell (0x11ab) */ +#define PCI_DEVICE_ID_MARVELL_GT6412X 0x4620 + +/* QEMU/Bochs VGA (0x1234) */ +#define PCI_VENDOR_ID_QEMU 0x1234 +#define PCI_DEVICE_ID_QEMU_VGA 0x1111 + +/* VMWare (0x15ad) */ +#define PCI_VENDOR_ID_VMWARE 0x15ad +#define PCI_DEVICE_ID_VMWARE_SVGA2 0x0405 +#define PCI_DEVICE_ID_VMWARE_SVGA 0x0710 +#define PCI_DEVICE_ID_VMWARE_NET 0x0720 +#define PCI_DEVICE_ID_VMWARE_SCSI 0x0730 +#define PCI_DEVICE_ID_VMWARE_IDE 0x1729 + +/* Intel (0x8086) */ +#define PCI_DEVICE_ID_INTEL_82551IT 0x1209 + +/* Red Hat / Qumranet (for QEMU) -- see pci-ids.txt */ +#define PCI_VENDOR_ID_REDHAT_QUMRANET 0x1af4 +#define PCI_SUBVENDOR_ID_REDHAT_QUMRANET 0x1af4 +#define PCI_SUBDEVICE_ID_QEMU 0x1100 + +#define PCI_DEVICE_ID_VIRTIO_NET 0x1000 +#define PCI_DEVICE_ID_VIRTIO_BLOCK 0x1001 +#define PCI_DEVICE_ID_VIRTIO_BALLOON 0x1002 +#define PCI_DEVICE_ID_VIRTIO_CONSOLE 0x1003 + 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); +typedef int PCIUnregisterFunc(PCIDevice *pci_dev); #define PCI_ADDRESS_SPACE_MEM 0x00 #define PCI_ADDRESS_SPACE_IO 0x01 @@ -29,20 +92,53 @@ typedef struct PCIIORegion { #define PCI_ROM_SLOT 6 #define PCI_NUM_REGIONS 7 -#define PCI_DEVICES_MAX 64 - +/* Declarations from linux/pci_regs.h */ #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_STATUS 0x06 /* 16 bits */ +#define PCI_REVISION_ID 0x08 /* 8 bits */ #define PCI_CLASS_DEVICE 0x0a /* Device class */ +#define PCI_HEADER_TYPE 0x0e /* 8 bits */ +#define PCI_HEADER_TYPE_NORMAL 0 +#define PCI_HEADER_TYPE_BRIDGE 1 +#define PCI_HEADER_TYPE_CARDBUS 2 +#define PCI_HEADER_TYPE_MULTI_FUNCTION 0x80 +#define PCI_SUBSYSTEM_VENDOR_ID 0x2c /* 16 bits */ +#define PCI_SUBSYSTEM_ID 0x2e /* 16 bits */ #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 */ +#define PCI_REVISION 0x08 /* obsolete, use PCI_REVISION_ID */ +#define PCI_SUBVENDOR_ID 0x2c /* obsolete, use PCI_SUBSYSTEM_VENDOR_ID */ +#define PCI_SUBDEVICE_ID 0x2e /* obsolete, use PCI_SUBSYSTEM_ID */ + +/* Bits in the PCI Status Register (PCI 2.3 spec) */ +#define PCI_STATUS_RESERVED1 0x007 +#define PCI_STATUS_INT_STATUS 0x008 +#define PCI_STATUS_CAPABILITIES 0x010 +#define PCI_STATUS_66MHZ 0x020 +#define PCI_STATUS_RESERVED2 0x040 +#define PCI_STATUS_FAST_BACK 0x080 +#define PCI_STATUS_DEVSEL 0x600 + +#define PCI_STATUS_RESERVED_MASK_LO (PCI_STATUS_RESERVED1 | \ + PCI_STATUS_INT_STATUS | PCI_STATUS_CAPABILITIES | \ + PCI_STATUS_66MHZ | PCI_STATUS_RESERVED2 | PCI_STATUS_FAST_BACK) + +#define PCI_STATUS_RESERVED_MASK_HI (PCI_STATUS_DEVSEL >> 8) + +/* Bits in the PCI Command Register (PCI 2.3 spec) */ +#define PCI_COMMAND_RESERVED 0xf800 + +#define PCI_COMMAND_RESERVED_MASK_HI (PCI_COMMAND_RESERVED >> 8) + struct PCIDevice { + DeviceState qdev; /* PCI config space */ uint8_t config[256]; @@ -55,8 +151,7 @@ struct PCIDevice { /* 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; + PCIUnregisterFunc *unregister; /* IRQ objects for the INTA-INTD pins. */ qemu_irq *irq; @@ -69,8 +164,9 @@ PCIDevice *pci_register_device(PCIBus *bus, const char *name, int instance_size, int devfn, PCIConfigReadFunc *config_read, PCIConfigWriteFunc *config_write); +int pci_unregister_device(PCIDevice *pci_dev); -void pci_register_io_region(PCIDevice *pci_dev, int region_num, +void pci_register_bar(PCIDevice *pci_dev, int region_num, uint32_t size, int type, PCIMapIORegionFunc *map_func); @@ -83,27 +179,55 @@ 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, +PCIBus *pci_register_bus(DeviceState *parent, const char *name, + 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); +PCIDevice *pci_nic_init(PCIBus *bus, NICInfo *nd, int devfn, + const char *default_model); 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)); +PCIBus *pci_find_bus(int bus_num); +PCIDevice *pci_find_device(int bus_num, int slot, int function); + +int pci_read_devaddr(const char *addr, int *domp, int *busp, unsigned *slotp); +int pci_assign_devaddr(const char *addr, int *domp, int *busp, unsigned *slotp); -void pci_info(void); -PCIBus *pci_bridge_init(PCIBus *bus, int devfn, uint32_t id, +void pci_info(Monitor *mon); +PCIBus *pci_bridge_init(PCIBus *bus, int devfn, uint16_t vid, uint16_t did, pci_map_irq_fn map_irq, const char *name); +static inline void +pci_config_set_vendor_id(uint8_t *pci_config, uint16_t val) +{ + cpu_to_le16wu((uint16_t *)&pci_config[PCI_VENDOR_ID], val); +} + +static inline void +pci_config_set_device_id(uint8_t *pci_config, uint16_t val) +{ + cpu_to_le16wu((uint16_t *)&pci_config[PCI_DEVICE_ID], val); +} + +static inline void +pci_config_set_class(uint8_t *pci_config, uint16_t val) +{ + cpu_to_le16wu((uint16_t *)&pci_config[PCI_CLASS_DEVICE], val); +} + +typedef void (*pci_qdev_initfn)(PCIDevice *dev); +void pci_qdev_register(const char *name, int size, pci_qdev_initfn init); + +PCIDevice *pci_create_simple(PCIBus *bus, int devfn, 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); +void lsi_scsi_attach(DeviceState *host, BlockDriverState *bd, int id); /* 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); +void pci_vmsvga_init(PCIBus *bus); /* usb-uhci.c */ void usb_uhci_piix3_init(PCIBus *bus, int devfn); @@ -112,31 +236,16 @@ 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); +PCIBus *pci_apb_init(target_phys_addr_t special_base, + target_phys_addr_t mem_base, + qemu_irq *pic, PCIBus **bus2, PCIBus **bus3); + +/* sh_pci.c */ +PCIBus *sh_pci_register_bus(pci_set_irq_fn set_irq, pci_map_irq_fn map_irq, + qemu_irq *pic, int devfn_min, int nirq); #endif diff --git a/hw/pci_host.h b/hw/pci_host.h index 49a0c59..757b0e2 100644 --- a/hw/pci_host.h +++ b/hw/pci_host.h @@ -25,6 +25,16 @@ /* Worker routines for a PCI host controller that uses an {address,data} register pair to access PCI configuration space. */ +/* debug PCI */ +//#define DEBUG_PCI + +#ifdef DEBUG_PCI +#define PCI_DPRINTF(fmt, ...) \ +do { printf("pci_host_data: " fmt , ## __VA_ARGS__); } while (0) +#else +#define PCI_DPRINTF(fmt, ...) +#endif + typedef struct { uint32_t config_reg; PCIBus *bus; @@ -33,6 +43,9 @@ typedef struct { static void pci_host_data_writeb(void* opaque, pci_addr_t addr, uint32_t val) { PCIHostState *s = opaque; + + PCI_DPRINTF("writeb addr " TARGET_FMT_plx " val %x\n", + (target_phys_addr_t)addr, val); if (s->config_reg & (1u << 31)) pci_data_write(s->bus, s->config_reg | (addr & 3), val, 1); } @@ -43,6 +56,8 @@ static void pci_host_data_writew(void* opaque, pci_addr_t addr, uint32_t val) #ifdef TARGET_WORDS_BIGENDIAN val = bswap16(val); #endif + PCI_DPRINTF("writew addr " TARGET_FMT_plx " val %x\n", + (target_phys_addr_t)addr, val); if (s->config_reg & (1u << 31)) pci_data_write(s->bus, s->config_reg | (addr & 3), val, 2); } @@ -53,6 +68,8 @@ static void pci_host_data_writel(void* opaque, pci_addr_t addr, uint32_t val) #ifdef TARGET_WORDS_BIGENDIAN val = bswap32(val); #endif + PCI_DPRINTF("writel addr " TARGET_FMT_plx " val %x\n", + (target_phys_addr_t)addr, val); if (s->config_reg & (1u << 31)) pci_data_write(s->bus, s->config_reg, val, 4); } @@ -60,9 +77,14 @@ static void pci_host_data_writel(void* opaque, pci_addr_t addr, uint32_t val) static uint32_t pci_host_data_readb(void* opaque, pci_addr_t addr) { PCIHostState *s = opaque; + uint32_t val; + if (!(s->config_reg & (1 << 31))) return 0xff; - return pci_data_read(s->bus, s->config_reg | (addr & 3), 1); + val = pci_data_read(s->bus, s->config_reg | (addr & 3), 1); + PCI_DPRINTF("readb addr " TARGET_FMT_plx " val %x\n", + (target_phys_addr_t)addr, val); + return val; } static uint32_t pci_host_data_readw(void* opaque, pci_addr_t addr) @@ -72,6 +94,8 @@ static uint32_t pci_host_data_readw(void* opaque, pci_addr_t addr) if (!(s->config_reg & (1 << 31))) return 0xffff; val = pci_data_read(s->bus, s->config_reg | (addr & 3), 2); + PCI_DPRINTF("readw addr " TARGET_FMT_plx " val %x\n", + (target_phys_addr_t)addr, val); #ifdef TARGET_WORDS_BIGENDIAN val = bswap16(val); #endif @@ -85,9 +109,10 @@ static uint32_t pci_host_data_readl(void* opaque, pci_addr_t addr) if (!(s->config_reg & (1 << 31))) return 0xffffffff; val = pci_data_read(s->bus, s->config_reg | (addr & 3), 4); + PCI_DPRINTF("readl addr " TARGET_FMT_plx " val %x\n", + (target_phys_addr_t)addr, val); #ifdef TARGET_WORDS_BIGENDIAN val = bswap32(val); #endif return val; } - diff --git a/hw/pci_ids.h b/hw/pci_ids.h new file mode 100644 index 0000000..3afe674 --- /dev/null +++ b/hw/pci_ids.h @@ -0,0 +1,97 @@ +/* + * PCI Class, Vendor and Device IDs + * + * Please keep sorted. + * + * Abbreviated version of linux/pci_ids.h + * + * QEMU-specific definitions belong in pci.h + */ + +/* Device classes and subclasses */ + +#define PCI_BASE_CLASS_STORAGE 0x01 +#define PCI_BASE_CLASS_NETWORK 0x02 + +#define PCI_CLASS_STORAGE_SCSI 0x0100 +#define PCI_CLASS_STORAGE_IDE 0x0101 +#define PCI_CLASS_STORAGE_OTHER 0x0180 + +#define PCI_CLASS_NETWORK_ETHERNET 0x0200 + +#define PCI_CLASS_DISPLAY_VGA 0x0300 +#define PCI_CLASS_DISPLAY_OTHER 0x0380 + +#define PCI_CLASS_MULTIMEDIA_AUDIO 0x0401 + +#define PCI_CLASS_MEMORY_RAM 0x0500 + +#define PCI_CLASS_SYSTEM_OTHER 0x0880 + +#define PCI_CLASS_SERIAL_USB 0x0c03 + +#define PCI_CLASS_BRIDGE_HOST 0x0600 +#define PCI_CLASS_BRIDGE_ISA 0x0601 +#define PCI_CLASS_BRIDGE_PCI 0x0604 +#define PCI_CLASS_BRIDGE_OTHER 0x0680 + +#define PCI_CLASS_COMMUNICATION_OTHER 0x0780 + +#define PCI_CLASS_PROCESSOR_CO 0x0b40 +#define PCI_CLASS_PROCESSOR_POWERPC 0x0b20 + +#define PCI_CLASS_OTHERS 0xff + +/* Vendors and devices. Sort key: vendor first, device next. */ + +#define PCI_VENDOR_ID_LSI_LOGIC 0x1000 +#define PCI_DEVICE_ID_LSI_53C895A 0x0012 + +#define PCI_VENDOR_ID_DEC 0x1011 +#define PCI_DEVICE_ID_DEC_21154 0x0026 + +#define PCI_VENDOR_ID_CIRRUS 0x1013 + +#define PCI_VENDOR_ID_IBM 0x1014 + +#define PCI_VENDOR_ID_AMD 0x1022 +#define PCI_DEVICE_ID_AMD_LANCE 0x2000 + +#define PCI_VENDOR_ID_MOTOROLA 0x1057 +#define PCI_DEVICE_ID_MOTOROLA_MPC106 0x0002 +#define PCI_DEVICE_ID_MOTOROLA_RAVEN 0x4801 + +#define PCI_VENDOR_ID_APPLE 0x106b +#define PCI_DEVICE_ID_APPLE_UNI_N_AGP 0x0020 + +#define PCI_VENDOR_ID_SUN 0x108e +#define PCI_DEVICE_ID_SUN_EBUS 0x1000 +#define PCI_DEVICE_ID_SUN_SIMBA 0x5000 +#define PCI_DEVICE_ID_SUN_SABRE 0xa000 + +#define PCI_VENDOR_ID_CMD 0x1095 +#define PCI_DEVICE_ID_CMD_646 0x0646 + +#define PCI_VENDOR_ID_REALTEK 0x10ec +#define PCI_DEVICE_ID_REALTEK_8139 0x8139 + +#define PCI_VENDOR_ID_XILINX 0x10ee + +#define PCI_VENDOR_ID_MARVELL 0x11ab + +#define PCI_VENDOR_ID_ENSONIQ 0x1274 +#define PCI_DEVICE_ID_ENSONIQ_ES1370 0x5000 + +#define PCI_VENDOR_ID_FREESCALE 0x1957 +#define PCI_DEVICE_ID_MPC8533E 0x0030 + +#define PCI_VENDOR_ID_INTEL 0x8086 +#define PCI_DEVICE_ID_INTEL_82441 0x1237 +#define PCI_DEVICE_ID_INTEL_82801AA_5 0x2415 +#define PCI_DEVICE_ID_INTEL_82371SB_0 0x7000 +#define PCI_DEVICE_ID_INTEL_82371SB_1 0x7010 +#define PCI_DEVICE_ID_INTEL_82371SB_2 0x7020 +#define PCI_DEVICE_ID_INTEL_82371AB_0 0x7110 +#define PCI_DEVICE_ID_INTEL_82371AB 0x7111 +#define PCI_DEVICE_ID_INTEL_82371AB_2 0x7112 +#define PCI_DEVICE_ID_INTEL_82371AB_3 0x7113 diff --git a/hw/pcmcia.h b/hw/pcmcia.h index bfa23ba..cd9e61c 100644 --- a/hw/pcmcia.h +++ b/hw/pcmcia.h @@ -1,19 +1,24 @@ /* PCMCIA/Cardbus */ -struct pcmcia_socket_s { +#ifndef _HW_PCMCIA_H +#define _HW_PCMCIA_H + +#include "qemu-common.h" + +typedef struct { qemu_irq irq; int attached; const char *slot_string; const char *card_string; -}; +} PCMCIASocket; -void pcmcia_socket_register(struct pcmcia_socket_s *socket); -void pcmcia_socket_unregister(struct pcmcia_socket_s *socket); -void pcmcia_info(void); +void pcmcia_socket_register(PCMCIASocket *socket); +void pcmcia_socket_unregister(PCMCIASocket *socket); +void pcmcia_info(Monitor *mon); -struct pcmcia_card_s { +struct PCMCIACardState { void *state; - struct pcmcia_socket_s *slot; + PCMCIASocket *slot; int (*attach)(void *state); int (*detach)(void *state); const uint8_t *cis; @@ -46,5 +51,6 @@ struct pcmcia_card_s { #define CISTPL_ENDMARK 0xff /* dscm1xxxx.c */ -struct pcmcia_card_s *dscm1xxxx_init(BlockDriverState *bdrv); +PCMCIACardState *dscm1xxxx_init(BlockDriverState *bdrv); +#endif /* _HW_PCMCIA_H */ @@ -70,40 +70,40 @@ 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, +typedef struct PXA2xxGPIOInfo PXA2xxGPIOInfo; +PXA2xxGPIOInfo *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, +qemu_irq *pxa2xx_gpio_in_get(PXA2xxGPIOInfo *s); +void pxa2xx_gpio_out_set(PXA2xxGPIOInfo *s, int line, qemu_irq handler); -void pxa2xx_gpio_read_notifier(struct pxa2xx_gpio_info_s *s, qemu_irq handler); +void pxa2xx_gpio_read_notifier(PXA2xxGPIOInfo *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, +typedef struct PXA2xxDMAState PXA2xxDMAState; +PXA2xxDMAState *pxa255_dma_init(target_phys_addr_t base, qemu_irq irq); -struct pxa2xx_dma_state_s *pxa27x_dma_init(target_phys_addr_t base, +PXA2xxDMAState *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); +void pxa2xx_dma_request(PXA2xxDMAState *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); +typedef struct PXA2xxLCDState PXA2xxLCDState; +PXA2xxLCDState *pxa2xx_lcdc_init(target_phys_addr_t base, + qemu_irq irq); +void pxa2xx_lcd_vsync_notifier(PXA2xxLCDState *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, +typedef struct PXA2xxMMCIState PXA2xxMMCIState; +PXA2xxMMCIState *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, +void pxa2xx_mmci_handlers(PXA2xxMMCIState *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); +typedef struct PXA2xxPCMCIAState PXA2xxPCMCIAState; +PXA2xxPCMCIAState *pxa2xx_pcmcia_init(target_phys_addr_t base); +int pxa2xx_pcmcia_attach(void *opaque, PCMCIACardState *card); int pxa2xx_pcmcia_dettach(void *opaque); void pxa2xx_pcmcia_set_irq_cb(void *opaque, qemu_irq irq, qemu_irq cd_irq); @@ -112,40 +112,35 @@ struct keymap { int column; int row; }; -struct pxa2xx_keypad_s; -struct pxa2xx_keypad_s *pxa27x_keypad_init(target_phys_addr_t base, +typedef struct PXA2xxKeyPadState PXA2xxKeyPadState; +PXA2xxKeyPadState *pxa27x_keypad_init(target_phys_addr_t base, qemu_irq irq); -void pxa27x_register_keypad(struct pxa2xx_keypad_s *kp, struct keymap *map, +void pxa27x_register_keypad(PXA2xxKeyPadState *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, +typedef struct PXA2xxI2CState PXA2xxI2CState; +PXA2xxI2CState *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); +i2c_bus *pxa2xx_i2c_bus(PXA2xxI2CState *s); -struct pxa2xx_i2s_s; -struct pxa2xx_fir_s; +typedef struct PXA2xxI2SState PXA2xxI2SState; +typedef struct PXA2xxFIrState PXA2xxFIrState; -struct pxa2xx_state_s { +typedef struct { 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; + PXA2xxDMAState *dma; + PXA2xxGPIOInfo *gpio; + PXA2xxLCDState *lcd; + SSIBus **ssp; + PXA2xxI2CState *i2c[2]; + PXA2xxMMCIState *mmc; + PXA2xxPCMCIAState *pcmcia[2]; + PXA2xxI2SState *i2s; + PXA2xxFIrState *fir; + PXA2xxKeyPadState *kp; /* Power management */ target_phys_addr_t pm_base; @@ -189,12 +184,11 @@ struct pxa2xx_state_s { QEMUTimer *rtc_swal1; QEMUTimer *rtc_swal2; QEMUTimer *rtc_pi; -}; +} PXA2xxState; -struct pxa2xx_i2s_s { - target_phys_addr_t base; +struct PXA2xxI2SState { qemu_irq irq; - struct pxa2xx_dma_state_s *dma; + PXA2xxDMAState *dma; void (*data_req)(void *, int, int); uint32_t control[2]; @@ -216,9 +210,8 @@ struct pxa2xx_i2s_s { # 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); +PXA2xxState *pxa270_init(unsigned int sdram_size, const char *revision); +PXA2xxState *pxa255_init(unsigned int sdram_size); /* usb-ohci.c */ void usb_ohci_init_pxa(target_phys_addr_t base, int num_ports, int devfn, diff --git a/hw/qdev.c b/hw/qdev.c new file mode 100644 index 0000000..385e709 --- /dev/null +++ b/hw/qdev.c @@ -0,0 +1,409 @@ +/* + * Dynamic device configuration and creation. + * + * Copyright (c) 2009 CodeSourcery + * + * 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., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA + */ + +/* The theory here is that it should be possible to create a machine without + knowledge of specific devices. Historically board init routines have + passed a bunch of arguments to each device, requiring the board know + exactly which device it is dealing with. This file provides an abstract + API for device configuration and initialization. Devices will generally + inherit from a particular bus (e.g. PCI or I2C) rather than + this API directly. */ + +#include "net.h" +#include "qdev.h" +#include "sysemu.h" +#include "monitor.h" + +struct DeviceProperty { + const char *name; + DevicePropType type; + union { + uint64_t i; + void *ptr; + } value; + DeviceProperty *next; +}; + +struct DeviceType { + DeviceInfo *info; + DeviceType *next; +}; + +/* This is a nasty hack to allow passing a NULL bus to qdev_create. */ +static BusState *main_system_bus; + +static DeviceType *device_type_list; + +/* Register a new device type. */ +void qdev_register(DeviceInfo *info) +{ + DeviceType *t; + + assert(info->size >= sizeof(DeviceState)); + + t = qemu_mallocz(sizeof(DeviceType)); + t->next = device_type_list; + device_type_list = t; + t->info = info; +} + +/* Create a new device. This only initializes the device state structure + and allows properties to be set. qdev_init should be called to + initialize the actual device emulation. */ +DeviceState *qdev_create(BusState *bus, const char *name) +{ + DeviceType *t; + DeviceState *dev; + + for (t = device_type_list; t; t = t->next) { + if (strcmp(t->info->name, name) == 0) { + break; + } + } + if (!t) { + hw_error("Unknown device '%s'\n", name); + } + + dev = qemu_mallocz(t->info->size); + dev->type = t; + + if (!bus) { + /* ???: This assumes system busses have no additional state. */ + if (!main_system_bus) { + main_system_bus = qbus_create(BUS_TYPE_SYSTEM, sizeof(BusState), + NULL, "main-system-bus"); + } + bus = main_system_bus; + } + if (t->info->bus_type != bus->type) { + /* TODO: Print bus type names. */ + hw_error("Device '%s' on wrong bus type (%d/%d)", name, + t->info->bus_type, bus->type); + } + dev->parent_bus = bus; + LIST_INSERT_HEAD(&bus->children, dev, sibling); + return dev; +} + +/* Initialize a device. Device properties should be set before calling + this function. IRQs and MMIO regions should be connected/mapped after + calling this function. */ +void qdev_init(DeviceState *dev) +{ + dev->type->info->init(dev, dev->type->info); +} + +/* Unlink device from bus and free the structure. */ +void qdev_free(DeviceState *dev) +{ + LIST_REMOVE(dev, sibling); + free(dev); +} + +static DeviceProperty *create_prop(DeviceState *dev, const char *name, + DevicePropType type) +{ + DeviceProperty *prop; + + /* TODO: Check for duplicate properties. */ + prop = qemu_mallocz(sizeof(*prop)); + prop->name = qemu_strdup(name); + prop->type = type; + prop->next = dev->props; + dev->props = prop; + + return prop; +} + +void qdev_set_prop_int(DeviceState *dev, const char *name, uint64_t value) +{ + DeviceProperty *prop; + + prop = create_prop(dev, name, PROP_TYPE_INT); + prop->value.i = value; +} + +void qdev_set_prop_dev(DeviceState *dev, const char *name, DeviceState *value) +{ + DeviceProperty *prop; + + prop = create_prop(dev, name, PROP_TYPE_DEV); + prop->value.ptr = value; +} + +void qdev_set_prop_ptr(DeviceState *dev, const char *name, void *value) +{ + DeviceProperty *prop; + + prop = create_prop(dev, name, PROP_TYPE_PTR); + prop->value.ptr = value; +} + +void qdev_set_netdev(DeviceState *dev, NICInfo *nd) +{ + assert(!dev->nd); + dev->nd = nd; +} + + +/* Get a character (serial) device interface. */ +CharDriverState *qdev_init_chardev(DeviceState *dev) +{ + static int next_serial; + static int next_virtconsole; + /* FIXME: This is a nasty hack that needs to go away. */ + if (strncmp(dev->type->info->name, "virtio", 6) == 0) { + return virtcon_hds[next_virtconsole++]; + } else { + return serial_hds[next_serial++]; + } +} + +BusState *qdev_get_parent_bus(DeviceState *dev) +{ + return dev->parent_bus; +} + +static DeviceProperty *find_prop(DeviceState *dev, const char *name, + DevicePropType type) +{ + DeviceProperty *prop; + + for (prop = dev->props; prop; prop = prop->next) { + if (strcmp(prop->name, name) == 0) { + assert (prop->type == type); + return prop; + } + } + return NULL; +} + +uint64_t qdev_get_prop_int(DeviceState *dev, const char *name, uint64_t def) +{ + DeviceProperty *prop; + + prop = find_prop(dev, name, PROP_TYPE_INT); + if (!prop) { + return def; + } + + return prop->value.i; +} + +void *qdev_get_prop_ptr(DeviceState *dev, const char *name) +{ + DeviceProperty *prop; + + prop = find_prop(dev, name, PROP_TYPE_PTR); + assert(prop); + return prop->value.ptr; +} + +DeviceState *qdev_get_prop_dev(DeviceState *dev, const char *name) +{ + DeviceProperty *prop; + + prop = find_prop(dev, name, PROP_TYPE_DEV); + if (!prop) { + return NULL; + } + return prop->value.ptr; +} + +void qdev_init_gpio_in(DeviceState *dev, qemu_irq_handler handler, int n) +{ + assert(dev->num_gpio_in == 0); + dev->num_gpio_in = n; + dev->gpio_in = qemu_allocate_irqs(handler, dev, n); +} + +void qdev_init_gpio_out(DeviceState *dev, qemu_irq *pins, int n) +{ + assert(dev->num_gpio_out == 0); + dev->num_gpio_out = n; + dev->gpio_out = pins; +} + +qemu_irq qdev_get_gpio_in(DeviceState *dev, int n) +{ + assert(n >= 0 && n < dev->num_gpio_in); + return dev->gpio_in[n]; +} + +void qdev_connect_gpio_out(DeviceState * dev, int n, qemu_irq pin) +{ + assert(n >= 0 && n < dev->num_gpio_out); + dev->gpio_out[n] = pin; +} + +VLANClientState *qdev_get_vlan_client(DeviceState *dev, + NetCanReceive *can_receive, + NetReceive *receive, + NetReceiveIOV *receive_iov, + NetCleanup *cleanup, + void *opaque) +{ + NICInfo *nd = dev->nd; + assert(nd); + return qemu_new_vlan_client(nd->vlan, nd->model, nd->name, can_receive, + receive, receive_iov, cleanup, opaque); +} + + +void qdev_get_macaddr(DeviceState *dev, uint8_t *macaddr) +{ + memcpy(macaddr, dev->nd->macaddr, 6); +} + +static int next_block_unit[IF_COUNT]; + +/* Get a block device. This should only be used for single-drive devices + (e.g. SD/Floppy/MTD). Multi-disk devices (scsi/ide) should use the + appropriate bus. */ +BlockDriverState *qdev_init_bdrv(DeviceState *dev, BlockInterfaceType type) +{ + int unit = next_block_unit[type]++; + int index; + + index = drive_get_index(type, 0, unit); + if (index == -1) { + return NULL; + } + return drives_table[index].bdrv; +} + +BusState *qdev_get_child_bus(DeviceState *dev, const char *name) +{ + BusState *bus; + + LIST_FOREACH(bus, &dev->child_bus, sibling) { + if (strcmp(name, bus->name) == 0) { + return bus; + } + } + return NULL; +} + +static int next_scsi_bus; + +/* Create a scsi bus, and attach devices to it. */ +/* TODO: Actually create a scsi bus for hotplug to use. */ +void scsi_bus_new(DeviceState *host, SCSIAttachFn attach) +{ + int bus = next_scsi_bus++; + int unit; + int index; + + for (unit = 0; unit < MAX_SCSI_DEVS; unit++) { + index = drive_get_index(IF_SCSI, bus, unit); + if (index == -1) { + continue; + } + attach(host, drives_table[index].bdrv, unit); + } +} + +BusState *qbus_create(BusType type, size_t size, + DeviceState *parent, const char *name) +{ + BusState *bus; + + bus = qemu_mallocz(size); + bus->type = type; + bus->parent = parent; + bus->name = qemu_strdup(name); + LIST_INIT(&bus->children); + if (parent) { + LIST_INSERT_HEAD(&parent->child_bus, bus, sibling); + } + return bus; +} + +static const char *bus_type_names[] = { + [ BUS_TYPE_SYSTEM ] = "System", + [ BUS_TYPE_PCI ] = "PCI", + [ BUS_TYPE_SCSI ] = "SCSI", + [ BUS_TYPE_I2C ] = "I2C", + [ BUS_TYPE_SSI ] = "SSI", +}; + +#define qdev_printf(fmt, ...) monitor_printf(mon, "%*s" fmt, indent, "", ## __VA_ARGS__) +static void qbus_print(Monitor *mon, BusState *bus, int indent); + +static void qdev_print(Monitor *mon, DeviceState *dev, int indent) +{ + DeviceProperty *prop; + BusState *child; + qdev_printf("dev: %s\n", dev->type->info->name); + indent += 2; + if (dev->num_gpio_in) { + qdev_printf("gpio-in %d\n", dev->num_gpio_in); + } + if (dev->num_gpio_out) { + qdev_printf("gpio-out %d\n", dev->num_gpio_out); + } + for (prop = dev->props; prop; prop = prop->next) { + switch (prop->type) { + case PROP_TYPE_INT: + qdev_printf("prop-int %s 0x%" PRIx64 "\n", prop->name, + prop->value.i); + break; + case PROP_TYPE_PTR: + qdev_printf("prop-ptr %s\n", prop->name); + break; + case PROP_TYPE_DEV: + qdev_printf("prop-dev %s %s\n", prop->name, + ((DeviceState *)prop->value.ptr)->type->info->name); + break; + default: + qdev_printf("prop-unknown%d %s\n", prop->type, prop->name); + break; + } + } + switch (dev->parent_bus->type) { + case BUS_TYPE_SYSTEM: + sysbus_dev_print(mon, dev, indent); + break; + default: + break; + } + LIST_FOREACH(child, &dev->child_bus, sibling) { + qbus_print(mon, child, indent); + } +} + +static void qbus_print(Monitor *mon, BusState *bus, int indent) +{ + struct DeviceState *dev; + + qdev_printf("bus: %s\n", bus->name); + indent += 2; + qdev_printf("type %s\n", bus_type_names[bus->type]); + LIST_FOREACH(dev, &bus->children, sibling) { + qdev_print(mon, dev, indent); + } +} +#undef qdev_printf + +void do_info_qtree(Monitor *mon) +{ + if (main_system_bus) + qbus_print(mon, main_system_bus, 0); +} diff --git a/hw/qdev.h b/hw/qdev.h new file mode 100644 index 0000000..ad10499 --- /dev/null +++ b/hw/qdev.h @@ -0,0 +1,129 @@ +#ifndef QDEV_H +#define QDEV_H + +#include "hw.h" +#include "sys-queue.h" + +typedef struct DeviceType DeviceType; + +typedef struct DeviceProperty DeviceProperty; + +typedef struct BusState BusState; + +/* This structure should not be accessed directly. We declare it here + so that it can be embedded in individual device state structures. */ +struct DeviceState { + DeviceType *type; + BusState *parent_bus; + DeviceProperty *props; + int num_gpio_out; + qemu_irq *gpio_out; + int num_gpio_in; + qemu_irq *gpio_in; + LIST_HEAD(, BusState) child_bus; + NICInfo *nd; + LIST_ENTRY(DeviceState) sibling; +}; + +typedef enum { + BUS_TYPE_SYSTEM, + BUS_TYPE_PCI, + BUS_TYPE_SCSI, + BUS_TYPE_I2C, + BUS_TYPE_SSI +} BusType; + +struct BusState { + DeviceState *parent; + const char *name; + BusType type; + LIST_HEAD(, DeviceState) children; + LIST_ENTRY(BusState) sibling; +}; + +/*** Board API. This should go away once we have a machine config file. ***/ + +DeviceState *qdev_create(BusState *bus, const char *name); +void qdev_init(DeviceState *dev); +void qdev_free(DeviceState *dev); + +/* Set properties between creation and init. */ +void qdev_set_prop_int(DeviceState *dev, const char *name, uint64_t value); +void qdev_set_prop_dev(DeviceState *dev, const char *name, DeviceState *value); +void qdev_set_prop_ptr(DeviceState *dev, const char *name, void *value); +void qdev_set_netdev(DeviceState *dev, NICInfo *nd); + +qemu_irq qdev_get_gpio_in(DeviceState *dev, int n); +void qdev_connect_gpio_out(DeviceState *dev, int n, qemu_irq pin); + +BusState *qdev_get_child_bus(DeviceState *dev, const char *name); + +/*** Device API. ***/ + +typedef enum { + PROP_TYPE_INT, + PROP_TYPE_PTR, + PROP_TYPE_DEV +} DevicePropType; + +typedef struct { + const char *name; + DevicePropType type; +} DevicePropList; + +typedef struct DeviceInfo DeviceInfo; + +typedef void (*qdev_initfn)(DeviceState *dev, DeviceInfo *info); +typedef void (*SCSIAttachFn)(DeviceState *host, BlockDriverState *bdrv, + int unit); + +struct DeviceInfo { + const char *name; + size_t size; + DevicePropList *props; + + /* Private to qdev / bus. */ + qdev_initfn init; + BusType bus_type; +}; + +void qdev_register(DeviceInfo *info); + +/* Register device properties. */ +/* GPIO inputs also double as IRQ sinks. */ +void qdev_init_gpio_in(DeviceState *dev, qemu_irq_handler handler, int n); +void qdev_init_gpio_out(DeviceState *dev, qemu_irq *pins, int n); + +void scsi_bus_new(DeviceState *host, SCSIAttachFn attach); + +CharDriverState *qdev_init_chardev(DeviceState *dev); + +BusState *qdev_get_parent_bus(DeviceState *dev); +uint64_t qdev_get_prop_int(DeviceState *dev, const char *name, uint64_t def); +DeviceState *qdev_get_prop_dev(DeviceState *dev, const char *name); +/* FIXME: Remove opaque pointer properties. */ +void *qdev_get_prop_ptr(DeviceState *dev, const char *name); + +/* Convery from a base type to a parent type, with compile time checking. */ +#ifdef __GNUC__ +#define DO_UPCAST(type, field, dev) ( __extension__ ( { \ + char __attribute__((unused)) offset_must_be_zero[ \ + -offsetof(type, field)]; \ + container_of(dev, type, field);})) +#else +#define DO_UPCAST(type, field, dev) container_of(dev, type, field) +#endif + +/*** BUS API. ***/ + +BusState *qbus_create(BusType type, size_t size, + DeviceState *parent, const char *name); + +#define FROM_QBUS(type, dev) DO_UPCAST(type, qbus, dev) + +/*** monitor commands ***/ + +void do_info_qtree(Monitor *mon); +void sysbus_dev_print(Monitor *mon, DeviceState *dev, int indent); + +#endif diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c index 16b3215..a0485db 100644 --- a/hw/scsi-disk.c +++ b/hw/scsi-disk.c @@ -13,17 +13,19 @@ * the host adapter emulator. */ +#include <qemu-common.h> +#include <sysemu.h> //#define DEBUG_SCSI #ifdef DEBUG_SCSI -#define DPRINTF(fmt, args...) \ -do { printf("scsi-disk: " fmt , ##args); } while (0) +#define DPRINTF(fmt, ...) \ +do { printf("scsi-disk: " fmt , ## __VA_ARGS__); } while (0) #else -#define DPRINTF(fmt, args...) do {} while(0) +#define DPRINTF(fmt, ...) do {} while(0) #endif -#define BADF(fmt, args...) \ -do { fprintf(stderr, "scsi-disk: " fmt , ##args); } while (0) +#define BADF(fmt, ...) \ +do { fprintf(stderr, "scsi-disk: " fmt , ## __VA_ARGS__); } while (0) #include "qemu-common.h" #include "block.h" @@ -34,21 +36,27 @@ do { fprintf(stderr, "scsi-disk: " fmt , ##args); } while (0) #define SENSE_HARDWARE_ERROR 4 #define SENSE_ILLEGAL_REQUEST 5 -#define SCSI_DMA_BUF_SIZE 65536 +#define STATUS_GOOD 0 +#define STATUS_CHECK_CONDITION 2 + +#define SCSI_DMA_BUF_SIZE 131072 +#define SCSI_MAX_INQUIRY_LEN 256 + +#define SCSI_REQ_STATUS_RETRY 0x01 typedef struct SCSIRequest { SCSIDeviceState *dev; uint32_t tag; - /* ??? We should probably keep track of whether the data trasfer is + /* ??? We should probably keep track of whether the data transfer 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; + uint64_t sector; + uint32_t sector_count; + struct iovec iov; + QEMUIOVector qiov; BlockDriverAIOCB *aiocb; struct SCSIRequest *next; + uint32_t status; } SCSIRequest; struct SCSIDeviceState @@ -58,12 +66,14 @@ struct SCSIDeviceState /* 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; + uint64_t max_lba; 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; + char drive_serial_str[21]; }; /* Global pool of SCSIRequest structures. */ @@ -78,13 +88,14 @@ static SCSIRequest *scsi_new_request(SCSIDeviceState *s, uint32_t tag) free_requests = r->next; } else { r = qemu_malloc(sizeof(SCSIRequest)); - r->dma_buf = qemu_memalign(512, SCSI_DMA_BUF_SIZE); + r->iov.iov_base = qemu_memalign(512, SCSI_DMA_BUF_SIZE); } r->dev = s; r->tag = tag; r->sector_count = 0; - r->buf_len = 0; + r->iov.iov_len = 0; r->aiocb = NULL; + r->status = 0; r->next = s->requests; s->requests = r; @@ -124,15 +135,15 @@ static SCSIRequest *scsi_find_request(SCSIDeviceState *s, uint32_t tag) } /* Helper function for command completion. */ -static void scsi_command_complete(SCSIRequest *r, int sense) +static void scsi_command_complete(SCSIRequest *r, int status, int sense) { SCSIDeviceState *s = r->dev; uint32_t tag; - DPRINTF("Command complete tag=0x%x sense=%d\n", r->tag, sense); + DPRINTF("Command complete tag=0x%x status=%d sense=%d\n", r->tag, status, sense); s->sense = sense; tag = r->tag; scsi_remove_request(r); - s->completion(s->opaque, SCSI_REASON_DONE, tag, sense); + s->completion(s->opaque, SCSI_REASON_DONE, tag, status); } /* Cancel a pending data transfer. */ @@ -157,12 +168,13 @@ static void scsi_read_complete(void * opaque, int ret) if (ret) { DPRINTF("IO error\n"); - scsi_command_complete(r, SENSE_HARDWARE_ERROR); + s->completion(s->opaque, SCSI_REASON_DATA, r->tag, 0); + scsi_command_complete(r, STATUS_CHECK_CONDITION, SENSE_NO_SENSE); return; } - DPRINTF("Data ready tag=0x%x len=%d\n", r->tag, r->buf_len); + DPRINTF("Data ready tag=0x%x len=%d\n", r->tag, r->iov.iov_len); - s->completion(s->opaque, SCSI_REASON_DATA, r->tag, r->buf_len); + s->completion(s->opaque, SCSI_REASON_DATA, r->tag, r->iov.iov_len); } /* Read more data from scsi device into buffer. */ @@ -176,18 +188,18 @@ static void scsi_read_data(SCSIDevice *d, uint32_t tag) if (!r) { BADF("Bad read tag 0x%x\n", tag); /* ??? This is the wrong error. */ - scsi_command_complete(r, SENSE_HARDWARE_ERROR); + scsi_command_complete(r, STATUS_CHECK_CONDITION, SENSE_HARDWARE_ERROR); return; } if (r->sector_count == (uint32_t)-1) { - DPRINTF("Read buf_len=%d\n", r->buf_len); + DPRINTF("Read buf_len=%d\n", r->iov.iov_len); r->sector_count = 0; - s->completion(s->opaque, SCSI_REASON_DATA, r->tag, r->buf_len); + s->completion(s->opaque, SCSI_REASON_DATA, r->tag, r->iov.iov_len); return; } DPRINTF("Read sector_count=%d\n", r->sector_count); if (r->sector_count == 0) { - scsi_command_complete(r, SENSE_NO_SENSE); + scsi_command_complete(r, STATUS_GOOD, SENSE_NO_SENSE); return; } @@ -195,73 +207,123 @@ static void scsi_read_data(SCSIDevice *d, uint32_t tag) 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); + r->iov.iov_len = n * 512; + qemu_iovec_init_external(&r->qiov, &r->iov, 1); + r->aiocb = bdrv_aio_readv(s->bdrv, r->sector, &r->qiov, n, + scsi_read_complete, r); if (r->aiocb == NULL) - scsi_command_complete(r, SENSE_HARDWARE_ERROR); + scsi_command_complete(r, STATUS_CHECK_CONDITION, SENSE_HARDWARE_ERROR); r->sector += n; r->sector_count -= n; } +static int scsi_handle_write_error(SCSIRequest *r, int error) +{ + BlockInterfaceErrorAction action = drive_get_onerror(r->dev->bdrv); + + if (action == BLOCK_ERR_IGNORE) + return 0; + + if ((error == ENOSPC && action == BLOCK_ERR_STOP_ENOSPC) + || action == BLOCK_ERR_STOP_ANY) { + r->status |= SCSI_REQ_STATUS_RETRY; + vm_stop(0); + } else { + scsi_command_complete(r, STATUS_CHECK_CONDITION, + SENSE_HARDWARE_ERROR); + } + + return 1; +} + static void scsi_write_complete(void * opaque, int ret) { SCSIRequest *r = (SCSIRequest *)opaque; SCSIDeviceState *s = r->dev; uint32_t len; + uint32_t n; + + r->aiocb = NULL; if (ret) { - fprintf(stderr, "scsi-disc: IO write error\n"); - exit(1); + if (scsi_handle_write_error(r, -ret)) + return; } - r->aiocb = NULL; + n = r->iov.iov_len / 512; + r->sector += n; + r->sector_count -= n; if (r->sector_count == 0) { - scsi_command_complete(r, SENSE_NO_SENSE); + scsi_command_complete(r, STATUS_GOOD, SENSE_NO_SENSE); } else { len = r->sector_count * 512; if (len > SCSI_DMA_BUF_SIZE) { len = SCSI_DMA_BUF_SIZE; } - r->buf_len = len; + r->iov.iov_len = len; DPRINTF("Write complete tag=0x%x more=%d\n", r->tag, len); s->completion(s->opaque, SCSI_REASON_DATA, r->tag, len); } } +static void scsi_write_request(SCSIRequest *r) +{ + SCSIDeviceState *s = r->dev; + uint32_t n; + + n = r->iov.iov_len / 512; + if (n) { + qemu_iovec_init_external(&r->qiov, &r->iov, 1); + r->aiocb = bdrv_aio_writev(s->bdrv, r->sector, &r->qiov, n, + scsi_write_complete, r); + if (r->aiocb == NULL) + scsi_command_complete(r, STATUS_CHECK_CONDITION, + SENSE_HARDWARE_ERROR); + } else { + /* Invoke completion routine to fetch data from host. */ + scsi_write_complete(r, 0); + } +} + /* 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); + scsi_command_complete(r, STATUS_CHECK_CONDITION, 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); - } + + scsi_write_request(r); return 0; } +static void scsi_dma_restart_cb(void *opaque, int running, int reason) +{ + SCSIDeviceState *s = opaque; + SCSIRequest *r = s->requests; + if (!running) + return; + + while (r) { + if (r->status & SCSI_REQ_STATUS_RETRY) { + r->status &= ~SCSI_REQ_STATUS_RETRY; + scsi_write_request(r); + } + r = r->next; + } +} + /* Return a pointer to the data buffer. */ static uint8_t *scsi_get_buf(SCSIDevice *d, uint32_t tag) { @@ -273,7 +335,7 @@ static uint8_t *scsi_get_buf(SCSIDevice *d, uint32_t tag) BADF("Bad buffer tag 0x%x\n", tag); return NULL; } - return r->dma_buf; + return (uint8_t *)r->iov.iov_base; } /* Execute a scsi command. Returns the length of the data expected by the @@ -286,7 +348,7 @@ static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag, { SCSIDeviceState *s = d->state; uint64_t nb_sectors; - uint32_t lba; + uint64_t lba; uint32_t len; int cmdlen; int is_write; @@ -303,28 +365,34 @@ static int32_t scsi_send_command(SCSIDevice *d, uint32_t 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; + outbuf = (uint8_t *)r->iov.iov_base; 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); + lba = (uint64_t) buf[3] | ((uint64_t) buf[2] << 8) | + (((uint64_t) 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); + lba = (uint64_t) buf[5] | ((uint64_t) buf[4] << 8) | + ((uint64_t) buf[3] << 16) | ((uint64_t) 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); + lba = (uint64_t) buf[9] | ((uint64_t) buf[8] << 8) | + ((uint64_t) buf[7] << 16) | ((uint64_t) buf[6] << 24) | + ((uint64_t) buf[5] << 32) | ((uint64_t) buf[4] << 40) | + ((uint64_t) buf[3] << 48) | ((uint64_t) buf[2] << 56); 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); + lba = (uint64_t) buf[5] | ((uint64_t) buf[4] << 8) | + ((uint64_t) buf[3] << 16) | ((uint64_t) buf[2] << 24); len = buf[9] | (buf[8] << 8) | (buf[7] << 16) | (buf[6] << 24); cmdlen = 12; break; @@ -344,21 +412,32 @@ static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag, if (lun || buf[1] >> 5) { /* Only LUN 0 supported. */ DPRINTF("Unimplemented LUN %d\n", lun ? lun : buf[1] >> 5); - goto fail; + if (command != 0x03 && command != 0x12) /* REQUEST SENSE and INQUIRY */ + goto fail; } switch (command) { case 0x0: DPRINTF("Test Unit Ready\n"); + if (!bdrv_is_inserted(s->bdrv)) + goto notready; break; case 0x03: DPRINTF("Request Sense (len %d)\n", len); if (len < 4) goto fail; memset(outbuf, 0, 4); + r->iov.iov_len = 4; + if (s->sense == SENSE_NOT_READY && len >= 18) { + memset(outbuf, 0, 18); + r->iov.iov_len = 18; + outbuf[7] = 10; + /* asc 0x3a, ascq 0: Medium not present */ + outbuf[12] = 0x3a; + outbuf[13] = 0; + } outbuf[0] = 0xf0; outbuf[1] = 0; outbuf[2] = s->sense; - r->buf_len = 4; break; case 0x12: DPRINTF("Inquiry (len %d)\n", len); @@ -383,24 +462,26 @@ static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag, DPRINTF("Inquiry EVPD[Supported pages] " "buffer size %d\n", len); - r->buf_len = 0; + r->iov.iov_len = 0; if (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM) { - outbuf[r->buf_len++] = 5; + outbuf[r->iov.iov_len++] = 5; } else { - outbuf[r->buf_len++] = 0; + outbuf[r->iov.iov_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 + outbuf[r->iov.iov_len++] = 0x00; // this page + outbuf[r->iov.iov_len++] = 0x00; + outbuf[r->iov.iov_len++] = 3; // number of pages + outbuf[r->iov.iov_len++] = 0x00; // list of supported pages (this page) + outbuf[r->iov.iov_len++] = 0x80; // unit serial number + outbuf[r->iov.iov_len++] = 0x83; // device identification } break; case 0x80: { + int l; + /* Device serial number, optional */ if (len < 4) { BADF("Error: EVPD[Serial number] Inquiry buffer " @@ -409,21 +490,22 @@ static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag, } DPRINTF("Inquiry EVPD[Serial number] buffer size %d\n", len); + l = MIN(len, strlen(s->drive_serial_str)); - r->buf_len = 0; + r->iov.iov_len = 0; /* Supported page codes */ if (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM) { - outbuf[r->buf_len++] = 5; + outbuf[r->iov.iov_len++] = 5; } else { - outbuf[r->buf_len++] = 0; + outbuf[r->iov.iov_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 + outbuf[r->iov.iov_len++] = 0x80; // this page + outbuf[r->iov.iov_len++] = 0x00; + outbuf[r->iov.iov_len++] = l; + memcpy(&outbuf[r->iov.iov_len], s->drive_serial_str, l); + r->iov.iov_len += l; } break; @@ -437,25 +519,25 @@ static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag, DPRINTF("Inquiry EVPD[Device identification] " "buffer size %d\n", len); - r->buf_len = 0; + r->iov.iov_len = 0; if (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM) { - outbuf[r->buf_len++] = 5; + outbuf[r->iov.iov_len++] = 5; } else { - outbuf[r->buf_len++] = 0; + outbuf[r->iov.iov_len++] = 0; } - outbuf[r->buf_len++] = 0x83; // this page - outbuf[r->buf_len++] = 0x00; - outbuf[r->buf_len++] = 3 + id_len; + outbuf[r->iov.iov_len++] = 0x83; // this page + outbuf[r->iov.iov_len++] = 0x00; + outbuf[r->iov.iov_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 + outbuf[r->iov.iov_len++] = 0x2; // ASCII + outbuf[r->iov.iov_len++] = 0; // not officially assigned + outbuf[r->iov.iov_len++] = 0; // reserved + outbuf[r->iov.iov_len++] = id_len; // length of data following - memcpy(&outbuf[r->buf_len], + memcpy(&outbuf[r->iov.iov_len], bdrv_get_device_name(s->bdrv), id_len); - r->buf_len += id_len; + r->iov.iov_len += id_len; } break; default: @@ -486,8 +568,15 @@ static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag, "is less than 36 (TODO: only 5 required)\n", len); } } - memset(outbuf, 0, 36); - if (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM) { + + if(len > SCSI_MAX_INQUIRY_LEN) + len = SCSI_MAX_INQUIRY_LEN; + + memset(outbuf, 0, len); + + if (lun || buf[1] >> 5) { + outbuf[0] = 0x7f; /* LUN not supported */ + } else if (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM) { outbuf[0] = 5; outbuf[1] = 0x80; memcpy(&outbuf[16], "QEMU CD-ROM ", 16); @@ -501,10 +590,10 @@ static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag, Some later commands are also implemented. */ outbuf[2] = 3; outbuf[3] = 2; /* Format 2 */ - outbuf[4] = 31; + outbuf[4] = len - 5; /* Additional Length = (Len - 1) - 4 */ /* Sync data transfer and TCQ. */ outbuf[7] = 0x10 | (s->tcq ? 0x02 : 0); - r->buf_len = 36; + r->iov.iov_len = len; break; case 0x16: DPRINTF("Reserve(6)\n"); @@ -639,14 +728,18 @@ static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag, 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; + r->iov.iov_len = p - outbuf; + outbuf[0] = r->iov.iov_len - 4; + if (r->iov.iov_len > len) + r->iov.iov_len = len; } break; case 0x1b: DPRINTF("Start Stop Unit\n"); + if (bdrv_get_type_hint(s->bdrv) == BDRV_TYPE_CDROM && + (buf[4] & 2)) + /* load/eject medium */ + bdrv_eject(s->bdrv, !(buf[4] & 1)); break; case 0x1e: DPRINTF("Prevent Allow Medium Removal (prevent = %d)\n", buf[4] & 3); @@ -657,9 +750,15 @@ static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag, /* The normal LEN field for this command is zero. */ memset(outbuf, 0, 8); bdrv_get_geometry(s->bdrv, &nb_sectors); + nb_sectors /= s->cluster_size; /* Returned value is the address of the last sector. */ if (nb_sectors) { nb_sectors--; + /* Remember the new size for read/write sanity checking. */ + s->max_lba = nb_sectors; + /* Clip to 2TB, instead of returning capacity modulo 2TB. */ + if (nb_sectors > UINT32_MAX) + nb_sectors = UINT32_MAX; outbuf[0] = (nb_sectors >> 24) & 0xff; outbuf[1] = (nb_sectors >> 16) & 0xff; outbuf[2] = (nb_sectors >> 8) & 0xff; @@ -668,21 +767,28 @@ static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag, outbuf[5] = 0; outbuf[6] = s->cluster_size * 2; outbuf[7] = 0; - r->buf_len = 8; + r->iov.iov_len = 8; } else { - scsi_command_complete(r, SENSE_NOT_READY); + notready: + scsi_command_complete(r, STATUS_CHECK_CONDITION, SENSE_NOT_READY); return 0; } break; case 0x08: case 0x28: - DPRINTF("Read (sector %d, count %d)\n", lba, len); + case 0x88: + DPRINTF("Read (sector %lld, count %d)\n", lba, len); + if (lba > s->max_lba) + goto illegal_lba; 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); + case 0x8a: + DPRINTF("Write (sector %lld, count %d)\n", lba, len); + if (lba > s->max_lba) + goto illegal_lba; r->sector = lba * s->cluster_size; r->sector_count = len * s->cluster_size; is_write = 1; @@ -700,6 +806,7 @@ static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag, 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); + nb_sectors /= s->cluster_size; switch(format) { case 0: toclen = cdrom_read_toc(nb_sectors, outbuf, msf, start_track); @@ -721,7 +828,7 @@ static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag, if (toclen > 0) { if (len > toclen) len = toclen; - r->buf_len = len; + r->iov.iov_len = len; break; } error_cmd: @@ -734,7 +841,7 @@ static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag, /* ??? 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; + r->iov.iov_len = 8; break; case 0x56: DPRINTF("Reserve(10)\n"); @@ -746,24 +853,64 @@ static int32_t scsi_send_command(SCSIDevice *d, uint32_t tag, if (buf[1] & 3) goto fail; break; + case 0x9e: + /* Service Action In subcommands. */ + if ((buf[1] & 31) == 0x10) { + DPRINTF("SAI READ CAPACITY(16)\n"); + memset(outbuf, 0, len); + bdrv_get_geometry(s->bdrv, &nb_sectors); + nb_sectors /= s->cluster_size; + /* Returned value is the address of the last sector. */ + if (nb_sectors) { + nb_sectors--; + /* Remember the new size for read/write sanity checking. */ + s->max_lba = nb_sectors; + outbuf[0] = (nb_sectors >> 56) & 0xff; + outbuf[1] = (nb_sectors >> 48) & 0xff; + outbuf[2] = (nb_sectors >> 40) & 0xff; + outbuf[3] = (nb_sectors >> 32) & 0xff; + outbuf[4] = (nb_sectors >> 24) & 0xff; + outbuf[5] = (nb_sectors >> 16) & 0xff; + outbuf[6] = (nb_sectors >> 8) & 0xff; + outbuf[7] = nb_sectors & 0xff; + outbuf[8] = 0; + outbuf[9] = 0; + outbuf[10] = s->cluster_size * 2; + outbuf[11] = 0; + /* Protection, exponent and lowest lba field left blank. */ + r->iov.iov_len = len; + } else { + scsi_command_complete(r, STATUS_CHECK_CONDITION, SENSE_NOT_READY); + return 0; + } + break; + } + DPRINTF("Unsupported Service Action In\n"); + goto fail; 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; + r->iov.iov_len = 16; + break; + case 0x2f: + DPRINTF("Verify (sector %d, count %d)\n", lba, len); break; default: DPRINTF("Unknown SCSI command (%2.2x)\n", buf[0]); fail: - scsi_command_complete(r, SENSE_ILLEGAL_REQUEST); + scsi_command_complete(r, STATUS_CHECK_CONDITION, SENSE_ILLEGAL_REQUEST); return 0; + illegal_lba: + scsi_command_complete(r, STATUS_CHECK_CONDITION, SENSE_HARDWARE_ERROR); + return 0; } - if (r->sector_count == 0 && r->buf_len == 0) { - scsi_command_complete(r, SENSE_NO_SENSE); + if (r->sector_count == 0 && r->iov.iov_len == 0) { + scsi_command_complete(r, STATUS_GOOD, SENSE_NO_SENSE); } - len = r->sector_count * 512 + r->buf_len; + len = r->sector_count * 512 + r->iov.iov_len; if (is_write) { return -len; } else { @@ -784,6 +931,7 @@ SCSIDevice *scsi_disk_init(BlockDriverState *bdrv, int tcq, { SCSIDevice *d; SCSIDeviceState *s; + uint64_t nb_sectors; s = (SCSIDeviceState *)qemu_mallocz(sizeof(SCSIDeviceState)); s->bdrv = bdrv; @@ -795,7 +943,16 @@ SCSIDevice *scsi_disk_init(BlockDriverState *bdrv, int tcq, } else { s->cluster_size = 1; } - + bdrv_get_geometry(s->bdrv, &nb_sectors); + nb_sectors /= s->cluster_size; + if (nb_sectors) + nb_sectors--; + s->max_lba = nb_sectors; + strncpy(s->drive_serial_str, drive_get_serial(s->bdrv), + sizeof(s->drive_serial_str)); + if (strlen(s->drive_serial_str) == 0) + pstrcpy(s->drive_serial_str, sizeof(s->drive_serial_str), "0"); + qemu_add_vm_change_state_handler(scsi_dma_restart_cb, s); d = (SCSIDevice *)qemu_mallocz(sizeof(SCSIDevice)); d->state = s; d->destroy = scsi_destroy; diff --git a/hw/smbios.h b/hw/smbios.h new file mode 100644 index 0000000..3a5169d --- /dev/null +++ b/hw/smbios.h @@ -0,0 +1,162 @@ +#ifndef QEMU_SMBIOS_H +#define QEMU_SMBIOS_H +/* + * SMBIOS Support + * + * Copyright (C) 2009 Hewlett-Packard Development Company, L.P. + * + * Authors: + * Alex Williamson <alex.williamson@hp.com> + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ + +int smbios_entry_add(const char *t); +void smbios_add_field(int type, int offset, int len, void *data); +uint8_t *smbios_get_table(size_t *length); + +/* + * SMBIOS spec defined tables + */ + +/* This goes at the beginning of every SMBIOS structure. */ +struct smbios_structure_header { + uint8_t type; + uint8_t length; + uint16_t handle; +} __attribute__((__packed__)); + +/* SMBIOS type 0 - BIOS Information */ +struct smbios_type_0 { + struct smbios_structure_header header; + uint8_t vendor_str; + uint8_t bios_version_str; + uint16_t bios_starting_address_segment; + uint8_t bios_release_date_str; + uint8_t bios_rom_size; + uint8_t bios_characteristics[8]; + uint8_t bios_characteristics_extension_bytes[2]; + uint8_t system_bios_major_release; + uint8_t system_bios_minor_release; + uint8_t embedded_controller_major_release; + uint8_t embedded_controller_minor_release; +} __attribute__((__packed__)); + +/* SMBIOS type 1 - System Information */ +struct smbios_type_1 { + struct smbios_structure_header header; + uint8_t manufacturer_str; + uint8_t product_name_str; + uint8_t version_str; + uint8_t serial_number_str; + uint8_t uuid[16]; + uint8_t wake_up_type; + uint8_t sku_number_str; + uint8_t family_str; +} __attribute__((__packed__)); + +/* SMBIOS type 3 - System Enclosure (v2.3) */ +struct smbios_type_3 { + struct smbios_structure_header header; + uint8_t manufacturer_str; + uint8_t type; + uint8_t version_str; + uint8_t serial_number_str; + uint8_t asset_tag_number_str; + uint8_t boot_up_state; + uint8_t power_supply_state; + uint8_t thermal_state; + uint8_t security_status; + uint32_t oem_defined; + uint8_t height; + uint8_t number_of_power_cords; + uint8_t contained_element_count; + // contained elements follow +} __attribute__((__packed__)); + +/* SMBIOS type 4 - Processor Information (v2.0) */ +struct smbios_type_4 { + struct smbios_structure_header header; + uint8_t socket_designation_str; + uint8_t processor_type; + uint8_t processor_family; + uint8_t processor_manufacturer_str; + uint32_t processor_id[2]; + uint8_t processor_version_str; + uint8_t voltage; + uint16_t external_clock; + uint16_t max_speed; + uint16_t current_speed; + uint8_t status; + uint8_t processor_upgrade; + uint16_t l1_cache_handle; + uint16_t l2_cache_handle; + uint16_t l3_cache_handle; +} __attribute__((__packed__)); + +/* SMBIOS type 16 - Physical Memory Array + * Associated with one type 17 (Memory Device). + */ +struct smbios_type_16 { + struct smbios_structure_header header; + uint8_t location; + uint8_t use; + uint8_t error_correction; + uint32_t maximum_capacity; + uint16_t memory_error_information_handle; + uint16_t number_of_memory_devices; +} __attribute__((__packed__)); +/* SMBIOS type 17 - Memory Device + * Associated with one type 19 + */ +struct smbios_type_17 { + struct smbios_structure_header header; + uint16_t physical_memory_array_handle; + uint16_t memory_error_information_handle; + uint16_t total_width; + uint16_t data_width; + uint16_t size; + uint8_t form_factor; + uint8_t device_set; + uint8_t device_locator_str; + uint8_t bank_locator_str; + uint8_t memory_type; + uint16_t type_detail; +} __attribute__((__packed__)); + +/* SMBIOS type 19 - Memory Array Mapped Address */ +struct smbios_type_19 { + struct smbios_structure_header header; + uint32_t starting_address; + uint32_t ending_address; + uint16_t memory_array_handle; + uint8_t partition_width; +} __attribute__((__packed__)); + +/* SMBIOS type 20 - Memory Device Mapped Address */ +struct smbios_type_20 { + struct smbios_structure_header header; + uint32_t starting_address; + uint32_t ending_address; + uint16_t memory_device_handle; + uint16_t memory_array_mapped_address_handle; + uint8_t partition_row_position; + uint8_t interleave_position; + uint8_t interleaved_data_depth; +} __attribute__((__packed__)); + +/* SMBIOS type 32 - System Boot Information */ +struct smbios_type_32 { + struct smbios_structure_header header; + uint8_t reserved[6]; + uint8_t boot_status; +} __attribute__((__packed__)); + +/* SMBIOS type 127 -- End-of-table */ +struct smbios_type_127 { + struct smbios_structure_header header; +} __attribute__((__packed__)); + +#endif /*QEMU_SMBIOS_H */ diff --git a/hw/smc91c111.c b/hw/smc91c111.c index 410051d..cf8d864 100644 --- a/hw/smc91c111.c +++ b/hw/smc91c111.c @@ -7,7 +7,7 @@ * This code is licenced under the GPL */ -#include "hw.h" +#include "sysbus.h" #include "net.h" #include "devices.h" /* For crc32 */ @@ -17,7 +17,7 @@ #define NUM_PACKETS 4 typedef struct { - uint32_t base; + SysBusDevice busdev; VLANClientState *vc; uint16_t tcr; uint16_t rcr; @@ -43,6 +43,7 @@ typedef struct { uint8_t int_level; uint8_t int_mask; uint8_t macaddr[6]; + int mmio_index; } smc91c111_state; #define RCR_SOFT_RST 0x8000 @@ -249,7 +250,6 @@ static void smc91c111_writeb(void *opaque, target_phys_addr_t offset, { smc91c111_state *s = (smc91c111_state *)opaque; - offset -= s->base; if (offset == 14) { s->bank = value; return; @@ -414,15 +414,13 @@ static void smc91c111_writeb(void *opaque, target_phys_addr_t offset, } break; } - cpu_abort (cpu_single_env, "smc91c111_write: Bad reg %d:%x\n", - s->bank, (int)offset); + hw_error("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; } @@ -556,8 +554,7 @@ static uint32_t smc91c111_readb(void *opaque, target_phys_addr_t offset) } break; } - cpu_abort (cpu_single_env, "smc91c111_read: Bad reg %d:%x\n", - s->bank, (int)offset); + hw_error("smc91c111_read: Bad reg %d:%x\n", s->bank, (int)offset); return 0; } @@ -571,10 +568,9 @@ static void smc91c111_writew(void *opaque, target_phys_addr_t offset, 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) + if (offset != 0xc) smc91c111_writew(opaque, offset, value & 0xffff); smc91c111_writew(opaque, offset + 2, value >> 16); } @@ -595,9 +591,9 @@ static uint32_t smc91c111_readl(void *opaque, target_phys_addr_t offset) return val; } -static int smc91c111_can_receive(void *opaque) +static int smc91c111_can_receive(VLANClientState *vc) { - smc91c111_state *s = (smc91c111_state *)opaque; + smc91c111_state *s = vc->opaque; if ((s->rcr & RCR_RXEN) == 0 || (s->rcr & RCR_SOFT_RST)) return 1; @@ -606,9 +602,9 @@ static int smc91c111_can_receive(void *opaque) return 1; } -static void smc91c111_receive(void *opaque, const uint8_t *buf, int size) +static ssize_t smc91c111_receive(VLANClientState *vc, const uint8_t *buf, size_t size) { - smc91c111_state *s = (smc91c111_state *)opaque; + smc91c111_state *s = vc->opaque; int status; int packetsize; uint32_t crc; @@ -616,7 +612,7 @@ static void smc91c111_receive(void *opaque, const uint8_t *buf, int size) uint8_t *p; if ((s->rcr & RCR_RXEN) == 0 || (s->rcr & RCR_SOFT_RST)) - return; + return -1; /* Short packets are padded with zeros. Receiving a packet < 64 bytes long is considered an error condition. */ if (size < 64) @@ -629,10 +625,10 @@ static void smc91c111_receive(void *opaque, const uint8_t *buf, int size) packetsize += 4; /* TODO: Flag overrun and receive errors. */ if (packetsize > 2048) - return; + return -1; packetnum = smc91c111_allocate_packet(s); if (packetnum == 0x80) - return; + return -1; s->rx_fifo[s->rx_fifo_len++] = packetnum; p = &s->data[packetnum][0]; @@ -680,6 +676,8 @@ static void smc91c111_receive(void *opaque, const uint8_t *buf, int size) /* TODO: Raise early RX interrupt? */ s->int_level |= INT_RCV; smc91c111_update(s); + + return size; } static CPUReadMemoryFunc *smc91c111_readfn[] = { @@ -694,22 +692,52 @@ static CPUWriteMemoryFunc *smc91c111_writefn[] = { smc91c111_writel }; -void smc91c111_init(NICInfo *nd, uint32_t base, qemu_irq irq) +static void smc91c111_cleanup(VLANClientState *vc) +{ + smc91c111_state *s = vc->opaque; + + cpu_unregister_io_memory(s->mmio_index); + qemu_free(s); +} + +static void smc91c111_init1(SysBusDevice *dev) { - smc91c111_state *s; - int iomemtype; + smc91c111_state *s = FROM_SYSBUS(smc91c111_state, dev); - 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); + s->mmio_index = cpu_register_io_memory(smc91c111_readfn, + smc91c111_writefn, s); + sysbus_init_mmio(dev, 16, s->mmio_index); + sysbus_init_irq(dev, &s->irq); + qdev_get_macaddr(&dev->qdev, s->macaddr); smc91c111_reset(s); - s->vc = qemu_new_vlan_client(nd->vlan, smc91c111_receive, - smc91c111_can_receive, s); + s->vc = qdev_get_vlan_client(&dev->qdev, + smc91c111_can_receive, smc91c111_receive, NULL, + smc91c111_cleanup, s); + qemu_format_nic_info_str(s->vc, s->macaddr); /* ??? Save/restore. */ } + +static void smc91c111_register_devices(void) +{ + sysbus_register_dev("smc91c111", sizeof(smc91c111_state), smc91c111_init1); +} + +/* Legacy helper function. Should go away when machine config files are + implemented. */ +void smc91c111_init(NICInfo *nd, uint32_t base, qemu_irq irq) +{ + DeviceState *dev; + SysBusDevice *s; + + qemu_check_nic_model(nd, "smc91c111"); + dev = qdev_create(NULL, "smc91c111"); + qdev_set_netdev(dev, nd); + qdev_init(dev); + s = sysbus_from_qdev(dev); + sysbus_mmio_map(s, 0, base); + sysbus_connect_irq(s, 0, irq); +} + +device_init(smc91c111_register_devices) diff --git a/hw/sysbus.c b/hw/sysbus.c new file mode 100644 index 0000000..ef3a701 --- /dev/null +++ b/hw/sysbus.c @@ -0,0 +1,165 @@ +/* + * System (CPU) Bus device support code + * + * Copyright (c) 2009 CodeSourcery + * + * 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., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA + */ + +#include "sysbus.h" +#include "sysemu.h" +#include "monitor.h" + +void sysbus_connect_irq(SysBusDevice *dev, int n, qemu_irq irq) +{ + assert(n >= 0 && n < dev->num_irq); + dev->irqs[n] = 0; + if (dev->irqp[n]) { + *dev->irqp[n] = irq; + } +} + +void sysbus_mmio_map(SysBusDevice *dev, int n, target_phys_addr_t addr) +{ + assert(n >= 0 && n < dev->num_mmio); + + if (dev->mmio[n].addr == addr) { + /* ??? region already mapped here. */ + return; + } + if (dev->mmio[n].addr != (target_phys_addr_t)-1) { + /* Unregister previous mapping. */ + cpu_register_physical_memory(dev->mmio[n].addr, dev->mmio[n].size, + IO_MEM_UNASSIGNED); + } + dev->mmio[n].addr = addr; + if (dev->mmio[n].cb) { + dev->mmio[n].cb(dev, addr); + } else { + cpu_register_physical_memory(addr, dev->mmio[n].size, + dev->mmio[n].iofunc); + } +} + + +/* Request an IRQ source. The actual IRQ object may be populated later. */ +void sysbus_init_irq(SysBusDevice *dev, qemu_irq *p) +{ + int n; + + assert(dev->num_irq < QDEV_MAX_IRQ); + n = dev->num_irq++; + dev->irqp[n] = p; +} + +/* Pass IRQs from a target device. */ +void sysbus_pass_irq(SysBusDevice *dev, SysBusDevice *target) +{ + int i; + assert(dev->num_irq == 0); + dev->num_irq = target->num_irq; + for (i = 0; i < dev->num_irq; i++) { + dev->irqp[i] = target->irqp[i]; + } +} + +void sysbus_init_mmio(SysBusDevice *dev, target_phys_addr_t size, int iofunc) +{ + int n; + + assert(dev->num_mmio < QDEV_MAX_MMIO); + n = dev->num_mmio++; + dev->mmio[n].addr = -1; + dev->mmio[n].size = size; + dev->mmio[n].iofunc = iofunc; +} + +void sysbus_init_mmio_cb(SysBusDevice *dev, target_phys_addr_t size, + mmio_mapfunc cb) +{ + int n; + + assert(dev->num_mmio < QDEV_MAX_MMIO); + n = dev->num_mmio++; + dev->mmio[n].addr = -1; + dev->mmio[n].size = size; + dev->mmio[n].cb = cb; +} + +static void sysbus_device_init(DeviceState *dev, DeviceInfo *base) +{ + SysBusDeviceInfo *info = container_of(base, SysBusDeviceInfo, qdev); + + info->init(sysbus_from_qdev(dev)); +} + +void sysbus_register_withprop(SysBusDeviceInfo *info) +{ + info->qdev.init = sysbus_device_init; + info->qdev.bus_type = BUS_TYPE_SYSTEM; + + assert(info->qdev.size >= sizeof(SysBusDevice)); + qdev_register(&info->qdev); +} + +void sysbus_register_dev(const char *name, size_t size, sysbus_initfn init) +{ + SysBusDeviceInfo *info; + + info = qemu_mallocz(sizeof(*info)); + info->qdev.name = qemu_strdup(name); + info->qdev.size = size; + info->init = init; + sysbus_register_withprop(info); +} + +DeviceState *sysbus_create_varargs(const char *name, + target_phys_addr_t addr, ...) +{ + DeviceState *dev; + SysBusDevice *s; + va_list va; + qemu_irq irq; + int n; + + dev = qdev_create(NULL, name); + s = sysbus_from_qdev(dev); + qdev_init(dev); + if (addr != (target_phys_addr_t)-1) { + sysbus_mmio_map(s, 0, addr); + } + va_start(va, addr); + n = 0; + while (1) { + irq = va_arg(va, qemu_irq); + if (!irq) { + break; + } + sysbus_connect_irq(s, n, irq); + n++; + } + return dev; +} + +void sysbus_dev_print(Monitor *mon, DeviceState *dev, int indent) +{ + SysBusDevice *s = sysbus_from_qdev(dev); + int i; + + for (i = 0; i < s->num_mmio; i++) { + monitor_printf(mon, "%*smmio " TARGET_FMT_plx "/" TARGET_FMT_plx "\n", + indent, "", s->mmio[i].addr, s->mmio[i].size); + } +} diff --git a/hw/sysbus.h b/hw/sysbus.h new file mode 100644 index 0000000..7c20808 --- /dev/null +++ b/hw/sysbus.h @@ -0,0 +1,62 @@ +#ifndef HW_SYSBUS_H +#define HW_SYSBUS_H 1 + +/* Devices attached directly to the main system bus. */ + +#include "qdev.h" + +#define QDEV_MAX_MMIO 5 +#define QDEV_MAX_IRQ 32 + +typedef struct SysBusDevice SysBusDevice; +typedef void (*mmio_mapfunc)(SysBusDevice *dev, target_phys_addr_t addr); + +struct SysBusDevice { + DeviceState qdev; + int num_irq; + qemu_irq irqs[QDEV_MAX_IRQ]; + qemu_irq *irqp[QDEV_MAX_IRQ]; + int num_mmio; + struct { + target_phys_addr_t addr; + target_phys_addr_t size; + mmio_mapfunc cb; + int iofunc; + } mmio[QDEV_MAX_MMIO]; +}; + +typedef void (*sysbus_initfn)(SysBusDevice *dev); + +/* Macros to compensate for lack of type inheritance in C. */ +#define sysbus_from_qdev(dev) ((SysBusDevice *)(dev)) +#define FROM_SYSBUS(type, dev) DO_UPCAST(type, busdev, dev) + +typedef struct { + DeviceInfo qdev; + sysbus_initfn init; +} SysBusDeviceInfo; + +void sysbus_register_dev(const char *name, size_t size, sysbus_initfn init); +void sysbus_register_withprop(SysBusDeviceInfo *info); +void *sysbus_new(void); +void sysbus_init_mmio(SysBusDevice *dev, target_phys_addr_t size, int iofunc); +void sysbus_init_mmio_cb(SysBusDevice *dev, target_phys_addr_t size, + mmio_mapfunc cb); +void sysbus_init_irq(SysBusDevice *dev, qemu_irq *p); +void sysbus_pass_irq(SysBusDevice *dev, SysBusDevice *target); + + +void sysbus_connect_irq(SysBusDevice *dev, int n, qemu_irq irq); +void sysbus_mmio_map(SysBusDevice *dev, int n, target_phys_addr_t addr); + +/* Legacy helper function for creating devices. */ +DeviceState *sysbus_create_varargs(const char *name, + target_phys_addr_t addr, ...); +static inline DeviceState *sysbus_create_simple(const char *name, + target_phys_addr_t addr, + qemu_irq irq) +{ + return sysbus_create_varargs(name, addr, irq, NULL); +} + +#endif /* !HW_SYSBUS_H */ diff --git a/hw/usb-hid.c b/hw/usb-hid.c index 406c9ab..c850a91 100644 --- a/hw/usb-hid.c +++ b/hw/usb-hid.c @@ -65,8 +65,10 @@ typedef struct USBHIDState { }; int kind; int protocol; - int idle; + uint8_t idle; int changed; + void *datain_opaque; + void (*datain)(void *); } USBHIDState; /* mostly the same values as the Bochs USB Mouse device */ @@ -402,6 +404,14 @@ static const uint8_t usb_hid_usage_keys[0x100] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +static void usb_hid_changed(USBHIDState *hs) +{ + hs->changed = 1; + + if (hs->datain) + hs->datain(hs->datain_opaque); +} + static void usb_mouse_event(void *opaque, int dx1, int dy1, int dz1, int buttons_state) { @@ -412,7 +422,8 @@ static void usb_mouse_event(void *opaque, s->dy += dy1; s->dz += dz1; s->buttons_state = buttons_state; - hs->changed = 1; + + usb_hid_changed(hs); } static void usb_tablet_event(void *opaque, @@ -425,7 +436,8 @@ static void usb_tablet_event(void *opaque, s->y = y; s->dz += dz; s->buttons_state = buttons_state; - hs->changed = 1; + + usb_hid_changed(hs); } static void usb_keyboard_event(void *opaque, int keycode) @@ -439,8 +451,6 @@ static void usb_keyboard_event(void *opaque, int keycode) 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; @@ -465,15 +475,23 @@ static void usb_keyboard_event(void *opaque, int keycode) if (s->key[i] == hid_code) { s->key[i] = s->key[-- s->keys]; s->key[s->keys] = 0x00; - return; + usb_hid_changed(hs); + break; } + if (i < 0) + 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; + break; + if (i < 0) { + if (s->keys < sizeof(s->key)) + s->key[s->keys ++] = hid_code; + } else + return; } + + usb_hid_changed(hs); } static inline int int_clamp(int val, int vmin, int vmax) @@ -776,7 +794,7 @@ static int usb_hid_handle_control(USBDevice *dev, int request, int value, data[0] = s->idle; break; case SET_IDLE: - s->idle = value; + s->idle = (uint8_t) (value >> 8); ret = 0; break; default: @@ -833,8 +851,6 @@ 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; @@ -856,8 +872,6 @@ 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; @@ -879,8 +893,6 @@ 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; @@ -894,3 +906,11 @@ USBDevice *usb_keyboard_init(void) return (USBDevice *) s; } + +void usb_hid_datain_cb(USBDevice *dev, void *opaque, void (*datain)(void *)) +{ + USBHIDState *s = (USBHIDState *)dev; + + s->datain_opaque = opaque; + s->datain = datain; +} diff --git a/hw/usb-hub.c b/hw/usb-hub.c index 97c3d05..9f26bbe 100644 --- a/hw/usb-hub.c +++ b/hw/usb-hub.c @@ -530,8 +530,6 @@ USBDevice *usb_hub_init(int nb_ports) 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; diff --git a/hw/usb-msd.c b/hw/usb-msd.c index f7ad25e..3a3eb4a 100644 --- a/hw/usb-msd.c +++ b/hw/usb-msd.c @@ -11,14 +11,15 @@ #include "usb.h" #include "block.h" #include "scsi-disk.h" +#include "console.h" //#define DEBUG_MSD #ifdef DEBUG_MSD -#define DPRINTF(fmt, args...) \ -do { printf("usb-msd: " fmt , ##args); } while (0) +#define DPRINTF(fmt, ...) \ +do { printf("usb-msd: " fmt , ## __VA_ARGS__); } while (0) #else -#define DPRINTF(fmt, args...) do {} while(0) +#define DPRINTF(fmt, ...) do {} while(0) #endif /* USB requests. */ @@ -548,14 +549,10 @@ USBDevice *usb_msd_init(const char *filename) } 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; @@ -576,3 +573,10 @@ USBDevice *usb_msd_init(const char *filename) qemu_free(s); return NULL; } + +BlockDriverState *usb_msd_get_bdrv(USBDevice *dev) +{ + MSDState *s = (MSDState *)dev; + + return s->bs; +} diff --git a/hw/usb-ohci.c b/hw/usb-ohci.c index 55cb77b..c575480 100644 --- a/hw/usb-ohci.c +++ b/hw/usb-ohci.c @@ -16,7 +16,7 @@ * * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA * * TODO: * o Isochronous transfers @@ -32,6 +32,7 @@ #include "usb.h" #include "pci.h" #include "pxa.h" +#include "devices.h" //#define DEBUG_OHCI /* Dump packet contents. */ @@ -60,13 +61,13 @@ typedef struct OHCIPort { enum ohci_type { OHCI_TYPE_PCI, - OHCI_TYPE_PXA + OHCI_TYPE_PXA, + OHCI_TYPE_SM501, }; typedef struct { qemu_irq irq; enum ohci_type type; - target_phys_addr_t mem_base; int mem; int num_ports; const char *name; @@ -109,6 +110,9 @@ typedef struct { uint32_t hreset; uint32_t htest; + /* SM501 local memory offset */ + target_phys_addr_t localmem_base; + /* Active packets. */ uint32_t old_ctl; USBPacket usb_packet; @@ -426,10 +430,13 @@ static void ohci_reset(void *opaque) } /* Get an array of dwords from main memory */ -static inline int get_dwords(uint32_t addr, uint32_t *buf, int num) +static inline int get_dwords(OHCIState *ohci, + uint32_t addr, uint32_t *buf, int num) { int i; + addr += ohci->localmem_base; + 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); @@ -439,10 +446,13 @@ static inline int get_dwords(uint32_t addr, uint32_t *buf, int num) } /* Put an array of dwords in to main memory */ -static inline int put_dwords(uint32_t addr, uint32_t *buf, int num) +static inline int put_dwords(OHCIState *ohci, + uint32_t addr, uint32_t *buf, int num) { int i; + addr += ohci->localmem_base; + 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); @@ -452,10 +462,13 @@ static inline int put_dwords(uint32_t addr, uint32_t *buf, int num) } /* Get an array of words from main memory */ -static inline int get_words(uint32_t addr, uint16_t *buf, int num) +static inline int get_words(OHCIState *ohci, + uint32_t addr, uint16_t *buf, int num) { int i; + addr += ohci->localmem_base; + 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); @@ -465,10 +478,13 @@ static inline int get_words(uint32_t addr, uint16_t *buf, int num) } /* Put an array of words in to main memory */ -static inline int put_words(uint32_t addr, uint16_t *buf, int num) +static inline int put_words(OHCIState *ohci, + uint32_t addr, uint16_t *buf, int num) { int i; + addr += ohci->localmem_base; + 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); @@ -477,40 +493,63 @@ static inline int put_words(uint32_t addr, uint16_t *buf, int num) return 1; } -static inline int ohci_read_ed(uint32_t addr, struct ohci_ed *ed) +static inline int ohci_read_ed(OHCIState *ohci, + uint32_t addr, struct ohci_ed *ed) +{ + return get_dwords(ohci, addr, (uint32_t *)ed, sizeof(*ed) >> 2); +} + +static inline int ohci_read_td(OHCIState *ohci, + uint32_t addr, struct ohci_td *td) +{ + return get_dwords(ohci, addr, (uint32_t *)td, sizeof(*td) >> 2); +} + +static inline int ohci_read_iso_td(OHCIState *ohci, + uint32_t addr, struct ohci_iso_td *td) { - return get_dwords(addr, (uint32_t *)ed, sizeof(*ed) >> 2); + return (get_dwords(ohci, addr, (uint32_t *)td, 4) && + get_words(ohci, addr + 16, td->offset, 8)); } -static inline int ohci_read_td(uint32_t addr, struct ohci_td *td) +static inline int ohci_read_hcca(OHCIState *ohci, + uint32_t addr, struct ohci_hcca *hcca) { - return get_dwords(addr, (uint32_t *)td, sizeof(*td) >> 2); + cpu_physical_memory_rw(addr + ohci->localmem_base, + (uint8_t *)hcca, sizeof(*hcca), 0); + return 1; } -static inline int ohci_read_iso_td(uint32_t addr, struct ohci_iso_td *td) +static inline int ohci_put_ed(OHCIState *ohci, + uint32_t addr, struct ohci_ed *ed) { - return (get_dwords(addr, (uint32_t *)td, 4) && - get_words(addr + 16, td->offset, 8)); + return put_dwords(ohci, addr, (uint32_t *)ed, sizeof(*ed) >> 2); } -static inline int ohci_put_ed(uint32_t addr, struct ohci_ed *ed) +static inline int ohci_put_td(OHCIState *ohci, + uint32_t addr, struct ohci_td *td) { - return put_dwords(addr, (uint32_t *)ed, sizeof(*ed) >> 2); + return put_dwords(ohci, addr, (uint32_t *)td, sizeof(*td) >> 2); } -static inline int ohci_put_td(uint32_t addr, struct ohci_td *td) +static inline int ohci_put_iso_td(OHCIState *ohci, + uint32_t addr, struct ohci_iso_td *td) { - return put_dwords(addr, (uint32_t *)td, sizeof(*td) >> 2); + return (put_dwords(ohci, addr, (uint32_t *)td, 4) && + put_words(ohci, addr + 16, td->offset, 8)); } -static inline int ohci_put_iso_td(uint32_t addr, struct ohci_iso_td *td) +static inline int ohci_put_hcca(OHCIState *ohci, + uint32_t addr, struct ohci_hcca *hcca) { - return (put_dwords(addr, (uint32_t *)td, 4) && - put_words(addr + 16, td->offset, 8)); + cpu_physical_memory_rw(addr + ohci->localmem_base, + (uint8_t *)hcca, sizeof(*hcca), 1); + return 1; } /* 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) +static void ohci_copy_td(OHCIState *ohci, struct ohci_td *td, + uint8_t *buf, int len, int write) { uint32_t ptr; uint32_t n; @@ -519,16 +558,17 @@ static void ohci_copy_td(struct ohci_td *td, uint8_t *buf, int len, int write) n = 0x1000 - (ptr & 0xfff); if (n > len) n = len; - cpu_physical_memory_rw(ptr, buf, n, write); + cpu_physical_memory_rw(ptr + ohci->localmem_base, buf, n, write); if (n == len) return; ptr = td->be & ~0xfffu; buf += n; - cpu_physical_memory_rw(ptr, buf, len - n, write); + cpu_physical_memory_rw(ptr + ohci->localmem_base, 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, +static void ohci_copy_iso_td(OHCIState *ohci, + uint32_t start_addr, uint32_t end_addr, uint8_t *buf, int len, int write) { uint32_t ptr; @@ -538,12 +578,12 @@ static void ohci_copy_iso_td(uint32_t start_addr, uint32_t end_addr, n = 0x1000 - (ptr & 0xfff); if (n > len) n = len; - cpu_physical_memory_rw(ptr, buf, n, write); + cpu_physical_memory_rw(ptr + ohci->localmem_base, buf, n, write); if (n == len) return; ptr = end_addr & ~0xfffu; buf += n; - cpu_physical_memory_rw(ptr, buf, len - n, write); + cpu_physical_memory_rw(ptr + ohci->localmem_base, buf, len - n, write); } static void ohci_process_lists(OHCIState *ohci, int completion); @@ -580,7 +620,7 @@ static int ohci_service_iso_td(OHCIState *ohci, struct ohci_ed *ed, addr = ed->head & OHCI_DPTR_MASK; - if (!ohci_read_iso_td(addr, &iso_td)) { + if (!ohci_read_iso_td(ohci, addr, &iso_td)) { printf("usb-ohci: ISO_TD read error at %x\n", addr); return 0; } @@ -622,7 +662,7 @@ static int ohci_service_iso_td(OHCIState *ohci, struct ohci_ed *ed, i = OHCI_BM(iso_td.flags, TD_DI); if (i < ohci->done_count) ohci->done_count = i; - ohci_put_iso_td(addr, &iso_td); + ohci_put_iso_td(ohci, addr, &iso_td); return 0; } @@ -697,7 +737,7 @@ static int ohci_service_iso_td(OHCIState *ohci, struct ohci_ed *ed, } if (len && dir != OHCI_TD_DIR_IN) { - ohci_copy_iso_td(start_addr, end_addr, ohci->usb_buf, len, 0); + ohci_copy_iso_td(ohci, start_addr, end_addr, ohci->usb_buf, len, 0); } if (completion) { @@ -733,7 +773,7 @@ static int ohci_service_iso_td(OHCIState *ohci, struct ohci_ed *ed, /* 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_copy_iso_td(ohci, 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); @@ -789,7 +829,7 @@ static int ohci_service_iso_td(OHCIState *ohci, struct ohci_ed *ed, if (i < ohci->done_count) ohci->done_count = i; } - ohci_put_iso_td(addr, &iso_td); + ohci_put_iso_td(ohci, addr, &iso_td); return 1; } @@ -819,7 +859,7 @@ static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed) #endif return 1; } - if (!ohci_read_td(addr, &td)) { + if (!ohci_read_td(ohci, addr, &td)) { fprintf(stderr, "usb-ohci: TD read error at %x\n", addr); return 0; } @@ -860,7 +900,7 @@ static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed) } if (len && dir != OHCI_TD_DIR_IN && !completion) { - ohci_copy_td(&td, ohci->usb_buf, len, 0); + ohci_copy_td(ohci, &td, ohci->usb_buf, len, 0); } } @@ -919,7 +959,7 @@ static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed) } if (ret >= 0) { if (dir == OHCI_TD_DIR_IN) { - ohci_copy_td(&td, ohci->usb_buf, ret, 1); + ohci_copy_td(ohci, &td, ohci->usb_buf, ret, 1); #ifdef DEBUG_PACKET dprintf(" data:"); for (i = 0; i < ret; i++) @@ -988,7 +1028,7 @@ static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed) i = OHCI_BM(td.flags, TD_DI); if (i < ohci->done_count) ohci->done_count = i; - ohci_put_td(addr, &td); + ohci_put_td(ohci, addr, &td); return OHCI_BM(td.flags, TD_CC) != OHCI_CC_NOERROR; } @@ -1006,7 +1046,7 @@ static int ohci_service_ed_list(OHCIState *ohci, uint32_t head, int completion) return 0; for (cur = head; cur; cur = next_ed) { - if (!ohci_read_ed(cur, &ed)) { + if (!ohci_read_ed(ohci, cur, &ed)) { fprintf(stderr, "usb-ohci: ED read error at %x\n", cur); return 0; } @@ -1047,7 +1087,7 @@ static int ohci_service_ed_list(OHCIState *ohci, uint32_t head, int completion) } } - ohci_put_ed(cur, &ed); + ohci_put_ed(ohci, cur, &ed); } return active; @@ -1088,7 +1128,7 @@ 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); + ohci_read_hcca(ohci, ohci->hcca, &hcca); /* Process all the lists at the end of the frame */ if (ohci->ctl & OHCI_CTL_PLE) { @@ -1132,7 +1172,7 @@ static void ohci_frame_boundary(void *opaque) ohci_sof(ohci); /* Writeback HCCA */ - cpu_physical_memory_rw(ohci->hcca, (uint8_t *)&hcca, sizeof(hcca), 1); + ohci_put_hcca(ohci, ohci->hcca, &hcca); } /* Start sending SOF tokens across the USB bus, lists are processed in @@ -1361,106 +1401,134 @@ static void ohci_port_set_status(OHCIState *ohci, int portnum, uint32_t val) static uint32_t ohci_mem_read(void *ptr, target_phys_addr_t addr) { OHCIState *ohci = ptr; - - addr -= ohci->mem_base; + uint32_t retval; /* 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) { + } else if (addr >= 0x54 && addr < 0x54 + ohci->num_ports * 4) { /* HcRhPortStatus */ - return ohci->rhport[(addr - 0x54) >> 2].ctrl | OHCI_PORT_PPS; + retval = ohci->rhport[(addr - 0x54) >> 2].ctrl | OHCI_PORT_PPS; + } else { + switch (addr >> 2) { + case 0: /* HcRevision */ + retval = 0x10; + break; + + case 1: /* HcControl */ + retval = ohci->ctl; + break; + + case 2: /* HcCommandStatus */ + retval = ohci->status; + break; + + case 3: /* HcInterruptStatus */ + retval = ohci->intr_status; + break; + + case 4: /* HcInterruptEnable */ + case 5: /* HcInterruptDisable */ + retval = ohci->intr; + break; + + case 6: /* HcHCCA */ + retval = ohci->hcca; + break; + + case 7: /* HcPeriodCurrentED */ + retval = ohci->per_cur; + break; + + case 8: /* HcControlHeadED */ + retval = ohci->ctrl_head; + break; + + case 9: /* HcControlCurrentED */ + retval = ohci->ctrl_cur; + break; + + case 10: /* HcBulkHeadED */ + retval = ohci->bulk_head; + break; + + case 11: /* HcBulkCurrentED */ + retval = ohci->bulk_cur; + break; + + case 12: /* HcDoneHead */ + retval = ohci->done; + break; + + case 13: /* HcFmInterretval */ + retval = (ohci->fit << 31) | (ohci->fsmps << 16) | (ohci->fi); + break; + + case 14: /* HcFmRemaining */ + retval = ohci_get_frame_remaining(ohci); + break; + + case 15: /* HcFmNumber */ + retval = ohci->frame_number; + break; + + case 16: /* HcPeriodicStart */ + retval = ohci->pstart; + break; + + case 17: /* HcLSThreshold */ + retval = ohci->lst; + break; + + case 18: /* HcRhDescriptorA */ + retval = ohci->rhdesc_a; + break; + + case 19: /* HcRhDescriptorB */ + retval = ohci->rhdesc_b; + break; + + case 20: /* HcRhStatus */ + retval = ohci->rhstatus; + break; + + /* PXA27x specific registers */ + case 24: /* HcStatus */ + retval = ohci->hstatus & ohci->hmask; + break; + + case 25: /* HcHReset */ + retval = ohci->hreset; + break; + + case 26: /* HcHInterruptEnable */ + retval = ohci->hmask; + break; + + case 27: /* HcHInterruptTest */ + retval = ohci->htest; + break; + + default: + fprintf(stderr, "ohci_read: Bad offset %x\n", (int)addr); + retval = 0xffffffff; + } } - 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; - } +#ifdef TARGET_WORDS_BIGENDIAN + retval = bswap32(retval); +#endif + return retval; } static void ohci_mem_write(void *ptr, target_phys_addr_t addr, uint32_t val) { OHCIState *ohci = ptr; - addr -= ohci->mem_base; +#ifdef TARGET_WORDS_BIGENDIAN + val = bswap32(val); +#endif /* Only aligned reads are allowed on OHCI */ if (addr & 3) { @@ -1593,7 +1661,8 @@ static CPUWriteMemoryFunc *ohci_writefn[3]={ }; static void usb_ohci_init(OHCIState *ohci, int num_ports, int devfn, - qemu_irq irq, enum ohci_type type, const char *name) + qemu_irq irq, enum ohci_type type, + const char *name, uint32_t localmem_base) { int i; @@ -1613,7 +1682,8 @@ static void usb_ohci_init(OHCIState *ohci, int num_ports, int devfn, usb_frame_time, usb_bit_time); } - ohci->mem = cpu_register_io_memory(0, ohci_readfn, ohci_writefn, ohci); + ohci->mem = cpu_register_io_memory(ohci_readfn, ohci_writefn, ohci); + ohci->localmem_base = localmem_base; ohci->name = name; ohci->irq = irq; @@ -1625,7 +1695,7 @@ static void usb_ohci_init(OHCIState *ohci, int num_ports, int devfn, } ohci->async_td = 0; - qemu_register_reset(ohci_reset, ohci); + qemu_register_reset(ohci_reset, 0, ohci); ohci_reset(ohci); } @@ -1638,15 +1708,12 @@ 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); @@ -1655,19 +1722,17 @@ void usb_ohci_init_pci(struct PCIBus *bus, int num_ports, int devfn) 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; + pci_config_set_vendor_id(ohci->pci_dev.config, PCI_VENDOR_ID_APPLE); + pci_config_set_device_id(ohci->pci_dev.config, + PCI_DEVICE_ID_APPLE_IPID_USB); ohci->pci_dev.config[0x09] = 0x10; /* OHCI */ - ohci->pci_dev.config[0x0a] = 0x3; - ohci->pci_dev.config[0x0b] = 0xc; + pci_config_set_class(ohci->pci_dev.config, PCI_CLASS_SERIAL_USB); 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); + OHCI_TYPE_PCI, ohci->pci_dev.name, 0); - pci_register_io_region((struct PCIDevice *)ohci, 0, 256, + pci_register_bar((struct PCIDevice *)ohci, 0, 256, PCI_ADDRESS_SPACE_MEM, ohci_mapfunc); } @@ -1677,8 +1742,19 @@ void usb_ohci_init_pxa(target_phys_addr_t base, int num_ports, int devfn, OHCIState *ohci = (OHCIState *)qemu_mallocz(sizeof(OHCIState)); usb_ohci_init(ohci, num_ports, devfn, irq, - OHCI_TYPE_PXA, "OHCI USB"); - ohci->mem_base = base; + OHCI_TYPE_PXA, "OHCI USB", 0); - cpu_register_physical_memory(ohci->mem_base, 0x1000, ohci->mem); + cpu_register_physical_memory(base, 0x1000, ohci->mem); } + +void usb_ohci_init_sm501(uint32_t mmio_base, uint32_t localmem_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_SM501, "OHCI USB", localmem_base); + + cpu_register_physical_memory(mmio_base, 0x1000, ohci->mem); +} + @@ -21,6 +21,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + +#ifndef _HW_USB_H +#define _HW_USB_H + +#include "block.h" + #define USB_TOKEN_SETUP 0x2d #define USB_TOKEN_IN 0x69 /* device -> host */ #define USB_TOKEN_OUT 0xe1 /* host -> device */ @@ -241,19 +247,24 @@ 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); +void usb_host_info(Monitor *mon); /* usb-hid.c */ USBDevice *usb_mouse_init(void); USBDevice *usb_tablet_init(void); USBDevice *usb_keyboard_init(void); +void usb_hid_datain_cb(USBDevice *dev, void *opaque, void (*datain)(void *)); /* usb-msd.c */ USBDevice *usb_msd_init(const char *filename); +BlockDriverState *usb_msd_get_bdrv(USBDevice *dev); /* usb-net.c */ USBDevice *usb_net_init(NICInfo *nd); +/* usb-bt.c */ +USBDevice *usb_bt_init(HCIInfo *hci); + /* usb-wacom.c */ USBDevice *usb_wacom_init(void); @@ -284,8 +295,10 @@ enum musb_irq_source_e { __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); +typedef struct MUSBState MUSBState; +MUSBState *musb_init(qemu_irq *irqs); +uint32_t musb_core_intr_get(MUSBState *s); +void musb_core_intr_clear(MUSBState *s, uint32_t mask); +void musb_set_size(MUSBState *s, int epnum, int size, int is_tx); + +#endif /* _HW_USB_H */ diff --git a/hw/watchdog.c b/hw/watchdog.c new file mode 100644 index 0000000..9a28621 --- /dev/null +++ b/hw/watchdog.c @@ -0,0 +1,138 @@ +/* + * Virtual hardware watchdog. + * + * Copyright (C) 2009 Red Hat Inc. + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * By Richard W.M. Jones (rjones@redhat.com). + */ + +#include "qemu-common.h" +#include "sys-queue.h" +#include "sysemu.h" +#include "hw/watchdog.h" + +static LIST_HEAD(watchdog_list, WatchdogTimerModel) watchdog_list; + +void watchdog_add_model(WatchdogTimerModel *model) +{ + LIST_INSERT_HEAD(&watchdog_list, model, entry); +} + +/* Returns: + * 0 = continue + * 1 = exit program with error + * 2 = exit program without error + */ +int select_watchdog(const char *p) +{ + WatchdogTimerModel *model; + + if (watchdog) { + fprintf(stderr, + "qemu: only one watchdog option may be given\n"); + return 1; + } + + /* -watchdog ? lists available devices and exits cleanly. */ + if (strcmp(p, "?") == 0) { + LIST_FOREACH(model, &watchdog_list, entry) { + fprintf(stderr, "\t%s\t%s\n", + model->wdt_name, model->wdt_description); + } + return 2; + } + + LIST_FOREACH(model, &watchdog_list, entry) { + if (strcasecmp(model->wdt_name, p) == 0) { + watchdog = model; + return 0; + } + } + + fprintf(stderr, "Unknown -watchdog device. Supported devices are:\n"); + LIST_FOREACH(model, &watchdog_list, entry) { + fprintf(stderr, "\t%s\t%s\n", + model->wdt_name, model->wdt_description); + } + return 1; +} + +int select_watchdog_action(const char *p) +{ + if (strcasecmp(p, "reset") == 0) + watchdog_action = WDT_RESET; + else if (strcasecmp(p, "shutdown") == 0) + watchdog_action = WDT_SHUTDOWN; + else if (strcasecmp(p, "poweroff") == 0) + watchdog_action = WDT_POWEROFF; + else if (strcasecmp(p, "pause") == 0) + watchdog_action = WDT_PAUSE; + else if (strcasecmp(p, "debug") == 0) + watchdog_action = WDT_DEBUG; + else if (strcasecmp(p, "none") == 0) + watchdog_action = WDT_NONE; + else + return -1; + + return 0; +} + +/* This actually performs the "action" once a watchdog has expired, + * ie. reboot, shutdown, exit, etc. + */ +void watchdog_perform_action(void) +{ + switch(watchdog_action) { + case WDT_RESET: /* same as 'system_reset' in monitor */ + qemu_system_reset_request(); + break; + + case WDT_SHUTDOWN: /* same as 'system_powerdown' in monitor */ + qemu_system_powerdown_request(); + break; + + case WDT_POWEROFF: /* same as 'quit' command in monitor */ + exit(0); + break; + + case WDT_PAUSE: /* same as 'stop' command in monitor */ + vm_stop(0); + break; + + case WDT_DEBUG: + fprintf(stderr, "watchdog: timer fired\n"); + break; + + case WDT_NONE: + break; + } +} + +void watchdog_pc_init(PCIBus *pci_bus) +{ + if (watchdog) + watchdog->wdt_pc_init(pci_bus); +} + +void register_watchdogs(void) +{ +#if 0 + wdt_ib700_init(); + wdt_i6300esb_init(); +#endif +} diff --git a/hw/watchdog.h b/hw/watchdog.h new file mode 100644 index 0000000..c2b2b36 --- /dev/null +++ b/hw/watchdog.h @@ -0,0 +1,65 @@ +/* + * Virtual hardware watchdog. + * + * Copyright (C) 2009 Red Hat Inc. + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * By Richard W.M. Jones (rjones@redhat.com). + */ + +#ifndef QEMU_WATCHDOG_H +#define QEMU_WATCHDOG_H + +extern void wdt_i6300esb_init(void); +extern void wdt_ib700_init(void); + +/* Possible values for action parameter. */ +#define WDT_RESET 1 /* Hard reset. */ +#define WDT_SHUTDOWN 2 /* Shutdown. */ +#define WDT_POWEROFF 3 /* Quit. */ +#define WDT_PAUSE 4 /* Pause. */ +#define WDT_DEBUG 5 /* Prints a message and continues running. */ +#define WDT_NONE 6 /* Do nothing. */ + +struct WatchdogTimerModel { + LIST_ENTRY(WatchdogTimerModel) entry; + + /* Short name of the device - used to select it on the command line. */ + const char *wdt_name; + /* Longer description (eg. manufacturer and full model number). */ + const char *wdt_description; + + /* This callback should create/register the device. It is called + * indirectly from hw/pc.c when the virtual PC is being set up. + */ + void (*wdt_pc_init)(PCIBus *pci_bus); +}; +typedef struct WatchdogTimerModel WatchdogTimerModel; + +/* in vl.c */ +extern WatchdogTimerModel *watchdog; +extern int watchdog_action; + +/* in hw/watchdog.c */ +extern int select_watchdog(const char *p); +extern int select_watchdog_action(const char *action); +extern void watchdog_add_model(WatchdogTimerModel *model); +extern void watchdog_perform_action(void); +extern void watchdog_pc_init(PCIBus *pci_bus); +extern void register_watchdogs(void); + +#endif /* QEMU_WATCHDOG_H */ diff --git a/hw/xen.h b/hw/xen.h new file mode 100644 index 0000000..780dcf7 --- /dev/null +++ b/hw/xen.h @@ -0,0 +1,21 @@ +#ifndef QEMU_HW_XEN_H +#define QEMU_HW_XEN_H 1 +/* + * public xen header + * stuff needed outside xen-*.c, i.e. interfaces to qemu. + * must not depend on any xen headers being present in + * /usr/include/xen, so it can be included unconditionally. + */ +#include <inttypes.h> + +/* xen-machine.c */ +enum xen_mode { + XEN_EMULATE = 0, // xen emulation, using xenner (default) + XEN_CREATE, // create xen domain + XEN_ATTACH // attach to xen domain created by xend +}; + +extern uint32_t xen_domid; +extern enum xen_mode xen_mode; + +#endif /* QEMU_HW_XEN_H */ |