// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019 MediaTek Inc. */ #include #include #include #include #include #include #include #include "sspm_define.h" #include "sspm_common.h" #include "sspm_mbox.h" #include "sspm_ipi.h" #include "sspm_ipi_mbox.h" #ifdef SSPM_STF_ENABLED #include #include "sspm_stf.h" #endif #define IPI_MONITOR #define TIMEOUT_COMPLETE msecs_to_jiffies(2000) /* #define GET_IPI_TIMESTAMP */ #ifdef GET_IPI_TIMESTAMP #include #define IPI_TS_TEST_MAX 20 #define IPI_TS_TEST_PIN IPI_ID_PMIC_WRAP u64 ipi_t0[IPI_TS_TEST_MAX]; u64 ipi_t4[IPI_TS_TEST_MAX]; u64 ipi_t5[IPI_TS_TEST_MAX]; static int test_cnt; #endif #ifdef IPI_MONITOR #define IPI_MONITOR_TIMESTAMP struct ipi_monitor { /* 0: has no timestamp of t1/t2/t3 otherwise 1*/ unsigned int has_time: 1, /* 0: no IPI, 1: t1 finished, 2: t2 finished, 3: t3 finished */ state : 2, /* count of the IPI pin used */ seqno : 29; #ifdef IPI_MONITOR_TIMESTAMP unsigned long long t0; unsigned long long t4; unsigned long long t5; #endif /* IPI_MONITOR_TIMESTAMP */ }; struct _mbox_info *mbox_table; struct _pin_send *send_pintable; struct _pin_recv *recv_pintable; char *(*pin_name); static struct ipi_monitor ipimon[IPI_ID_TOTAL]; static int ipi_last; static spinlock_t lock_monitor; #ifdef IPI_MONITOR_TIMESTAMP static int err_pin; static unsigned long long err_ts; #endif /* IPI_MONITOR_TIMESTAMP */ static inline int check_table_tag(int mcnt) { int i, j = 0, k = 0, n = 0; uint32_t data, check; struct _pin_recv *rpin = NULL; struct _pin_send *spin = NULL; for (i = 0; i < mcnt; i++) { if (mbox_table[i].mode == 0) continue; /* Write init data into mailbox */ j = mbox_table[i].start; if (mbox_table[i].mode == 1) { /* for recev */ rpin = &(recv_pintable[j]); data = 2; } else if (mbox_table[i].mode == 2) { /* for send */ spin = &(send_pintable[j]); data = 1; } else { pr_debug("Error: mbox %d has unsupported mode=%d\n", i, mbox_table[i].mode); return -2; } data = ((data<<16)|('M'<<24)); if (IPI_MBOX_MODE & (1 << i)) { /* 64 slots in the mailbox */ data |= 0x00800000; } /* for each pin in the mbox */ for (; j <= mbox_table[i].end; j++) { if (mbox_table[i].mode == 1) { /* for recev */ k = rpin->slot; n = rpin->size; rpin++; } else if (mbox_table[i].mode == 2) { /* for send */ k = spin->slot; n = spin->size; spin++; } /* for each slot in the pin */ data &= ~0xFFFF; data |= (j << 8)|((uint8_t)(n)); while (n) { sspm_mbox_read(i, k, &check, 1); if (check != data) { pr_debug("Error: IPI Dismatch!! mbox:%d pin:%d slot=%d should be %08X but now %08X\n", i, j, k, data, check); return -3; } k++; n--; } } } return 0; } static void ipi_monitor_dump_timeout(int mid, int opts) { int i; unsigned long flags = 0; spin_lock_irqsave(&lock_monitor, flags); #ifdef IPI_MONITOR_TIMESTAMP err_pin = -1; err_ts = ULLONG_MAX; for (i = 0; i < IPI_ID_TOTAL; i++) { if ((ipimon[i].state == 1) || (ipimon[i].state == 2)) { if (ipimon[i].t0 < err_ts) { err_ts = ipimon[i].t0; err_pin = i; } } } if (err_pin >= 0) { pr_err("Error: possible error IPI %d pin=%s: t0=%llu\n", err_pin, pin_name[err_pin], err_ts); } #endif /* IPI_MONITOR_TIMESTAMP */ pr_err("Error: IPI %d pin=%s mode=%d timeout at %lld (lastOK IPI=%d)\n", mid, pin_name[mid], opts, cpu_clock(0), ipi_last); #ifdef IPI_MONITOR_TIMESTAMP for (i = 0; i < IPI_ID_TOTAL; i++) { if ((ipimon[i].state == 0) || (ipimon[i].state == 3)) pr_err("IPI %d: seqno=%d, state=%d, t0=%lld, t4=%lld, t5=%lld\n", i, ipimon[i].seqno, ipimon[i].state, ipimon[i].t0, ipimon[i].t4, ipimon[i].t5); else pr_err("IPI %d: seqno=%d, state_err=%d, t0=%lld, t4=%lld, t5=%lld\n", i, ipimon[i].seqno, ipimon[i].state, ipimon[i].t0, ipimon[i].t4, ipimon[i].t5); } #else for (i = 0; i < IPI_ID_TOTAL; i++) { if ((ipimon[i].state == 0) || (ipimon[i].state == 3)) pr_err("IPI %d: seqno=%d, state=%d\n", i, ipimon[i].seqno, ipimon[i].state); else pr_err("IPI %d: seqno=%d, state_err=%d\n", i, ipimon[i].seqno, ipimon[i].state); } #endif /* IPI_MONITOR_TIMESTAMP */ spin_unlock_irqrestore(&lock_monitor, flags); pr_err("Error: SSPM IPI=%d timeout\n", mid); sspm_ipi_timeout_cb(mid); BUG_ON(1); } #endif static void ipi_check_send(int mid) { #ifdef SSPM_STF_ENABLED if (test_table[mid].data) test_table[mid].start_us = (unsigned int)(cpu_clock(0)/1000); #endif /* SSPM_STF_ENABLED */ #ifdef GET_IPI_TIMESTAMP if ((mid == IPI_TS_TEST_PIN) && (test_cnt < IPI_TS_TEST_MAX)) ipi_t0[test_cnt] = cpu_clock(0); #endif /* GET_IPI_TIMESTAMP */ #ifdef IPI_MONITOR ipimon[mid].seqno++; #ifdef IPI_MONITOR_TIMESTAMP ipimon[mid].t0 = cpu_clock(0); ipimon[mid].t4 = 0; ipimon[mid].t5 = 0; #endif /* IPI_MONITOR_TIMESTAMP */ ipimon[mid].state = 1; #endif /* IPI_MONITOR */ } static void ipi_check_ack(int mid, int opts, int ret) { if (!ret) { #ifdef SSPM_STF_ENABLED if (test_table[mid].data) { struct chk_data *pdata = test_table[mid].data; int cnt = test_table[mid].test_cnt; pdata[cnt].time_spent = ((unsigned int) (cpu_clock(0)/1000) - test_table[mid].start_us); if (retbuf) pdata[cnt].ack_data_feedback = *((unsigned int *)retbuf); else pdata[cnt].ack_data_feedback = 0; test_table[mid].test_cnt++; } #endif /* SSPM_STF_ENABLED */ #ifdef GET_IPI_TIMESTAMP if ((mid == IPI_TS_TEST_PIN) && (test_cnt < IPI_TS_TEST_MAX)) { ipi_t5[test_cnt] = cpu_clock(0); test_cnt++; } if (test_cnt >= IPI_TS_TEST_MAX) { int i; for (i = 0; i < IPI_TS_TEST_MAX; i++) pr_err("IPI %d: t0=%llu, t4=%llu, t5=%llu\n", i, ipi_t0[i], ipi_t4[i], ipi_t5[i]); test_cnt = 0; } #endif /* GET_IPI_TIMESTAMP */ #ifdef IPI_MONITOR #ifdef IPI_MONITOR_TIMESTAMP ipimon[mid].t5 = cpu_clock(0); #endif /* IPI_MONITOR_TIMESTAMP */ ipimon[mid].state = 3; ipi_last = mid; } else { /* timeout case */ ipi_monitor_dump_timeout(mid, opts); } #else } #endif /* IPI_MONITOR */ } atomic_t lock_send[TOTAL_SEND_PIN]; atomic_t lock_ack[TOTAL_SEND_PIN]; spinlock_t lock_polling[TOTAL_SEND_PIN]; /* used for IPI module isr to sync with its task */ struct completion sema_ipi_task[TOTAL_RECV_PIN]; struct mutex mutex_ipi_reg; static int sspm_ipi_inited; static unsigned int ipi_isr_cb(unsigned int mbox, void __iomem *base, unsigned int irq); int sspm_ipi_init(void) { int i, ret; struct _pin_send *pin; #ifdef IPI_MONITOR spin_lock_init(&lock_monitor); #ifdef IPI_MONITOR_TIMESTAMP for (i = 0; i < IPI_ID_TOTAL; i++) ipimon[i].has_time = 1; #endif /* IPI_MONITOR_TIMESTAMP */ #endif /* IPI_MONITOR */ mutex_init(&mutex_ipi_reg); for (i = 0; i < TOTAL_SEND_PIN; i++) { mutex_init(&send_pintable[i].mutex_send); init_completion(&send_pintable[i].comp_ack); atomic_set(&lock_send[i], 1); atomic_set(&lock_ack[i], 0); spin_lock_init(&lock_polling[i]); } /* IPI HW initialize and ISR registration */ if (sspm_mbox_init(IPI_MBOX_MODE, IPI_MBOX_TOTAL, ipi_isr_cb) != 0) { pr_err("Error: sspm_mbox_init failed\n"); return -1; } for (i = 0; i < TOTAL_SEND_PIN; i++) { pin = &(send_pintable[i]); pin->prdata = sspm_mbox_addr(pin->mbox, pin->slot); } ret = check_table_tag(IPI_MBOX_TOTAL); if (ret == 0) sspm_ipi_inited = 1; return ret; } extern int sspm_ipi_is_inited(void) { return sspm_ipi_inited; } int sspm_ipi_recv_registration(int mid, struct ipi_action *act) { struct _pin_recv *pin; if (sspm_ipi_inited == 0) return IPI_SERVICE_NOT_INITED; if ((mid < 0) || (mid >= TOTAL_RECV_PIN)) return IPI_SERVICE_NOT_AVAILABLE; if (act == NULL) return IPI_REG_ACTION_ERROR; pin = &(recv_pintable[mid]); act->id = mid; act->lock = NULL; mutex_lock(&mutex_ipi_reg); if (pin->act != NULL) { mutex_unlock(&mutex_ipi_reg); return IPI_REG_ALREADY; } mutex_unlock(&mutex_ipi_reg); init_completion(&sema_ipi_task[mid]); pin->act = act; return IPI_REG_OK; } EXPORT_SYMBOL_GPL(sspm_ipi_recv_registration); int sspm_ipi_recv_registration_ex(int mid, spinlock_t *lock, struct ipi_action *act) { int ret = IPI_REG_OK; ret = sspm_ipi_recv_registration(mid, act); if (ret != IPI_REG_OK) return ret; act->lock = lock; return IPI_REG_OK; } EXPORT_SYMBOL_GPL(sspm_ipi_recv_registration_ex); int sspm_ipi_recv_wait(int mid) { struct _pin_recv *pin; if (sspm_ipi_inited == 0) return IPI_SERVICE_NOT_INITED; if ((mid < 0) || (mid >= TOTAL_RECV_PIN)) return IPI_SERVICE_NOT_AVAILABLE; pin = &(recv_pintable[mid]); wait_for_completion(&sema_ipi_task[mid]); /* if the pin is waiting async data, eliminate multiple completions */ if (pin->act->lock) while (try_wait_for_completion(&sema_ipi_task[mid])) ; return 0; } EXPORT_SYMBOL_GPL(sspm_ipi_recv_wait); void sspm_ipi_recv_complete(int mid) { complete(&sema_ipi_task[mid]); } EXPORT_SYMBOL_GPL(sspm_ipi_recv_complete); int sspm_ipi_recv_unregistration(int mid) { struct _pin_recv *pin; pin = &(recv_pintable[mid]); pin->act = NULL; return IPI_REG_OK; } EXPORT_SYMBOL_GPL(sspm_ipi_recv_unregistration); static void ipi_do_ack(struct _mbox_info *mbox, unsigned int in_irq, void __iomem *base) { /* executed from ISR */ int idx_end = mbox->end; int idx_start = mbox->start; int i; struct _pin_send *pin = &(send_pintable[idx_start]); for (i = idx_start; i <= idx_end; i++, pin++) { if ((in_irq & 0x01) == 0x01) { /* irq bit enable */ atomic_inc(&lock_ack[i]); /* check if pin user send in WAIT mode, * wait lock & continue if not */ if (mutex_is_locked(&pin->mutex_send)) { /* WAIT mode */ #ifdef GET_IPI_TIMESTAMP if ((i == IPI_TS_TEST_PIN) && (test_cnt < IPI_TS_TEST_MAX)) ipi_t4[test_cnt] = cpu_clock(0); #endif /* GET_IPI_TIMESTAMP */ #ifdef IPI_MONITOR #ifdef IPI_MONITOR_TIMESTAMP ipimon[i].t4 = cpu_clock(0); #endif /* IPI_MONITOR_TIMESTAMP */ ipimon[i].state = 2; #endif /* IPI_MONITOR */ complete(&pin->comp_ack); } } in_irq >>= 1; } } static int handle_action(struct ipi_action *action, void *mbox_addr, int bytelen) { /* if user has no data, just wakeup user without data */ if (action->data == NULL) return 1; /* if user async send without waiting ACK from SSPM */ /* use spin lock to mempcy without overwriting user data */ if (action->lock) { if (spin_trylock(action->lock)) { memcpy_from_sspm(action->data, mbox_addr, bytelen); } else { /* Users has lock. Just drop data */ return 0; } spin_unlock(action->lock); } else { memcpy_from_sspm(action->data, mbox_addr, bytelen); } return 1; } static void ipi_do_recv(struct _mbox_info *mbox, unsigned int in_irq, void __iomem *base) { /* executed from ISR */ /* get the value from INT_IRQ_x (MD32 side) or OUT_IRQ_0 (Linux side) */ int idx_end = mbox->end; int idx_start = mbox->start; int i, ret; struct _pin_recv *pin; struct ipi_action *action; if (in_irq == 0) return; /* check each bit for interrupt triggered */ /* the bit is used to determine the index of callback array */ pin = &(recv_pintable[idx_start]); for (i = idx_start; i <= idx_end; i++, pin++) { if ((in_irq & 0x01) == 0x01) { /* irq bit enable */ action = pin->act; if (action != NULL) { /* do the action */ ret = handle_action(action, (void *) (base + (pin->slot * MBOX_SLOT_SIZE)), pin->size * MBOX_SLOT_SIZE); if (ret) complete(&sema_ipi_task[i]); } } /* check bit is enabled */ in_irq >>= 1; } /* check INT_IRQ bits */ } int sspm_ipi_send_async(int mid, int opts, void *buffer, int slot) { int mbno, ret, lock = 0; struct _pin_send *pin; struct _mbox_info *mbox; int timeout; if (sspm_ipi_inited == 0) return IPI_SERVICE_NOT_INITED; if ((mid < 0) || (mid >= TOTAL_SEND_PIN)) return IPI_SERVICE_NOT_AVAILABLE; pin = &(send_pintable[mid]); if (!pin->async) return IPI_PIN_MISUES; if (slot > pin->size) return IPI_NO_MEMORY; ipi_check_send(mid); mbno = pin->mbox; mbox = &(mbox_table[mbno]); if (!(opts & IPI_OPT_REDEF_MASK)) { lock = pin->lock & IPI_LOCK_ORIGINAL; } else { if (opts & IPI_OPT_LOCK_MASK) { lock = 1; pin->lock |= 0x6; } else { pin->lock |= 0x4; } } if (lock == 0) { /* use mutex */ mutex_lock(&pin->mutex_send); } else { /* use spin method */ timeout = 0xffff; while (atomic_read(&lock_send[mid]) == 0) { timeout--; udelay(10); /* fix me later, should we add this one? */ if (timeout == 0) { if (pin->lock & IPI_LOCK_CHANGE) pin->lock &= IPI_LOCK_ORIGINAL; return IPI_TIMEOUT_AVL; } } atomic_dec(&lock_send[mid]); } atomic_set(&lock_ack[mid], 0); mbno = pin->mbox; mbox = &(mbox_table[mbno]); /* note: the bit of INT(OUT)_IRQ is depending on mid */ if (slot == 0) slot = pin->size; ret = sspm_mbox_send(mbno, pin->slot, mid - mbox->start, buffer, slot); if (ret != 0) { #ifdef IPI_MONITOR ipimon[mid].seqno--; #endif /* release lock */ if (lock == 0) /* use mutex */ mutex_unlock(&pin->mutex_send); else atomic_inc(&lock_send[mid]); if (pin->lock & IPI_LOCK_CHANGE) pin->lock &= IPI_LOCK_ORIGINAL; return IPI_HW_ERROR; } return IPI_DONE; } EXPORT_SYMBOL_GPL(sspm_ipi_send_async); int sspm_ipi_send_async_wait(int mid, int opts, void *retbuf) { int slot = 1; if (retbuf == NULL) slot = 0; return sspm_ipi_send_async_wait_ex(mid, opts, retbuf, slot); } EXPORT_SYMBOL_GPL(sspm_ipi_send_async_wait); int sspm_ipi_send_async_wait_ex(int mid, int opts, void *retbuf, int retslot) { int ret = 0, lock = 0, polling = 0; struct _pin_send *pin; unsigned long wait_comp; if ((mid < 0) || (mid >= TOTAL_SEND_PIN)) return IPI_SERVICE_NOT_AVAILABLE; pin = &(send_pintable[mid]); if (!pin->async) return IPI_PIN_MISUES; if (retslot > pin->size) return IPI_NO_MEMORY; if (!(opts & IPI_OPT_REDEF_MASK)) { lock = pin->lock & IPI_LOCK_ORIGINAL; polling = pin->polling; } else { if (opts & IPI_OPT_LOCK_MASK) { lock = 1; pin->lock |= 0x6; if (opts & IPI_OPT_POLLING_MASK) polling = 1; } else { pin->lock |= 0x4; } } if (!lock) { /* use completion */ wait_comp = wait_for_completion_timeout(&pin->comp_ack, TIMEOUT_COMPLETE); if ((wait_comp == 0) && (atomic_read(&lock_ack[mid]) == 0)) { /* wait mode timeout */ ret = IPI_TIMEOUT_ACK; } else { if (retbuf) memcpy_from_sspm(retbuf, pin->prdata, (MBOX_SLOT_SIZE * retslot)); } atomic_set(&lock_ack[mid], 0); } else { /* use spin method */ int retries = 2000000; while (retries-- > 0) { int mbno = pin->mbox; struct _mbox_info *mbox = &(mbox_table[mbno]); ret = sspm_mbox_polling(mbno, mid - mbox->start, pin->slot, retbuf, retslot, 2000); if (!ret) break; if (atomic_read(&lock_ack[mid])) { if (retbuf) memcpy_from_sspm(retbuf, pin->prdata, (MBOX_SLOT_SIZE * retslot)); ret = 0; break; } udelay(1); } atomic_set(&lock_ack[mid], 0); if (retries == 0) ret = IPI_TIMEOUT_ACK; } /* Release mutex */ if (!lock) /* use mutex */ mutex_unlock(&pin->mutex_send); else atomic_inc(&lock_send[mid]); if (pin->lock & IPI_LOCK_CHANGE) pin->lock &= IPI_LOCK_ORIGINAL; ipi_check_ack(mid, opts, ret); return ret; } EXPORT_SYMBOL_GPL(sspm_ipi_send_async_wait_ex); int sspm_ipi_send_ack(int mid, unsigned int *data) { int len = 1; if (!data) len = 0; return sspm_ipi_send_ack_ex(mid, data, len); } EXPORT_SYMBOL_GPL(sspm_ipi_send_ack); int sspm_ipi_send_ack_ex(int mid, void *data, int retslot) { struct _pin_recv *pin; struct _mbox_info *mbox; int len, mbno, irq, slot, ret; if ((mid < 0) || (mid >= TOTAL_RECV_PIN)) return IPI_SERVICE_NOT_AVAILABLE; pin = &(recv_pintable[mid]); if (retslot > pin->size) return IPI_NO_MEMORY; mbno = pin->mbox; mbox = &(mbox_table[mbno]); irq = mid - (mbox->start); /* return data length */ if (data && (pin->retdata)) len = retslot; else len = 0; /* where to put the return data */ slot = pin->slot; ret = sspm_mbox_send(mbno, slot, irq, (void *)data, len); if (ret) return -1; return 0; } EXPORT_SYMBOL_GPL(sspm_ipi_send_ack_ex); int sspm_ipi_send_sync(int mid, int opts, void *buffer, int slot, void *retbuf, int retslot) { unsigned long flags = 0; unsigned long wait_comp; int mbno, ret; struct _pin_send *pin; struct _mbox_info *mbox; if (sspm_ipi_inited == 0) return IPI_SERVICE_NOT_INITED; /* check if mid is in the predefined range */ if ((mid < 0) || (mid >= TOTAL_SEND_PIN)) return IPI_SERVICE_NOT_AVAILABLE; /* get the predefined pin info from mid */ pin = &(send_pintable[mid]); if ((slot > pin->size) || (retslot > pin->size)) return IPI_NO_MEMORY; /* check if IPI can be send in different mode */ if (opts&IPI_OPT_POLLING) { /* POLLING mode */ spin_lock_irqsave(&lock_polling[mid], flags); if (mutex_is_locked(&pin->mutex_send)) { spin_unlock_irqrestore(&lock_polling[mid], flags); pr_err("Error: IPI pin=%d has been used in WAIT mode\n", mid); BUG_ON(1); return IPI_USED_IN_WAIT; } } else { /* WAIT mode */ /* Check if users call in atomic/interrupt/IRQ disabled */ if (preempt_count() || in_interrupt() || irqs_disabled()) { pr_err("IPI panic: pin id=%d, atomic=%d, interrupt=%ld, irq disabled=%d\n", mid, preempt_count(), in_interrupt(), irqs_disabled()); BUG_ON(1); } mutex_lock(&pin->mutex_send); } ipi_check_send(mid); mbno = pin->mbox; mbox = &(mbox_table[mbno]); /* note: the bit of INT(OUT)_IRQ is depending on mid */ if (slot == 0) slot = pin->size; atomic_set(&lock_ack[mid], 0); /* send IPI data to SSPM */ ret = sspm_mbox_send(mbno, pin->slot, mid - mbox->start, buffer, slot); if (ret != 0) { #ifdef IPI_MONITOR ipimon[mid].seqno--; #endif /* release lock */ if (opts&IPI_OPT_POLLING) /* POLLING mode */ spin_unlock_irqrestore(&lock_polling[mid], flags); else mutex_unlock(&pin->mutex_send); return IPI_HW_ERROR; } /* if there is no retdata in predefined table */ if (!pin->retdata || !retslot) retbuf = NULL; /* wait ACK from SSPM */ if (opts&IPI_OPT_POLLING) { /* POLLING mode */ int retries = 2000000; while (retries-- > 0) { ret = sspm_mbox_polling(mbno, mid - mbox->start, pin->slot, retbuf, retslot, 2000); if (!ret) break; if (atomic_read(&lock_ack[mid])) { if (retbuf) memcpy_from_sspm(retbuf, pin->prdata, (MBOX_SLOT_SIZE * retslot)); ret = 0; break; } udelay(1); } atomic_set(&lock_ack[mid], 0); if (retries == 0) /* polling mode timeout */ ret = IPI_TIMEOUT_ACK; ipi_check_ack(mid, opts, ret); spin_unlock_irqrestore(&lock_polling[mid], flags); } else { /* WAIT mode */ wait_comp = wait_for_completion_timeout(&pin->comp_ack, TIMEOUT_COMPLETE); if ((wait_comp == 0) && (atomic_read(&lock_ack[mid]) == 0)) { /* wait mode timeout */ ret = IPI_TIMEOUT_ACK; } else { if (retbuf) memcpy_from_sspm(retbuf, pin->prdata, (MBOX_SLOT_SIZE * retslot)); } atomic_set(&lock_ack[mid], 0); ipi_check_ack(mid, opts, ret); mutex_unlock(&pin->mutex_send); } return ret; } EXPORT_SYMBOL_GPL(sspm_ipi_send_sync); static unsigned int ipi_isr_cb(unsigned int mbno, void __iomem *base, unsigned int irq) { struct _mbox_info *mbox; if (mbno >= IPI_MBOX_TOTAL) return irq; mbox = &(mbox_table[mbno]); if (mbox->mode == 2) /* ipi_do_ack */ ipi_do_ack(mbox, irq, base); else if (mbox->mode == 1) /* ipi_do_recv */ ipi_do_recv(mbox, irq, base); return irq; }