| /* 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 3 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, see <http://www.gnu.org/licenses/>. |
| |
| */ |
| |
| |
| #ifndef _HW_OPIC_C_ |
| #define _HW_OPIC_C_ |
| |
| #include "device_table.h" |
| |
| #include <string.h> |
| |
| |
| /* 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, ®)) |
| 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), ®.address, |
| &opic->isu_block[isb].space, |
| &opic->isu_block[isb].address, |
| me) |
| || !device_size_to_attach_size(device_parent(me), ®.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, |
| (signed_cell *) |
| &opic->isu_block[isb].int_number) |
| || !device_find_integer_array_property(me, "interrupt-ranges", |
| reg_nr * 2 + 1, |
| (signed_cell *) |
| &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_ */ |