#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
typedef struct _hw_opic_device hw_opic_device;
enum {
max_nr_interrupt_sources = 2048,
max_nr_interrupt_destinations = 32,
max_nr_task_priorities = 16,
};
enum {
opic_alignment = 16,
};
enum {
gcr0_8259_bit = 0x20000000,
gcr0_reset_bit = 0x80000000,
};
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,
};
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;
unsigned is_multicast;
unsigned is_positive_polarity;
unsigned is_level_triggered;
unsigned priority;
unsigned vector;
int nr;
unsigned destination;
unsigned pending;
unsigned in_service;
} opic_interrupt_source;
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;
typedef struct _opic_isu_block {
int space;
unsigned_word address;
unsigned size;
unsigned_cell int_number;
unsigned_cell range;
int reg;
} opic_isu_block;
typedef struct _opic_idu {
int reg;
int space;
unsigned_word address;
unsigned size;
} opic_idu;
typedef enum {
invalid_opic_register,
interrupt_source_N_destination_register,
interrupt_source_N_vector_priority_register,
timer_N_destination_register,
timer_N_vector_priority_register,
timer_N_base_count_register,
timer_N_current_count_register,
timer_frequency_reporting_register,
ipi_N_vector_priority_register,
ipi_N_dispatch_register,
spurious_vector_register,
processor_init_register,
vendor_identification_register,
global_configuration_register_N,
feature_reporting_register_N,
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;
}
typedef struct _opic_timer {
int nr;
device *me;
hw_opic_device *opic;
unsigned base_count;
int inhibited;
signed64 count;
event_entry_tag timeout_event;
opic_interrupt_source *interrupt_source;
} opic_timer;
struct _hw_opic_device {
unsigned vendor_identification;
int nr_interrupt_destinations;
opic_interrupt_destination *interrupt_destination;
unsigned sizeof_interrupt_destination;
int spurious_vector;
int nr_interrupt_sources;
opic_interrupt_source *interrupt_source;
unsigned sizeof_interrupt_source;
int nr_external_interrupts;
opic_interrupt_source *external_interrupt_source;
int nr_interprocessor_interrupts;
opic_interrupt_source *interprocessor_interrupt_source;
int nr_timer_interrupts;
opic_timer *timer;
unsigned sizeof_timer;
opic_interrupt_source *timer_interrupt_source;
unsigned timer_frequency;
unsigned32 init;
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;
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))) {
nr_isu_blocks += 1;
}
else {
idu_reg += 1;
}
}
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,
&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);
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);
}
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));
opic->nr_interprocessor_interrupts = 4;
opic->nr_timer_interrupts = 4;
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));
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;
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));
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));
}
{
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));
}
opic->init = 0;
if (device_find_property(me, "vendor-identification") != NULL)
opic->vendor_identification = device_find_integer_property(me, "vendor-identification");
else
opic->vendor_identification = 0;
opic->spurious_vector = 0xff;
}
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];
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;
}
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;
else if (lowest->current_in_service != NULL
&& dest->current_in_service != NULL
&& (lowest->current_in_service->priority
> dest->current_in_service->priority))
lowest = dest;
}
}
}
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) {
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) {
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 {
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;
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);
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 {
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);
if (reg != 0) {
DTRACE(opic, ("eoi %d - ignoring nonzero value\n", dest->nr));
}
if (dest->current_in_service == NULL) {
DTRACE(opic, ("eoi %d - strange, no current interrupt\n", dest->nr));
return;
}
if (!(dest->current_in_service->in_service & dest->bit)) {
device_error(me, "eoi %d - current interrupt not in service", dest->nr);
}
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));
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;
if (nr_bytes != 4) {
*type = invalid_opic_register;
*index = -1;
return;
}
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;
}
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;
}
}
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;
}
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;
}
*type = invalid_opic_register;
DTRACE(opic, ("invalid register %d:0x%lx\n",
space, (unsigned long)address));
return;
}
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);
}
}
}
}
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);
reg |= interrupt->is_multicast;
reg |= interrupt->is_positive_polarity;
reg |= interrupt->is_level_triggered;
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;
write_vector_priority_register(me, opic,
&opic->interrupt_source[index],
reg, "interrupt source", index);
}
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;
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);
}
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;
DTRACE(opic, ("spurious vector register - write 0x%x\n", reg));
opic->spurious_vector = reg;
}
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;
}
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;
}
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;
else
reg = timer->count - device_event_queue_time(me);
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;
reg |= isu_positive_polarity_bit;
reg |= isu_multicast_bit;
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);
}
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;
reg |= isu_positive_polarity_bit;
reg |= isu_multicast_bit;
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);
}
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);
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;
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));
}
}
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;
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));
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, },
{ hw_opic_io_read_buffer,
hw_opic_io_write_buffer },
{ NULL, },
{ hw_opic_interrupt_event, NULL, hw_opic_interrupt_ports },
{ NULL, },
NULL,
};
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