0,0 → 1,425 |
/* |
* Copyright (c) 2007 Michal Kebrt, Petr Stepan |
* All rights reserved. |
* |
* Redistribution and use in source and binary forms, with or without |
* modification, are permitted provided that the following conditions |
* are met: |
* |
* - Redistributions of source code must retain the above copyright |
* notice, this list of conditions and the following disclaimer. |
* - Redistributions in binary form must reproduce the above copyright |
* notice, this list of conditions and the following disclaimer in the |
* documentation and/or other materials provided with the distribution. |
* - The name of the author may not be used to endorse or promote products |
* derived from this software without specific prior written permission. |
* |
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
*/ |
|
/** @addtogroup arm32qemu_icp |
* @{ |
*/ |
/** @file |
* @brief QEMU icp drivers. |
*/ |
|
#include <interrupt.h> |
#include <ipc/irq.h> |
#include <console/chardev.h> |
#include <arch/drivers/qemu.h> |
#include <console/console.h> |
#include <sysinfo/sysinfo.h> |
#include <print.h> |
#include <ddi/device.h> |
#include <mm/page.h> |
#include <arch/machine.h> |
#include <arch/debug/print.h> |
|
/* Addresses of devices. */ |
#define QEMU_ICP_VIDEORAM 0x16000000 |
#define QEMU_ICP_KBD 0x18000000 |
#define QEMU_ICP_HALT_OFFSET 0x10 |
#define QEMU_ICP_RTC 0x13000000 |
#define QEMU_ICP_RTC_FREQ_OFFSET 0x100 |
#define QEMU_ICP_RTC_ACK_OFFSET 0x110 |
#define QEMU_ICP_IRQC 0x14000000 |
#define QEMU_ICP_IRQC_MASK_OFFSET 0x8 |
#define QEMU_ICP_IRQC_UNMASK_OFFSET 0xC |
#define QEMU_ICP_MP 0x11000000 |
#define QEMU_ICP_MP_MEMSIZE_OFFSET 0x0090 |
#define QEMU_ICP_FB 0x94000 |
|
#define ICP_VGA 0xC0000000 |
#define ICP_CMCR 0x10000000 |
|
/* IRQs */ |
#define QEMU_ICP_KBD_IRQ 3 |
#define QEMU_ICP_TIMER_IRQ 5 |
|
static qemu_icp_hw_map_t qemu_icp_hw_map; |
static chardev_t console; |
static irq_t qemu_icp_console_irq; |
static irq_t qemu_icp_timer_irq; |
|
static bool hw_map_init_called = false; |
static bool vga_init = false; |
|
static void qemu_icp_kbd_enable(chardev_t *dev); |
static void qemu_icp_kbd_disable(chardev_t *dev); |
static void qemu_icp_write(chardev_t *dev, const char ch); |
static char qemu_icp_do_read(chardev_t *dev); |
void icp_vga_init(void); |
|
static chardev_operations_t qemu_icp_ops = { |
.resume = qemu_icp_kbd_enable, |
.suspend = qemu_icp_kbd_disable, |
.write = qemu_icp_write, |
.read = qemu_icp_do_read, |
}; |
|
/** Initializes the vga |
* |
*/ |
void icp_vga_init(void) |
{ |
*(uint32_t*)((char *)(qemu_icp_hw_map.cmcr)+0x14) = 0xA05F0000; |
*(uint32_t*)((char *)(qemu_icp_hw_map.cmcr)+0x1C) = 0x12C11000; |
*(uint32_t*)qemu_icp_hw_map.vga = 0x3F1F3F9C; |
*(uint32_t*)((char *)(qemu_icp_hw_map.vga) + 0x4) = 0x080B61DF; |
*(uint32_t*)((char *)(qemu_icp_hw_map.vga) + 0x8) = 0x067F3800; |
*(uint32_t*)((char *)(qemu_icp_hw_map.vga) + 0x10) = QEMU_ICP_FB; |
*(uint32_t *)((char *)(qemu_icp_hw_map.vga) + 0x1C) = 0x182B; |
*(uint32_t*)((char *)(qemu_icp_hw_map.cmcr)+0xC) = 0x33805000; |
|
} |
|
/** Returns the mask of active interrupts. */ |
static inline uint32_t qemu_icp_irqc_get_sources(void) |
{ |
return *((uint32_t *) qemu_icp_hw_map.irqc); |
} |
|
|
/** Masks interrupt. |
* |
* @param irq interrupt number |
*/ |
static inline void qemu_icp_irqc_mask(uint32_t irq) |
{ |
*((uint32_t *) qemu_icp_hw_map.irqc_mask) = irq; |
} |
|
|
/** Unmasks interrupt. |
* |
* @param irq interrupt number |
*/ |
static inline void qemu_icp_irqc_unmask(uint32_t irq) |
{ |
*((uint32_t *) qemu_icp_hw_map.irqc_unmask) = irq; |
} |
|
|
/** Initializes #qemu_icp_hw_map. */ |
void qemu_icp_hw_map_init(void) |
{ |
qemu_icp_hw_map.videoram = hw_map(QEMU_ICP_VIDEORAM, PAGE_SIZE); |
qemu_icp_hw_map.kbd = hw_map(QEMU_ICP_KBD, PAGE_SIZE); |
qemu_icp_hw_map.rtc = hw_map(QEMU_ICP_RTC, PAGE_SIZE); |
qemu_icp_hw_map.irqc = hw_map(QEMU_ICP_IRQC, PAGE_SIZE); |
|
qemu_icp_hw_map.rtc_freq = qemu_icp_hw_map.rtc + QEMU_ICP_RTC_FREQ_OFFSET; |
qemu_icp_hw_map.rtc_ack = qemu_icp_hw_map.rtc + QEMU_ICP_RTC_ACK_OFFSET; |
qemu_icp_hw_map.irqc_mask = qemu_icp_hw_map.irqc + QEMU_ICP_IRQC_MASK_OFFSET; |
qemu_icp_hw_map.irqc_unmask = qemu_icp_hw_map.irqc + |
QEMU_ICP_IRQC_UNMASK_OFFSET; |
qemu_icp_hw_map.cmcr = hw_map(ICP_CMCR, PAGE_SIZE); |
qemu_icp_hw_map.vga = hw_map(ICP_VGA, PAGE_SIZE); |
|
//icp_vga_init(); |
|
hw_map_init_called = true; |
} |
|
|
/** Putchar that works with qemu_icp. |
* |
* @param dev Not used. |
* @param ch Characted to be printed. |
*/ |
static void qemu_icp_write(chardev_t *dev, const char ch) |
{ |
*((char *) qemu_icp_hw_map.videoram) = ch; |
} |
|
/** Enables qemu_icp keyboard (interrupt unmasked). |
* |
* @param dev Not used. |
* |
* Called from getc(). |
*/ |
static void qemu_icp_kbd_enable(chardev_t *dev) |
{ |
qemu_icp_irqc_unmask(QEMU_ICP_KBD_IRQ); |
} |
|
/** Disables qemu_icp keyboard (interrupt masked). |
* |
* @param dev not used |
* |
* Called from getc(). |
*/ |
static void qemu_icp_kbd_disable(chardev_t *dev) |
{ |
qemu_icp_irqc_mask(QEMU_ICP_KBD_IRQ); |
} |
|
/** Read character using polling, assume interrupts disabled. |
* |
* @param dev Not used. |
*/ |
static char qemu_icp_do_read(chardev_t *dev) |
{ |
char ch; |
|
while (1) { |
ch = *((volatile char *) qemu_icp_hw_map.kbd); |
if (ch) { |
if (ch == '\r') |
return '\n'; |
if (ch == 0x7f) |
return '\b'; |
return ch; |
} |
} |
} |
|
/** Process keyboard interrupt. |
* |
* @param irq IRQ information. |
* @param arg Not used. |
*/ |
static void qemu_icp_irq_handler(irq_t *irq, void *arg, ...) |
{ |
if ((irq->notif_cfg.notify) && (irq->notif_cfg.answerbox)) { |
ipc_irq_send_notif(irq); |
} else { |
char ch = 0; |
|
ch = *((char *) qemu_icp_hw_map.kbd); |
if (ch == '\r') { |
ch = '\n'; |
} |
if (ch == 0x7f) { |
ch = '\b'; |
} |
chardev_push_character(&console, ch); |
} |
} |
|
static irq_ownership_t qemu_icp_claim(void) |
{ |
return IRQ_ACCEPT; |
} |
|
|
/** Acquire console back for kernel. */ |
void qemu_icp_grab_console(void) |
{ |
ipl_t ipl = interrupts_disable(); |
spinlock_lock(&qemu_icp_console_irq.lock); |
qemu_icp_console_irq.notif_cfg.notify = false; |
spinlock_unlock(&qemu_icp_console_irq.lock); |
interrupts_restore(ipl); |
} |
|
/** Return console to userspace. */ |
void qemu_icp_release_console(void) |
{ |
ipl_t ipl = interrupts_disable(); |
spinlock_lock(&qemu_icp_console_irq.lock); |
if (qemu_icp_console_irq.notif_cfg.answerbox) { |
qemu_icp_console_irq.notif_cfg.notify = true; |
} |
spinlock_unlock(&qemu_icp_console_irq.lock); |
interrupts_restore(ipl); |
} |
|
/** Initializes console object representing qemu_icp console. |
* |
* @param devno device number. |
*/ |
void qemu_icp_console_init(devno_t devno) |
{ |
chardev_initialize("qemu_icp_console", &console, &qemu_icp_ops); |
stdin = &console; |
stdout = &console; |
|
irq_initialize(&qemu_icp_console_irq); |
qemu_icp_console_irq.devno = devno; |
qemu_icp_console_irq.inr = QEMU_ICP_KBD_IRQ; |
qemu_icp_console_irq.claim = qemu_icp_claim; |
qemu_icp_console_irq.handler = qemu_icp_irq_handler; |
irq_register(&qemu_icp_console_irq); |
|
qemu_icp_irqc_unmask(QEMU_ICP_KBD_IRQ); |
|
sysinfo_set_item_val("kbd", NULL, true); |
sysinfo_set_item_val("kbd.devno", NULL, devno); |
sysinfo_set_item_val("kbd.inr", NULL, QEMU_ICP_KBD_IRQ); |
sysinfo_set_item_val("kbd.address.virtual", NULL, qemu_icp_hw_map.kbd); |
} |
|
/** Starts qemu_icp Real Time Clock device, which asserts regular interrupts. |
* |
* @param frequency Interrupts frequency (0 disables RTC). |
*/ |
static void qemu_icp_timer_start(uint32_t frequency) |
{ |
*((uint32_t*) qemu_icp_hw_map.rtc_freq) = frequency; |
} |
|
static irq_ownership_t qemu_icp_timer_claim(void) |
{ |
return IRQ_ACCEPT; |
} |
|
/** Timer interrupt handler. |
* |
* @param irq Interrupt information. |
* @param arg Not used. |
*/ |
static void qemu_icp_timer_irq_handler(irq_t *irq, void *arg, ...) |
{ |
/* |
* We are holding a lock which prevents preemption. |
* Release the lock, call clock() and reacquire the lock again. |
*/ |
spinlock_unlock(&irq->lock); |
clock(); |
spinlock_lock(&irq->lock); |
|
/* acknowledge tick */ |
*((uint32_t*) qemu_icp_hw_map.rtc_ack) = 0; |
} |
|
/** Initializes and registers timer interrupt handler. */ |
static void qemu_icp_timer_irq_init(void) |
{ |
irq_initialize(&qemu_icp_timer_irq); |
qemu_icp_timer_irq.devno = device_assign_devno(); |
qemu_icp_timer_irq.inr = QEMU_ICP_TIMER_IRQ; |
qemu_icp_timer_irq.claim = qemu_icp_timer_claim; |
qemu_icp_timer_irq.handler = qemu_icp_timer_irq_handler; |
|
irq_register(&qemu_icp_timer_irq); |
} |
|
|
/** Starts timer. |
* |
* Initiates regular timer interrupts after initializing |
* corresponding interrupt handler. |
*/ |
void qemu_icp_timer_irq_start(void) |
{ |
qemu_icp_timer_irq_init(); |
qemu_icp_timer_start(QEMU_ICP_TIMER_FREQ); |
} |
|
/** Returns the size of emulated memory. |
* |
* @return Size in bytes. |
*/ |
size_t qemu_icp_get_memory_size(void) |
{ |
//return *((int *) (QEMU_ICP_MP + QEMU_ICP_MP_MEMSIZE_OFFSET)); |
return 0x2000000; |
} |
|
/** Prints a character. |
* |
* @param ch Character to be printed. |
*/ |
void qemu_icp_debug_putc(char ch) |
{ |
char *addr = 0; |
if (!hw_map_init_called) { |
addr = (char *) QEMU_ICP_KBD; |
} else { |
addr = (char *) qemu_icp_hw_map.videoram; |
} |
|
if (ch == '\n') |
*(addr) = '\r'; |
*(addr) = ch; |
} |
|
/** Stops qemu_icp. */ |
void qemu_icp_cpu_halt(void) |
{ |
char * addr = 0; |
if (!hw_map_init_called) { |
addr = (char *) QEMU_ICP_KBD; |
} else { |
addr = (char *) qemu_icp_hw_map.videoram; |
} |
|
*(addr + QEMU_ICP_HALT_OFFSET) = '\0'; |
} |
|
/** Gxemul specific interrupt exception handler. |
* |
* Determines sources of the interrupt from interrupt controller and |
* calls high-level handlers for them. |
* |
* @param exc_no Interrupt exception number. |
* @param istate Saved processor state. |
*/ |
void qemu_icp_irq_exception(int exc_no, istate_t *istate) |
{ |
uint32_t sources = qemu_icp_irqc_get_sources(); |
int i; |
|
for (i = 0; i < QEMU_ICP_IRQC_MAX_IRQ; i++) { |
if (sources & (1 << i)) { |
irq_t *irq = irq_dispatch_and_lock(i); |
if (irq) { |
/* The IRQ handler was found. */ |
irq->handler(irq, irq->arg); |
spinlock_unlock(&irq->lock); |
} else { |
/* Spurious interrupt.*/ |
dprintf("cpu%d: spurious interrupt (inum=%d)\n", |
CPU->id, i); |
} |
} |
} |
} |
|
/** Returns address of framebuffer device. |
* |
* @return Address of framebuffer device. |
*/ |
uintptr_t qemu_icp_get_fb_address(void) |
{ |
if (!vga_init) { |
icp_vga_init(); |
vga_init = true; |
} |
return (uintptr_t) QEMU_ICP_FB; |
} |
|
|
/** @} |
*/ |