aboutsummaryrefslogtreecommitdiff
path: root/sim/ppc/hw_opic.c
diff options
context:
space:
mode:
Diffstat (limited to 'sim/ppc/hw_opic.c')
-rw-r--r--sim/ppc/hw_opic.c1827
1 files changed, 1827 insertions, 0 deletions
diff --git a/sim/ppc/hw_opic.c b/sim/ppc/hw_opic.c
new file mode 100644
index 0000000..c314347
--- /dev/null
+++ b/sim/ppc/hw_opic.c
@@ -0,0 +1,1827 @@
+/* This file is part of the program psim.
+
+ Copyright (C) 1994-1996, Andrew Cagney <cagney@highland.com.au>
+
+ 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+ */
+
+
+#ifndef _HW_OPIC_C_
+#define _HW_OPIC_C_
+
+#include "device_table.h"
+
+#ifdef HAVE_STRING_H
+#include <string.h>
+#else
+#ifdef HAVE_STRINGS_H
+#include <strings.h>
+#endif
+#endif
+
+
+/* DEVICE
+
+
+ opic - Open Programmable Interrupt Controller (OpenPIC)
+
+
+ DESCRIPTION
+
+
+ This device implements the core of the OpenPIC interrupt controller
+ as described in the OpenPIC specification 1.2 and other related
+ documents.
+
+ The model includes:
+
+ o Up to 2048 external interrupt sources
+
+ o The four count down timers
+
+ o The four interprocessor multicast interrupts
+
+ o multiprocessor support
+
+ o Full tracing to assist help debugging
+
+ o Support for all variations of edge/level x high/low polarity.
+
+
+
+ PROPERTIES
+
+
+ reg = <address> <size> ... (required)
+
+ Determine where the device lives in the parents address space. The
+ first <<address>> <<size>> pair specifies the address of the
+ interrupt destination unit (which might contain an interrupt source
+ unit) while successive reg entries specify additional interrupt
+ source units.
+
+ Note that for an <<opic>> device attached to a <<pci>> bus, the
+ first <<reg>> entry may need to be ignored it will be the address
+ of the devices configuration registers.
+
+
+ interrupt-ranges = <int-number> <range> ... (required)
+
+ A list of pairs. Each pair corresponds to a block of interrupt
+ source units (the address of which being specified by the
+ corresponding reg tupple). <<int-number>> is the number of the
+ first interrupt in the block while <<range>> is the number of
+ interrupts in the block.
+
+
+ timer-frequency = <integer> (optional)
+
+ If present, specifies the default value of the timer frequency
+ reporting register. By default a value of 1 HZ is used. The value
+ is arbitrary, the timers are always updated once per machine cycle.
+
+
+ vendor-identification = <integer> (optional)
+
+ If present, specifies the value to be returned when the vendor
+ identification register is read.
+
+
+ EXAMPLES
+
+
+ See the test suite directory:
+
+ | psim-test/hw-opic
+
+
+ BUGS
+
+ For an OPIC controller attached to a PCI bus, it is not clear what
+ the value of the <<reg>> and <<interrupt-ranges>> properties should
+ be. In particular, the PCI firmware bindings require the first
+ value of the <<reg>> property to specify the devices configuration
+ address while the OpenPIC bindings require that same entry to
+ specify the address of the Interrupt Delivery Unit. This
+ implementation checks for and, if present, ignores any
+ configuration address (and its corresponding <<interrupt-ranges>>
+ entry).
+
+ The OpenPIC specification requires the controller to be fair when
+ distributing interrupts between processors. At present the
+ algorithm used isn't fair. It is biased towards processor zero.
+
+ The OpenPIC specification includes a 8259 pass through mode. This
+ is not supported.
+
+
+ REFERENCES
+
+
+ PowerPC Multiprocessor Interrupt Controller (MPIC), January 19,
+ 1996. Available from IBM.
+
+
+ The Open Programmable Interrupt Controller (PIC) Register Interface
+ Specification Revision 1.2. Issue Date: Opctober 1995. Available
+ somewhere on AMD's web page (http://www.amd.com/)
+
+
+ PowerPC Microprocessor Common Hardware Reference Platform (CHRP)
+ System bindings to: IEEE Std 1275-1994 Standard for Boot
+ (Initialization, Configuration) Firmware. Revision 1.2b (INTERIM
+ DRAFT). April 22, 1996. Available on the Open Firmware web site
+ http://playground.sun.com/p1275/.
+
+
+ */
+
+
+/* forward types */
+
+typedef struct _hw_opic_device hw_opic_device;
+
+
+/* bounds */
+
+enum {
+ max_nr_interrupt_sources = 2048,
+ max_nr_interrupt_destinations = 32,
+ max_nr_task_priorities = 16,
+};
+
+
+enum {
+ opic_alignment = 16,
+};
+
+
+/* global configuration register */
+
+enum {
+ gcr0_8259_bit = 0x20000000,
+ gcr0_reset_bit = 0x80000000,
+};
+
+
+/* offsets and sizes */
+
+enum {
+ idu_isu_base = 0x10000,
+ sizeof_isu_register_block = 32,
+ idu_per_processor_register_base = 0x20000,
+ sizeof_idu_per_processor_register_block = 0x1000,
+ idu_timer_base = 0x01100,
+ sizeof_timer_register_block = 0x00040,
+};
+
+
+/* Interrupt sources */
+
+enum {
+ isu_mask_bit = 0x80000000,
+ isu_active_bit = 0x40000000,
+ isu_multicast_bit = 0x20000000,
+ isu_positive_polarity_bit = 0x00800000,
+ isu_level_triggered_bit = 0x00400000,
+ isu_priority_shift = 16,
+ isu_vector_bits = 0x000000ff,
+};
+
+
+typedef struct _opic_interrupt_source {
+ unsigned is_masked; /* left in place */
+ unsigned is_multicast; /* left in place */
+ unsigned is_positive_polarity; /* left in place */
+ unsigned is_level_triggered; /* left in place */
+ unsigned priority;
+ unsigned vector;
+ /* misc */
+ int nr;
+ unsigned destination;
+ unsigned pending;
+ unsigned in_service;
+} opic_interrupt_source;
+
+
+/* interrupt destinations (normally processors) */
+
+typedef struct _opic_interrupt_destination {
+ int nr;
+ unsigned base_priority;
+ opic_interrupt_source *current_pending;
+ opic_interrupt_source *current_in_service;
+ unsigned bit;
+ int init_port;
+ int intr_port;
+} opic_interrupt_destination;
+
+
+/* address map descriptors */
+
+typedef struct _opic_isu_block { /* interrupt source unit block */
+ int space;
+ unsigned_word address;
+ unsigned size;
+ unsigned_cell int_number;
+ unsigned_cell range;
+ int reg;
+} opic_isu_block;
+
+
+typedef struct _opic_idu { /* interrupt delivery unit */
+ int reg;
+ int space;
+ unsigned_word address;
+ unsigned size;
+} opic_idu;
+
+typedef enum {
+ /* bad */
+ invalid_opic_register,
+ /* interrupt source */
+ interrupt_source_N_destination_register,
+ interrupt_source_N_vector_priority_register,
+ /* timers */
+ timer_N_destination_register,
+ timer_N_vector_priority_register,
+ timer_N_base_count_register,
+ timer_N_current_count_register,
+ timer_frequency_reporting_register,
+ /* inter-processor interrupts */
+ ipi_N_vector_priority_register,
+ ipi_N_dispatch_register,
+ /* global configuration */
+ spurious_vector_register,
+ processor_init_register,
+ vendor_identification_register,
+ global_configuration_register_N,
+ feature_reporting_register_N,
+ /* per processor */
+ end_of_interrupt_register_N,
+ interrupt_acknowledge_register_N,
+ current_task_priority_register_N,
+} opic_register;
+
+static const char *
+opic_register_name(opic_register type)
+{
+ switch (type) {
+ case invalid_opic_register: return "invalid_opic_register";
+ case interrupt_source_N_destination_register: return "interrupt_source_N_destination_register";
+ case interrupt_source_N_vector_priority_register: return "interrupt_source_N_vector_priority_register";
+ case timer_N_destination_register: return "timer_N_destination_register";
+ case timer_N_vector_priority_register: return "timer_N_vector_priority_register";
+ case timer_N_base_count_register: return "timer_N_base_count_register";
+ case timer_N_current_count_register: return "timer_N_current_count_register";
+ case timer_frequency_reporting_register: return "timer_frequency_reporting_register";
+ case ipi_N_vector_priority_register: return "ipi_N_vector_priority_register";
+ case ipi_N_dispatch_register: return "ipi_N_dispatch_register";
+ case spurious_vector_register: return "spurious_vector_register";
+ case processor_init_register: return "processor_init_register";
+ case vendor_identification_register: return "vendor_identification_register";
+ case global_configuration_register_N: return "global_configuration_register_N";
+ case feature_reporting_register_N: return "feature_reporting_register_N";
+ case end_of_interrupt_register_N: return "end_of_interrupt_register_N";
+ case interrupt_acknowledge_register_N: return "interrupt_acknowledge_register_N";
+ case current_task_priority_register_N: return "current_task_priority_register_N";
+ }
+ return NULL;
+}
+
+
+
+/* timers */
+
+typedef struct _opic_timer {
+ int nr;
+ device *me; /* find my way home */
+ hw_opic_device *opic; /* ditto */
+ unsigned base_count;
+ int inhibited;
+ signed64 count; /* *ONLY* if inhibited */
+ event_entry_tag timeout_event;
+ opic_interrupt_source *interrupt_source;
+} opic_timer;
+
+
+/* the OPIC */
+
+struct _hw_opic_device {
+
+ /* vendor id */
+ unsigned vendor_identification;
+
+ /* interrupt destinations - processors */
+ int nr_interrupt_destinations;
+ opic_interrupt_destination *interrupt_destination;
+ unsigned sizeof_interrupt_destination;
+
+ /* bogus interrupts */
+ int spurious_vector;
+
+ /* interrupt sources - external interrupt source units + extra internal ones */
+ int nr_interrupt_sources;
+ opic_interrupt_source *interrupt_source;
+ unsigned sizeof_interrupt_source;
+
+ /* external interrupts */
+ int nr_external_interrupts;
+ opic_interrupt_source *external_interrupt_source;
+
+ /* inter-processor-interrupts */
+ int nr_interprocessor_interrupts;
+ opic_interrupt_source *interprocessor_interrupt_source;
+
+ /* timers */
+ int nr_timer_interrupts;
+ opic_timer *timer;
+ unsigned sizeof_timer;
+ opic_interrupt_source *timer_interrupt_source;
+ unsigned timer_frequency;
+
+ /* init register */
+ unsigned32 init;
+
+ /* address maps */
+ opic_idu idu;
+ int nr_isu_blocks;
+ opic_isu_block *isu_block;
+};
+
+
+static void
+hw_opic_init_data(device *me)
+{
+ hw_opic_device *opic = (hw_opic_device*)device_data(me);
+ int isb;
+ int idu_reg;
+ int nr_isu_blocks;
+ int i;
+
+ /* determine the first valid reg property entry (there could be
+ leading reg entries with invalid (zero) size fields) and the
+ number of isu entries found in the reg property. */
+ idu_reg = 0;
+ nr_isu_blocks = 0;
+ while (1) {
+ reg_property_spec unit;
+ int attach_space;
+ unsigned_word attach_address;
+ unsigned attach_size;
+ if (!device_find_reg_array_property(me, "reg", idu_reg + nr_isu_blocks,
+ &unit))
+ break;
+ if (nr_isu_blocks > 0
+ || (device_address_to_attach_address(device_parent(me), &unit.address,
+ &attach_space, &attach_address,
+ me)
+ && device_size_to_attach_size(device_parent(me), &unit.size,
+ &attach_size,
+ me))) {
+ /* we count any thing once we've found one valid address/size pair */
+ nr_isu_blocks += 1;
+ }
+ else {
+ idu_reg += 1;
+ }
+ }
+
+ /* determine the number and location of the multiple interrupt
+ source units and the single interrupt delivery unit */
+ if (opic->isu_block == NULL) {
+ int reg_nr;
+ opic->nr_isu_blocks = nr_isu_blocks;
+ opic->isu_block = zalloc(sizeof(opic_isu_block) * opic->nr_isu_blocks);
+ isb = 0;
+ reg_nr = idu_reg;
+ while (isb < opic->nr_isu_blocks) {
+ reg_property_spec reg;
+ if (!device_find_reg_array_property(me, "reg", reg_nr, &reg))
+ device_error(me, "reg property missing entry number %d", reg_nr);
+ opic->isu_block[isb].reg = reg_nr;
+ if (!device_address_to_attach_address(device_parent(me), &reg.address,
+ &opic->isu_block[isb].space,
+ &opic->isu_block[isb].address,
+ me)
+ || !device_size_to_attach_size(device_parent(me), &reg.size,
+ &opic->isu_block[isb].size,
+ me)) {
+ device_error(me, "reg property entry %d invalid", reg_nr);
+ }
+ if (!device_find_integer_array_property(me, "interrupt-ranges",
+ reg_nr * 2,
+ &opic->isu_block[isb].int_number)
+ || !device_find_integer_array_property(me, "interrupt-ranges",
+ reg_nr * 2 + 1,
+ &opic->isu_block[isb].range))
+ device_error(me, "missing or invalid interrupt-ranges property entry %d", reg_nr);
+ /* first reg entry specifies the address of both the IDU and the
+ first set of ISU registers, adjust things accordingly */
+ if (reg_nr == idu_reg) {
+ opic->idu.reg = opic->isu_block[isb].reg;
+ opic->idu.space = opic->isu_block[isb].space;
+ opic->idu.address = opic->isu_block[isb].address;
+ opic->idu.size = opic->isu_block[isb].size;
+ opic->isu_block[isb].address += idu_isu_base;
+ opic->isu_block[isb].size = opic->isu_block[isb].range * (16 + 16);
+ }
+ /* was this a valid reg entry? */
+ if (opic->isu_block[isb].range == 0) {
+ opic->nr_isu_blocks -= 1;
+ }
+ else {
+ opic->nr_external_interrupts += opic->isu_block[isb].range;
+ isb++;
+ }
+ reg_nr++;
+ }
+ }
+ DTRACE(opic, ("interrupt source unit block - effective number of blocks %d\n",
+ (int)opic->nr_isu_blocks));
+
+
+ /* the number of other interrupts */
+ opic->nr_interprocessor_interrupts = 4;
+ opic->nr_timer_interrupts = 4;
+
+
+ /* create space for the interrupt source registers */
+ if (opic->interrupt_source != NULL) {
+ memset(opic->interrupt_source, 0, opic->sizeof_interrupt_source);
+ }
+ else {
+ opic->nr_interrupt_sources = (opic->nr_external_interrupts
+ + opic->nr_interprocessor_interrupts
+ + opic->nr_timer_interrupts);
+ if (opic->nr_interrupt_sources > max_nr_interrupt_sources)
+ device_error(me, "number of interrupt sources exceeded");
+ opic->sizeof_interrupt_source = (sizeof(opic_interrupt_source)
+ * opic->nr_interrupt_sources);
+ opic->interrupt_source = zalloc(opic->sizeof_interrupt_source);
+ opic->external_interrupt_source = opic->interrupt_source;
+ opic->interprocessor_interrupt_source = (opic->external_interrupt_source
+ + opic->nr_external_interrupts);
+ opic->timer_interrupt_source = (opic->interprocessor_interrupt_source
+ + opic->nr_interprocessor_interrupts);
+ }
+ for (i = 0; i < opic->nr_interrupt_sources; i++) {
+ opic_interrupt_source *source = &opic->interrupt_source[i];
+ source->nr = i;
+ source->is_masked = isu_mask_bit;
+ }
+ DTRACE(opic, ("interrupt sources - external %d, timer %d, ipi %d, total %d\n",
+ opic->nr_external_interrupts,
+ opic->nr_timer_interrupts,
+ opic->nr_interprocessor_interrupts,
+ opic->nr_interrupt_sources));
+
+
+ /* timers or interprocessor interrupts */
+ if (opic->timer != NULL)
+ memset(opic->timer, 0, opic->sizeof_timer);
+ else {
+ opic->nr_timer_interrupts = 4;
+ opic->sizeof_timer = sizeof(opic_timer) * opic->nr_timer_interrupts;
+ opic->timer = zalloc(opic->sizeof_timer);
+ }
+ for (i = 0; i < opic->nr_timer_interrupts; i++) {
+ opic_timer *timer = &opic->timer[i];
+ timer->nr = i;
+ timer->me = me;
+ timer->opic = opic;
+ timer->inhibited = 1;
+ timer->interrupt_source = &opic->timer_interrupt_source[i];
+ }
+ if (device_find_property(me, "timer-frequency"))
+ opic->timer_frequency = device_find_integer_property(me, "timer-frequency");
+ else
+ opic->timer_frequency = 1;
+
+
+ /* create space for the interrupt destination registers */
+ if (opic->interrupt_destination != NULL) {
+ memset(opic->interrupt_destination, 0, opic->sizeof_interrupt_destination);
+ }
+ else {
+ opic->nr_interrupt_destinations = tree_find_integer_property(me, "/openprom/options/smp");
+ opic->sizeof_interrupt_destination = (sizeof(opic_interrupt_destination)
+ * opic->nr_interrupt_destinations);
+ opic->interrupt_destination = zalloc(opic->sizeof_interrupt_destination);
+ if (opic->nr_interrupt_destinations > max_nr_interrupt_destinations)
+ device_error(me, "number of interrupt destinations exceeded");
+ }
+ for (i = 0; i < opic->nr_interrupt_destinations; i++) {
+ opic_interrupt_destination *dest = &opic->interrupt_destination[i];
+ dest->bit = (1 << i);
+ dest->nr = i;
+ dest->init_port = (device_interrupt_decode(me, "init0", output_port)
+ + i);
+ dest->intr_port = (device_interrupt_decode(me, "intr0", output_port)
+ + i);
+ dest->base_priority = max_nr_task_priorities - 1;
+ }
+ DTRACE(opic, ("interrupt destinations - total %d\n",
+ (int)opic->nr_interrupt_destinations));
+
+
+ /* verify and print out the ISU's */
+ for (isb = 0; isb < opic->nr_isu_blocks; isb++) {
+ unsigned correct_size;
+ if ((opic->isu_block[isb].address % opic_alignment) != 0)
+ device_error(me, "interrupt source unit %d address not aligned to %d byte boundary",
+ isb, opic_alignment);
+ correct_size = opic->isu_block[isb].range * sizeof_isu_register_block;
+ if (opic->isu_block[isb].size != correct_size)
+ device_error(me, "interrupt source unit %d (reg %d) has an incorrect size, should be 0x%x",
+ isb, opic->isu_block[isb].reg, correct_size);
+ DTRACE(opic, ("interrupt source unit block %ld - address %d:0x%lx, size 0x%lx, int-number %ld, range %ld\n",
+ (long)isb,
+ (int)opic->isu_block[isb].space,
+ (unsigned long)opic->isu_block[isb].address,
+ (unsigned long)opic->isu_block[isb].size,
+ (long)opic->isu_block[isb].int_number,
+ (long)opic->isu_block[isb].range));
+ }
+
+
+ /* verify and print out the IDU */
+ {
+ unsigned correct_size;
+ unsigned alternate_size;
+ if ((opic->idu.address % opic_alignment) != 0)
+ device_error(me, "interrupt delivery unit not aligned to %d byte boundary",
+ opic_alignment);
+ correct_size = (idu_per_processor_register_base
+ + (sizeof_idu_per_processor_register_block
+ * opic->nr_interrupt_destinations));
+ alternate_size = (idu_per_processor_register_base
+ + (sizeof_idu_per_processor_register_block
+ * max_nr_interrupt_destinations));
+ if (opic->idu.size != correct_size
+ && opic->idu.size != alternate_size)
+ device_error(me, "interrupt delivery unit has incorrect size, should be 0x%x or 0x%x",
+ correct_size, alternate_size);
+ DTRACE(opic, ("interrupt delivery unit - address %d:0x%lx, size 0x%lx\n",
+ (int)opic->idu.space,
+ (unsigned long)opic->idu.address,
+ (unsigned long)opic->idu.size));
+ }
+
+ /* initialize the init interrupts */
+ opic->init = 0;
+
+
+ /* vendor ident */
+ if (device_find_property(me, "vendor-identification") != NULL)
+ opic->vendor_identification = device_find_integer_property(me, "vendor-identification");
+ else
+ opic->vendor_identification = 0;
+
+ /* misc registers */
+ opic->spurious_vector = 0xff;
+
+}
+
+
+/* interrupt related actions */
+
+static void
+assert_interrupt(device *me,
+ hw_opic_device *opic,
+ opic_interrupt_destination *dest)
+{
+ ASSERT(dest >= opic->interrupt_destination);
+ ASSERT(dest < opic->interrupt_destination + opic->nr_interrupt_destinations);
+ DTRACE(opic, ("assert interrupt - intr port %d\n", dest->intr_port));
+ device_interrupt_event(me, dest->intr_port, 1, NULL, 0);
+}
+
+
+static void
+negate_interrupt(device *me,
+ hw_opic_device *opic,
+ opic_interrupt_destination *dest)
+{
+ ASSERT(dest >= opic->interrupt_destination);
+ ASSERT(dest < opic->interrupt_destination + opic->nr_interrupt_destinations);
+ DTRACE(opic, ("negate interrupt - intr port %d\n", dest->intr_port));
+ device_interrupt_event(me, dest->intr_port, 0, NULL, 0);
+}
+
+
+static int
+can_deliver(device *me,
+ opic_interrupt_source *source,
+ opic_interrupt_destination *dest)
+{
+ return (source != NULL && dest != NULL
+ && source->priority > dest->base_priority
+ && (dest->current_in_service == NULL
+ || source->priority > dest->current_in_service->priority));
+}
+
+
+static unsigned
+deliver_pending(device *me,
+ hw_opic_device *opic,
+ opic_interrupt_destination *dest)
+{
+ ASSERT(can_deliver(me, dest->current_pending, dest));
+ dest->current_in_service = dest->current_pending;
+ dest->current_in_service->in_service |= dest->bit;
+ if (!dest->current_pending->is_level_triggered) {
+ if (dest->current_pending->is_multicast)
+ dest->current_pending->pending &= ~dest->bit;
+ else
+ dest->current_pending->pending = 0;
+ }
+ dest->current_pending = NULL;
+ negate_interrupt(me, opic, dest);
+ return dest->current_in_service->vector;
+}
+
+
+typedef enum {
+ pending_interrupt,
+ in_service_interrupt,
+} interrupt_class;
+
+static opic_interrupt_source *
+find_interrupt_for_dest(device *me,
+ hw_opic_device *opic,
+ opic_interrupt_destination *dest,
+ interrupt_class class)
+{
+ int i;
+ opic_interrupt_source *pending = NULL;
+ for (i = 0; i < opic->nr_interrupt_sources; i++) {
+ opic_interrupt_source *src = &opic->interrupt_source[i];
+ /* is this a potential hit? */
+ switch (class) {
+ case in_service_interrupt:
+ if ((src->in_service & dest->bit) == 0)
+ continue;
+ break;
+ case pending_interrupt:
+ if ((src->pending & dest->bit) == 0)
+ continue;
+ break;
+ }
+ /* see if it is the highest priority */
+ if (pending == NULL)
+ pending = src;
+ else if (src->priority > pending->priority)
+ pending = src;
+ }
+ return pending;
+}
+
+
+static opic_interrupt_destination *
+find_lowest_dest(device *me,
+ hw_opic_device *opic,
+ opic_interrupt_source *src)
+{
+ int i;
+ opic_interrupt_destination *lowest = NULL;
+ for (i = 0; i < opic->nr_interrupt_destinations; i++) {
+ opic_interrupt_destination *dest = &opic->interrupt_destination[i];
+ if (src->destination & dest->bit) {
+ if (dest->base_priority < src->priority) {
+ if (lowest == NULL)
+ lowest = dest;
+ else if (lowest->base_priority > dest->base_priority)
+ lowest = dest;
+ else if (lowest->current_in_service != NULL
+ && dest->current_in_service == NULL)
+ lowest = dest; /* not doing anything */
+ else if (lowest->current_in_service != NULL
+ && dest->current_in_service != NULL
+ && (lowest->current_in_service->priority
+ > dest->current_in_service->priority))
+ lowest = dest; /* less urgent */
+ /* FIXME - need to be more fair */
+ }
+ }
+ }
+ return lowest;
+}
+
+
+static void
+handle_interrupt(device *me,
+ hw_opic_device *opic,
+ opic_interrupt_source *src,
+ int asserted)
+{
+ if (src->is_masked) {
+ DTRACE(opic, ("interrupt %d - ignore masked\n", src->nr));
+ }
+ else if (src->is_multicast) {
+ /* always try to deliver multicast interrupts - just easier */
+ int i;
+ ASSERT(!src->is_level_triggered);
+ ASSERT(src->is_positive_polarity);
+ ASSERT(asserted);
+ for (i = 0; i < opic->nr_interrupt_destinations; i++) {
+ opic_interrupt_destination *dest = &opic->interrupt_destination[i];
+ if (src->destination & dest->bit) {
+ if (src->pending & dest->bit) {
+ DTRACE(opic, ("interrupt %d - multicast still pending to %d\n",
+ src->nr, dest->nr));
+ }
+ else if (can_deliver(me, src, dest)) {
+ dest->current_pending = src;
+ src->pending |= dest->bit;
+ assert_interrupt(me, opic, dest);
+ DTRACE(opic, ("interrupt %d - multicast to %d\n",
+ src->nr, dest->nr));
+ }
+ else {
+ src->pending |= dest->bit;
+ DTRACE(opic, ("interrupt %d - multicast pending to %d\n",
+ src->nr, dest->nr));
+ }
+ }
+ }
+ }
+ else if (src->is_level_triggered
+ && src->is_positive_polarity
+ && !asserted) {
+ if (src->pending)
+ DTRACE(opic, ("interrupt %d - ignore withdrawn (active high)\n",
+ src->nr));
+ else
+ DTRACE(opic, ("interrupt %d - ignore low level (active high)\n",
+ src->nr));
+ ASSERT(!src->is_multicast);
+ src->pending = 0;
+ }
+ else if (src->is_level_triggered
+ && !src->is_positive_polarity
+ && asserted) {
+ if (src->pending)
+ DTRACE(opic, ("interrupt %d - ignore withdrawn (active low)\n",
+ src->nr));
+ else
+ DTRACE(opic, ("interrupt %d - ignore high level (active low)\n",
+ src->nr));
+
+ ASSERT(!src->is_multicast);
+ src->pending = 0;
+ }
+ else if (!src->is_level_triggered
+ && src->is_positive_polarity
+ && !asserted) {
+ DTRACE(opic, ("interrupt %d - ignore falling edge (positive edge trigered)\n",
+ src->nr));
+ }
+ else if (!src->is_level_triggered
+ && !src->is_positive_polarity
+ && asserted) {
+ DTRACE(opic, ("interrupt %d - ignore rising edge (negative edge trigered)\n",
+ src->nr));
+ }
+ else if (src->in_service != 0) {
+ /* leave the interrupt where it is */
+ ASSERT(!src->is_multicast);
+ ASSERT(src->pending == 0 || src->pending == src->in_service);
+ src->pending = src->in_service;
+ DTRACE(opic, ("interrupt %ld - ignore already in service to 0x%lx\n",
+ (long)src->nr, (long)src->in_service));
+ }
+ else if (src->pending != 0) {
+ DTRACE(opic, ("interrupt %ld - ignore still pending to 0x%lx\n",
+ (long)src->nr, (long)src->pending));
+ }
+ else {
+ /* delivery is needed */
+ opic_interrupt_destination *dest = find_lowest_dest(me, opic, src);
+ if (can_deliver(me, src, dest)) {
+ dest->current_pending = src;
+ src->pending = dest->bit;
+ DTRACE(opic, ("interrupt %d - delivered to %d\n", src->nr, dest->nr));
+ assert_interrupt(me, opic, dest);
+ }
+ else {
+ src->pending = src->destination; /* any can take this */
+ DTRACE(opic, ("interrupt %ld - pending to 0x%lx\n",
+ (long)src->nr, (long)src->pending));
+ }
+ }
+}
+
+static unsigned
+do_interrupt_acknowledge_register_N_read(device *me,
+ hw_opic_device *opic,
+ int dest_nr)
+{
+ opic_interrupt_destination *dest = &opic->interrupt_destination[dest_nr];
+ unsigned vector;
+
+ ASSERT(dest_nr >= 0 && dest_nr < opic->nr_interrupt_destinations);
+ ASSERT(dest_nr == dest->nr);
+
+ /* try the current pending */
+ if (can_deliver(me, dest->current_pending, dest)) {
+ ASSERT(dest->current_pending->pending & dest->bit);
+ vector = deliver_pending(me, opic, dest);
+ DTRACE(opic, ("interrupt ack %d - entering %d (pending) - vector %d (%d), priority %d\n",
+ dest->nr,
+ dest->current_in_service->nr,
+ dest->current_in_service->vector, vector,
+ dest->current_in_service->priority));
+ }
+ else {
+ /* try for something else */
+ dest->current_pending = find_interrupt_for_dest(me, opic, dest, pending_interrupt);
+ if (can_deliver(me, dest->current_pending, dest)) {
+ vector = deliver_pending(me, opic, dest);
+ DTRACE(opic, ("interrupt ack %d - entering %d (not pending) - vector %d (%d), priority %d\n",
+ dest->nr,
+ dest->current_in_service->nr,
+ dest->current_in_service->vector, vector,
+ dest->current_in_service->priority));
+ }
+ else {
+ dest->current_pending = NULL;
+ vector = opic->spurious_vector;
+ DTRACE(opic, ("interrupt ack %d - spurious interrupt %d\n",
+ dest->nr, vector));
+ }
+ }
+ return vector;
+}
+
+
+static void
+do_end_of_interrupt_register_N_write(device *me,
+ hw_opic_device *opic,
+ int dest_nr,
+ unsigned reg)
+{
+ opic_interrupt_destination *dest = &opic->interrupt_destination[dest_nr];
+
+ ASSERT(dest_nr >= 0 && dest_nr < opic->nr_interrupt_destinations);
+ ASSERT(dest_nr == dest->nr);
+
+ /* check the value written is zero */
+ if (reg != 0) {
+ DTRACE(opic, ("eoi %d - ignoring nonzero value\n", dest->nr));
+ }
+
+ /* user doing wierd things? */
+ if (dest->current_in_service == NULL) {
+ DTRACE(opic, ("eoi %d - strange, no current interrupt\n", dest->nr));
+ return;
+ }
+
+ /* an internal stuff up? */
+ if (!(dest->current_in_service->in_service & dest->bit)) {
+ device_error(me, "eoi %d - current interrupt not in service", dest->nr);
+ }
+
+ /* find what was probably the previous in service interrupt */
+ dest->current_in_service->in_service &= ~dest->bit;
+ DTRACE(opic, ("eoi %d - ending %d - priority %d, vector %d\n",
+ dest->nr,
+ dest->current_in_service->nr,
+ dest->current_in_service->priority,
+ dest->current_in_service->vector));
+ dest->current_in_service = find_interrupt_for_dest(me, opic, dest, in_service_interrupt);
+ if (dest->current_in_service != NULL)
+ DTRACE(opic, ("eoi %d - resuming %d - priority %d, vector %d\n",
+ dest->nr,
+ dest->current_in_service->nr,
+ dest->current_in_service->priority,
+ dest->current_in_service->vector));
+ else
+ DTRACE(opic, ("eoi %d - resuming none\n", dest->nr));
+
+ /* check to see if that shouldn't be interrupted */
+ dest->current_pending = find_interrupt_for_dest(me, opic, dest, pending_interrupt);
+ if (can_deliver(me, dest->current_pending, dest)) {
+ ASSERT(dest->current_pending->pending & dest->bit);
+ assert_interrupt(me, opic, dest);
+ }
+ else {
+ dest->current_pending = NULL;
+ }
+}
+
+
+static void
+decode_opic_address(device *me,
+ hw_opic_device *opic,
+ int space,
+ unsigned_word address,
+ unsigned nr_bytes,
+ opic_register *type,
+ int *index)
+{
+ int isb = 0;
+
+ /* is the size valid? */
+ if (nr_bytes != 4) {
+ *type = invalid_opic_register;
+ *index = -1;
+ return;
+ }
+
+ /* try for a per-processor register within the interrupt delivery
+ unit */
+ if (space == opic->idu.space
+ && address >= (opic->idu.address + idu_per_processor_register_base)
+ && address < (opic->idu.address + idu_per_processor_register_base
+ + (sizeof_idu_per_processor_register_block
+ * opic->nr_interrupt_destinations))) {
+ unsigned_word block_offset = (address
+ - opic->idu.address
+ - idu_per_processor_register_base);
+ unsigned_word offset = block_offset % sizeof_idu_per_processor_register_block;
+ *index = block_offset / sizeof_idu_per_processor_register_block;
+ switch (offset) {
+ case 0x040:
+ *type = ipi_N_dispatch_register;
+ *index = 0;
+ break;
+ case 0x050:
+ *type = ipi_N_dispatch_register;
+ *index = 1;
+ break;
+ case 0x060:
+ *type = ipi_N_dispatch_register;
+ *index = 2;
+ break;
+ case 0x070:
+ *type = ipi_N_dispatch_register;
+ *index = 3;
+ break;
+ case 0x080:
+ *type = current_task_priority_register_N;
+ break;
+ case 0x0a0:
+ *type = interrupt_acknowledge_register_N;
+ break;
+ case 0x0b0:
+ *type = end_of_interrupt_register_N;
+ break;
+ default:
+ *type = invalid_opic_register;
+ break;
+ }
+ DTRACE(opic, ("per-processor register %d:0x%lx - %s[%d]\n",
+ space, (unsigned long)address,
+ opic_register_name(*type),
+ *index));
+ return;
+ }
+
+ /* try for an interrupt source unit */
+ for (isb = 0; isb < opic->nr_isu_blocks; isb++) {
+ if (opic->isu_block[isb].space == space
+ && address >= opic->isu_block[isb].address
+ && address < (opic->isu_block[isb].address + opic->isu_block[isb].size)) {
+ unsigned_word block_offset = address - opic->isu_block[isb].address;
+ unsigned_word offset = block_offset % sizeof_isu_register_block;
+ *index = (opic->isu_block[isb].int_number
+ + (block_offset / sizeof_isu_register_block));
+ switch (offset) {
+ case 0x00:
+ *type = interrupt_source_N_vector_priority_register;
+ break;
+ case 0x10:
+ *type = interrupt_source_N_destination_register;
+ break;
+ default:
+ *type = invalid_opic_register;
+ break;
+ }
+ DTRACE(opic, ("isu register %d:0x%lx - %s[%d]\n",
+ space, (unsigned long)address,
+ opic_register_name(*type),
+ *index));
+ return;
+ }
+ }
+
+ /* try for a timer */
+ if (space == opic->idu.space
+ && address >= (opic->idu.address + idu_timer_base)
+ && address < (opic->idu.address + idu_timer_base
+ + opic->nr_timer_interrupts * sizeof_timer_register_block)) {
+ unsigned_word offset = address % sizeof_timer_register_block;
+ *index = ((address - opic->idu.address - idu_timer_base)
+ / sizeof_timer_register_block);
+ switch (offset) {
+ case 0x00:
+ *type = timer_N_current_count_register;
+ break;
+ case 0x10:
+ *type = timer_N_base_count_register;
+ break;
+ case 0x20:
+ *type = timer_N_vector_priority_register;
+ break;
+ case 0x30:
+ *type = timer_N_destination_register;
+ break;
+ default:
+ *type = invalid_opic_register;
+ break;
+ }
+ DTRACE(opic, ("timer register %d:0x%lx - %s[%d]\n",
+ space, (unsigned long)address,
+ opic_register_name(*type),
+ *index));
+ return;
+ }
+
+ /* finally some other misc global register */
+ if (space == opic->idu.space
+ && address >= opic->idu.address
+ && address < opic->idu.address + opic->idu.size) {
+ unsigned_word block_offset = address - opic->idu.address;
+ switch (block_offset) {
+ case 0x010f0:
+ *type = timer_frequency_reporting_register;
+ *index = -1;
+ break;
+ case 0x010e0:
+ *type = spurious_vector_register;
+ *index = -1;
+ break;
+ case 0x010d0:
+ case 0x010c0:
+ case 0x010b0:
+ case 0x010a0:
+ *type = ipi_N_vector_priority_register;
+ *index = (block_offset - 0x010a0) / 16;
+ break;
+ case 0x01090:
+ *type = processor_init_register;
+ *index = -1;
+ break;
+ case 0x01080:
+ *type = vendor_identification_register;
+ *index = -1;
+ break;
+ case 0x01020:
+ *type = global_configuration_register_N;
+ *index = 0;
+ break;
+ case 0x01000:
+ *type = feature_reporting_register_N;
+ *index = 0;
+ break;
+ default:
+ *type = invalid_opic_register;
+ *index = -1;
+ break;
+ }
+ DTRACE(opic, ("global register %d:0x%lx - %s[%d]\n",
+ space, (unsigned long)address,
+ opic_register_name(*type),
+ *index));
+ return;
+ }
+
+ /* nothing matched */
+ *type = invalid_opic_register;
+ DTRACE(opic, ("invalid register %d:0x%lx\n",
+ space, (unsigned long)address));
+ return;
+}
+
+
+/* Processor init register:
+
+ The bits in this register (one per processor) are directly wired to
+ output "init" interrupt ports. */
+
+static unsigned
+do_processor_init_register_read(device *me,
+ hw_opic_device *opic)
+{
+ unsigned reg = opic->init;
+ DTRACE(opic, ("processor init register - read 0x%lx\n",
+ (long)reg));
+ return reg;
+}
+
+static void
+do_processor_init_register_write(device *me,
+ hw_opic_device *opic,
+ unsigned reg)
+{
+ int i;
+ for (i = 0; i < opic->nr_interrupt_destinations; i++) {
+ opic_interrupt_destination *dest = &opic->interrupt_destination[i];
+ if ((reg & dest->bit) != (opic->init & dest->bit)) {
+ if (reg & dest->bit) {
+ DTRACE(opic, ("processor init register - write 0x%lx - asserting init%d\n",
+ (long)reg, i));
+ opic->init |= dest->bit;
+ device_interrupt_event(me, dest->init_port, 1, NULL, 0);
+ }
+ else {
+ DTRACE(opic, ("processor init register - write 0x%lx - negating init%d\n",
+ (long)reg, i));
+ opic->init &= ~dest->bit;
+ device_interrupt_event(me, dest->init_port, 0, NULL, 0);
+ }
+ }
+ }
+}
+
+
+
+/* Interrupt Source Vector/Priority Register: */
+
+static unsigned
+read_vector_priority_register(device *me,
+ hw_opic_device *opic,
+ opic_interrupt_source *interrupt,
+ const char *reg_name,
+ int reg_index)
+{
+ unsigned reg;
+ reg = 0;
+ reg |= interrupt->is_masked;
+ reg |= (interrupt->in_service || interrupt->pending
+ ? isu_active_bit : 0); /* active */
+ reg |= interrupt->is_multicast;
+ reg |= interrupt->is_positive_polarity;
+ reg |= interrupt->is_level_triggered; /* sense? */
+ reg |= interrupt->priority << isu_priority_shift;
+ reg |= interrupt->vector;
+ DTRACE(opic, ("%s %d vector/priority register - read 0x%lx\n",
+ reg_name, reg_index, (unsigned long)reg));
+ return reg;
+}
+
+static unsigned
+do_interrupt_source_N_vector_priority_register_read(device *me,
+ hw_opic_device *opic,
+ int index)
+{
+ unsigned reg;
+ ASSERT(index < opic->nr_external_interrupts);
+ reg = read_vector_priority_register(me, opic,
+ &opic->interrupt_source[index],
+ "interrupt source", index);
+ return reg;
+}
+
+static void
+write_vector_priority_register(device *me,
+ hw_opic_device *opic,
+ opic_interrupt_source *interrupt,
+ unsigned reg,
+ const char *reg_name,
+ int reg_index)
+{
+ interrupt->is_masked = (reg & isu_mask_bit);
+ interrupt->is_multicast = (reg & isu_multicast_bit);
+ interrupt->is_positive_polarity = (reg & isu_positive_polarity_bit);
+ interrupt->is_level_triggered = (reg & isu_level_triggered_bit);
+ interrupt->priority = ((reg >> isu_priority_shift)
+ % max_nr_task_priorities);
+ interrupt->vector = (reg & isu_vector_bits);
+ DTRACE(opic, ("%s %d vector/priority register - write 0x%lx - %s%s%s-polarity, %s-triggered, priority %ld vector %ld\n",
+ reg_name,
+ reg_index,
+ (unsigned long)reg,
+ interrupt->is_masked ? "masked, " : "",
+ interrupt->is_multicast ? "multicast, " : "",
+ interrupt->is_positive_polarity ? "positive" : "negative",
+ interrupt->is_level_triggered ? "level" : "edge",
+ (long)interrupt->priority,
+ (long)interrupt->vector));
+}
+
+static void
+do_interrupt_source_N_vector_priority_register_write(device *me,
+ hw_opic_device *opic,
+ int index,
+ unsigned reg)
+{
+ ASSERT(index < opic->nr_external_interrupts);
+ reg &= ~isu_multicast_bit; /* disable multicast */
+ write_vector_priority_register(me, opic,
+ &opic->interrupt_source[index],
+ reg, "interrupt source", index);
+}
+
+
+
+/* Interrupt Source Destination Register: */
+
+static unsigned
+read_destination_register(device *me,
+ hw_opic_device *opic,
+ opic_interrupt_source *interrupt,
+ const char *reg_name,
+ int reg_index)
+{
+ unsigned long reg;
+ reg = interrupt->destination;
+ DTRACE(opic, ("%s %d destination register - read 0x%lx\n",
+ reg_name, reg_index, reg));
+ return reg;
+}
+
+static unsigned
+do_interrupt_source_N_destination_register_read(device *me,
+ hw_opic_device *opic,
+ int index)
+{
+ unsigned reg;
+ ASSERT(index < opic->nr_external_interrupts);
+ reg = read_destination_register(me, opic, &opic->external_interrupt_source[index],
+ "interrupt source", index);
+ return reg;
+}
+
+static void
+write_destination_register(device *me,
+ hw_opic_device *opic,
+ opic_interrupt_source *interrupt,
+ unsigned reg,
+ const char *reg_name,
+ int reg_index)
+{
+ reg &= (1 << opic->nr_interrupt_destinations) - 1; /* mask out invalid */
+ DTRACE(opic, ("%s %d destination register - write 0x%x\n",
+ reg_name, reg_index, reg));
+ interrupt->destination = reg;
+}
+
+static void
+do_interrupt_source_N_destination_register_write(device *me,
+ hw_opic_device *opic,
+ int index,
+ unsigned reg)
+{
+ ASSERT(index < opic->nr_external_interrupts);
+ write_destination_register(me, opic, &opic->external_interrupt_source[index],
+ reg, "interrupt source", index);
+}
+
+
+
+/* Spurious vector register: */
+
+static unsigned
+do_spurious_vector_register_read(device *me,
+ hw_opic_device *opic)
+{
+ unsigned long reg = opic->spurious_vector;
+ DTRACE(opic, ("spurious vector register - read 0x%lx\n", reg));
+ return reg;
+}
+
+static void
+do_spurious_vector_register_write(device *me,
+ hw_opic_device *opic,
+ unsigned reg)
+{
+ reg &= 0xff; /* mask off invalid */
+ DTRACE(opic, ("spurious vector register - write 0x%x\n", reg));
+ opic->spurious_vector = reg;
+}
+
+
+
+/* current task priority register: */
+
+static unsigned
+do_current_task_priority_register_N_read(device *me,
+ hw_opic_device *opic,
+ int index)
+{
+ opic_interrupt_destination *interrupt_destination = &opic->interrupt_destination[index];
+ unsigned reg;
+ ASSERT(index >= 0 && index < opic->nr_interrupt_destinations);
+ reg = interrupt_destination->base_priority;
+ DTRACE(opic, ("current task priority register %d - read 0x%x\n", index, reg));
+ return reg;
+}
+
+static void
+do_current_task_priority_register_N_write(device *me,
+ hw_opic_device *opic,
+ int index,
+ unsigned reg)
+{
+ opic_interrupt_destination *interrupt_destination = &opic->interrupt_destination[index];
+ ASSERT(index >= 0 && index < opic->nr_interrupt_destinations);
+ reg %= max_nr_task_priorities;
+ DTRACE(opic, ("current task priority register %d - write 0x%x\n", index, reg));
+ interrupt_destination->base_priority = reg;
+}
+
+
+
+/* Timer Frequency Reporting Register: */
+
+static unsigned
+do_timer_frequency_reporting_register_read(device *me,
+ hw_opic_device *opic)
+{
+ unsigned reg;
+ reg = opic->timer_frequency;
+ DTRACE(opic, ("timer frequency reporting register - read 0x%x\n", reg));
+ return reg;
+}
+
+static void
+do_timer_frequency_reporting_register_write(device *me,
+ hw_opic_device *opic,
+ unsigned reg)
+{
+ DTRACE(opic, ("timer frequency reporting register - write 0x%x\n", reg));
+ opic->timer_frequency = reg;
+}
+
+
+/* timer registers: */
+
+static unsigned
+do_timer_N_current_count_register_read(device *me,
+ hw_opic_device *opic,
+ int index)
+{
+ opic_timer *timer = &opic->timer[index];
+ unsigned reg;
+ ASSERT(index >= 0 && index < opic->nr_timer_interrupts);
+ if (timer->inhibited)
+ reg = timer->count; /* stalled value */
+ else
+ reg = timer->count - device_event_queue_time(me); /* time remaining */
+ DTRACE(opic, ("timer %d current count register - read 0x%x\n", index, reg));
+ return reg;
+}
+
+
+static unsigned
+do_timer_N_base_count_register_read(device *me,
+ hw_opic_device *opic,
+ int index)
+{
+ opic_timer *timer = &opic->timer[index];
+ unsigned reg;
+ ASSERT(index >= 0 && index < opic->nr_timer_interrupts);
+ reg = timer->base_count;
+ DTRACE(opic, ("timer %d base count register - read 0x%x\n", index, reg));
+ return reg;
+}
+
+
+static void
+timer_event(void *data)
+{
+ opic_timer *timer = data;
+ device *me = timer->me;
+ if (timer->inhibited)
+ device_error(timer->me, "internal-error - timer event occured when timer %d inhibited",
+ timer->nr);
+ handle_interrupt(timer->me, timer->opic, timer->interrupt_source, 1);
+ timer->timeout_event = device_event_queue_schedule(me, timer->base_count,
+ timer_event, timer);
+ DTRACE(opic, ("timer %d - interrupt at %ld, next at %d\n",
+ timer->nr, (long)device_event_queue_time(me), timer->base_count));
+}
+
+
+static void
+do_timer_N_base_count_register_write(device *me,
+ hw_opic_device *opic,
+ int index,
+ unsigned reg)
+{
+ opic_timer *timer = &opic->timer[index];
+ int inhibit;
+ ASSERT(index >= 0 && index < opic->nr_timer_interrupts);
+ inhibit = reg & 0x80000000;
+ if (timer->inhibited && !inhibit) {
+ timer->inhibited = 0;
+ if (timer->timeout_event != NULL)
+ device_event_queue_deschedule(me, timer->timeout_event);
+ timer->count = device_event_queue_time(me) + reg;
+ timer->base_count = reg;
+ timer->timeout_event = device_event_queue_schedule(me, timer->base_count,
+ timer_event, (void*)timer);
+ DTRACE(opic, ("timer %d base count register - write 0x%x - timer started\n",
+ index, reg));
+ }
+ else if (!timer->inhibited && inhibit) {
+ if (timer->timeout_event != NULL)
+ device_event_queue_deschedule(me, timer->timeout_event);
+ timer->count = timer->count - device_event_queue_time(me);
+ timer->inhibited = 1;
+ timer->base_count = reg;
+ DTRACE(opic, ("timer %d base count register - write 0x%x - timer stopped\n",
+ index, reg));
+ }
+ else {
+ ASSERT((timer->inhibited && inhibit) || (!timer->inhibited && !inhibit));
+ DTRACE(opic, ("timer %d base count register - write 0x%x\n", index, reg));
+ timer->base_count = reg;
+ }
+}
+
+
+static unsigned
+do_timer_N_vector_priority_register_read(device *me,
+ hw_opic_device *opic,
+ int index)
+{
+ unsigned reg;
+ ASSERT(index >= 0 && index < opic->nr_timer_interrupts);
+ reg = read_vector_priority_register(me, opic,
+ &opic->timer_interrupt_source[index],
+ "timer", index);
+ return reg;
+}
+
+static void
+do_timer_N_vector_priority_register_write(device *me,
+ hw_opic_device *opic,
+ int index,
+ unsigned reg)
+{
+ ASSERT(index >= 0 && index < opic->nr_timer_interrupts);
+ reg &= ~isu_level_triggered_bit; /* force edge trigger */
+ reg |= isu_positive_polarity_bit; /* force rising (positive) edge */
+ reg |= isu_multicast_bit; /* force multicast */
+ write_vector_priority_register(me, opic,
+ &opic->timer_interrupt_source[index],
+ reg, "timer", index);
+}
+
+
+static unsigned
+do_timer_N_destination_register_read(device *me,
+ hw_opic_device *opic,
+ int index)
+{
+ unsigned reg;
+ ASSERT(index >= 0 && index < opic->nr_timer_interrupts);
+ reg = read_destination_register(me, opic, &opic->timer_interrupt_source[index],
+ "timer", index);
+ return reg;
+}
+
+static void
+do_timer_N_destination_register_write(device *me,
+ hw_opic_device *opic,
+ int index,
+ unsigned reg)
+{
+ ASSERT(index >= 0 && index < opic->nr_timer_interrupts);
+ write_destination_register(me, opic, &opic->timer_interrupt_source[index],
+ reg, "timer", index);
+}
+
+
+/* IPI registers */
+
+static unsigned
+do_ipi_N_vector_priority_register_read(device *me,
+ hw_opic_device *opic,
+ int index)
+{
+ unsigned reg;
+ ASSERT(index >= 0 && index < opic->nr_interprocessor_interrupts);
+ reg = read_vector_priority_register(me, opic,
+ &opic->interprocessor_interrupt_source[index],
+ "ipi", index);
+ return reg;
+}
+
+static void
+do_ipi_N_vector_priority_register_write(device *me,
+ hw_opic_device *opic,
+ int index,
+ unsigned reg)
+{
+ ASSERT(index >= 0 && index < opic->nr_interprocessor_interrupts);
+ reg &= ~isu_level_triggered_bit; /* force edge trigger */
+ reg |= isu_positive_polarity_bit; /* force rising (positive) edge */
+ reg |= isu_multicast_bit; /* force a multicast source */
+ write_vector_priority_register(me, opic,
+ &opic->interprocessor_interrupt_source[index],
+ reg, "ipi", index);
+}
+
+static void
+do_ipi_N_dispatch_register_write(device *me,
+ hw_opic_device *opic,
+ int index,
+ unsigned reg)
+{
+ opic_interrupt_source *source = &opic->interprocessor_interrupt_source[index];
+ ASSERT(index >= 0 && index < opic->nr_interprocessor_interrupts);
+ DTRACE(opic, ("ipi %d interrupt dispatch register - write 0x%x\n", index, reg));
+ source->destination = reg;
+ handle_interrupt(me, opic, source, 1);
+}
+
+
+/* vendor and other global registers */
+
+static unsigned
+do_vendor_identification_register_read(device *me,
+ hw_opic_device *opic)
+{
+ unsigned reg;
+ reg = opic->vendor_identification;
+ DTRACE(opic, ("vendor identification register - read 0x%x\n", reg));
+ return reg;
+}
+
+static unsigned
+do_feature_reporting_register_N_read(device *me,
+ hw_opic_device *opic,
+ int index)
+{
+ unsigned reg = 0;
+ ASSERT(index == 0);
+ switch (index) {
+ case 0:
+ reg |= (opic->nr_external_interrupts << 16);
+ reg |= (opic->nr_interrupt_destinations << 8);
+ reg |= (2/*version 1.2*/);
+ break;
+ }
+ DTRACE(opic, ("feature reporting register %d - read 0x%x\n", index, reg));
+ return reg;
+}
+
+static unsigned
+do_global_configuration_register_N_read(device *me,
+ hw_opic_device *opic,
+ int index)
+{
+ unsigned reg = 0;
+ ASSERT(index == 0);
+ switch (index) {
+ case 0:
+ reg |= gcr0_8259_bit; /* hardwire 8259 disabled */
+ break;
+ }
+ DTRACE(opic, ("global configuration register %d - read 0x%x\n", index, reg));
+ return reg;
+}
+
+static void
+do_global_configuration_register_N_write(device *me,
+ hw_opic_device *opic,
+ int index,
+ unsigned reg)
+{
+ ASSERT(index == 0);
+ if (reg & gcr0_reset_bit) {
+ DTRACE(opic, ("global configuration register %d - write 0x%x - reseting opic\n", index, reg));
+ hw_opic_init_data(me);
+ }
+ if (!(reg & gcr0_8259_bit)) {
+ DTRACE(opic, ("global configuration register %d - write 0x%x - ignoring 8259 enable\n", index, reg));
+ }
+}
+
+
+
+/* register read-write */
+
+static unsigned
+hw_opic_io_read_buffer(device *me,
+ void *dest,
+ int space,
+ unsigned_word addr,
+ unsigned nr_bytes,
+ cpu *processor,
+ unsigned_word cia)
+{
+ hw_opic_device *opic = (hw_opic_device*)device_data(me);
+ opic_register type;
+ int index;
+ decode_opic_address(me, opic, space, addr, nr_bytes, &type, &index);
+ if (type == invalid_opic_register) {
+ device_error(me, "invalid opic read access to %d:0x%lx (%d bytes)",
+ space, (unsigned long)addr, nr_bytes);
+ }
+ else {
+ unsigned reg;
+ switch (type) {
+ case processor_init_register:
+ reg = do_processor_init_register_read(me, opic);
+ break;
+ case interrupt_source_N_vector_priority_register:
+ reg = do_interrupt_source_N_vector_priority_register_read(me, opic, index);
+ break;
+ case interrupt_source_N_destination_register:
+ reg = do_interrupt_source_N_destination_register_read(me, opic, index);
+ break;
+ case interrupt_acknowledge_register_N:
+ reg = do_interrupt_acknowledge_register_N_read(me, opic, index);
+ break;
+ case spurious_vector_register:
+ reg = do_spurious_vector_register_read(me, opic);
+ break;
+ case current_task_priority_register_N:
+ reg = do_current_task_priority_register_N_read(me, opic, index);
+ break;
+ case timer_frequency_reporting_register:
+ reg = do_timer_frequency_reporting_register_read(me, opic);
+ break;
+ case timer_N_current_count_register:
+ reg = do_timer_N_current_count_register_read(me, opic, index);
+ break;
+ case timer_N_base_count_register:
+ reg = do_timer_N_base_count_register_read(me, opic, index);
+ break;
+ case timer_N_vector_priority_register:
+ reg = do_timer_N_vector_priority_register_read(me, opic, index);
+ break;
+ case timer_N_destination_register:
+ reg = do_timer_N_destination_register_read(me, opic, index);
+ break;
+ case ipi_N_vector_priority_register:
+ reg = do_ipi_N_vector_priority_register_read(me, opic, index);
+ break;
+ case feature_reporting_register_N:
+ reg = do_feature_reporting_register_N_read(me, opic, index);
+ break;
+ case global_configuration_register_N:
+ reg = do_global_configuration_register_N_read(me, opic, index);
+ break;
+ case vendor_identification_register:
+ reg = do_vendor_identification_register_read(me, opic);
+ break;
+ default:
+ reg = 0;
+ device_error(me, "unimplemented read of register %s[%d]",
+ opic_register_name(type), index);
+ }
+ *(unsigned_4*)dest = H2LE_4(reg);
+ }
+ return nr_bytes;
+}
+
+
+static unsigned
+hw_opic_io_write_buffer(device *me,
+ const void *source,
+ int space,
+ unsigned_word addr,
+ unsigned nr_bytes,
+ cpu *processor,
+ unsigned_word cia)
+{
+ hw_opic_device *opic = (hw_opic_device*)device_data(me);
+ opic_register type;
+ int index;
+ decode_opic_address(me, opic, space, addr, nr_bytes, &type, &index);
+ if (type == invalid_opic_register) {
+ device_error(me, "invalid opic write access to %d:0x%lx (%d bytes)",
+ space, (unsigned long)addr, nr_bytes);
+ }
+ else {
+ unsigned reg = LE2H_4(*(unsigned_4*)source);
+ switch (type) {
+ case processor_init_register:
+ do_processor_init_register_write(me, opic, reg);
+ break;
+ case interrupt_source_N_vector_priority_register:
+ do_interrupt_source_N_vector_priority_register_write(me, opic, index, reg);
+ break;
+ case interrupt_source_N_destination_register:
+ do_interrupt_source_N_destination_register_write(me, opic, index, reg);
+ break;
+ case end_of_interrupt_register_N:
+ do_end_of_interrupt_register_N_write(me, opic, index, reg);
+ break;
+ case spurious_vector_register:
+ do_spurious_vector_register_write(me, opic, reg);
+ break;
+ case current_task_priority_register_N:
+ do_current_task_priority_register_N_write(me, opic, index, reg);
+ break;
+ case timer_frequency_reporting_register:
+ do_timer_frequency_reporting_register_write(me, opic, reg);
+ break;
+ case timer_N_base_count_register:
+ do_timer_N_base_count_register_write(me, opic, index, reg);
+ break;
+ case timer_N_vector_priority_register:
+ do_timer_N_vector_priority_register_write(me, opic, index, reg);
+ break;
+ case timer_N_destination_register:
+ do_timer_N_destination_register_write(me, opic, index, reg);
+ break;
+ case ipi_N_dispatch_register:
+ do_ipi_N_dispatch_register_write(me, opic, index, reg);
+ break;
+ case ipi_N_vector_priority_register:
+ do_ipi_N_vector_priority_register_write(me, opic, index, reg);
+ break;
+ case global_configuration_register_N:
+ do_global_configuration_register_N_write(me, opic, index, reg);
+ break;
+ default:
+ device_error(me, "unimplemented write to register %s[%d]",
+ opic_register_name(type), index);
+ }
+ }
+ return nr_bytes;
+}
+
+
+static void
+hw_opic_interrupt_event(device *me,
+ int my_port,
+ device *source,
+ int source_port,
+ int level,
+ cpu *processor,
+ unsigned_word cia)
+{
+ hw_opic_device *opic = (hw_opic_device*)device_data(me);
+
+ int isb;
+ int src_nr = 0;
+
+ /* find the corresponding internal input port */
+ for (isb = 0; isb < opic->nr_isu_blocks; isb++) {
+ if (my_port >= opic->isu_block[isb].int_number
+ && my_port < opic->isu_block[isb].int_number + opic->isu_block[isb].range) {
+ src_nr += my_port - opic->isu_block[isb].int_number;
+ break;
+ }
+ else
+ src_nr += opic->isu_block[isb].range;
+ }
+ if (isb == opic->nr_isu_blocks)
+ device_error(me, "interrupt %d out of range", my_port);
+ DTRACE(opic, ("external-interrupt %d, internal %d, level %d\n",
+ my_port, src_nr, level));
+
+ /* pass it on */
+ ASSERT(src_nr >= 0 && src_nr < opic->nr_external_interrupts);
+ handle_interrupt(me, opic, &opic->external_interrupt_source[src_nr], level);
+}
+
+
+static const device_interrupt_port_descriptor hw_opic_interrupt_ports[] = {
+ { "irq", 0, max_nr_interrupt_sources, input_port, },
+ { "intr", 0, max_nr_interrupt_destinations, output_port, },
+ { "init", max_nr_interrupt_destinations, max_nr_interrupt_destinations, output_port, },
+ { NULL }
+};
+
+
+static device_callbacks const hw_opic_callbacks = {
+ { generic_device_init_address,
+ hw_opic_init_data },
+ { NULL, }, /* address */
+ { hw_opic_io_read_buffer,
+ hw_opic_io_write_buffer }, /* IO */
+ { NULL, }, /* DMA */
+ { hw_opic_interrupt_event, NULL, hw_opic_interrupt_ports }, /* interrupt */
+ { NULL, }, /* unit */
+ NULL, /* instance */
+};
+
+static void *
+hw_opic_create(const char *name,
+ const device_unit *unit_address,
+ const char *args)
+{
+ hw_opic_device *opic = ZALLOC(hw_opic_device);
+ return opic;
+}
+
+
+
+const device_descriptor hw_opic_device_descriptor[] = {
+ { "opic", hw_opic_create, &hw_opic_callbacks },
+ { NULL },
+};
+
+#endif /* _HW_OPIC_C_ */