From ab9b4478a3ea40b75f98d9bb0f1a424a38a4cc30 Mon Sep 17 00:00:00 2001 From: Daniel Hellstrom Date: Sun, 22 Jan 2017 15:01:55 +0100 Subject: leon, grspw_pkt: added work-task configuration options Following changes: * possible for user to create work-tasks and assign custom message queues. * possible for user to override default ISR message to implement custom handling of DMA error, DMA RX/TX and link error from ISR. * work-task now checks message to determine which work to perform rather than looking at registers only, this makes it possible for user to implement custom handling. * exported work-queue message definitions and separated them so that a user can assign custom DMA RX/TX handling of a specific DMA channel. * added a work-task event callback to let user add custom handling or monitoring of DMA Stop, DMA error, Link Error or work-task exits etc. --- c/src/lib/libbsp/sparc/shared/include/grspw_pkt.h | 98 +++++++++ c/src/lib/libbsp/sparc/shared/spw/grspw_pkt.c | 252 +++++++++++++++------- 2 files changed, 277 insertions(+), 73 deletions(-) diff --git a/c/src/lib/libbsp/sparc/shared/include/grspw_pkt.h b/c/src/lib/libbsp/sparc/shared/include/grspw_pkt.h index 3a079c571d..5b3a606c4e 100644 --- a/c/src/lib/libbsp/sparc/shared/include/grspw_pkt.h +++ b/c/src/lib/libbsp/sparc/shared/include/grspw_pkt.h @@ -311,6 +311,50 @@ struct grspw_dma_stats { int rx_work_enabled; /* No. RX BDs enabled by work thread */ }; +/* ISR message sending call back. Compatible with rtems_message_queue_send(). + * The 'buf' parameter has a pointer to a WORK-TASK message defined by the + * WORK_* macros below. The message indicates what GRSPW device operations + * are pending, thus what caused the interrupt. + * + * \param data defined by grspw_work_config.msgisr_arg, default a rtems_id. + * \param buf Pointer to a 32-bit message word + * \param n Always 4 (byte size of buf). + */ +typedef int (*grspw_msgqisr_t)(void *data, unsigned int *buf, unsigned int n); + +/* Work message definitions, the int sent to *buf + * Bits 31..24: reserved. + * Bits 23..16: GRSPW device number message is associated with. + * Bit 15: reserved. + * Bit 14: work-task shall delete message queue on exit. + * Bit 13: work-task shall exit and delete itself. + * Bit 12: link error - shut down all DMA operations (stop DMA channels). + * Bit 11..8: Indicats DMA error on DMA channel 3..0. + * Bit 7..0: Indicats RX and/or TX packets completed on channel 3..0. + */ +#define WORK_NONE 0 +#define WORK_SHUTDOWN 0x1000 /* Signal shut down */ +#define WORK_QUIT_TASK 0x2000 /* Work task shall exit (delete itself) */ +#define WORK_FREE_MSGQ 0x4000 /* Delete MsgQ (valid when WORK_QUIT_TASK) */ +#define WORK_DMA(chan, rxtx) (((rxtx) & 0x3) << ((chan) * 2)) +#define WORK_DMA_TX(chan) WORK_DMA(chan, 1) +#define WORK_DMA_RX(chan) WORK_DMA(chan, 2) +#define WORK_DMA_ER(chan) (0x1 << ((chan) + 8)) +#define WORK_DMA_MASK 0xfff /* max 4 channels all work */ +#define WORK_DMA_TX_MASK 0x055 /* max 4 channels TX work */ +#define WORK_DMA_RX_MASK 0x0aa /* max 4 channels RX work */ +#define WORK_DMA_ER_MASK 0xf00 /* max 4 channels Error work */ +#define WORK_DMA_CHAN_MASK(chan) (WORK_DMA_ER(chan) | WORK_DMA(chan, 0x3)) +#define WORK_CORE_BIT 16 +#define WORK_CORE_MASK 0x00ff0000 +#define WORK_CORE(device) ((device) << WORK_CORE_BIT) + +/* Message Q used to send messages to work task */ +struct grspw_work_config { + grspw_msgqisr_t msgisr; + void *msgisr_arg; /* example: rtems_id to Msg Q */ +}; + extern void grspw_initialize_user( /* Callback every time a GRSPW device is found. Args: DeviceIndex */ void *(*devfound)(int), @@ -320,6 +364,60 @@ extern void grspw_initialize_user( */ void (*devremove)(int,void*) ); + +/* Creates a MsgQ (optional) and spawns a worker task associated with the + * message Q. The task can also be associated with a custom msgQ if *msgQ. + * is non-zero. + * + * \param prio Task priority, set to -1 for default. + * \param stack Task stack size, set to 0 for default. + * \param msgQ pMsgQ=NULL: illegal, + * pMsqQ==0: create new MsgQ with task and place in *pMsgQ, + * *pmsqQ!=0: pointer to MsgQ used for task. + * \param msgMax Maximum number of messages, set to 0 for default. + * \return 0 on failure, task id on success. + */ +extern rtems_id grspw_work_spawn(int prio, int stack, rtems_id *pMsgQ, int msgMax); + +/* Free task associated with message queue and optionally also the message + * queue itself. The message queue is deleted by the work task and is therefore + * delayed until it the work task resumes its execution. + */ +extern rtems_status_code grspw_work_free(rtems_id msgQ, int freeMsgQ); + +/* Configure a GRSPW device Work task and Message Q set up. + * This affects messages to: + * - DMA AHB error interrupt handling (mandatory) + * - Link status interrupt handling (optional) + * - RX DMA, defaults to common msgQ (configured per DMA channel) + */ +extern void grspw_work_cfg(void *d, struct grspw_work_config *wc); + +/* Work-task function, called only from the work task. The function is provided + * as a way for the user to create its own work tasks. + * The argument determines which message queue the task shall read its + * work jobs from. + * + * The messages are always 32-bit words and follows the format defined by the + * WORK_* macros above. + */ +extern void grspw_work_func(rtems_id msgQ); + +enum grspw_worktask_ev { + WORKTASK_EV_NONE = 0, + WORKTASK_EV_QUIT = 1, + WORKTASK_EV_SHUTDOWN = 2, + WORKTASK_EV_DMA_STOP = 3, +}; + +/* Weak function to let user override. Function called every time one of the + * events above is handled by the work-task. The message 'msg' is the current + * message being processed by the work-task. + * The user can for example add custom code to invoke on a DMA error, link + * error or monitor when the work-task exits after a call to grspw_work_free(). + */ +extern void grspw_work_event(enum grspw_worktask_ev ev, unsigned int msg); + extern int grspw_dev_count(void); extern void *grspw_open(int dev_no); extern int grspw_close(void *d); diff --git a/c/src/lib/libbsp/sparc/shared/spw/grspw_pkt.c b/c/src/lib/libbsp/sparc/shared/spw/grspw_pkt.c index cfeebe47b7..8f660e7617 100644 --- a/c/src/lib/libbsp/sparc/shared/spw/grspw_pkt.c +++ b/c/src/lib/libbsp/sparc/shared/spw/grspw_pkt.c @@ -478,13 +478,15 @@ struct grspw_priv { /* Bit mask for link status bits to clear by ISR */ unsigned int stscfg; + /*** Message Queue Handling ***/ + struct grspw_work_config wc; + /* "Core Global" Statistics gathered, not dependent on DMA channel */ struct grspw_core_stats stats; }; int grspw_initialized = 0; int grspw_count = 0; -struct workqueue_struct *grspw_workq = NULL; rtems_id grspw_sem; static struct grspw_priv *priv_tab[GRSPW_MAX]; @@ -492,20 +494,22 @@ static struct grspw_priv *priv_tab[GRSPW_MAX]; void *(*grspw_dev_add)(int) = NULL; void (*grspw_dev_del)(int,void*) = NULL; +/* Defaults to do nothing - user can override this function. + * Called from work-task. + */ +void __attribute__((weak)) grspw_work_event( + enum grspw_worktask_ev ev, + unsigned int msg) +{ + +} + /* USER OVERRIDABLE - The work task priority. Set to -1 to disable creating * the work-task and work-queue to save space. */ int grspw_work_task_priority __attribute__((weak)) = 100; -int grspw_task_stop = 0; rtems_id grspw_work_task; -rtems_id grspw_work_queue = 0; -#define WORK_NONE 0 -#define WORK_SHUTDOWN 0x100 -#define WORK_DMA(channel) (0x1 << (channel)) -#define WORK_DMA_MASK 0xf /* max 4 channels */ -#define WORK_CORE_BIT 16 -#define WORK_CORE_MASK 0xffff -#define WORK_CORE(device) ((device) << WORK_CORE_BIT) +static struct grspw_work_config grspw_wc_def; STATIC void grspw_hw_stop(struct grspw_priv *priv); STATIC void grspw_hw_dma_stop(struct grspw_dma_priv *dma); @@ -546,6 +550,11 @@ void *grspw_open(int dev_no) priv->icisr_arg = NULL; priv->stscfg = LINKSTS_MASK; + /* Default to common work queue and message queue, if not created + * during initialization then its disabled. + */ + grspw_work_cfg(priv, &grspw_wc_def); + grspw_stats_clr(priv); /* Allocate TX & RX Descriptor memory area for all DMA @@ -2531,7 +2540,7 @@ static void grspw_work_shutdown_func(struct grspw_priv *priv) } /* Do DMA work on one channel, invoked indirectly from ISR */ -static void grspw_work_dma_func(struct grspw_dma_priv *dma) +static void grspw_work_dma_func(struct grspw_dma_priv *dma, unsigned int msg) { int tx_cond_true, rx_cond_true; unsigned int ctrl; @@ -2557,19 +2566,32 @@ static void grspw_work_dma_func(struct grspw_dma_priv *dma) if (ctrl & GRSPW_DMA_STATUS_ERROR) { /* DMA error -> Stop DMA channel (both RX and TX) */ SPIN_UNLOCK_IRQ(&priv->devlock, irqflags); - grspw_dma_stop(dma); - } else if (ctrl & (GRSPW_DMACTRL_PR | GRSPW_DMACTRL_PS)) { - /* DMA has finished a TX/RX packet */ + if (msg & WORK_DMA_ER_MASK) { + /* DMA error and user wants work-task to handle error */ + grspw_dma_stop(dma); + grspw_work_event(WORKTASK_EV_DMA_STOP, msg); + } + } else if (((msg & WORK_DMA_RX_MASK) && (ctrl & GRSPW_DMACTRL_PR)) || + ((msg & WORK_DMA_TX_MASK) && (ctrl & GRSPW_DMACTRL_PS))) { + /* DMA has finished a TX/RX packet and user wants work-task to + * take care of DMA table processing. + */ ctrl &= ~GRSPW_DMACTRL_AT; - if (dma->cfg.rx_irq_en_cnt != 0 || - (dma->cfg.flags & DMAFLAG2_RXIE)) + + if ((msg & WORK_DMA_RX_MASK) == 0) + ctrl &= ~GRSPW_DMACTRL_PR; + else if (dma->cfg.rx_irq_en_cnt != 0 || + (dma->cfg.flags & DMAFLAG2_RXIE)) ctrl |= GRSPW_DMACTRL_RI; - if (dma->cfg.tx_irq_en_cnt != 0 || - (dma->cfg.flags & DMAFLAG2_TXIE)) + if ((msg & WORK_DMA_TX_MASK) == 0) + ctrl &= ~GRSPW_DMACTRL_PS; + else if ((dma->cfg.tx_irq_en_cnt != 0 || + (dma->cfg.flags & DMAFLAG2_TXIE))) ctrl |= GRSPW_DMACTRL_TI; + REG_WRITE(&dma->regs->ctrl, ctrl); SPIN_UNLOCK_IRQ(&priv->devlock, irqflags); - if (ctrl & GRSPW_DMACTRL_PR) { + if ((msg & WORK_DMA_RX_MASK) && (ctrl & GRSPW_DMACTRL_PR)) { /* Do RX Work */ /* Take DMA channel RX lock */ @@ -2590,7 +2612,7 @@ static void grspw_work_dma_func(struct grspw_dma_priv *dma) } rtems_semaphore_release(dma->sem_rxdma); } - if (ctrl & GRSPW_DMACTRL_PS) { + if ((msg & WORK_DMA_TX_MASK) && (ctrl & GRSPW_DMACTRL_PS)) { /* Do TX Work */ /* Take DMA channel TX lock */ @@ -2624,42 +2646,50 @@ static void grspw_work_dma_func(struct grspw_dma_priv *dma) /* Work task is receiving work for the work message queue posted from * the ISR. */ -static void grspw_work_func(rtems_task_argument unused) +void grspw_work_func(rtems_id msgQ) { - rtems_status_code status; - unsigned int message; + unsigned int message = 0, msg; size_t size; struct grspw_priv *priv; int i; - while (grspw_task_stop == 0) { - /* Wait for ISR to schedule work */ - status = rtems_message_queue_receive( - grspw_work_queue, &message, - &size, RTEMS_WAIT, RTEMS_NO_TIMEOUT); - if (status != RTEMS_SUCCESSFUL) + /* Wait for ISR to schedule work */ + while (rtems_message_queue_receive(msgQ, &message, &size, + RTEMS_WAIT, RTEMS_NO_TIMEOUT) == RTEMS_SUCCESSFUL) { + if (message & WORK_QUIT_TASK) break; /* Handle work */ priv = priv_tab[message >> WORK_CORE_BIT]; - if (message & WORK_SHUTDOWN) + if (message & WORK_SHUTDOWN) { grspw_work_shutdown_func(priv); - else if (message & WORK_DMA_MASK) { - for (i = 0; i < 4; i++) { - if (message & WORK_DMA(i)) - grspw_work_dma_func(&priv->dma[i]); + + grspw_work_event(WORKTASK_EV_SHUTDOWN, message); + } else if (message & WORK_DMA_MASK) { + for (i = 0; i < priv->hwsup.ndma_chans; i++) { + msg = message & + (WORK_CORE_MASK | WORK_DMA_CHAN_MASK(i)); + if (msg) + grspw_work_dma_func(&priv->dma[i], msg); } } + message = 0; } + + if (message & WORK_FREE_MSGQ) + rtems_message_queue_delete(msgQ); + + grspw_work_event(WORKTASK_EV_QUIT, message); rtems_task_delete(RTEMS_SELF); } STATIC void grspw_isr(void *data) { struct grspw_priv *priv = data; - unsigned int dma_stat, stat, stat_clrmsk, ctrl, icctrl, timecode; + unsigned int dma_stat, stat, stat_clrmsk, ctrl, icctrl, timecode, irqs; unsigned int rxirq, rxack, intto; - int i, handled = 0, message = WORK_NONE, call_user_int_isr; + int i, handled = 0, call_user_int_isr; + unsigned int message = WORK_NONE; #ifdef RTEMS_HAS_SMP IRQFLAGS_TYPE irqflags; #endif @@ -2767,24 +2797,32 @@ STATIC void grspw_isr(void *data) /* Check for Errors and if Packets been sent or received if * respective IRQ are enabled */ -#ifdef HW_WITH_GI - if ( dma_stat & (GRSPW_DMA_STATUS_ERROR | GRSPW_DMACTRL_GI) ) { -#else - if ( (((dma_stat << 3) & (GRSPW_DMACTRL_PR | GRSPW_DMACTRL_PS)) - | GRSPW_DMA_STATUS_ERROR) & dma_stat ) { -#endif - /* Disable Further IRQs (until enabled again) - * from this DMA channel. Let the status - * bit remain so that they can be handled by - * work function. - */ - REG_WRITE(&priv->regs->dma[i].ctrl, dma_stat & - ~(GRSPW_DMACTRL_RI|GRSPW_DMACTRL_TI| - GRSPW_DMACTRL_PR|GRSPW_DMACTRL_PS| - GRSPW_DMACTRL_RA|GRSPW_DMACTRL_TA| - GRSPW_DMACTRL_AT)); - message |= WORK_DMA(i); - handled = 1; + irqs = (((dma_stat << 3) & (GRSPW_DMACTRL_PR | GRSPW_DMACTRL_PS)) + | GRSPW_DMA_STATUS_ERROR) & dma_stat; + if (!irqs) + continue; + + /* Disable Further IRQs (until enabled again) + * from this DMA channel. Let the status + * bit remain so that they can be handled by + * work function. + */ + REG_WRITE(&priv->regs->dma[i].ctrl, dma_stat & + ~(GRSPW_DMACTRL_RI|GRSPW_DMACTRL_TI| + GRSPW_DMACTRL_PR|GRSPW_DMACTRL_PS| + GRSPW_DMACTRL_RA|GRSPW_DMACTRL_TA| + GRSPW_DMACTRL_AT)); + handled = 1; + + /* DMA error has priority, if error happens it is assumed that + * the common work-queue stops the DMA operation for that + * channel and makes the DMA tasks exit from their waiting + * functions (both RX and TX tasks). + */ + if (irqs & GRSPW_DMA_STATUS_ERROR) { + message |= WORK_DMA_ER(i); + } else { + message |= WORK_DMA(i, irqs >> GRSPW_DMACTRL_PS_BIT); } } SPIN_UNLOCK(&priv->devlock, irqflags); @@ -2793,12 +2831,19 @@ STATIC void grspw_isr(void *data) priv->stats.irq_cnt++; /* Schedule work by sending message to work thread */ - if ((message != WORK_NONE) && grspw_work_queue) { + if (message != WORK_NONE && priv->wc.msgisr) { + int status; message |= WORK_CORE(priv->index); - stat = rtems_message_queue_send(grspw_work_queue, &message, 4); - if (stat != RTEMS_SUCCESSFUL) + /* func interface compatible with msgQSend() on purpose, but + * at the same time the user can assign a custom function to + * handle DMA RX/TX operations as indicated by the "message" + * and clear the handled bits before given to msgQSend(). + */ + status = priv->wc.msgisr(priv->wc.msgisr_arg, &message, 4); + if (status != RTEMS_SUCCESSFUL) { printk("grspw_isr(%d): message fail %d (0x%x)\n", - priv->index, stat, message); + priv->index, status, message); + } } } @@ -3070,6 +3115,72 @@ static int grspw2_init3(struct drvmgr_dev *dev) } /******************* Driver Implementation ***********************/ +/* Creates a MsgQ (optional) and spawns a worker task associated with the + * message Q. The task can also be associated with a custom msgQ if *msgQ. + * is non-zero. + */ +rtems_id grspw_work_spawn(int prio, int stack, rtems_id *pMsgQ, int msgMax) +{ + rtems_id tid; + int created_msgq = 0; + + if (pMsgQ == NULL) + return OBJECTS_ID_NONE; + + if (*pMsgQ == OBJECTS_ID_NONE) { + if (msgMax <= 0) + msgMax = 32; + + if (rtems_message_queue_create( + rtems_build_name('S', 'G', 'L', 'Q'), + msgMax, 4, RTEMS_FIFO, pMsgQ) != + RTEMS_SUCCESSFUL) + return OBJECTS_ID_NONE; + created_msgq = 1; + } + + if (prio < 0) + prio = grspw_work_task_priority; /* default prio */ + if (stack < 0x800) + stack = RTEMS_MINIMUM_STACK_SIZE; /* default stack size */ + + if (rtems_task_create(rtems_build_name('S', 'G', 'L', 'T'), + prio, stack, RTEMS_PREEMPT | RTEMS_NO_ASR, + RTEMS_NO_FLOATING_POINT, &tid) != RTEMS_SUCCESSFUL) + tid = OBJECTS_ID_NONE; + else if (rtems_task_start(tid, (rtems_task_entry)grspw_work_func, *pMsgQ) != + RTEMS_SUCCESSFUL) { + rtems_task_delete(tid); + tid = OBJECTS_ID_NONE; + } + + if (tid == OBJECTS_ID_NONE && created_msgq) { + rtems_message_queue_delete(*pMsgQ); + *pMsgQ = OBJECTS_ID_NONE; + } + return tid; +} + +/* Free task associated with message queue and optionally also the message + * queue itself. The message queue is deleted by the work task and is therefore + * delayed until it the work task resumes its execution. + */ +rtems_status_code grspw_work_free(rtems_id msgQ, int freeMsgQ) +{ + int msg = WORK_QUIT_TASK; + if (freeMsgQ) + msg |= WORK_FREE_MSGQ; + return rtems_message_queue_send(msgQ, &msg, 4); +} + +void grspw_work_cfg(void *d, struct grspw_work_config *wc) +{ + struct grspw_priv *priv = (struct grspw_priv *)d; + + if (wc == NULL) + wc = &grspw_wc_def; /* use default config */ + priv->wc = *wc; +} static int grspw_common_init(void) { @@ -3090,21 +3201,16 @@ static int grspw_common_init(void) * user can disable it when interrupt is not used to save resources */ if (grspw_work_task_priority != -1) { - if (rtems_message_queue_create( - rtems_build_name('S', 'G', 'L', 'Q'), 32, 4, RTEMS_FIFO, - &grspw_work_queue) != RTEMS_SUCCESSFUL) - return -1; - - if (rtems_task_create(rtems_build_name('S', 'G', 'L', 'T'), - grspw_work_task_priority, RTEMS_MINIMUM_STACK_SIZE, - RTEMS_PREEMPT | RTEMS_NO_ASR, RTEMS_NO_FLOATING_POINT, - &grspw_work_task) != RTEMS_SUCCESSFUL) - return -1; - - if (rtems_task_start(grspw_work_task, grspw_work_func, 0) != - RTEMS_SUCCESSFUL) - return -1; -} + grspw_work_task = grspw_work_spawn(-1, 0, + (rtems_id *)&grspw_wc_def.msgisr_arg, 0); + if (grspw_work_task == OBJECTS_ID_NONE) + return -2; + grspw_wc_def.msgisr = + (grspw_msgqisr_t) rtems_message_queue_send; + } else { + grspw_wc_def.msgisr = NULL; + grspw_wc_def.msgisr_arg = NULL; + } grspw_initialized = 1; return 0; -- cgit v1.2.3