2009-05-20 23:07:56 +03:00
|
|
|
#pypp 0
|
2009-06-01 15:26:42 +03:00
|
|
|
// Iris: micro-kernel for a capability-based operating system.
|
|
|
|
// mips/arch.ccp: Most mips-specific parts.
|
|
|
|
// Copyright 2009 Bas Wijnen <wijnen@debian.org>
|
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 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/>.
|
|
|
|
|
2009-05-22 23:48:49 +03:00
|
|
|
#define ARCH
|
2009-09-27 11:23:33 +03:00
|
|
|
#include "kernel.hh"
|
2009-05-20 23:07:56 +03:00
|
|
|
|
2009-08-18 00:11:15 +03:00
|
|
|
void kThread_arch_init (kThread *thread):
|
2009-05-20 23:07:56 +03:00
|
|
|
thread->arch.at = 0
|
2009-08-24 22:02:35 +03:00
|
|
|
for unsigned i = 0; i < 2; ++i:
|
|
|
|
thread->arch.v[i] = 0
|
|
|
|
thread->arch.k[i] = 0
|
|
|
|
for unsigned i = 0; i < 4; ++i:
|
|
|
|
thread->arch.a[i] = 0
|
|
|
|
for unsigned i = 0; i < 10; ++i:
|
|
|
|
thread->arch.t[i] = 0
|
2009-05-20 23:07:56 +03:00
|
|
|
thread->arch.gp = 0
|
|
|
|
thread->arch.fp = 0
|
|
|
|
thread->arch.ra = 0
|
|
|
|
thread->arch.hi = 0
|
|
|
|
thread->arch.lo = 0
|
|
|
|
|
2009-09-06 12:04:09 +03:00
|
|
|
void kThread_arch_receive (kThread *thread, Kernel::Num protected_data, Kernel::Num *data):
|
2009-08-24 22:02:35 +03:00
|
|
|
thread->arch.a[0] = data[0].l
|
|
|
|
thread->arch.a[1] = data[0].h
|
|
|
|
thread->arch.a[2] = data[1].l
|
|
|
|
thread->arch.a[3] = data[1].h
|
2009-09-06 12:04:09 +03:00
|
|
|
thread->arch.t[0] = protected_data.l
|
|
|
|
thread->arch.t[1] = protected_data.h
|
2009-05-25 01:31:35 +03:00
|
|
|
|
2009-08-18 00:11:15 +03:00
|
|
|
unsigned *kThread_arch_info (kThread *thread, unsigned num):
|
2009-05-27 19:33:05 +03:00
|
|
|
switch num:
|
|
|
|
case 1:
|
|
|
|
return &thread->arch.at
|
|
|
|
case 2:
|
2009-08-24 22:02:35 +03:00
|
|
|
return &thread->arch.v[0]
|
2009-05-27 19:33:05 +03:00
|
|
|
case 3:
|
2009-08-24 22:02:35 +03:00
|
|
|
return &thread->arch.v[1]
|
2009-05-27 19:33:05 +03:00
|
|
|
case 4:
|
2009-08-24 22:02:35 +03:00
|
|
|
return &thread->arch.a[0]
|
2009-05-27 19:33:05 +03:00
|
|
|
case 5:
|
2009-08-24 22:02:35 +03:00
|
|
|
return &thread->arch.a[1]
|
2009-05-27 19:33:05 +03:00
|
|
|
case 6:
|
2009-08-24 22:02:35 +03:00
|
|
|
return &thread->arch.a[2]
|
2009-05-27 19:33:05 +03:00
|
|
|
case 7:
|
2009-08-24 22:02:35 +03:00
|
|
|
return &thread->arch.a[3]
|
2009-05-27 19:33:05 +03:00
|
|
|
case 8:
|
2009-08-24 22:02:35 +03:00
|
|
|
return &thread->arch.t[0]
|
2009-05-27 19:33:05 +03:00
|
|
|
case 9:
|
2009-08-24 22:02:35 +03:00
|
|
|
return &thread->arch.t[1]
|
2009-05-27 19:33:05 +03:00
|
|
|
case 10:
|
2009-08-24 22:02:35 +03:00
|
|
|
return &thread->arch.t[2]
|
2009-05-27 19:33:05 +03:00
|
|
|
case 11:
|
2009-08-24 22:02:35 +03:00
|
|
|
return &thread->arch.t[3]
|
2009-05-27 19:33:05 +03:00
|
|
|
case 12:
|
2009-08-24 22:02:35 +03:00
|
|
|
return &thread->arch.t[4]
|
2009-05-27 19:33:05 +03:00
|
|
|
case 13:
|
2009-08-24 22:02:35 +03:00
|
|
|
return &thread->arch.t[5]
|
2009-05-27 19:33:05 +03:00
|
|
|
case 14:
|
2009-08-24 22:02:35 +03:00
|
|
|
return &thread->arch.t[6]
|
2009-05-27 19:33:05 +03:00
|
|
|
case 15:
|
2009-08-24 22:02:35 +03:00
|
|
|
return &thread->arch.t[7]
|
2009-05-27 19:33:05 +03:00
|
|
|
case 16:
|
2009-08-24 22:02:35 +03:00
|
|
|
return &thread->arch.s[0]
|
2009-05-27 19:33:05 +03:00
|
|
|
case 17:
|
2009-08-24 22:02:35 +03:00
|
|
|
return &thread->arch.s[1]
|
2009-05-27 19:33:05 +03:00
|
|
|
case 18:
|
2009-08-24 22:02:35 +03:00
|
|
|
return &thread->arch.s[2]
|
2009-05-27 19:33:05 +03:00
|
|
|
case 19:
|
2009-08-24 22:02:35 +03:00
|
|
|
return &thread->arch.s[3]
|
2009-05-27 19:33:05 +03:00
|
|
|
case 20:
|
2009-08-24 22:02:35 +03:00
|
|
|
return &thread->arch.s[4]
|
2009-05-27 19:33:05 +03:00
|
|
|
case 21:
|
2009-08-24 22:02:35 +03:00
|
|
|
return &thread->arch.s[5]
|
2009-05-27 19:33:05 +03:00
|
|
|
case 22:
|
2009-08-24 22:02:35 +03:00
|
|
|
return &thread->arch.s[6]
|
2009-05-27 19:33:05 +03:00
|
|
|
case 23:
|
2009-08-24 22:02:35 +03:00
|
|
|
return &thread->arch.s[7]
|
2009-05-27 19:33:05 +03:00
|
|
|
case 24:
|
2009-08-24 22:02:35 +03:00
|
|
|
return &thread->arch.t[8]
|
2009-05-27 19:33:05 +03:00
|
|
|
case 25:
|
2009-08-24 22:02:35 +03:00
|
|
|
return &thread->arch.t[9]
|
2009-05-27 19:33:05 +03:00
|
|
|
case 26:
|
2009-08-24 22:02:35 +03:00
|
|
|
return &thread->arch.k[0]
|
2009-05-27 19:33:05 +03:00
|
|
|
case 27:
|
2009-08-24 22:02:35 +03:00
|
|
|
return &thread->arch.k[1]
|
2009-05-27 19:33:05 +03:00
|
|
|
case 28:
|
|
|
|
return &thread->arch.gp
|
|
|
|
case 29:
|
|
|
|
return &thread->sp
|
|
|
|
case 30:
|
|
|
|
return &thread->arch.fp
|
|
|
|
case 31:
|
|
|
|
return &thread->arch.ra
|
|
|
|
default:
|
|
|
|
return NULL
|
|
|
|
|
2009-08-18 00:11:15 +03:00
|
|
|
void kMemory_arch_init (kMemory *mem):
|
2009-05-24 13:22:22 +03:00
|
|
|
mem->arch.asid = 1
|
2009-05-20 23:07:56 +03:00
|
|
|
mem->arch.directory = NULL
|
2009-05-26 01:42:47 +03:00
|
|
|
mem->arch.shadow = NULL
|
2009-05-20 23:07:56 +03:00
|
|
|
|
2009-08-18 00:11:15 +03:00
|
|
|
void kMemory_arch_free (kMemory *mem):
|
2009-12-30 23:41:45 +02:00
|
|
|
//dbg_log_line ()
|
2009-05-26 01:42:47 +03:00
|
|
|
while mem->arch.first_page_table:
|
|
|
|
mem->unmap (mem->arch.first_page_table->first_page->page, mem->arch.first_page_table->first_page->mapping)
|
2009-08-18 00:11:15 +03:00
|
|
|
if (kMemory *)asids[mem->arch.asid] == mem:
|
2009-05-24 13:22:22 +03:00
|
|
|
flush_tlb (mem->arch.asid)
|
|
|
|
asids[mem->arch.asid] = asids[0]
|
|
|
|
asids[0] = mem->arch.asid
|
2009-05-20 23:07:56 +03:00
|
|
|
mem->unuse ()
|
2009-05-22 23:48:49 +03:00
|
|
|
mem->zfree ((unsigned)mem->arch.directory)
|
2009-05-20 23:07:56 +03:00
|
|
|
|
2009-08-18 00:11:15 +03:00
|
|
|
static arch_page_table *alloc_page_table (kMemory *mem):
|
2009-12-30 23:41:45 +02:00
|
|
|
//dbg_log_line ()
|
2009-05-26 01:42:47 +03:00
|
|
|
arch_page_table *ret = (arch_page_table *)mem->search_free (sizeof (arch_page_table), (void **)&mem->arch.first_page_table)
|
|
|
|
if !ret:
|
|
|
|
return NULL
|
|
|
|
ret->first_page = NULL
|
|
|
|
return ret
|
|
|
|
|
2009-08-18 00:11:15 +03:00
|
|
|
static arch_page *alloc_page (kMemory *mem, arch_page_table *t):
|
2009-12-30 23:41:45 +02:00
|
|
|
//dbg_log_line ()
|
2009-05-26 01:42:47 +03:00
|
|
|
arch_page *ret = (arch_page *)mem->search_free (sizeof (arch_page), (void **)&t->first_page)
|
|
|
|
if !ret:
|
|
|
|
return NULL
|
|
|
|
ret->page = NULL
|
|
|
|
ret->mapping = ~0
|
|
|
|
ret->prev_mapped = NULL
|
|
|
|
ret->next_mapped = NULL
|
|
|
|
return ret
|
|
|
|
|
|
|
|
static void free_page_table (arch_page_table *t, unsigned idx):
|
2009-12-30 23:41:45 +02:00
|
|
|
//dbg_log_line ()
|
2009-08-18 00:11:15 +03:00
|
|
|
kMemory *mem = t->address_space
|
2009-05-26 01:42:47 +03:00
|
|
|
mem->zfree ((unsigned)mem->arch.directory[idx])
|
|
|
|
mem->arch.directory[idx] = NULL
|
|
|
|
mem->arch.shadow[idx] = NULL
|
2009-07-25 01:54:12 +03:00
|
|
|
mem->free_obj (t, (void **)&mem->arch.first_page_table)
|
2009-05-26 01:42:47 +03:00
|
|
|
if !mem->arch.first_page_table:
|
|
|
|
mem->zfree ((unsigned)mem->arch.directory)
|
|
|
|
mem->zfree ((unsigned)mem->arch.shadow)
|
|
|
|
mem->arch.directory = NULL
|
|
|
|
mem->arch.shadow = NULL
|
|
|
|
|
2009-06-01 02:12:54 +03:00
|
|
|
static void tlb_reset (unsigned address, unsigned asid, unsigned value):
|
2009-12-30 23:41:45 +02:00
|
|
|
//dbg_log_line ()
|
2009-06-01 02:12:54 +03:00
|
|
|
cp0_set (CP0_ENTRY_HI, address | asid)
|
|
|
|
__asm__ volatile ("tlbp")
|
|
|
|
unsigned idx
|
|
|
|
cp0_get (CP0_INDEX, idx)
|
|
|
|
if ~idx & 0x80000000:
|
|
|
|
if address & (1 << PAGE_BITS):
|
|
|
|
cp0_set (CP0_ENTRY_LO1, value)
|
|
|
|
else:
|
|
|
|
cp0_set (CP0_ENTRY_LO0, value)
|
|
|
|
__asm__ volatile ("tlbwi")
|
|
|
|
|
2009-05-26 01:42:47 +03:00
|
|
|
static void free_page (arch_page_table *t, arch_page *p):
|
2009-12-30 23:41:45 +02:00
|
|
|
//dbg_log_line ()
|
|
|
|
if !p:
|
|
|
|
dpanic (0, "freeing page 0")
|
|
|
|
return
|
2009-05-26 01:42:47 +03:00
|
|
|
if p->prev_mapped:
|
|
|
|
p->prev_mapped->next_mapped = p->next_mapped
|
|
|
|
else:
|
|
|
|
p->page->arch.first_mapped = p->next_mapped
|
|
|
|
if p->next_mapped:
|
|
|
|
p->next_mapped->prev_mapped = p->prev_mapped
|
2009-06-01 02:12:54 +03:00
|
|
|
tlb_reset (p->mapping, p->address_space->arch.asid, 0)
|
2009-05-26 01:42:47 +03:00
|
|
|
unsigned idx = p->mapping >> 21
|
2009-07-25 01:54:12 +03:00
|
|
|
p->address_space->free_obj (p, (void **)&t->first_page)
|
2009-05-26 01:42:47 +03:00
|
|
|
if !t->first_page:
|
|
|
|
free_page_table (t, idx)
|
|
|
|
|
2009-08-18 00:11:15 +03:00
|
|
|
static unsigned make_entry_lo (kPage *page, bool readonly):
|
2009-12-30 23:41:45 +02:00
|
|
|
//dbg_log_line ()
|
2009-08-05 11:16:24 +03:00
|
|
|
if !page->frame:
|
2010-01-16 17:13:54 +02:00
|
|
|
dbg_log ("not mapping because there is no frame\n")
|
2009-06-01 02:12:54 +03:00
|
|
|
return 0
|
|
|
|
unsigned flags
|
2009-09-06 12:04:09 +03:00
|
|
|
if page->flags & Kernel::Page::UNCACHED:
|
2009-06-08 14:46:13 +03:00
|
|
|
flags = 0x10 | 0x2
|
|
|
|
else:
|
2009-10-31 10:32:23 +02:00
|
|
|
// 18 is write-back cache; 00 is write-through cache.
|
2009-06-01 02:12:54 +03:00
|
|
|
flags = 0x18 | 0x2
|
2009-08-18 00:11:15 +03:00
|
|
|
if !readonly:
|
2009-06-08 14:46:13 +03:00
|
|
|
flags |= 0x4
|
2009-08-05 11:16:24 +03:00
|
|
|
return ((page->frame & ~0x80000000) >> 6) | flags
|
2009-06-01 02:12:54 +03:00
|
|
|
|
2009-08-18 00:11:15 +03:00
|
|
|
bool kMemory_arch_map (kMemory *mem, kPage *page, unsigned address, bool readonly):
|
2009-12-30 23:41:45 +02:00
|
|
|
//dbg_log_line ()
|
2009-06-01 02:12:54 +03:00
|
|
|
if address >= 0x80000000:
|
2010-01-16 17:13:54 +02:00
|
|
|
dpanic (address, "trying to map to kernel address")
|
2009-06-01 02:12:54 +03:00
|
|
|
return false
|
|
|
|
address &= PAGE_MASK
|
2009-05-23 21:55:31 +03:00
|
|
|
if !mem->arch.directory:
|
|
|
|
mem->arch.directory = (unsigned **)mem->zalloc ()
|
|
|
|
if !mem->arch.directory:
|
|
|
|
return false
|
2009-05-26 01:42:47 +03:00
|
|
|
mem->arch.shadow = (arch_page_table **)mem->zalloc ()
|
|
|
|
if !mem->arch.shadow:
|
|
|
|
mem->zfree ((unsigned)mem->arch.directory)
|
|
|
|
mem->arch.directory = NULL
|
|
|
|
return false
|
|
|
|
unsigned *table = mem->arch.directory[address >> 21]
|
|
|
|
arch_page_table *t = mem->arch.shadow[address >> 21]
|
2009-05-20 23:07:56 +03:00
|
|
|
if !table:
|
|
|
|
table = (unsigned *)mem->zalloc ()
|
|
|
|
if !table:
|
2009-05-26 01:42:47 +03:00
|
|
|
if !mem->arch.first_page_table:
|
|
|
|
mem->zfree ((unsigned)mem->arch.directory)
|
|
|
|
mem->zfree ((unsigned)mem->arch.shadow)
|
2009-05-27 15:38:52 +03:00
|
|
|
mem->arch.directory = NULL
|
|
|
|
mem->arch.shadow = NULL
|
2009-05-26 01:42:47 +03:00
|
|
|
return false
|
|
|
|
t = alloc_page_table (mem)
|
|
|
|
if !t:
|
|
|
|
mem->zfree ((unsigned)table)
|
|
|
|
if !mem->arch.first_page_table:
|
|
|
|
mem->zfree ((unsigned)mem->arch.directory)
|
|
|
|
mem->zfree ((unsigned)mem->arch.shadow)
|
2009-05-27 15:38:52 +03:00
|
|
|
mem->arch.directory = NULL
|
|
|
|
mem->arch.shadow = NULL
|
2009-05-20 23:07:56 +03:00
|
|
|
return false
|
2009-05-26 01:42:47 +03:00
|
|
|
mem->arch.directory[address >> 21] = table
|
2009-05-27 15:38:52 +03:00
|
|
|
mem->arch.shadow[address >> 21] = t
|
2009-05-26 01:42:47 +03:00
|
|
|
arch_page *p = alloc_page (mem, t)
|
|
|
|
if !p:
|
|
|
|
if !t->first_page:
|
|
|
|
// This automatically cleans up the rest.
|
|
|
|
free_page_table (t, address >> 21)
|
|
|
|
return false
|
|
|
|
unsigned idx = (address >> 12) & ((1 << 9) - 1)
|
2009-05-20 23:07:56 +03:00
|
|
|
if table[idx]:
|
2009-12-30 23:41:45 +02:00
|
|
|
dbg_log ("page already mapped: ")
|
|
|
|
dbg_log_num (idx, 3)
|
|
|
|
dbg_log (";")
|
|
|
|
dbg_log_num (table[idx])
|
|
|
|
dbg_log ("/")
|
|
|
|
dbg_log_num (table[idx + 0x200])
|
|
|
|
dbg_log (" table: ")
|
|
|
|
dbg_log_num ((unsigned)table)
|
|
|
|
dbg_log ("\n")
|
2009-08-18 00:11:15 +03:00
|
|
|
mem->unmap ((kPage *)table[idx + 0x200], address)
|
|
|
|
table[idx] = make_entry_lo (page, readonly)
|
2009-05-26 01:42:47 +03:00
|
|
|
table[idx + 0x200] = (unsigned)p
|
2010-01-16 17:13:54 +02:00
|
|
|
dbg_log ("mapped at address ")
|
|
|
|
dbg_log_num (address)
|
|
|
|
dbg_log_char ('\n')
|
2009-08-18 00:11:15 +03:00
|
|
|
p->mapping = address + readonly
|
2009-05-26 01:42:47 +03:00
|
|
|
p->page = page
|
|
|
|
p->next_mapped = page->arch.first_mapped
|
|
|
|
if p->next_mapped:
|
|
|
|
p->next_mapped->prev_mapped = p
|
|
|
|
page->arch.first_mapped = p
|
2009-05-23 21:55:31 +03:00
|
|
|
return true
|
2009-05-20 23:07:56 +03:00
|
|
|
|
2009-08-18 00:11:15 +03:00
|
|
|
void kMemory_arch_unmap (kMemory *mem, kPage *page, unsigned address):
|
2009-12-30 23:41:45 +02:00
|
|
|
//dbg_log_line ()
|
2009-05-26 01:42:47 +03:00
|
|
|
unsigned didx = address >> 21
|
|
|
|
unsigned tidx = (address >> 12) & ((1 << 9) - 1)
|
|
|
|
unsigned *table = mem->arch.directory[didx]
|
|
|
|
arch_page_table *t = mem->arch.shadow[didx]
|
|
|
|
table[tidx] = 0
|
|
|
|
arch_page *p = (arch_page *)table[tidx + 0x200]
|
|
|
|
table[tidx + 0x200] = 0
|
|
|
|
free_page (t, p)
|
2009-05-20 23:07:56 +03:00
|
|
|
|
2009-08-18 00:11:15 +03:00
|
|
|
kPage *kMemory_arch_get_mapping (kMemory *mem, unsigned address, bool *readonly):
|
2009-12-30 23:41:45 +02:00
|
|
|
//dbg_log_line ()
|
2009-12-27 01:12:35 +02:00
|
|
|
if address >= 0x80000000 || !mem->arch.directory:
|
2009-06-01 02:12:54 +03:00
|
|
|
return NULL
|
2009-05-26 01:42:47 +03:00
|
|
|
unsigned *table = mem->arch.directory[address >> 21]
|
2009-12-27 01:12:35 +02:00
|
|
|
if !table:
|
|
|
|
return NULL
|
2009-05-26 01:42:47 +03:00
|
|
|
unsigned idx = (address >> 12) & ((1 << 9) - 1)
|
|
|
|
arch_page *page = (arch_page *)table[idx + 0x200]
|
2009-12-27 01:12:35 +02:00
|
|
|
if !page:
|
|
|
|
return NULL
|
2009-08-18 00:11:15 +03:00
|
|
|
if readonly:
|
|
|
|
*readonly = !(table[idx] & 4)
|
2009-05-26 01:42:47 +03:00
|
|
|
return page->page
|
2009-05-20 23:07:56 +03:00
|
|
|
|
2009-08-18 00:11:15 +03:00
|
|
|
void kPage_arch_update_mapping (kPage *page):
|
2009-12-30 23:41:45 +02:00
|
|
|
//dbg_log_line ()
|
2009-06-01 02:12:54 +03:00
|
|
|
if !page->arch.first_mapped:
|
|
|
|
return
|
2009-08-18 00:11:15 +03:00
|
|
|
kMemory *as = page->address_space
|
2009-09-06 12:04:09 +03:00
|
|
|
unsigned target = make_entry_lo (page, page->flags & Kernel::Page::READONLY)
|
2009-06-01 02:12:54 +03:00
|
|
|
for arch_page *p = page->arch.first_mapped; p; p = p->next_mapped:
|
|
|
|
unsigned de = p->mapping >> 21
|
|
|
|
unsigned te = (p->mapping >> 12) & ((1 << 9) - 1)
|
2009-08-18 00:11:15 +03:00
|
|
|
bool readonly = p->mapping & 1
|
2009-06-01 02:12:54 +03:00
|
|
|
unsigned t
|
2009-08-18 00:11:15 +03:00
|
|
|
if readonly:
|
2009-06-01 02:12:54 +03:00
|
|
|
t = target & ~0x4
|
2009-08-18 00:11:15 +03:00
|
|
|
else:
|
|
|
|
t = target
|
2009-06-01 02:12:54 +03:00
|
|
|
as->arch.directory[de][te] = t
|
|
|
|
tlb_reset (p->mapping & ~1, as->arch.asid, t)
|
2009-05-29 00:35:27 +03:00
|
|
|
|
2009-12-30 23:41:45 +02:00
|
|
|
typedef unsigned cacheline[8]
|
|
|
|
void arch_uncache_page (unsigned page):
|
|
|
|
for cacheline *line = (cacheline *)page; line < (cacheline *)(page + PAGE_SIZE); ++line:
|
|
|
|
__asm__ volatile ("lw $k0, %0; cache 0x10, 0($k0); cache 0x11, 0($k0)" :: "m"(line))
|
|
|
|
|
2009-08-18 00:11:15 +03:00
|
|
|
void arch_register_interrupt (unsigned num, kReceiver *r):
|
2009-05-27 19:33:05 +03:00
|
|
|
arch_interrupt_receiver[num] = r
|
2009-06-01 02:12:54 +03:00
|
|
|
// And enable or disable the interrupt.
|
|
|
|
if r:
|
2009-07-04 17:21:28 +03:00
|
|
|
intc_unmask_irq (num)
|
2009-06-01 02:12:54 +03:00
|
|
|
else:
|
2009-07-04 17:21:28 +03:00
|
|
|
intc_mask_irq (num)
|