| /* |
| * |
| * linux/drivers/s390/cio/qdio.c |
| * |
| * Linux for S/390 QDIO base support, Hipersocket base support |
| * version 2 |
| * |
| * Copyright 2000,2002 IBM Corporation |
| * Author(s): Utz Bacher <utz.bacher@de.ibm.com> |
| * 2.6 cio integration by Cornelia Huck <cohuck@de.ibm.com> |
| * |
| * Restriction: only 63 iqdio subchannels would have its own indicator, |
| * after that, subsequent subchannels share one indicator |
| * |
| * |
| * |
| * |
| * 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 2, 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, write to the Free Software |
| * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
| */ |
| |
| #include <linux/config.h> |
| #include <linux/module.h> |
| #include <linux/init.h> |
| |
| #include <linux/slab.h> |
| #include <linux/kernel.h> |
| #include <linux/proc_fs.h> |
| #include <linux/timer.h> |
| |
| #include <asm/ccwdev.h> |
| #include <asm/io.h> |
| #include <asm/atomic.h> |
| #include <asm/semaphore.h> |
| #include <asm/timex.h> |
| |
| #include <asm/debug.h> |
| #include <asm/qdio.h> |
| |
| #include "cio.h" |
| #include "css.h" |
| #include "device.h" |
| #include "airq.h" |
| #include "qdio.h" |
| #include "ioasm.h" |
| #include "chsc.h" |
| |
| #define VERSION_QDIO_C "$Revision: 1.101 $" |
| |
| /****************** MODULE PARAMETER VARIABLES ********************/ |
| MODULE_AUTHOR("Utz Bacher <utz.bacher@de.ibm.com>"); |
| MODULE_DESCRIPTION("QDIO base support version 2, " \ |
| "Copyright 2000 IBM Corporation"); |
| MODULE_LICENSE("GPL"); |
| |
| /******************** HERE WE GO ***********************************/ |
| |
| static const char version[] = "QDIO base support version 2 (" |
| VERSION_QDIO_C "/" VERSION_QDIO_H "/" VERSION_CIO_QDIO_H ")"; |
| |
| #ifdef QDIO_PERFORMANCE_STATS |
| static int proc_perf_file_registration; |
| static unsigned long i_p_c, i_p_nc, o_p_c, o_p_nc, ii_p_c, ii_p_nc; |
| static struct qdio_perf_stats perf_stats; |
| #endif /* QDIO_PERFORMANCE_STATS */ |
| |
| static int hydra_thinints; |
| static int omit_svs; |
| |
| static int indicator_used[INDICATORS_PER_CACHELINE]; |
| static __u32 * volatile indicators; |
| static __u32 volatile spare_indicator; |
| static atomic_t spare_indicator_usecount; |
| |
| static debug_info_t *qdio_dbf_setup; |
| static debug_info_t *qdio_dbf_sbal; |
| static debug_info_t *qdio_dbf_trace; |
| static debug_info_t *qdio_dbf_sense; |
| #ifdef CONFIG_QDIO_DEBUG |
| static debug_info_t *qdio_dbf_slsb_out; |
| static debug_info_t *qdio_dbf_slsb_in; |
| #endif /* CONFIG_QDIO_DEBUG */ |
| |
| /* iQDIO stuff: */ |
| static volatile struct qdio_q *tiq_list=NULL; /* volatile as it could change |
| during a while loop */ |
| static DEFINE_SPINLOCK(ttiq_list_lock); |
| static int register_thinint_result; |
| static void tiqdio_tl(unsigned long); |
| static DECLARE_TASKLET(tiqdio_tasklet,tiqdio_tl,0); |
| |
| /* not a macro, as one of the arguments is atomic_read */ |
| static inline int |
| qdio_min(int a,int b) |
| { |
| if (a<b) |
| return a; |
| else |
| return b; |
| } |
| |
| /***************** SCRUBBER HELPER ROUTINES **********************/ |
| |
| static inline volatile __u64 |
| qdio_get_micros(void) |
| { |
| return (get_clock() >> 10); /* time>>12 is microseconds */ |
| } |
| |
| /* |
| * unfortunately, we can't just xchg the values; in do_QDIO we want to reserve |
| * the q in any case, so that we'll not be interrupted when we are in |
| * qdio_mark_tiq... shouldn't have a really bad impact, as reserving almost |
| * ever works (last famous words) |
| */ |
| static inline int |
| qdio_reserve_q(struct qdio_q *q) |
| { |
| return atomic_add_return(1,&q->use_count) - 1; |
| } |
| |
| static inline void |
| qdio_release_q(struct qdio_q *q) |
| { |
| atomic_dec(&q->use_count); |
| } |
| |
| static volatile inline void |
| qdio_set_slsb(volatile char *slsb, unsigned char value) |
| { |
| xchg((char*)slsb,value); |
| } |
| |
| static inline int |
| qdio_siga_sync(struct qdio_q *q, unsigned int gpr2, |
| unsigned int gpr3) |
| { |
| int cc; |
| |
| QDIO_DBF_TEXT4(0,trace,"sigasync"); |
| QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); |
| |
| #ifdef QDIO_PERFORMANCE_STATS |
| perf_stats.siga_syncs++; |
| #endif /* QDIO_PERFORMANCE_STATS */ |
| |
| cc = do_siga_sync(q->irq, gpr2, gpr3); |
| if (cc) |
| QDIO_DBF_HEX3(0,trace,&cc,sizeof(int*)); |
| |
| return cc; |
| } |
| |
| static inline int |
| qdio_siga_sync_q(struct qdio_q *q) |
| { |
| if (q->is_input_q) |
| return qdio_siga_sync(q, 0, q->mask); |
| return qdio_siga_sync(q, q->mask, 0); |
| } |
| |
| /* |
| * returns QDIO_SIGA_ERROR_ACCESS_EXCEPTION as cc, when SIGA returns |
| * an access exception |
| */ |
| static inline int |
| qdio_siga_output(struct qdio_q *q) |
| { |
| int cc; |
| __u32 busy_bit; |
| __u64 start_time=0; |
| |
| #ifdef QDIO_PERFORMANCE_STATS |
| perf_stats.siga_outs++; |
| #endif /* QDIO_PERFORMANCE_STATS */ |
| |
| QDIO_DBF_TEXT4(0,trace,"sigaout"); |
| QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); |
| |
| for (;;) { |
| cc = do_siga_output(q->irq, q->mask, &busy_bit); |
| //QDIO_PRINT_ERR("cc=%x, busy=%x\n",cc,busy_bit); |
| if ((cc==2) && (busy_bit) && (q->is_iqdio_q)) { |
| if (!start_time) |
| start_time=NOW; |
| if ((NOW-start_time)>QDIO_BUSY_BIT_PATIENCE) |
| break; |
| } else |
| break; |
| } |
| |
| if ((cc==2) && (busy_bit)) |
| cc |= QDIO_SIGA_ERROR_B_BIT_SET; |
| |
| if (cc) |
| QDIO_DBF_HEX3(0,trace,&cc,sizeof(int*)); |
| |
| return cc; |
| } |
| |
| static inline int |
| qdio_siga_input(struct qdio_q *q) |
| { |
| int cc; |
| |
| QDIO_DBF_TEXT4(0,trace,"sigain"); |
| QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); |
| |
| #ifdef QDIO_PERFORMANCE_STATS |
| perf_stats.siga_ins++; |
| #endif /* QDIO_PERFORMANCE_STATS */ |
| |
| cc = do_siga_input(q->irq, q->mask); |
| |
| if (cc) |
| QDIO_DBF_HEX3(0,trace,&cc,sizeof(int*)); |
| |
| return cc; |
| } |
| |
| /* locked by the locks in qdio_activate and qdio_cleanup */ |
| static __u32 * volatile |
| qdio_get_indicator(void) |
| { |
| int i; |
| |
| for (i=1;i<INDICATORS_PER_CACHELINE;i++) |
| if (!indicator_used[i]) { |
| indicator_used[i]=1; |
| return indicators+i; |
| } |
| atomic_inc(&spare_indicator_usecount); |
| return (__u32 * volatile) &spare_indicator; |
| } |
| |
| /* locked by the locks in qdio_activate and qdio_cleanup */ |
| static void |
| qdio_put_indicator(__u32 *addr) |
| { |
| int i; |
| |
| if ( (addr) && (addr!=&spare_indicator) ) { |
| i=addr-indicators; |
| indicator_used[i]=0; |
| } |
| if (addr == &spare_indicator) |
| atomic_dec(&spare_indicator_usecount); |
| } |
| |
| static inline volatile void |
| tiqdio_clear_summary_bit(__u32 *location) |
| { |
| QDIO_DBF_TEXT5(0,trace,"clrsummb"); |
| QDIO_DBF_HEX5(0,trace,&location,sizeof(void*)); |
| |
| xchg(location,0); |
| } |
| |
| static inline volatile void |
| tiqdio_set_summary_bit(__u32 *location) |
| { |
| QDIO_DBF_TEXT5(0,trace,"setsummb"); |
| QDIO_DBF_HEX5(0,trace,&location,sizeof(void*)); |
| |
| xchg(location,-1); |
| } |
| |
| static inline void |
| tiqdio_sched_tl(void) |
| { |
| tasklet_hi_schedule(&tiqdio_tasklet); |
| } |
| |
| static inline void |
| qdio_mark_tiq(struct qdio_q *q) |
| { |
| unsigned long flags; |
| |
| QDIO_DBF_TEXT4(0,trace,"mark iq"); |
| QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); |
| |
| spin_lock_irqsave(&ttiq_list_lock,flags); |
| if (unlikely(atomic_read(&q->is_in_shutdown))) |
| goto out_unlock; |
| |
| if (!q->is_input_q) |
| goto out_unlock; |
| |
| if ((q->list_prev) || (q->list_next)) |
| goto out_unlock; |
| |
| if (!tiq_list) { |
| tiq_list=q; |
| q->list_prev=q; |
| q->list_next=q; |
| } else { |
| q->list_next=tiq_list; |
| q->list_prev=tiq_list->list_prev; |
| tiq_list->list_prev->list_next=q; |
| tiq_list->list_prev=q; |
| } |
| spin_unlock_irqrestore(&ttiq_list_lock,flags); |
| |
| tiqdio_set_summary_bit((__u32*)q->dev_st_chg_ind); |
| tiqdio_sched_tl(); |
| return; |
| out_unlock: |
| spin_unlock_irqrestore(&ttiq_list_lock,flags); |
| return; |
| } |
| |
| static inline void |
| qdio_mark_q(struct qdio_q *q) |
| { |
| QDIO_DBF_TEXT4(0,trace,"mark q"); |
| QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); |
| |
| if (unlikely(atomic_read(&q->is_in_shutdown))) |
| return; |
| |
| tasklet_schedule(&q->tasklet); |
| } |
| |
| static inline int |
| qdio_stop_polling(struct qdio_q *q) |
| { |
| #ifdef QDIO_USE_PROCESSING_STATE |
| int gsf; |
| |
| if (!atomic_swap(&q->polling,0)) |
| return 1; |
| |
| QDIO_DBF_TEXT4(0,trace,"stoppoll"); |
| QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); |
| |
| /* show the card that we are not polling anymore */ |
| if (!q->is_input_q) |
| return 1; |
| |
| gsf=GET_SAVED_FRONTIER(q); |
| set_slsb(&q->slsb.acc.val[(gsf+QDIO_MAX_BUFFERS_PER_Q-1)& |
| (QDIO_MAX_BUFFERS_PER_Q-1)], |
| SLSB_P_INPUT_NOT_INIT); |
| /* |
| * we don't issue this SYNC_MEMORY, as we trust Rick T and |
| * moreover will not use the PROCESSING state under VM, so |
| * q->polling was 0 anyway |
| */ |
| /*SYNC_MEMORY;*/ |
| if (q->slsb.acc.val[gsf]!=SLSB_P_INPUT_PRIMED) |
| return 1; |
| /* |
| * set our summary bit again, as otherwise there is a |
| * small window we can miss between resetting it and |
| * checking for PRIMED state |
| */ |
| if (q->is_thinint_q) |
| tiqdio_set_summary_bit((__u32*)q->dev_st_chg_ind); |
| return 0; |
| |
| #else /* QDIO_USE_PROCESSING_STATE */ |
| return 1; |
| #endif /* QDIO_USE_PROCESSING_STATE */ |
| } |
| |
| /* |
| * see the comment in do_QDIO and before qdio_reserve_q about the |
| * sophisticated locking outside of unmark_q, so that we don't need to |
| * disable the interrupts :-) |
| */ |
| static inline void |
| qdio_unmark_q(struct qdio_q *q) |
| { |
| unsigned long flags; |
| |
| QDIO_DBF_TEXT4(0,trace,"unmark q"); |
| QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); |
| |
| if ((!q->list_prev)||(!q->list_next)) |
| return; |
| |
| if ((q->is_thinint_q)&&(q->is_input_q)) { |
| /* iQDIO */ |
| spin_lock_irqsave(&ttiq_list_lock,flags); |
| /* in case cleanup has done this already and simultanously |
| * qdio_unmark_q is called from the interrupt handler, we've |
| * got to check this in this specific case again */ |
| if ((!q->list_prev)||(!q->list_next)) |
| goto out; |
| if (q->list_next==q) { |
| /* q was the only interesting q */ |
| tiq_list=NULL; |
| q->list_next=NULL; |
| q->list_prev=NULL; |
| } else { |
| q->list_next->list_prev=q->list_prev; |
| q->list_prev->list_next=q->list_next; |
| tiq_list=q->list_next; |
| q->list_next=NULL; |
| q->list_prev=NULL; |
| } |
| out: |
| spin_unlock_irqrestore(&ttiq_list_lock,flags); |
| } |
| } |
| |
| static inline unsigned long |
| tiqdio_clear_global_summary(void) |
| { |
| unsigned long time; |
| |
| QDIO_DBF_TEXT5(0,trace,"clrglobl"); |
| |
| time = do_clear_global_summary(); |
| |
| QDIO_DBF_HEX5(0,trace,&time,sizeof(unsigned long)); |
| |
| return time; |
| } |
| |
| |
| /************************* OUTBOUND ROUTINES *******************************/ |
| |
| inline static int |
| qdio_get_outbound_buffer_frontier(struct qdio_q *q) |
| { |
| int f,f_mod_no; |
| volatile char *slsb; |
| int first_not_to_check; |
| char dbf_text[15]; |
| |
| QDIO_DBF_TEXT4(0,trace,"getobfro"); |
| QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); |
| |
| slsb=&q->slsb.acc.val[0]; |
| f_mod_no=f=q->first_to_check; |
| /* |
| * f points to already processed elements, so f+no_used is correct... |
| * ... but: we don't check 128 buffers, as otherwise |
| * qdio_has_outbound_q_moved would return 0 |
| */ |
| first_not_to_check=f+qdio_min(atomic_read(&q->number_of_buffers_used), |
| (QDIO_MAX_BUFFERS_PER_Q-1)); |
| |
| if ((!q->is_iqdio_q)&&(!q->hydra_gives_outbound_pcis)) |
| SYNC_MEMORY; |
| |
| check_next: |
| if (f==first_not_to_check) |
| goto out; |
| |
| switch(slsb[f_mod_no]) { |
| |
| /* the adapter has not fetched the output yet */ |
| case SLSB_CU_OUTPUT_PRIMED: |
| QDIO_DBF_TEXT5(0,trace,"outpprim"); |
| break; |
| |
| /* the adapter got it */ |
| case SLSB_P_OUTPUT_EMPTY: |
| atomic_dec(&q->number_of_buffers_used); |
| f++; |
| f_mod_no=f&(QDIO_MAX_BUFFERS_PER_Q-1); |
| QDIO_DBF_TEXT5(0,trace,"outpempt"); |
| goto check_next; |
| |
| case SLSB_P_OUTPUT_ERROR: |
| QDIO_DBF_TEXT3(0,trace,"outperr"); |
| sprintf(dbf_text,"%x-%x-%x",f_mod_no, |
| q->sbal[f_mod_no]->element[14].sbalf.value, |
| q->sbal[f_mod_no]->element[15].sbalf.value); |
| QDIO_DBF_TEXT3(1,trace,dbf_text); |
| QDIO_DBF_HEX2(1,sbal,q->sbal[f_mod_no],256); |
| |
| /* kind of process the buffer */ |
| set_slsb(&q->slsb.acc.val[f_mod_no], SLSB_P_OUTPUT_NOT_INIT); |
| |
| /* |
| * we increment the frontier, as this buffer |
| * was processed obviously |
| */ |
| atomic_dec(&q->number_of_buffers_used); |
| f_mod_no=(f_mod_no+1)&(QDIO_MAX_BUFFERS_PER_Q-1); |
| |
| if (q->qdio_error) |
| q->error_status_flags|= |
| QDIO_STATUS_MORE_THAN_ONE_QDIO_ERROR; |
| q->qdio_error=SLSB_P_OUTPUT_ERROR; |
| q->error_status_flags|=QDIO_STATUS_LOOK_FOR_ERROR; |
| |
| break; |
| |
| /* no new buffers */ |
| default: |
| QDIO_DBF_TEXT5(0,trace,"outpni"); |
| } |
| out: |
| return (q->first_to_check=f_mod_no); |
| } |
| |
| /* all buffers are processed */ |
| inline static int |
| qdio_is_outbound_q_done(struct qdio_q *q) |
| { |
| int no_used; |
| #ifdef CONFIG_QDIO_DEBUG |
| char dbf_text[15]; |
| #endif |
| |
| no_used=atomic_read(&q->number_of_buffers_used); |
| |
| #ifdef CONFIG_QDIO_DEBUG |
| if (no_used) { |
| sprintf(dbf_text,"oqisnt%02x",no_used); |
| QDIO_DBF_TEXT4(0,trace,dbf_text); |
| } else { |
| QDIO_DBF_TEXT4(0,trace,"oqisdone"); |
| } |
| QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); |
| #endif /* CONFIG_QDIO_DEBUG */ |
| return (no_used==0); |
| } |
| |
| inline static int |
| qdio_has_outbound_q_moved(struct qdio_q *q) |
| { |
| int i; |
| |
| i=qdio_get_outbound_buffer_frontier(q); |
| |
| if ( (i!=GET_SAVED_FRONTIER(q)) || |
| (q->error_status_flags&QDIO_STATUS_LOOK_FOR_ERROR) ) { |
| SAVE_FRONTIER(q,i); |
| QDIO_DBF_TEXT4(0,trace,"oqhasmvd"); |
| QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); |
| return 1; |
| } else { |
| QDIO_DBF_TEXT4(0,trace,"oqhsntmv"); |
| QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); |
| return 0; |
| } |
| } |
| |
| inline static void |
| qdio_kick_outbound_q(struct qdio_q *q) |
| { |
| int result; |
| #ifdef CONFIG_QDIO_DEBUG |
| char dbf_text[15]; |
| |
| QDIO_DBF_TEXT4(0,trace,"kickoutq"); |
| QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); |
| #endif /* CONFIG_QDIO_DEBUG */ |
| |
| if (!q->siga_out) |
| return; |
| |
| /* here's the story with cc=2 and busy bit set (thanks, Rick): |
| * VM's CP could present us cc=2 and busy bit set on SIGA-write |
| * during reconfiguration of their Guest LAN (only in HIPERS mode, |
| * QDIO mode is asynchronous -- cc=2 and busy bit there will take |
| * the queues down immediately; and not being under VM we have a |
| * problem on cc=2 and busy bit set right away). |
| * |
| * Therefore qdio_siga_output will try for a short time constantly, |
| * if such a condition occurs. If it doesn't change, it will |
| * increase the busy_siga_counter and save the timestamp, and |
| * schedule the queue for later processing (via mark_q, using the |
| * queue tasklet). __qdio_outbound_processing will check out the |
| * counter. If non-zero, it will call qdio_kick_outbound_q as often |
| * as the value of the counter. This will attempt further SIGA |
| * instructions. For each successful SIGA, the counter is |
| * decreased, for failing SIGAs the counter remains the same, after |
| * all. |
| * After some time of no movement, qdio_kick_outbound_q will |
| * finally fail and reflect corresponding error codes to call |
| * the upper layer module and have it take the queues down. |
| * |
| * Note that this is a change from the original HiperSockets design |
| * (saying cc=2 and busy bit means take the queues down), but in |
| * these days Guest LAN didn't exist... excessive cc=2 with busy bit |
| * conditions will still take the queues down, but the threshold is |
| * higher due to the Guest LAN environment. |
| */ |
| |
| |
| result=qdio_siga_output(q); |
| |
| switch (result) { |
| case 0: |
| /* went smooth this time, reset timestamp */ |
| #ifdef CONFIG_QDIO_DEBUG |
| QDIO_DBF_TEXT3(0,trace,"cc2reslv"); |
| sprintf(dbf_text,"%4x%2x%2x",q->irq,q->q_no, |
| atomic_read(&q->busy_siga_counter)); |
| QDIO_DBF_TEXT3(0,trace,dbf_text); |
| #endif /* CONFIG_QDIO_DEBUG */ |
| q->timing.busy_start=0; |
| break; |
| case (2|QDIO_SIGA_ERROR_B_BIT_SET): |
| /* cc=2 and busy bit: */ |
| atomic_inc(&q->busy_siga_counter); |
| |
| /* if the last siga was successful, save |
| * timestamp here */ |
| if (!q->timing.busy_start) |
| q->timing.busy_start=NOW; |
| |
| /* if we're in time, don't touch error_status_flags |
| * and siga_error */ |
| if (NOW-q->timing.busy_start<QDIO_BUSY_BIT_GIVE_UP) { |
| qdio_mark_q(q); |
| break; |
| } |
| QDIO_DBF_TEXT2(0,trace,"cc2REPRT"); |
| #ifdef CONFIG_QDIO_DEBUG |
| sprintf(dbf_text,"%4x%2x%2x",q->irq,q->q_no, |
| atomic_read(&q->busy_siga_counter)); |
| QDIO_DBF_TEXT3(0,trace,dbf_text); |
| #endif /* CONFIG_QDIO_DEBUG */ |
| /* else fallthrough and report error */ |
| default: |
| /* for plain cc=1, 2 or 3: */ |
| if (q->siga_error) |
| q->error_status_flags|= |
| QDIO_STATUS_MORE_THAN_ONE_SIGA_ERROR; |
| q->error_status_flags|= |
| QDIO_STATUS_LOOK_FOR_ERROR; |
| q->siga_error=result; |
| } |
| } |
| |
| inline static void |
| qdio_kick_outbound_handler(struct qdio_q *q) |
| { |
| int start, end, real_end, count; |
| #ifdef CONFIG_QDIO_DEBUG |
| char dbf_text[15]; |
| #endif |
| |
| start = q->first_element_to_kick; |
| /* last_move_ftc was just updated */ |
| real_end = GET_SAVED_FRONTIER(q); |
| end = (real_end+QDIO_MAX_BUFFERS_PER_Q-1)& |
| (QDIO_MAX_BUFFERS_PER_Q-1); |
| count = (end+QDIO_MAX_BUFFERS_PER_Q+1-start)& |
| (QDIO_MAX_BUFFERS_PER_Q-1); |
| |
| #ifdef CONFIG_QDIO_DEBUG |
| QDIO_DBF_TEXT4(0,trace,"kickouth"); |
| QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); |
| |
| sprintf(dbf_text,"s=%2xc=%2x",start,count); |
| QDIO_DBF_TEXT4(0,trace,dbf_text); |
| #endif /* CONFIG_QDIO_DEBUG */ |
| |
| if (q->state==QDIO_IRQ_STATE_ACTIVE) |
| q->handler(q->cdev,QDIO_STATUS_OUTBOUND_INT| |
| q->error_status_flags, |
| q->qdio_error,q->siga_error,q->q_no,start,count, |
| q->int_parm); |
| |
| /* for the next time: */ |
| q->first_element_to_kick=real_end; |
| q->qdio_error=0; |
| q->siga_error=0; |
| q->error_status_flags=0; |
| } |
| |
| static inline void |
| __qdio_outbound_processing(struct qdio_q *q) |
| { |
| int siga_attempts; |
| |
| QDIO_DBF_TEXT4(0,trace,"qoutproc"); |
| QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); |
| |
| if (unlikely(qdio_reserve_q(q))) { |
| qdio_release_q(q); |
| #ifdef QDIO_PERFORMANCE_STATS |
| o_p_c++; |
| #endif /* QDIO_PERFORMANCE_STATS */ |
| /* as we're sissies, we'll check next time */ |
| if (likely(!atomic_read(&q->is_in_shutdown))) { |
| qdio_mark_q(q); |
| QDIO_DBF_TEXT4(0,trace,"busy,agn"); |
| } |
| return; |
| } |
| #ifdef QDIO_PERFORMANCE_STATS |
| o_p_nc++; |
| perf_stats.tl_runs++; |
| #endif /* QDIO_PERFORMANCE_STATS */ |
| |
| /* see comment in qdio_kick_outbound_q */ |
| siga_attempts=atomic_read(&q->busy_siga_counter); |
| while (siga_attempts) { |
| atomic_dec(&q->busy_siga_counter); |
| qdio_kick_outbound_q(q); |
| siga_attempts--; |
| } |
| |
| if (qdio_has_outbound_q_moved(q)) |
| qdio_kick_outbound_handler(q); |
| |
| if (q->is_iqdio_q) { |
| /* |
| * for asynchronous queues, we better check, if the fill |
| * level is too high. for synchronous queues, the fill |
| * level will never be that high. |
| */ |
| if (atomic_read(&q->number_of_buffers_used)> |
| IQDIO_FILL_LEVEL_TO_POLL) |
| qdio_mark_q(q); |
| |
| } else if (!q->hydra_gives_outbound_pcis) |
| if (!qdio_is_outbound_q_done(q)) |
| qdio_mark_q(q); |
| |
| qdio_release_q(q); |
| } |
| |
| static void |
| qdio_outbound_processing(struct qdio_q *q) |
| { |
| __qdio_outbound_processing(q); |
| } |
| |
| /************************* INBOUND ROUTINES *******************************/ |
| |
| |
| inline static int |
| qdio_get_inbound_buffer_frontier(struct qdio_q *q) |
| { |
| int f,f_mod_no; |
| volatile char *slsb; |
| int first_not_to_check; |
| #ifdef CONFIG_QDIO_DEBUG |
| char dbf_text[15]; |
| #endif /* CONFIG_QDIO_DEBUG */ |
| #ifdef QDIO_USE_PROCESSING_STATE |
| int last_position=-1; |
| #endif /* QDIO_USE_PROCESSING_STATE */ |
| |
| QDIO_DBF_TEXT4(0,trace,"getibfro"); |
| QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); |
| |
| slsb=&q->slsb.acc.val[0]; |
| f_mod_no=f=q->first_to_check; |
| /* |
| * we don't check 128 buffers, as otherwise qdio_has_inbound_q_moved |
| * would return 0 |
| */ |
| first_not_to_check=f+qdio_min(atomic_read(&q->number_of_buffers_used), |
| (QDIO_MAX_BUFFERS_PER_Q-1)); |
| |
| /* |
| * we don't use this one, as a PCI or we after a thin interrupt |
| * will sync the queues |
| */ |
| /* SYNC_MEMORY;*/ |
| |
| check_next: |
| f_mod_no=f&(QDIO_MAX_BUFFERS_PER_Q-1); |
| if (f==first_not_to_check) |
| goto out; |
| switch (slsb[f_mod_no]) { |
| |
| /* CU_EMPTY means frontier is reached */ |
| case SLSB_CU_INPUT_EMPTY: |
| QDIO_DBF_TEXT5(0,trace,"inptempt"); |
| break; |
| |
| /* P_PRIMED means set slsb to P_PROCESSING and move on */ |
| case SLSB_P_INPUT_PRIMED: |
| QDIO_DBF_TEXT5(0,trace,"inptprim"); |
| |
| #ifdef QDIO_USE_PROCESSING_STATE |
| /* |
| * as soon as running under VM, polling the input queues will |
| * kill VM in terms of CP overhead |
| */ |
| if (q->siga_sync) { |
| set_slsb(&slsb[f_mod_no],SLSB_P_INPUT_NOT_INIT); |
| } else { |
| /* set the previous buffer to NOT_INIT. The current |
| * buffer will be set to PROCESSING at the end of |
| * this function to avoid further interrupts. */ |
| if (last_position>=0) |
| set_slsb(&slsb[last_position], |
| SLSB_P_INPUT_NOT_INIT); |
| atomic_set(&q->polling,1); |
| last_position=f_mod_no; |
| } |
| #else /* QDIO_USE_PROCESSING_STATE */ |
| set_slsb(&slsb[f_mod_no],SLSB_P_INPUT_NOT_INIT); |
| #endif /* QDIO_USE_PROCESSING_STATE */ |
| /* |
| * not needed, as the inbound queue will be synced on the next |
| * siga-r, resp. tiqdio_is_inbound_q_done will do the siga-s |
| */ |
| /*SYNC_MEMORY;*/ |
| f++; |
| atomic_dec(&q->number_of_buffers_used); |
| goto check_next; |
| |
| case SLSB_P_INPUT_NOT_INIT: |
| case SLSB_P_INPUT_PROCESSING: |
| QDIO_DBF_TEXT5(0,trace,"inpnipro"); |
| break; |
| |
| /* P_ERROR means frontier is reached, break and report error */ |
| case SLSB_P_INPUT_ERROR: |
| #ifdef CONFIG_QDIO_DEBUG |
| sprintf(dbf_text,"inperr%2x",f_mod_no); |
| QDIO_DBF_TEXT3(1,trace,dbf_text); |
| #endif /* CONFIG_QDIO_DEBUG */ |
| QDIO_DBF_HEX2(1,sbal,q->sbal[f_mod_no],256); |
| |
| /* kind of process the buffer */ |
| set_slsb(&slsb[f_mod_no],SLSB_P_INPUT_NOT_INIT); |
| |
| if (q->qdio_error) |
| q->error_status_flags|= |
| QDIO_STATUS_MORE_THAN_ONE_QDIO_ERROR; |
| q->qdio_error=SLSB_P_INPUT_ERROR; |
| q->error_status_flags|=QDIO_STATUS_LOOK_FOR_ERROR; |
| |
| /* we increment the frontier, as this buffer |
| * was processed obviously */ |
| f_mod_no=(f_mod_no+1)&(QDIO_MAX_BUFFERS_PER_Q-1); |
| atomic_dec(&q->number_of_buffers_used); |
| |
| #ifdef QDIO_USE_PROCESSING_STATE |
| last_position=-1; |
| #endif /* QDIO_USE_PROCESSING_STATE */ |
| |
| break; |
| |
| /* everything else means frontier not changed (HALTED or so) */ |
| default: |
| break; |
| } |
| out: |
| q->first_to_check=f_mod_no; |
| |
| #ifdef QDIO_USE_PROCESSING_STATE |
| if (last_position>=0) |
| set_slsb(&slsb[last_position],SLSB_P_INPUT_PROCESSING); |
| #endif /* QDIO_USE_PROCESSING_STATE */ |
| |
| QDIO_DBF_HEX4(0,trace,&q->first_to_check,sizeof(int)); |
| |
| return q->first_to_check; |
| } |
| |
| inline static int |
| qdio_has_inbound_q_moved(struct qdio_q *q) |
| { |
| int i; |
| |
| #ifdef QDIO_PERFORMANCE_STATS |
| static int old_pcis=0; |
| static int old_thinints=0; |
| |
| if ((old_pcis==perf_stats.pcis)&&(old_thinints==perf_stats.thinints)) |
| perf_stats.start_time_inbound=NOW; |
| else |
| old_pcis=perf_stats.pcis; |
| #endif /* QDIO_PERFORMANCE_STATS */ |
| |
| i=qdio_get_inbound_buffer_frontier(q); |
| if ( (i!=GET_SAVED_FRONTIER(q)) || |
| (q->error_status_flags&QDIO_STATUS_LOOK_FOR_ERROR) ) { |
| SAVE_FRONTIER(q,i); |
| if ((!q->siga_sync)&&(!q->hydra_gives_outbound_pcis)) |
| SAVE_TIMESTAMP(q); |
| |
| QDIO_DBF_TEXT4(0,trace,"inhasmvd"); |
| QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); |
| return 1; |
| } else { |
| QDIO_DBF_TEXT4(0,trace,"inhsntmv"); |
| QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); |
| return 0; |
| } |
| } |
| |
| /* means, no more buffers to be filled */ |
| inline static int |
| tiqdio_is_inbound_q_done(struct qdio_q *q) |
| { |
| int no_used; |
| #ifdef CONFIG_QDIO_DEBUG |
| char dbf_text[15]; |
| #endif |
| |
| no_used=atomic_read(&q->number_of_buffers_used); |
| |
| /* propagate the change from 82 to 80 through VM */ |
| SYNC_MEMORY; |
| |
| #ifdef CONFIG_QDIO_DEBUG |
| if (no_used) { |
| sprintf(dbf_text,"iqisnt%02x",no_used); |
| QDIO_DBF_TEXT4(0,trace,dbf_text); |
| } else { |
| QDIO_DBF_TEXT4(0,trace,"iniqisdo"); |
| } |
| QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); |
| #endif /* CONFIG_QDIO_DEBUG */ |
| |
| if (!no_used) |
| return 1; |
| |
| if (!q->siga_sync) |
| /* we'll check for more primed buffers in qeth_stop_polling */ |
| return 0; |
| |
| if (q->slsb.acc.val[q->first_to_check]!=SLSB_P_INPUT_PRIMED) |
| /* |
| * nothing more to do, if next buffer is not PRIMED. |
| * note that we did a SYNC_MEMORY before, that there |
| * has been a sychnronization. |
| * we will return 0 below, as there is nothing to do |
| * (stop_polling not necessary, as we have not been |
| * using the PROCESSING state |
| */ |
| return 0; |
| |
| /* |
| * ok, the next input buffer is primed. that means, that device state |
| * change indicator and adapter local summary are set, so we will find |
| * it next time. |
| * we will return 0 below, as there is nothing to do, except scheduling |
| * ourselves for the next time. |
| */ |
| tiqdio_set_summary_bit((__u32*)q->dev_st_chg_ind); |
| tiqdio_sched_tl(); |
| return 0; |
| } |
| |
| inline static int |
| qdio_is_inbound_q_done(struct qdio_q *q) |
| { |
| int no_used; |
| #ifdef CONFIG_QDIO_DEBUG |
| char dbf_text[15]; |
| #endif |
| |
| no_used=atomic_read(&q->number_of_buffers_used); |
| |
| /* |
| * we need that one for synchronization with the adapter, as it |
| * does a kind of PCI avoidance |
| */ |
| SYNC_MEMORY; |
| |
| if (!no_used) { |
| QDIO_DBF_TEXT4(0,trace,"inqisdnA"); |
| QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); |
| QDIO_DBF_TEXT4(0,trace,dbf_text); |
| return 1; |
| } |
| |
| if (q->slsb.acc.val[q->first_to_check]==SLSB_P_INPUT_PRIMED) { |
| /* we got something to do */ |
| QDIO_DBF_TEXT4(0,trace,"inqisntA"); |
| QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); |
| return 0; |
| } |
| |
| /* on VM, we don't poll, so the q is always done here */ |
| if (q->siga_sync) |
| return 1; |
| if (q->hydra_gives_outbound_pcis) |
| return 1; |
| |
| /* |
| * at this point we know, that inbound first_to_check |
| * has (probably) not moved (see qdio_inbound_processing) |
| */ |
| if (NOW>GET_SAVED_TIMESTAMP(q)+q->timing.threshold) { |
| #ifdef CONFIG_QDIO_DEBUG |
| QDIO_DBF_TEXT4(0,trace,"inqisdon"); |
| QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); |
| sprintf(dbf_text,"pf%02xcn%02x",q->first_to_check,no_used); |
| QDIO_DBF_TEXT4(0,trace,dbf_text); |
| #endif /* CONFIG_QDIO_DEBUG */ |
| return 1; |
| } else { |
| #ifdef CONFIG_QDIO_DEBUG |
| QDIO_DBF_TEXT4(0,trace,"inqisntd"); |
| QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); |
| sprintf(dbf_text,"pf%02xcn%02x",q->first_to_check,no_used); |
| QDIO_DBF_TEXT4(0,trace,dbf_text); |
| #endif /* CONFIG_QDIO_DEBUG */ |
| return 0; |
| } |
| } |
| |
| inline static void |
| qdio_kick_inbound_handler(struct qdio_q *q) |
| { |
| int count, start, end, real_end, i; |
| #ifdef CONFIG_QDIO_DEBUG |
| char dbf_text[15]; |
| #endif |
| |
| QDIO_DBF_TEXT4(0,trace,"kickinh"); |
| QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); |
| |
| start=q->first_element_to_kick; |
| real_end=q->first_to_check; |
| end=(real_end+QDIO_MAX_BUFFERS_PER_Q-1)&(QDIO_MAX_BUFFERS_PER_Q-1); |
| |
| i=start; |
| count=0; |
| while (1) { |
| count++; |
| if (i==end) |
| break; |
| i=(i+1)&(QDIO_MAX_BUFFERS_PER_Q-1); |
| } |
| |
| #ifdef CONFIG_QDIO_DEBUG |
| sprintf(dbf_text,"s=%2xc=%2x",start,count); |
| QDIO_DBF_TEXT4(0,trace,dbf_text); |
| #endif /* CONFIG_QDIO_DEBUG */ |
| |
| if (likely(q->state==QDIO_IRQ_STATE_ACTIVE)) |
| q->handler(q->cdev, |
| QDIO_STATUS_INBOUND_INT|q->error_status_flags, |
| q->qdio_error,q->siga_error,q->q_no,start,count, |
| q->int_parm); |
| |
| /* for the next time: */ |
| q->first_element_to_kick=real_end; |
| q->qdio_error=0; |
| q->siga_error=0; |
| q->error_status_flags=0; |
| |
| #ifdef QDIO_PERFORMANCE_STATS |
| perf_stats.inbound_time+=NOW-perf_stats.start_time_inbound; |
| perf_stats.inbound_cnt++; |
| #endif /* QDIO_PERFORMANCE_STATS */ |
| } |
| |
| static inline void |
| __tiqdio_inbound_processing(struct qdio_q *q, int spare_ind_was_set) |
| { |
| struct qdio_irq *irq_ptr; |
| struct qdio_q *oq; |
| int i; |
| |
| QDIO_DBF_TEXT4(0,trace,"iqinproc"); |
| QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); |
| |
| /* |
| * we first want to reserve the q, so that we know, that we don't |
| * interrupt ourselves and call qdio_unmark_q, as is_in_shutdown might |
| * be set |
| */ |
| if (unlikely(qdio_reserve_q(q))) { |
| qdio_release_q(q); |
| #ifdef QDIO_PERFORMANCE_STATS |
| ii_p_c++; |
| #endif /* QDIO_PERFORMANCE_STATS */ |
| /* |
| * as we might just be about to stop polling, we make |
| * sure that we check again at least once more |
| */ |
| tiqdio_sched_tl(); |
| return; |
| } |
| #ifdef QDIO_PERFORMANCE_STATS |
| ii_p_nc++; |
| #endif /* QDIO_PERFORMANCE_STATS */ |
| if (unlikely(atomic_read(&q->is_in_shutdown))) { |
| qdio_unmark_q(q); |
| goto out; |
| } |
| |
| /* |
| * we reset spare_ind_was_set, when the queue does not use the |
| * spare indicator |
| */ |
| if (spare_ind_was_set) |
| spare_ind_was_set = (q->dev_st_chg_ind == &spare_indicator); |
| |
| if (!(*(q->dev_st_chg_ind)) && !spare_ind_was_set) |
| goto out; |
| /* |
| * q->dev_st_chg_ind is the indicator, be it shared or not. |
| * only clear it, if indicator is non-shared |
| */ |
| if (!spare_ind_was_set) |
| tiqdio_clear_summary_bit((__u32*)q->dev_st_chg_ind); |
| |
| if (q->hydra_gives_outbound_pcis) { |
| if (!q->siga_sync_done_on_thinints) { |
| SYNC_MEMORY_ALL; |
| } else if ((!q->siga_sync_done_on_outb_tis)&& |
| (q->hydra_gives_outbound_pcis)) { |
| SYNC_MEMORY_ALL_OUTB; |
| } |
| } else { |
| SYNC_MEMORY; |
| } |
| /* |
| * maybe we have to do work on our outbound queues... at least |
| * we have to check the outbound-int-capable thinint-capable |
| * queues |
| */ |
| if (q->hydra_gives_outbound_pcis) { |
| irq_ptr = (struct qdio_irq*)q->irq_ptr; |
| for (i=0;i<irq_ptr->no_output_qs;i++) { |
| oq = irq_ptr->output_qs[i]; |
| #ifdef QDIO_PERFORMANCE_STATS |
| perf_stats.tl_runs--; |
| #endif /* QDIO_PERFORMANCE_STATS */ |
| if (!qdio_is_outbound_q_done(oq)) |
| __qdio_outbound_processing(oq); |
| } |
| } |
| |
| if (!qdio_has_inbound_q_moved(q)) |
| goto out; |
| |
| qdio_kick_inbound_handler(q); |
| if (tiqdio_is_inbound_q_done(q)) |
| if (!qdio_stop_polling(q)) { |
| /* |
| * we set the flags to get into the stuff next time, |
| * see also comment in qdio_stop_polling |
| */ |
| tiqdio_set_summary_bit((__u32*)q->dev_st_chg_ind); |
| tiqdio_sched_tl(); |
| } |
| out: |
| qdio_release_q(q); |
| } |
| |
| static void |
| tiqdio_inbound_processing(struct qdio_q *q) |
| { |
| __tiqdio_inbound_processing(q, atomic_read(&spare_indicator_usecount)); |
| } |
| |
| static inline void |
| __qdio_inbound_processing(struct qdio_q *q) |
| { |
| int q_laps=0; |
| |
| QDIO_DBF_TEXT4(0,trace,"qinproc"); |
| QDIO_DBF_HEX4(0,trace,&q,sizeof(void*)); |
| |
| if (unlikely(qdio_reserve_q(q))) { |
| qdio_release_q(q); |
| #ifdef QDIO_PERFORMANCE_STATS |
| i_p_c++; |
| #endif /* QDIO_PERFORMANCE_STATS */ |
| /* as we're sissies, we'll check next time */ |
| if (likely(!atomic_read(&q->is_in_shutdown))) { |
| qdio_mark_q(q); |
| QDIO_DBF_TEXT4(0,trace,"busy,agn"); |
| } |
| return; |
| } |
| #ifdef QDIO_PERFORMANCE_STATS |
| i_p_nc++; |
| perf_stats.tl_runs++; |
| #endif /* QDIO_PERFORMANCE_STATS */ |
| |
| again: |
| if (qdio_has_inbound_q_moved(q)) { |
| qdio_kick_inbound_handler(q); |
| if (!qdio_stop_polling(q)) { |
| q_laps++; |
| if (q_laps<QDIO_Q_LAPS) |
| goto again; |
| } |
| qdio_mark_q(q); |
| } else { |
| if (!qdio_is_inbound_q_done(q)) |
| /* means poll time is not yet over */ |
| qdio_mark_q(q); |
| } |
| |
| qdio_release_q(q); |
| } |
| |
| static void |
| qdio_inbound_processing(struct qdio_q *q) |
| { |
| __qdio_inbound_processing(q); |
| } |
| |
| /************************* MAIN ROUTINES *******************************/ |
| |
| #ifdef QDIO_USE_PROCESSING_STATE |
| static inline int |
| tiqdio_reset_processing_state(struct qdio_q *q, int q_laps) |
| { |
| if (!q) { |
| tiqdio_sched_tl(); |
| return 0; |
| } |
| |
| /* |
| * under VM, we have not used the PROCESSING state, so no |
| * need to stop polling |
| */ |
| if (q->siga_sync) |
| return 2; |
| |
| if (unlikely(qdio_reserve_q(q))) { |
| qdio_release_q(q); |
| #ifdef QDIO_PERFORMANCE_STATS |
| ii_p_c++; |
| #endif /* QDIO_PERFORMANCE_STATS */ |
| /* |
| * as we might just be about to stop polling, we make |
| * sure that we check again at least once more |
| */ |
| |
| /* |
| * sanity -- we'd get here without setting the |
| * dev st chg ind |
| */ |
| tiqdio_set_summary_bit((__u32*)q->dev_st_chg_ind); |
| tiqdio_sched_tl(); |
| return 0; |
| } |
| if (qdio_stop_polling(q)) { |
| qdio_release_q(q); |
| return 2; |
| } |
| if (q_laps<QDIO_Q_LAPS-1) { |
| qdio_release_q(q); |
| return 3; |
| } |
| /* |
| * we set the flags to get into the stuff |
| * next time, see also comment in qdio_stop_polling |
| */ |
| tiqdio_set_summary_bit((__u32*)q->dev_st_chg_ind); |
| tiqdio_sched_tl(); |
| qdio_release_q(q); |
| return 1; |
| |
| } |
| #endif /* QDIO_USE_PROCESSING_STATE */ |
| |
| static inline void |
| tiqdio_inbound_checks(void) |
| { |
| struct qdio_q *q; |
| int spare_ind_was_set=0; |
| #ifdef QDIO_USE_PROCESSING_STATE |
| int q_laps=0; |
| #endif /* QDIO_USE_PROCESSING_STATE */ |
| |
| QDIO_DBF_TEXT4(0,trace,"iqdinbck"); |
| QDIO_DBF_TEXT5(0,trace,"iqlocsum"); |
| |
| #ifdef QDIO_USE_PROCESSING_STATE |
| again: |
| #endif /* QDIO_USE_PROCESSING_STATE */ |
| |
| /* when the spare indicator is used and set, save that and clear it */ |
| if ((atomic_read(&spare_indicator_usecount)) && spare_indicator) { |
| spare_ind_was_set = 1; |
| tiqdio_clear_summary_bit((__u32*)&spare_indicator); |
| } |
| |
| q=(struct qdio_q*)tiq_list; |
| do { |
| if (!q) |
| break; |
| __tiqdio_inbound_processing(q, spare_ind_was_set); |
| q=(struct qdio_q*)q->list_next; |
| } while (q!=(struct qdio_q*)tiq_list); |
| |
| #ifdef QDIO_USE_PROCESSING_STATE |
| q=(struct qdio_q*)tiq_list; |
| do { |
| int ret; |
| |
| ret = tiqdio_reset_processing_state(q, q_laps); |
| switch (ret) { |
| case 0: |
| return; |
| case 1: |
| q_laps++; |
| case 2: |
| q = (struct qdio_q*)q->list_next; |
| break; |
| default: |
| q_laps++; |
| goto again; |
| } |
| } while (q!=(struct qdio_q*)tiq_list); |
| #endif /* QDIO_USE_PROCESSING_STATE */ |
| } |
| |
| static void |
| tiqdio_tl(unsigned long data) |
| { |
| QDIO_DBF_TEXT4(0,trace,"iqdio_tl"); |
| |
| #ifdef QDIO_PERFORMANCE_STATS |
| perf_stats.tl_runs++; |
| #endif /* QDIO_PERFORMANCE_STATS */ |
| |
| tiqdio_inbound_checks(); |
| } |
| |
| /********************* GENERAL HELPER_ROUTINES ***********************/ |
| |
| static void |
| qdio_release_irq_memory(struct qdio_irq *irq_ptr) |
| { |
| int i; |
| |
| for (i=0;i<QDIO_MAX_QUEUES_PER_IRQ;i++) { |
| if (!irq_ptr->input_qs[i]) |
| goto next; |
| |
| if (irq_ptr->input_qs[i]->slib) |
| kfree(irq_ptr->input_qs[i]->slib); |
| kfree(irq_ptr->input_qs[i]); |
| |
| next: |
| if (!irq_ptr->output_qs[i]) |
| continue; |
| |
| if (irq_ptr->output_qs[i]->slib) |
| kfree(irq_ptr->output_qs[i]->slib); |
| kfree(irq_ptr->output_qs[i]); |
| |
| } |
| kfree(irq_ptr->qdr); |
| kfree(irq_ptr); |
| } |
| |
| static void |
| qdio_set_impl_params(struct qdio_irq *irq_ptr, |
| unsigned int qib_param_field_format, |
| /* pointer to 128 bytes or NULL, if no param field */ |
| unsigned char *qib_param_field, |
| /* pointer to no_queues*128 words of data or NULL */ |
| unsigned int no_input_qs, |
| unsigned int no_output_qs, |
| unsigned long *input_slib_elements, |
| unsigned long *output_slib_elements) |
| { |
| int i,j; |
| |
| if (!irq_ptr) |
| return; |
| |
| irq_ptr->qib.pfmt=qib_param_field_format; |
| if (qib_param_field) |
| memcpy(irq_ptr->qib.parm,qib_param_field, |
| QDIO_MAX_BUFFERS_PER_Q); |
| |
| if (input_slib_elements) |
| for (i=0;i<no_input_qs;i++) { |
| for (j=0;j<QDIO_MAX_BUFFERS_PER_Q;j++) |
| irq_ptr->input_qs[i]->slib->slibe[j].parms= |
| input_slib_elements[ |
| i*QDIO_MAX_BUFFERS_PER_Q+j]; |
| } |
| if (output_slib_elements) |
| for (i=0;i<no_output_qs;i++) { |
| for (j=0;j<QDIO_MAX_BUFFERS_PER_Q;j++) |
| irq_ptr->output_qs[i]->slib->slibe[j].parms= |
| output_slib_elements[ |
| i*QDIO_MAX_BUFFERS_PER_Q+j]; |
| } |
| } |
| |
| static int |
| qdio_alloc_qs(struct qdio_irq *irq_ptr, |
| int no_input_qs, int no_output_qs) |
| { |
| int i; |
| struct qdio_q *q; |
| int result=-ENOMEM; |
| |
| for (i=0;i<no_input_qs;i++) { |
| q=kmalloc(sizeof(struct qdio_q),GFP_KERNEL); |
| |
| if (!q) { |
| QDIO_PRINT_ERR("kmalloc of q failed!\n"); |
| goto out; |
| } |
| |
| memset(q,0,sizeof(struct qdio_q)); |
| |
| q->slib=kmalloc(PAGE_SIZE,GFP_KERNEL); |
| if (!q->slib) { |
| QDIO_PRINT_ERR("kmalloc of slib failed!\n"); |
| goto out; |
| } |
| |
| irq_ptr->input_qs[i]=q; |
| } |
| |
| for (i=0;i<no_output_qs;i++) { |
| q=kmalloc(sizeof(struct qdio_q),GFP_KERNEL); |
| |
| if (!q) { |
| goto out; |
| } |
| |
| memset(q,0,sizeof(struct qdio_q)); |
| |
| q->slib=kmalloc(PAGE_SIZE,GFP_KERNEL); |
| if (!q->slib) { |
| QDIO_PRINT_ERR("kmalloc of slib failed!\n"); |
| goto out; |
| } |
| |
| irq_ptr->output_qs[i]=q; |
| } |
| |
| result=0; |
| out: |
| return result; |
| } |
| |
| static void |
| qdio_fill_qs(struct qdio_irq *irq_ptr, struct ccw_device *cdev, |
| int no_input_qs, int no_output_qs, |
| qdio_handler_t *input_handler, |
| qdio_handler_t *output_handler, |
| unsigned long int_parm,int q_format, |
| unsigned long flags, |
| void **inbound_sbals_array, |
| void **outbound_sbals_array) |
| { |
| struct qdio_q *q; |
| int i,j; |
| char dbf_text[20]; /* see qdio_initialize */ |
| void *ptr; |
| int available; |
| |
| sprintf(dbf_text,"qfqs%4x",cdev->private->irq); |
| QDIO_DBF_TEXT0(0,setup,dbf_text); |
| for (i=0;i<no_input_qs;i++) { |
| q=irq_ptr->input_qs[i]; |
| |
| memset(q,0,((char*)&q->slib)-((char*)q)); |
| sprintf(dbf_text,"in-q%4x",i); |
| QDIO_DBF_TEXT0(0,setup,dbf_text); |
| QDIO_DBF_HEX0(0,setup,&q,sizeof(void*)); |
| |
| memset(q->slib,0,PAGE_SIZE); |
| q->sl=(struct sl*)(((char*)q->slib)+PAGE_SIZE/2); |
| |
| available=0; |
| |
| for (j=0;j<QDIO_MAX_BUFFERS_PER_Q;j++) |
| q->sbal[j]=*(inbound_sbals_array++); |
| |
| q->queue_type=q_format; |
| q->int_parm=int_parm; |
| q->irq=irq_ptr->irq; |
| q->irq_ptr = irq_ptr; |
| q->cdev = cdev; |
| q->mask=1<<(31-i); |
| q->q_no=i; |
| q->is_input_q=1; |
| q->first_to_check=0; |
| q->last_move_ftc=0; |
| q->handler=input_handler; |
| q->dev_st_chg_ind=irq_ptr->dev_st_chg_ind; |
| |
| q->tasklet.data=(unsigned long)q; |
| /* q->is_thinint_q isn't valid at this time, but |
| * irq_ptr->is_thinint_irq is */ |
| q->tasklet.func=(void(*)(unsigned long)) |
| ((irq_ptr->is_thinint_irq)?&tiqdio_inbound_processing: |
| &qdio_inbound_processing); |
| |
| /* actually this is not used for inbound queues. yet. */ |
| atomic_set(&q->busy_siga_counter,0); |
| q->timing.busy_start=0; |
| |
| /* for (j=0;j<QDIO_STATS_NUMBER;j++) |
| q->timing.last_transfer_times[j]=(qdio_get_micros()/ |
| QDIO_STATS_NUMBER)*j; |
| q->timing.last_transfer_index=QDIO_STATS_NUMBER-1; |
| */ |
| |
| /* fill in slib */ |
| if (i>0) irq_ptr->input_qs[i-1]->slib->nsliba= |
| (unsigned long)(q->slib); |
| q->slib->sla=(unsigned long)(q->sl); |
| q->slib->slsba=(unsigned long)(&q->slsb.acc.val[0]); |
| |
| /* fill in sl */ |
| for (j=0;j<QDIO_MAX_BUFFERS_PER_Q;j++) |
| q->sl->element[j].sbal=(unsigned long)(q->sbal[j]); |
| |
| QDIO_DBF_TEXT2(0,setup,"sl-sb-b0"); |
| ptr=(void*)q->sl; |
| QDIO_DBF_HEX2(0,setup,&ptr,sizeof(void*)); |
| ptr=(void*)&q->slsb; |
| QDIO_DBF_HEX2(0,setup,&ptr,sizeof(void*)); |
| ptr=(void*)q->sbal[0]; |
| QDIO_DBF_HEX2(0,setup,&ptr,sizeof(void*)); |
| |
| /* fill in slsb */ |
| for (j=0;j<QDIO_MAX_BUFFERS_PER_Q;j++) { |
| set_slsb(&q->slsb.acc.val[j], |
| SLSB_P_INPUT_NOT_INIT); |
| /* q->sbal[j]->element[1].sbalf.i1.key=QDIO_STORAGE_KEY;*/ |
| } |
| } |
| |
| for (i=0;i<no_output_qs;i++) { |
| q=irq_ptr->output_qs[i]; |
| memset(q,0,((char*)&q->slib)-((char*)q)); |
| |
| sprintf(dbf_text,"outq%4x",i); |
| QDIO_DBF_TEXT0(0,setup,dbf_text); |
| QDIO_DBF_HEX0(0,setup,&q,sizeof(void*)); |
| |
| memset(q->slib,0,PAGE_SIZE); |
| q->sl=(struct sl*)(((char*)q->slib)+PAGE_SIZE/2); |
| |
| available=0; |
| |
| for (j=0;j<QDIO_MAX_BUFFERS_PER_Q;j++) |
| q->sbal[j]=*(outbound_sbals_array++); |
| |
| q->queue_type=q_format; |
| q->int_parm=int_parm; |
| q->is_input_q=0; |
| q->irq=irq_ptr->irq; |
| q->cdev = cdev; |
| q->irq_ptr = irq_ptr; |
| q->mask=1<<(31-i); |
| q->q_no=i; |
| q->first_to_check=0; |
| q->last_move_ftc=0; |
| q->handler=output_handler; |
| |
| q->tasklet.data=(unsigned long)q; |
| q->tasklet.func=(void(*)(unsigned long)) |
| &qdio_outbound_processing; |
| |
| atomic_set(&q->busy_siga_counter,0); |
| q->timing.busy_start=0; |
| |
| /* fill in slib */ |
| if (i>0) irq_ptr->output_qs[i-1]->slib->nsliba= |
| (unsigned long)(q->slib); |
| q->slib->sla=(unsigned long)(q->sl); |
| q->slib->slsba=(unsigned long)(&q->slsb.acc.val[0]); |
| |
| /* fill in sl */ |
| for (j=0;j<QDIO_MAX_BUFFERS_PER_Q;j++) |
| q->sl->element[j].sbal=(unsigned long)(q->sbal[j]); |
| |
| QDIO_DBF_TEXT2(0,setup,"sl-sb-b0"); |
| ptr=(void*)q->sl; |
| QDIO_DBF_HEX2(0,setup,&ptr,sizeof(void*)); |
| ptr=(void*)&q->slsb; |
| QDIO_DBF_HEX2(0,setup,&ptr,sizeof(void*)); |
| ptr=(void*)q->sbal[0]; |
| QDIO_DBF_HEX2(0,setup,&ptr,sizeof(void*)); |
| |
| /* fill in slsb */ |
| for (j=0;j<QDIO_MAX_BUFFERS_PER_Q;j++) { |
| set_slsb(&q->slsb.acc.val[j], |
| SLSB_P_OUTPUT_NOT_INIT); |
| /* q->sbal[j]->element[1].sbalf.i1.key=QDIO_STORAGE_KEY;*/ |
| } |
| } |
| } |
| |
| static void |
| qdio_fill_thresholds(struct qdio_irq *irq_ptr, |
| unsigned int no_input_qs, |
| unsigned int no_output_qs, |
| unsigned int min_input_threshold, |
| unsigned int max_input_threshold, |
| unsigned int min_output_threshold, |
| unsigned int max_output_threshold) |
| { |
| int i; |
| struct qdio_q *q; |
| |
| for (i=0;i<no_input_qs;i++) { |
| q=irq_ptr->input_qs[i]; |
| q->timing.threshold=max_input_threshold; |
| /* for (j=0;j<QDIO_STATS_CLASSES;j++) { |
| q->threshold_classes[j].threshold= |
| min_input_threshold+ |
| (max_input_threshold-min_input_threshold)/ |
| QDIO_STATS_CLASSES; |
| } |
| qdio_use_thresholds(q,QDIO_STATS_CLASSES/2);*/ |
| } |
| for (i=0;i<no_output_qs;i++) { |
| q=irq_ptr->output_qs[i]; |
| q->timing.threshold=max_output_threshold; |
| /* for (j=0;j<QDIO_STATS_CLASSES;j++) { |
| q->threshold_classes[j].threshold= |
| min_output_threshold+ |
| (max_output_threshold-min_output_threshold)/ |
| QDIO_STATS_CLASSES; |
| } |
| qdio_use_thresholds(q,QDIO_STATS_CLASSES/2);*/ |
| } |
| } |
| |
| static int |
| tiqdio_thinint_handler(void) |
| { |
| QDIO_DBF_TEXT4(0,trace,"thin_int"); |
| |
| #ifdef QDIO_PERFORMANCE_STATS |
| perf_stats.thinints++; |
| perf_stats.start_time_inbound=NOW; |
| #endif /* QDIO_PERFORMANCE_STATS */ |
| |
| /* SVS only when needed: |
| * issue SVS to benefit from iqdio interrupt avoidance |
| * (SVS clears AISOI)*/ |
| if (!omit_svs) |
| tiqdio_clear_global_summary(); |
| |
| tiqdio_inbound_checks(); |
| return 0; |
| } |
| |
| static void |
| qdio_set_state(struct qdio_irq *irq_ptr, enum qdio_irq_states state) |
| { |
| int i; |
| #ifdef CONFIG_QDIO_DEBUG |
| char dbf_text[15]; |
| |
| QDIO_DBF_TEXT5(0,trace,"newstate"); |
| sprintf(dbf_text,"%4x%4x",irq_ptr->irq,state); |
| QDIO_DBF_TEXT5(0,trace,dbf_text); |
| #endif /* CONFIG_QDIO_DEBUG */ |
| |
| irq_ptr->state=state; |
| for (i=0;i<irq_ptr->no_input_qs;i++) |
| irq_ptr->input_qs[i]->state=state; |
| for (i=0;i<irq_ptr->no_output_qs;i++) |
| irq_ptr->output_qs[i]->state=state; |
| mb(); |
| } |
| |
| static inline void |
| qdio_irq_check_sense(int irq, struct irb *irb) |
| { |
| char dbf_text[15]; |
| |
| if (irb->esw.esw0.erw.cons) { |
| sprintf(dbf_text,"sens%4x",irq); |
| QDIO_DBF_TEXT2(1,trace,dbf_text); |
| QDIO_DBF_HEX0(0,sense,irb,QDIO_DBF_SENSE_LEN); |
| |
| QDIO_PRINT_WARN("sense data available on qdio channel.\n"); |
| HEXDUMP16(WARN,"irb: ",irb); |
| HEXDUMP16(WARN,"sense data: ",irb->ecw); |
| } |
| |
| } |
| |
| static inline void |
| qdio_handle_pci(struct qdio_irq *irq_ptr) |
| { |
| int i; |
| struct qdio_q *q; |
| |
| #ifdef QDIO_PERFORMANCE_STATS |
| perf_stats.pcis++; |
| perf_stats.start_time_inbound=NOW; |
| #endif /* QDIO_PERFORMANCE_STATS */ |
| for (i=0;i<irq_ptr->no_input_qs;i++) { |
| q=irq_ptr->input_qs[i]; |
| if (q->is_input_q&QDIO_FLAG_NO_INPUT_INTERRUPT_CONTEXT) |
| qdio_mark_q(q); |
| else { |
| #ifdef QDIO_PERFORMANCE_STATS |
| perf_stats.tl_runs--; |
| #endif /* QDIO_PERFORMANCE_STATS */ |
| __qdio_inbound_processing(q); |
| } |
| } |
| if (!irq_ptr->hydra_gives_outbound_pcis) |
| return; |
| for (i=0;i<irq_ptr->no_output_qs;i++) { |
| q=irq_ptr->output_qs[i]; |
| #ifdef QDIO_PERFORMANCE_STATS |
| perf_stats.tl_runs--; |
| #endif /* QDIO_PERFORMANCE_STATS */ |
| if (qdio_is_outbound_q_done(q)) |
| continue; |
| if (!irq_ptr->sync_done_on_outb_pcis) |
| SYNC_MEMORY; |
| __qdio_outbound_processing(q); |
| } |
| } |
| |
| static void qdio_establish_handle_irq(struct ccw_device*, int, int); |
| |
| static inline void |
| qdio_handle_activate_check(struct ccw_device *cdev, unsigned long intparm, |
| int cstat, int dstat) |
| { |
| struct qdio_irq *irq_ptr; |
| struct qdio_q *q; |
| char dbf_text[15]; |
| |
| irq_ptr = cdev->private->qdio_data; |
| |
| QDIO_DBF_TEXT2(1, trace, "ick2"); |
| sprintf(dbf_text,"%s", cdev->dev.bus_id); |
| QDIO_DBF_TEXT2(1,trace,dbf_text); |
| QDIO_DBF_HEX2(0,trace,&intparm,sizeof(int)); |
| QDIO_DBF_HEX2(0,trace,&dstat,sizeof(int)); |
| QDIO_DBF_HEX2(0,trace,&cstat,sizeof(int)); |
| QDIO_PRINT_ERR("received check condition on activate " \ |
| "queues on device %s (cs=x%x, ds=x%x).\n", |
| cdev->dev.bus_id, cstat, dstat); |
| if (irq_ptr->no_input_qs) { |
| q=irq_ptr->input_qs[0]; |
| } else if (irq_ptr->no_output_qs) { |
| q=irq_ptr->output_qs[0]; |
| } else { |
| QDIO_PRINT_ERR("oops... no queue registered for device %s!?\n", |
| cdev->dev.bus_id); |
| goto omit_handler_call; |
| } |
| q->handler(q->cdev,QDIO_STATUS_ACTIVATE_CHECK_CONDITION| |
| QDIO_STATUS_LOOK_FOR_ERROR, |
| 0,0,0,-1,-1,q->int_parm); |
| omit_handler_call: |
| qdio_set_state(irq_ptr,QDIO_IRQ_STATE_STOPPED); |
| |
| } |
| |
| static void |
| qdio_call_shutdown(void *data) |
| { |
| struct ccw_device *cdev; |
| |
| cdev = (struct ccw_device *)data; |
| qdio_shutdown(cdev, QDIO_FLAG_CLEANUP_USING_CLEAR); |
| put_device(&cdev->dev); |
| } |
| |
| static void |
| qdio_timeout_handler(struct ccw_device *cdev) |
| { |
| struct qdio_irq *irq_ptr; |
| char dbf_text[15]; |
| |
| QDIO_DBF_TEXT2(0, trace, "qtoh"); |
| sprintf(dbf_text, "%s", cdev->dev.bus_id); |
| QDIO_DBF_TEXT2(0, trace, dbf_text); |
| |
| irq_ptr = cdev->private->qdio_data; |
| sprintf(dbf_text, "state:%d", irq_ptr->state); |
| QDIO_DBF_TEXT2(0, trace, dbf_text); |
| |
| switch (irq_ptr->state) { |
| case QDIO_IRQ_STATE_INACTIVE: |
| QDIO_PRINT_ERR("establish queues on irq %04x: timed out\n", |
| irq_ptr->irq); |
| QDIO_DBF_TEXT2(1,setup,"eq:timeo"); |
| qdio_set_state(irq_ptr, QDIO_IRQ_STATE_ERR); |
| break; |
| case QDIO_IRQ_STATE_CLEANUP: |
| QDIO_PRINT_INFO("Did not get interrupt on cleanup, irq=0x%x.\n", |
| irq_ptr->irq); |
| qdio_set_state(irq_ptr, QDIO_IRQ_STATE_ERR); |
| break; |
| case QDIO_IRQ_STATE_ESTABLISHED: |
| case QDIO_IRQ_STATE_ACTIVE: |
| /* I/O has been terminated by common I/O layer. */ |
| QDIO_PRINT_INFO("Queues on irq %04x killed by cio.\n", |
| irq_ptr->irq); |
| QDIO_DBF_TEXT2(1, trace, "cio:term"); |
| qdio_set_state(irq_ptr, QDIO_IRQ_STATE_STOPPED); |
| if (get_device(&cdev->dev)) { |
| /* Can't call shutdown from interrupt context. */ |
| PREPARE_WORK(&cdev->private->kick_work, |
| qdio_call_shutdown, (void *)cdev); |
| queue_work(ccw_device_work, &cdev->private->kick_work); |
| } |
| break; |
| default: |
| BUG(); |
| } |
| ccw_device_set_timeout(cdev, 0); |
| wake_up(&cdev->private->wait_q); |
| } |
| |
| static void |
| qdio_handler(struct ccw_device *cdev, unsigned long intparm, struct irb *irb) |
| { |
| struct qdio_irq *irq_ptr; |
| int cstat,dstat; |
| char dbf_text[15]; |
| |
| #ifdef CONFIG_QDIO_DEBUG |
| QDIO_DBF_TEXT4(0, trace, "qint"); |
| sprintf(dbf_text, "%s", cdev->dev.bus_id); |
| QDIO_DBF_TEXT4(0, trace, dbf_text); |
| #endif /* CONFIG_QDIO_DEBUG */ |
| |
| if (!intparm) { |
| QDIO_PRINT_ERR("got unsolicited interrupt in qdio " \ |
| "handler, device %s\n", cdev->dev.bus_id); |
| return; |
| } |
| |
| irq_ptr = cdev->private->qdio_data; |
| if (!irq_ptr) { |
| QDIO_DBF_TEXT2(1, trace, "uint"); |
| sprintf(dbf_text,"%s", cdev->dev.bus_id); |
| QDIO_DBF_TEXT2(1,trace,dbf_text); |
| QDIO_PRINT_ERR("received interrupt on unused device %s!\n", |
| cdev->dev.bus_id); |
| return; |
| } |
| |
| if (IS_ERR(irb)) { |
| /* Currently running i/o is in error. */ |
| switch (PTR_ERR(irb)) { |
| case -EIO: |
| QDIO_PRINT_ERR("i/o error on device %s\n", |
| cdev->dev.bus_id); |
| return; |
| case -ETIMEDOUT: |
| qdio_timeout_handler(cdev); |
| return; |
| default: |
| QDIO_PRINT_ERR("unknown error state %ld on device %s\n", |
| PTR_ERR(irb), cdev->dev.bus_id); |
| return; |
| } |
| } |
| |
| qdio_irq_check_sense(irq_ptr->irq, irb); |
| |
| #ifdef CONFIG_QDIO_DEBUG |
| sprintf(dbf_text, "state:%d", irq_ptr->state); |
| QDIO_DBF_TEXT4(0, trace, dbf_text); |
| #endif /* CONFIG_QDIO_DEBUG */ |
| |
| cstat = irb->scsw.cstat; |
| dstat = irb->scsw.dstat; |
| |
| switch (irq_ptr->state) { |
| case QDIO_IRQ_STATE_INACTIVE: |
| qdio_establish_handle_irq(cdev, cstat, dstat); |
| break; |
| |
| case QDIO_IRQ_STATE_CLEANUP: |
| qdio_set_state(irq_ptr, QDIO_IRQ_STATE_INACTIVE); |
| break; |
| |
| case QDIO_IRQ_STATE_ESTABLISHED: |
| case QDIO_IRQ_STATE_ACTIVE: |
| if (cstat & SCHN_STAT_PCI) { |
| qdio_handle_pci(irq_ptr); |
| break; |
| } |
| |
| if ((cstat&~SCHN_STAT_PCI)||dstat) { |
| qdio_handle_activate_check(cdev, intparm, cstat, dstat); |
| break; |
| } |
| default: |
| QDIO_PRINT_ERR("got interrupt for queues in state %d on " \ |
| "device %s?!\n", |
| irq_ptr->state, cdev->dev.bus_id); |
| } |
| wake_up(&cdev->private->wait_q); |
| |
| } |
| |
| int |
| qdio_synchronize(struct ccw_device *cdev, unsigned int flags, |
| unsigned int queue_number) |
| { |
| int cc; |
| struct qdio_q *q; |
| struct qdio_irq *irq_ptr; |
| void *ptr; |
| #ifdef CONFIG_QDIO_DEBUG |
| char dbf_text[15]="SyncXXXX"; |
| #endif |
| |
| irq_ptr = cdev->private->qdio_data; |
| if (!irq_ptr) |
| return -ENODEV; |
| |
| #ifdef CONFIG_QDIO_DEBUG |
| *((int*)(&dbf_text[4])) = irq_ptr->irq; |
| QDIO_DBF_HEX4(0,trace,dbf_text,QDIO_DBF_TRACE_LEN); |
| *((int*)(&dbf_text[0]))=flags; |
| *((int*)(&dbf_text[4]))=queue_number; |
| QDIO_DBF_HEX4(0,trace,dbf_text,QDIO_DBF_TRACE_LEN); |
| #endif /* CONFIG_QDIO_DEBUG */ |
| |
| if (flags&QDIO_FLAG_SYNC_INPUT) { |
| q=irq_ptr->input_qs[queue_number]; |
| if (!q) |
| return -EINVAL; |
| cc = do_siga_sync(q->irq, 0, q->mask); |
| } else if (flags&QDIO_FLAG_SYNC_OUTPUT) { |
| q=irq_ptr->output_qs[queue_number]; |
| if (!q) |
| return -EINVAL; |
| cc = do_siga_sync(q->irq, q->mask, 0); |
| } else |
| return -EINVAL; |
| |
| ptr=&cc; |
| if (cc) |
| QDIO_DBF_HEX3(0,trace,&ptr,sizeof(int)); |
| |
| return cc; |
| } |
| |
| static unsigned char |
| qdio_check_siga_needs(int sch) |
| { |
| int result; |
| unsigned char qdioac; |
| |
| struct { |
| struct chsc_header request; |
| u16 reserved1; |
| u16 first_sch; |
| u16 reserved2; |
| u16 last_sch; |
| u32 reserved3; |
| struct chsc_header response; |
| u32 reserved4; |
| u8 flags; |
| u8 reserved5; |
| u16 sch; |
| u8 qfmt; |
| u8 reserved6; |
| u8 qdioac; |
| u8 sch_class; |
| u8 reserved7; |
| u8 icnt; |
| u8 reserved8; |
| u8 ocnt; |
| } *ssqd_area; |
| |
| ssqd_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); |
| if (!ssqd_area) { |
| QDIO_PRINT_WARN("Could not get memory for chsc. Using all " \ |
| "SIGAs for sch x%x.\n", sch); |
| return CHSC_FLAG_SIGA_INPUT_NECESSARY || |
| CHSC_FLAG_SIGA_OUTPUT_NECESSARY || |
| CHSC_FLAG_SIGA_SYNC_NECESSARY; /* all flags set */ |
| } |
| ssqd_area->request = (struct chsc_header) { |
| .length = 0x0010, |
| .code = 0x0024, |
| }; |
| |
| ssqd_area->first_sch = sch; |
| ssqd_area->last_sch = sch; |
| |
| result=chsc(ssqd_area); |
| |
| if (result) { |
| QDIO_PRINT_WARN("CHSC returned cc %i. Using all " \ |
| "SIGAs for sch x%x.\n", |
| result,sch); |
| qdioac = CHSC_FLAG_SIGA_INPUT_NECESSARY || |
| CHSC_FLAG_SIGA_OUTPUT_NECESSARY || |
| CHSC_FLAG_SIGA_SYNC_NECESSARY; /* all flags set */ |
| goto out; |
| } |
| |
| if (ssqd_area->response.code != QDIO_CHSC_RESPONSE_CODE_OK) { |
| QDIO_PRINT_WARN("response upon checking SIGA needs " \ |
| "is 0x%x. Using all SIGAs for sch x%x.\n", |
| ssqd_area->response.code, sch); |
| qdioac = CHSC_FLAG_SIGA_INPUT_NECESSARY || |
| CHSC_FLAG_SIGA_OUTPUT_NECESSARY || |
| CHSC_FLAG_SIGA_SYNC_NECESSARY; /* all flags set */ |
| goto out; |
| } |
| if (!(ssqd_area->flags & CHSC_FLAG_QDIO_CAPABILITY) || |
| !(ssqd_area->flags & CHSC_FLAG_VALIDITY) || |
| (ssqd_area->sch != sch)) { |
| QDIO_PRINT_WARN("huh? problems checking out sch x%x... " \ |
| "using all SIGAs.\n",sch); |
| qdioac = CHSC_FLAG_SIGA_INPUT_NECESSARY | |
| CHSC_FLAG_SIGA_OUTPUT_NECESSARY | |
| CHSC_FLAG_SIGA_SYNC_NECESSARY; /* worst case */ |
| goto out; |
| } |
| |
| qdioac = ssqd_area->qdioac; |
| out: |
| free_page ((unsigned long) ssqd_area); |
| return qdioac; |
| } |
| |
| static unsigned int |
| tiqdio_check_chsc_availability(void) |
| { |
| char dbf_text[15]; |
| |
| if (!css_characteristics_avail) |
| return -EIO; |
| |
| /* Check for bit 41. */ |
| if (!css_general_characteristics.aif) { |
| QDIO_PRINT_WARN("Adapter interruption facility not " \ |
| "installed.\n"); |
| return -ENOENT; |
| } |
| |
| /* Check for bits 107 and 108. */ |
| if (!css_chsc_characteristics.scssc || |
| !css_chsc_characteristics.scsscf) { |
| QDIO_PRINT_WARN("Set Chan Subsys. Char. & Fast-CHSCs " \ |
| "not available.\n"); |
| return -ENOENT; |
| } |
| |
| /* Check for OSA/FCP thin interrupts (bit 67). */ |
| hydra_thinints = css_general_characteristics.aif_osa; |
| sprintf(dbf_text,"hydrati%1x", hydra_thinints); |
| QDIO_DBF_TEXT0(0,setup,dbf_text); |
| |
| /* Check for aif time delay disablement fac (bit 56). If installed, |
| * omit svs even under lpar (good point by rick again) */ |
| omit_svs = css_general_characteristics.aif_tdd; |
| sprintf(dbf_text,"omitsvs%1x", omit_svs); |
| QDIO_DBF_TEXT0(0,setup,dbf_text); |
| return 0; |
| } |
| |
| |
| static unsigned int |
| tiqdio_set_subchannel_ind(struct qdio_irq *irq_ptr, int reset_to_zero) |
| { |
| unsigned long real_addr_local_summary_bit; |
| unsigned long real_addr_dev_st_chg_ind; |
| void *ptr; |
| char dbf_text[15]; |
| |
| unsigned int resp_code; |
| int result; |
| |
| struct { |
| struct chsc_header request; |
| u16 operation_code; |
| u16 reserved1; |
| u32 reserved2; |
| u32 reserved3; |
| u64 summary_indicator_addr; |
| u64 subchannel_indicator_addr; |
| u32 ks:4; |
| u32 kc:4; |
| u32 reserved4:21; |
| u32 isc:3; |
| u32 word_with_d_bit; |
| /* set to 0x10000000 to enable |
| * time delay disablement facility */ |
| u32 reserved5; |
| u32 subsystem_id; |
| u32 reserved6[1004]; |
| struct chsc_header response; |
| u32 reserved7; |
| } *scssc_area; |
| |
| if (!irq_ptr->is_thinint_irq) |
| return -ENODEV; |
| |
| if (reset_to_zero) { |
| real_addr_local_summary_bit=0; |
| real_addr_dev_st_chg_ind=0; |
| } else { |
| real_addr_local_summary_bit= |
| virt_to_phys((volatile void *)indicators); |
| real_addr_dev_st_chg_ind= |
| virt_to_phys((volatile void *)irq_ptr->dev_st_chg_ind); |
| } |
| |
| scssc_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); |
| if (!scssc_area) { |
| QDIO_PRINT_WARN("No memory for setting indicators on " \ |
| "subchannel x%x.\n", irq_ptr->irq); |
| return -ENOMEM; |
| } |
| scssc_area->request = (struct chsc_header) { |
| .length = 0x0fe0, |
| .code = 0x0021, |
| }; |
| scssc_area->operation_code = 0; |
| |
| scssc_area->summary_indicator_addr = real_addr_local_summary_bit; |
| scssc_area->subchannel_indicator_addr = real_addr_dev_st_chg_ind; |
| scssc_area->ks = QDIO_STORAGE_KEY; |
| scssc_area->kc = QDIO_STORAGE_KEY; |
| scssc_area->isc = TIQDIO_THININT_ISC; |
| scssc_area->subsystem_id = (1<<16) + irq_ptr->irq; |
| /* enables the time delay disablement facility. Don't care |
| * whether it is really there (i.e. we haven't checked for |
| * it) */ |
| if (css_general_characteristics.aif_tdd) |
| scssc_area->word_with_d_bit = 0x10000000; |
| else |
| QDIO_PRINT_WARN("Time delay disablement facility " \ |
| "not available\n"); |
| |
| |
| |
| result = chsc(scssc_area); |
| if (result) { |
| QDIO_PRINT_WARN("could not set indicators on irq x%x, " \ |
| "cc=%i.\n",irq_ptr->irq,result); |
| result = -EIO; |
| goto out; |
| } |
| |
| resp_code = scssc_area->response.code; |
| if (resp_code!=QDIO_CHSC_RESPONSE_CODE_OK) { |
| QDIO_PRINT_WARN("response upon setting indicators " \ |
| "is 0x%x.\n",resp_code); |
| sprintf(dbf_text,"sidR%4x",resp_code); |
| QDIO_DBF_TEXT1(0,trace,dbf_text); |
| QDIO_DBF_TEXT1(0,setup,dbf_text); |
| ptr=&scssc_area->response; |
| QDIO_DBF_HEX2(1,setup,&ptr,QDIO_DBF_SETUP_LEN); |
| result = -EIO; |
| goto out; |
| } |
| |
| QDIO_DBF_TEXT2(0,setup,"setscind"); |
| QDIO_DBF_HEX2(0,setup,&real_addr_local_summary_bit, |
| sizeof(unsigned long)); |
| QDIO_DBF_HEX2(0,setup,&real_addr_dev_st_chg_ind,sizeof(unsigned long)); |
| result = 0; |
| out: |
| free_page ((unsigned long) scssc_area); |
| return result; |
| |
| } |
| |
| static unsigned int |
| tiqdio_set_delay_target(struct qdio_irq *irq_ptr, unsigned long delay_target) |
| { |
| unsigned int resp_code; |
| int result; |
| void *ptr; |
| char dbf_text[15]; |
| |
| struct { |
| struct chsc_header request; |
| u16 operation_code; |
| u16 reserved1; |
| u32 reserved2; |
| u32 reserved3; |
| u32 reserved4[2]; |
| u32 delay_target; |
| u32 reserved5[1009]; |
| struct chsc_header response; |
| u32 reserved6; |
| } *scsscf_area; |
| |
| if (!irq_ptr->is_thinint_irq) |
| return -ENODEV; |
| |
| scsscf_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); |
| if (!scsscf_area) { |
| QDIO_PRINT_WARN("No memory for setting delay target on " \ |
| "subchannel x%x.\n", irq_ptr->irq); |
| return -ENOMEM; |
| } |
| scsscf_area->request = (struct chsc_header) { |
| .length = 0x0fe0, |
| .code = 0x1027, |
| }; |
| |
| scsscf_area->delay_target = delay_target<<16; |
| |
| result=chsc(scsscf_area); |
| if (result) { |
| QDIO_PRINT_WARN("could not set delay target on irq x%x, " \ |
| "cc=%i. Continuing.\n",irq_ptr->irq,result); |
| result = -EIO; |
| goto out; |
| } |
| |
| resp_code = scsscf_area->response.code; |
| if (resp_code!=QDIO_CHSC_RESPONSE_CODE_OK) { |
| QDIO_PRINT_WARN("response upon setting delay target " \ |
| "is 0x%x. Continuing.\n",resp_code); |
| sprintf(dbf_text,"sdtR%4x",resp_code); |
| QDIO_DBF_TEXT1(0,trace,dbf_text); |
| QDIO_DBF_TEXT1(0,setup,dbf_text); |
| ptr=&scsscf_area->response; |
| QDIO_DBF_HEX2(1,trace,&ptr,QDIO_DBF_TRACE_LEN); |
| } |
| QDIO_DBF_TEXT2(0,trace,"delytrgt"); |
| QDIO_DBF_HEX2(0,trace,&delay_target,sizeof(unsigned long)); |
| result = 0; /* not critical */ |
| out: |
| free_page ((unsigned long) scsscf_area); |
| return result; |
| } |
| |
| int |
| qdio_cleanup(struct ccw_device *cdev, int how) |
| { |
| struct qdio_irq *irq_ptr; |
| char dbf_text[15]; |
| int rc; |
| |
| irq_ptr = cdev->private->qdio_data; |
| if (!irq_ptr) |
| return -ENODEV; |
| |
| sprintf(dbf_text,"qcln%4x",irq_ptr->irq); |
| QDIO_DBF_TEXT1(0,trace,dbf_text); |
| QDIO_DBF_TEXT0(0,setup,dbf_text); |
| |
| rc = qdio_shutdown(cdev, how); |
| if ((rc == 0) || (rc == -EINPROGRESS)) |
| rc = qdio_free(cdev); |
| return rc; |
| } |
| |
| int |
| qdio_shutdown(struct ccw_device *cdev, int how) |
| { |
| struct qdio_irq *irq_ptr; |
| int i; |
| int result = 0; |
| int rc; |
| unsigned long flags; |
| int timeout; |
| char dbf_text[15]; |
| |
| irq_ptr = cdev->private->qdio_data; |
| if (!irq_ptr) |
| return -ENODEV; |
| |
| down(&irq_ptr->setting_up_sema); |
| |
| sprintf(dbf_text,"qsqs%4x",irq_ptr->irq); |
| QDIO_DBF_TEXT1(0,trace,dbf_text); |
| QDIO_DBF_TEXT0(0,setup,dbf_text); |
| |
| /* mark all qs as uninteresting */ |
| for (i=0;i<irq_ptr->no_input_qs;i++) |
| atomic_set(&irq_ptr->input_qs[i]->is_in_shutdown,1); |
| |
| for (i=0;i<irq_ptr->no_output_qs;i++) |
| atomic_set(&irq_ptr->output_qs[i]->is_in_shutdown,1); |
| |
| tasklet_kill(&tiqdio_tasklet); |
| |
| for (i=0;i<irq_ptr->no_input_qs;i++) { |
| qdio_unmark_q(irq_ptr->input_qs[i]); |
| tasklet_kill(&irq_ptr->input_qs[i]->tasklet); |
| wait_event_interruptible_timeout(cdev->private->wait_q, |
| !atomic_read(&irq_ptr-> |
| input_qs[i]-> |
| use_count), |
| QDIO_NO_USE_COUNT_TIMEOUT); |
| if (atomic_read(&irq_ptr->input_qs[i]->use_count)) |
| result=-EINPROGRESS; |
| } |
| |
| for (i=0;i<irq_ptr->no_output_qs;i++) { |
| tasklet_kill(&irq_ptr->output_qs[i]->tasklet); |
| wait_event_interruptible_timeout(cdev->private->wait_q, |
| !atomic_read(&irq_ptr-> |
| output_qs[i]-> |
| use_count), |
| QDIO_NO_USE_COUNT_TIMEOUT); |
| if (atomic_read(&irq_ptr->output_qs[i]->use_count)) |
| result=-EINPROGRESS; |
| } |
| |
| /* cleanup subchannel */ |
| spin_lock_irqsave(get_ccwdev_lock(cdev),flags); |
| if (how&QDIO_FLAG_CLEANUP_USING_CLEAR) { |
| rc = ccw_device_clear(cdev, QDIO_DOING_CLEANUP); |
| timeout=QDIO_CLEANUP_CLEAR_TIMEOUT; |
| } else if (how&QDIO_FLAG_CLEANUP_USING_HALT) { |
| rc = ccw_device_halt(cdev, QDIO_DOING_CLEANUP); |
| timeout=QDIO_CLEANUP_HALT_TIMEOUT; |
| } else { /* default behaviour */ |
| rc = ccw_device_halt(cdev, QDIO_DOING_CLEANUP); |
| timeout=QDIO_CLEANUP_HALT_TIMEOUT; |
| } |
| if (rc == -ENODEV) { |
| /* No need to wait for device no longer present. */ |
| qdio_set_state(irq_ptr, QDIO_IRQ_STATE_INACTIVE); |
| spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags); |
| } else if (((void *)cdev->handler != (void *)qdio_handler) && rc == 0) { |
| /* |
| * Whoever put another handler there, has to cope with the |
| * interrupt theirself. Might happen if qdio_shutdown was |
| * called on already shutdown queues, but this shouldn't have |
| * bad side effects. |
| */ |
| qdio_set_state(irq_ptr, QDIO_IRQ_STATE_INACTIVE); |
| spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags); |
| } else if (rc == 0) { |
| qdio_set_state(irq_ptr, QDIO_IRQ_STATE_CLEANUP); |
| ccw_device_set_timeout(cdev, timeout); |
| spin_unlock_irqrestore(get_ccwdev_lock(cdev),flags); |
| |
| wait_event(cdev->private->wait_q, |
| irq_ptr->state == QDIO_IRQ_STATE_INACTIVE || |
| irq_ptr->state == QDIO_IRQ_STATE_ERR); |
| } else { |
| QDIO_PRINT_INFO("ccw_device_{halt,clear} returned %d for " |
| "device %s\n", result, cdev->dev.bus_id); |
| spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags); |
| result = rc; |
| goto out; |
| } |
| if (irq_ptr->is_thinint_irq) { |
| qdio_put_indicator((__u32*)irq_ptr->dev_st_chg_ind); |
| tiqdio_set_subchannel_ind(irq_ptr,1); |
| /* reset adapter interrupt indicators */ |
| } |
| |
| /* exchange int handlers, if necessary */ |
| if ((void*)cdev->handler == (void*)qdio_handler) |
| cdev->handler=irq_ptr->original_int_handler; |
| |
| /* Ignore errors. */ |
| qdio_set_state(irq_ptr, QDIO_IRQ_STATE_INACTIVE); |
| ccw_device_set_timeout(cdev, 0); |
| out: |
| up(&irq_ptr->setting_up_sema); |
| return result; |
| } |
| |
| int |
| qdio_free(struct ccw_device *cdev) |
| { |
| struct qdio_irq *irq_ptr; |
| char dbf_text[15]; |
| |
| irq_ptr = cdev->private->qdio_data; |
| if (!irq_ptr) |
| return -ENODEV; |
| |
| down(&irq_ptr->setting_up_sema); |
| |
| sprintf(dbf_text,"qfqs%4x",irq_ptr->irq); |
| QDIO_DBF_TEXT1(0,trace,dbf_text); |
| QDIO_DBF_TEXT0(0,setup,dbf_text); |
| |
| cdev->private->qdio_data = 0; |
| |
| up(&irq_ptr->setting_up_sema); |
| |
| qdio_release_irq_memory(irq_ptr); |
| module_put(THIS_MODULE); |
| return 0; |
| } |
| |
| static inline void |
| qdio_allocate_do_dbf(struct qdio_initialize *init_data) |
| { |
| char dbf_text[20]; /* if a printf printed out more than 8 chars */ |
| |
| sprintf(dbf_text,"qfmt:%x",init_data->q_format); |
| QDIO_DBF_TEXT0(0,setup,dbf_text); |
| QDIO_DBF_HEX0(0,setup,init_data->adapter_name,8); |
| sprintf(dbf_text,"qpff%4x",init_data->qib_param_field_format); |
| QDIO_DBF_TEXT0(0,setup,dbf_text); |
| QDIO_DBF_HEX0(0,setup,&init_data->qib_param_field,sizeof(char*)); |
| QDIO_DBF_HEX0(0,setup,&init_data->input_slib_elements,sizeof(long*)); |
| QDIO_DBF_HEX0(0,setup,&init_data->output_slib_elements,sizeof(long*)); |
| sprintf(dbf_text,"miit%4x",init_data->min_input_threshold); |
| QDIO_DBF_TEXT0(0,setup,dbf_text); |
| sprintf(dbf_text,"mait%4x",init_data->max_input_threshold); |
| QDIO_DBF_TEXT0(0,setup,dbf_text); |
| sprintf(dbf_text,"miot%4x",init_data->min_output_threshold); |
| QDIO_DBF_TEXT0(0,setup,dbf_text); |
| sprintf(dbf_text,"maot%4x",init_data->max_output_threshold); |
| QDIO_DBF_TEXT0(0,setup,dbf_text); |
| sprintf(dbf_text,"niq:%4x",init_data->no_input_qs); |
| QDIO_DBF_TEXT0(0,setup,dbf_text); |
| sprintf(dbf_text,"noq:%4x",init_data->no_output_qs); |
| QDIO_DBF_TEXT0(0,setup,dbf_text); |
| QDIO_DBF_HEX0(0,setup,&init_data->input_handler,sizeof(void*)); |
| QDIO_DBF_HEX0(0,setup,&init_data->output_handler,sizeof(void*)); |
| QDIO_DBF_HEX0(0,setup,&init_data->int_parm,sizeof(long)); |
| QDIO_DBF_HEX0(0,setup,&init_data->flags,sizeof(long)); |
| QDIO_DBF_HEX0(0,setup,&init_data->input_sbal_addr_array,sizeof(void*)); |
| QDIO_DBF_HEX0(0,setup,&init_data->output_sbal_addr_array,sizeof(void*)); |
| } |
| |
| static inline void |
| qdio_allocate_fill_input_desc(struct qdio_irq *irq_ptr, int i, int iqfmt) |
| { |
| irq_ptr->input_qs[i]->is_iqdio_q = iqfmt; |
| irq_ptr->input_qs[i]->is_thinint_q = irq_ptr->is_thinint_irq; |
| |
| irq_ptr->qdr->qdf0[i].sliba=(unsigned long)(irq_ptr->input_qs[i]->slib); |
| |
| irq_ptr->qdr->qdf0[i].sla=(unsigned long)(irq_ptr->input_qs[i]->sl); |
| |
| irq_ptr->qdr->qdf0[i].slsba= |
| (unsigned long)(&irq_ptr->input_qs[i]->slsb.acc.val[0]); |
| |
| irq_ptr->qdr->qdf0[i].akey=QDIO_STORAGE_KEY; |
| irq_ptr->qdr->qdf0[i].bkey=QDIO_STORAGE_KEY; |
| irq_ptr->qdr->qdf0[i].ckey=QDIO_STORAGE_KEY; |
| irq_ptr->qdr->qdf0[i].dkey=QDIO_STORAGE_KEY; |
| } |
| |
| static inline void |
| qdio_allocate_fill_output_desc(struct qdio_irq *irq_ptr, int i, |
| int j, int iqfmt) |
| { |
| irq_ptr->output_qs[i]->is_iqdio_q = iqfmt; |
| irq_ptr->output_qs[i]->is_thinint_q = irq_ptr->is_thinint_irq; |
| |
| irq_ptr->qdr->qdf0[i+j].sliba=(unsigned long)(irq_ptr->output_qs[i]->slib); |
| |
| irq_ptr->qdr->qdf0[i+j].sla=(unsigned long)(irq_ptr->output_qs[i]->sl); |
| |
| irq_ptr->qdr->qdf0[i+j].slsba= |
| (unsigned long)(&irq_ptr->output_qs[i]->slsb.acc.val[0]); |
| |
| irq_ptr->qdr->qdf0[i+j].akey=QDIO_STORAGE_KEY; |
| irq_ptr->qdr->qdf0[i+j].bkey=QDIO_STORAGE_KEY; |
| irq_ptr->qdr->qdf0[i+j].ckey=QDIO_STORAGE_KEY; |
| irq_ptr->qdr->qdf0[i+j].dkey=QDIO_STORAGE_KEY; |
| } |
| |
| |
| static inline void |
| qdio_initialize_set_siga_flags_input(struct qdio_irq *irq_ptr) |
| { |
| int i; |
| |
| for (i=0;i<irq_ptr->no_input_qs;i++) { |
| irq_ptr->input_qs[i]->siga_sync= |
| irq_ptr->qdioac&CHSC_FLAG_SIGA_SYNC_NECESSARY; |
| irq_ptr->input_qs[i]->siga_in= |
| irq_ptr->qdioac&CHSC_FLAG_SIGA_INPUT_NECESSARY; |
| irq_ptr->input_qs[i]->siga_out= |
| irq_ptr->qdioac&CHSC_FLAG_SIGA_OUTPUT_NECESSARY; |
| irq_ptr->input_qs[i]->siga_sync_done_on_thinints= |
| irq_ptr->qdioac&CHSC_FLAG_SIGA_SYNC_DONE_ON_THININTS; |
| irq_ptr->input_qs[i]->hydra_gives_outbound_pcis= |
| irq_ptr->hydra_gives_outbound_pcis; |
| irq_ptr->input_qs[i]->siga_sync_done_on_outb_tis= |
| ((irq_ptr->qdioac& |
| (CHSC_FLAG_SIGA_SYNC_DONE_ON_OUTB_PCIS| |
| CHSC_FLAG_SIGA_SYNC_DONE_ON_THININTS))== |
| (CHSC_FLAG_SIGA_SYNC_DONE_ON_OUTB_PCIS| |
| CHSC_FLAG_SIGA_SYNC_DONE_ON_THININTS)); |
| |
| } |
| } |
| |
| static inline void |
| qdio_initialize_set_siga_flags_output(struct qdio_irq *irq_ptr) |
| { |
| int i; |
| |
| for (i=0;i<irq_ptr->no_output_qs;i++) { |
| irq_ptr->output_qs[i]->siga_sync= |
| irq_ptr->qdioac&CHSC_FLAG_SIGA_SYNC_NECESSARY; |
| irq_ptr->output_qs[i]->siga_in= |
| irq_ptr->qdioac&CHSC_FLAG_SIGA_INPUT_NECESSARY; |
| irq_ptr->output_qs[i]->siga_out= |
| irq_ptr->qdioac&CHSC_FLAG_SIGA_OUTPUT_NECESSARY; |
| irq_ptr->output_qs[i]->siga_sync_done_on_thinints= |
| irq_ptr->qdioac&CHSC_FLAG_SIGA_SYNC_DONE_ON_THININTS; |
| irq_ptr->output_qs[i]->hydra_gives_outbound_pcis= |
| irq_ptr->hydra_gives_outbound_pcis; |
| irq_ptr->output_qs[i]->siga_sync_done_on_outb_tis= |
| ((irq_ptr->qdioac& |
| (CHSC_FLAG_SIGA_SYNC_DONE_ON_OUTB_PCIS| |
| CHSC_FLAG_SIGA_SYNC_DONE_ON_THININTS))== |
| (CHSC_FLAG_SIGA_SYNC_DONE_ON_OUTB_PCIS| |
| CHSC_FLAG_SIGA_SYNC_DONE_ON_THININTS)); |
| |
| } |
| } |
| |
| static inline int |
| qdio_establish_irq_check_for_errors(struct ccw_device *cdev, int cstat, |
| int dstat) |
| { |
| char dbf_text[15]; |
| struct qdio_irq *irq_ptr; |
| |
| irq_ptr = cdev->private->qdio_data; |
| |
| if (cstat || (dstat & ~(DEV_STAT_CHN_END|DEV_STAT_DEV_END))) { |
| sprintf(dbf_text,"ick1%4x",irq_ptr->irq); |
| QDIO_DBF_TEXT2(1,trace,dbf_text); |
| QDIO_DBF_HEX2(0,trace,&dstat,sizeof(int)); |
| QDIO_DBF_HEX2(0,trace,&cstat,sizeof(int)); |
| QDIO_PRINT_ERR("received check condition on establish " \ |
| "queues on irq 0x%x (cs=x%x, ds=x%x).\n", |
| irq_ptr->irq,cstat,dstat); |
| qdio_set_state(irq_ptr,QDIO_IRQ_STATE_ERR); |
| } |
| |
| if (!(dstat & DEV_STAT_DEV_END)) { |
| QDIO_DBF_TEXT2(1,setup,"eq:no de"); |
| QDIO_DBF_HEX2(0,setup,&dstat, sizeof(dstat)); |
| QDIO_DBF_HEX2(0,setup,&cstat, sizeof(cstat)); |
| QDIO_PRINT_ERR("establish queues on irq %04x: didn't get " |
| "device end: dstat=%02x, cstat=%02x\n", |
| irq_ptr->irq, dstat, cstat); |
| qdio_set_state(irq_ptr, QDIO_IRQ_STATE_ERR); |
| return 1; |
| } |
| |
| if (dstat & ~(DEV_STAT_CHN_END|DEV_STAT_DEV_END)) { |
| QDIO_DBF_TEXT2(1,setup,"eq:badio"); |
| QDIO_DBF_HEX2(0,setup,&dstat, sizeof(dstat)); |
| QDIO_DBF_HEX2(0,setup,&cstat, sizeof(cstat)); |
| QDIO_PRINT_ERR("establish queues on irq %04x: got " |
| "the following devstat: dstat=%02x, " |
| "cstat=%02x\n", |
| irq_ptr->irq, dstat, cstat); |
| qdio_set_state(irq_ptr, QDIO_IRQ_STATE_ERR); |
| return 1; |
| } |
| return 0; |
| } |
| |
| static void |
| qdio_establish_handle_irq(struct ccw_device *cdev, int cstat, int dstat) |
| { |
| struct qdio_irq *irq_ptr; |
| char dbf_text[15]; |
| |
| irq_ptr = cdev->private->qdio_data; |
| |
| sprintf(dbf_text,"qehi%4x",cdev->private->irq); |
| QDIO_DBF_TEXT0(0,setup,dbf_text); |
| QDIO_DBF_TEXT0(0,trace,dbf_text); |
| |
| if (qdio_establish_irq_check_for_errors(cdev, cstat, dstat)) { |
| ccw_device_set_timeout(cdev, 0); |
| return; |
| } |
| |
| qdio_set_state(irq_ptr,QDIO_IRQ_STATE_ESTABLISHED); |
| ccw_device_set_timeout(cdev, 0); |
| } |
| |
| int |
| qdio_initialize(struct qdio_initialize *init_data) |
| { |
| int rc; |
| char dbf_text[15]; |
| |
| sprintf(dbf_text,"qini%4x",init_data->cdev->private->irq); |
| QDIO_DBF_TEXT0(0,setup,dbf_text); |
| QDIO_DBF_TEXT0(0,trace,dbf_text); |
| |
| rc = qdio_allocate(init_data); |
| if (rc == 0) { |
| rc = qdio_establish(init_data); |
| if (rc != 0) |
| qdio_free(init_data->cdev); |
| } |
| |
| return rc; |
| } |
| |
| |
| int |
| qdio_allocate(struct qdio_initialize *init_data) |
| { |
| struct qdio_irq *irq_ptr; |
| char dbf_text[15]; |
| |
| sprintf(dbf_text,"qalc%4x",init_data->cdev->private->irq); |
| QDIO_DBF_TEXT0(0,setup,dbf_text); |
| QDIO_DBF_TEXT0(0,trace,dbf_text); |
| if ( (init_data->no_input_qs>QDIO_MAX_QUEUES_PER_IRQ) || |
| (init_data->no_output_qs>QDIO_MAX_QUEUES_PER_IRQ) || |
| ((init_data->no_input_qs) && (!init_data->input_handler)) || |
| ((init_data->no_output_qs) && (!init_data->output_handler)) ) |
| return -EINVAL; |
| |
| if (!init_data->input_sbal_addr_array) |
| return -EINVAL; |
| |
| if (!init_data->output_sbal_addr_array) |
| return -EINVAL; |
| |
| qdio_allocate_do_dbf(init_data); |
| |
| /* create irq */ |
| irq_ptr=kmalloc(sizeof(struct qdio_irq), GFP_KERNEL | GFP_DMA); |
| |
| QDIO_DBF_TEXT0(0,setup,"irq_ptr:"); |
| QDIO_DBF_HEX0(0,setup,&irq_ptr,sizeof(void*)); |
| |
| if (!irq_ptr) { |
| QDIO_PRINT_ERR("kmalloc of irq_ptr failed!\n"); |
| return -ENOMEM; |
| } |
| |
| memset(irq_ptr,0,sizeof(struct qdio_irq)); |
| |
| init_MUTEX(&irq_ptr->setting_up_sema); |
| |
| /* QDR must be in DMA area since CCW data address is only 32 bit */ |
| irq_ptr->qdr=kmalloc(sizeof(struct qdr), GFP_KERNEL | GFP_DMA); |
| if (!(irq_ptr->qdr)) { |
| kfree(irq_ptr); |
| QDIO_PRINT_ERR("kmalloc of irq_ptr->qdr failed!\n"); |
| return -ENOMEM; |
| } |
| QDIO_DBF_TEXT0(0,setup,"qdr:"); |
| QDIO_DBF_HEX0(0,setup,&irq_ptr->qdr,sizeof(void*)); |
| |
| if (qdio_alloc_qs(irq_ptr, |
| init_data->no_input_qs, |
| init_data->no_output_qs)) { |
| qdio_release_irq_memory(irq_ptr); |
| return -ENOMEM; |
| } |
| |
| init_data->cdev->private->qdio_data = irq_ptr; |
| |
| qdio_set_state(irq_ptr,QDIO_IRQ_STATE_INACTIVE); |
| |
| return 0; |
| } |
| |
| int qdio_fill_irq(struct qdio_initialize *init_data) |
| { |
| int i; |
| char dbf_text[15]; |
| struct ciw *ciw; |
| int is_iqdio; |
| struct qdio_irq *irq_ptr; |
| |
| irq_ptr = init_data->cdev->private->qdio_data; |
| |
| memset(irq_ptr,0,((char*)&irq_ptr->qdr)-((char*)irq_ptr)); |
| |
| /* wipes qib.ac, required by ar7063 */ |
| memset(irq_ptr->qdr,0,sizeof(struct qdr)); |
| |
| irq_ptr->int_parm=init_data->int_parm; |
| |
| irq_ptr->irq = init_data->cdev->private->irq; |
| irq_ptr->no_input_qs=init_data->no_input_qs; |
| irq_ptr->no_output_qs=init_data->no_output_qs; |
| |
| if (init_data->q_format==QDIO_IQDIO_QFMT) { |
| irq_ptr->is_iqdio_irq=1; |
| irq_ptr->is_thinint_irq=1; |
| } else { |
| irq_ptr->is_iqdio_irq=0; |
| irq_ptr->is_thinint_irq=hydra_thinints; |
| } |
| sprintf(dbf_text,"is_i_t%1x%1x", |
| irq_ptr->is_iqdio_irq,irq_ptr->is_thinint_irq); |
| QDIO_DBF_TEXT2(0,setup,dbf_text); |
| |
| if (irq_ptr->is_thinint_irq) { |
| irq_ptr->dev_st_chg_ind=qdio_get_indicator(); |
| QDIO_DBF_HEX1(0,setup,&irq_ptr->dev_st_chg_ind,sizeof(void*)); |
| if (!irq_ptr->dev_st_chg_ind) { |
| QDIO_PRINT_WARN("no indicator location available " \ |
| "for irq 0x%x\n",irq_ptr->irq); |
| qdio_release_irq_memory(irq_ptr); |
| return -ENOBUFS; |
| } |
| } |
| |
| /* defaults */ |
| irq_ptr->equeue.cmd=DEFAULT_ESTABLISH_QS_CMD; |
| irq_ptr->equeue.count=DEFAULT_ESTABLISH_QS_COUNT; |
| irq_ptr->aqueue.cmd=DEFAULT_ACTIVATE_QS_CMD; |
| irq_ptr->aqueue.count=DEFAULT_ACTIVATE_QS_COUNT; |
| |
| qdio_fill_qs(irq_ptr, init_data->cdev, |
| init_data->no_input_qs, |
| init_data->no_output_qs, |
| init_data->input_handler, |
| init_data->output_handler,init_data->int_parm, |
| init_data->q_format,init_data->flags, |
| init_data->input_sbal_addr_array, |
| init_data->output_sbal_addr_array); |
| |
| if (!try_module_get(THIS_MODULE)) { |
| QDIO_PRINT_CRIT("try_module_get() failed!\n"); |
| qdio_release_irq_memory(irq_ptr); |
| return -EINVAL; |
| } |
| |
| qdio_fill_thresholds(irq_ptr,init_data->no_input_qs, |
| init_data->no_output_qs, |
| init_data->min_input_threshold, |
| init_data->max_input_threshold, |
| init_data->min_output_threshold, |
| init_data->max_output_threshold); |
| |
| /* fill in qdr */ |
| irq_ptr->qdr->qfmt=init_data->q_format; |
| irq_ptr->qdr->iqdcnt=init_data->no_input_qs; |
| irq_ptr->qdr->oqdcnt=init_data->no_output_qs; |
| irq_ptr->qdr->iqdsz=sizeof(struct qdesfmt0)/4; /* size in words */ |
| irq_ptr->qdr->oqdsz=sizeof(struct qdesfmt0)/4; |
| |
| irq_ptr->qdr->qiba=(unsigned long)&irq_ptr->qib; |
| irq_ptr->qdr->qkey=QDIO_STORAGE_KEY; |
| |
| /* fill in qib */ |
| irq_ptr->qib.qfmt=init_data->q_format; |
| if (init_data->no_input_qs) |
| irq_ptr->qib.isliba=(unsigned long)(irq_ptr->input_qs[0]->slib); |
| if (init_data->no_output_qs) |
| irq_ptr->qib.osliba=(unsigned long)(irq_ptr->output_qs[0]->slib); |
| memcpy(irq_ptr->qib.ebcnam,init_data->adapter_name,8); |
| |
| qdio_set_impl_params(irq_ptr,init_data->qib_param_field_format, |
| init_data->qib_param_field, |
| init_data->no_input_qs, |
| init_data->no_output_qs, |
| init_data->input_slib_elements, |
| init_data->output_slib_elements); |
| |
| /* first input descriptors, then output descriptors */ |
| is_iqdio = (init_data->q_format == QDIO_IQDIO_QFMT) ? 1 : 0; |
| for (i=0;i<init_data->no_input_qs;i++) |
| qdio_allocate_fill_input_desc(irq_ptr, i, is_iqdio); |
| |
| for (i=0;i<init_data->no_output_qs;i++) |
| qdio_allocate_fill_output_desc(irq_ptr, i, |
| init_data->no_input_qs, |
| is_iqdio); |
| |
| /* qdr, qib, sls, slsbs, slibs, sbales filled. */ |
| |
| /* get qdio commands */ |
| ciw = ccw_device_get_ciw(init_data->cdev, CIW_TYPE_EQUEUE); |
| if (!ciw) { |
| QDIO_DBF_TEXT2(1,setup,"no eq"); |
| QDIO_PRINT_INFO("No equeue CIW found for QDIO commands. " |
| "Trying to use default.\n"); |
| } else |
| irq_ptr->equeue = *ciw; |
| ciw = ccw_device_get_ciw(init_data->cdev, CIW_TYPE_AQUEUE); |
| if (!ciw) { |
| QDIO_DBF_TEXT2(1,setup,"no aq"); |
| QDIO_PRINT_INFO("No aqueue CIW found for QDIO commands. " |
| "Trying to use default.\n"); |
| } else |
| irq_ptr->aqueue = *ciw; |
| |
| /* Set new interrupt handler. */ |
| irq_ptr->original_int_handler = init_data->cdev->handler; |
| init_data->cdev->handler = qdio_handler; |
| |
| return 0; |
| } |
| |
| int |
| qdio_establish(struct qdio_initialize *init_data) |
| { |
| struct qdio_irq *irq_ptr; |
| unsigned long saveflags; |
| int result, result2; |
| struct ccw_device *cdev; |
| char dbf_text[20]; |
| |
| cdev=init_data->cdev; |
| irq_ptr = cdev->private->qdio_data; |
| if (!irq_ptr) |
| return -EINVAL; |
| |
| if (cdev->private->state != DEV_STATE_ONLINE) |
| return -EINVAL; |
| |
| down(&irq_ptr->setting_up_sema); |
| |
| qdio_fill_irq(init_data); |
| |
| /* the thinint CHSC stuff */ |
| if (irq_ptr->is_thinint_irq) { |
| |
| result = tiqdio_set_subchannel_ind(irq_ptr,0); |
| if (result) { |
| up(&irq_ptr->setting_up_sema); |
| qdio_shutdown(cdev, QDIO_FLAG_CLEANUP_USING_CLEAR); |
| return result; |
| } |
| tiqdio_set_delay_target(irq_ptr,TIQDIO_DELAY_TARGET); |
| } |
| |
| sprintf(dbf_text,"qest%4x",cdev->private->irq); |
| QDIO_DBF_TEXT0(0,setup,dbf_text); |
| QDIO_DBF_TEXT0(0,trace,dbf_text); |
| |
| /* establish q */ |
| irq_ptr->ccw.cmd_code=irq_ptr->equeue.cmd; |
| irq_ptr->ccw.flags=CCW_FLAG_SLI; |
| irq_ptr->ccw.count=irq_ptr->equeue.count; |
| irq_ptr->ccw.cda=QDIO_GET_ADDR(irq_ptr->qdr); |
| |
| spin_lock_irqsave(get_ccwdev_lock(cdev),saveflags); |
| |
| ccw_device_set_options(cdev, 0); |
| result=ccw_device_start_timeout(cdev,&irq_ptr->ccw, |
| QDIO_DOING_ESTABLISH,0, 0, |
| QDIO_ESTABLISH_TIMEOUT); |
| if (result) { |
| result2=ccw_device_start_timeout(cdev,&irq_ptr->ccw, |
| QDIO_DOING_ESTABLISH,0,0, |
| QDIO_ESTABLISH_TIMEOUT); |
| sprintf(dbf_text,"eq:io%4x",result); |
| QDIO_DBF_TEXT2(1,setup,dbf_text); |
| if (result2) { |
| sprintf(dbf_text,"eq:io%4x",result); |
| QDIO_DBF_TEXT2(1,setup,dbf_text); |
| } |
| QDIO_PRINT_WARN("establish queues on irq %04x: do_IO " \ |
| "returned %i, next try returned %i\n", |
| irq_ptr->irq,result,result2); |
| result=result2; |
| if (result) |
| ccw_device_set_timeout(cdev, 0); |
| } |
| |
| spin_unlock_irqrestore(get_ccwdev_lock(cdev),saveflags); |
| |
| if (result) { |
| up(&irq_ptr->setting_up_sema); |
| qdio_shutdown(cdev,QDIO_FLAG_CLEANUP_USING_CLEAR); |
| return result; |
| } |
| |
| wait_event_interruptible_timeout(cdev->private->wait_q, |
| irq_ptr->state == QDIO_IRQ_STATE_ESTABLISHED || |
| irq_ptr->state == QDIO_IRQ_STATE_ERR, |
| QDIO_ESTABLISH_TIMEOUT); |
| |
| if (irq_ptr->state == QDIO_IRQ_STATE_ESTABLISHED) |
| result = 0; |
| else { |
| up(&irq_ptr->setting_up_sema); |
| qdio_shutdown(cdev, QDIO_FLAG_CLEANUP_USING_CLEAR); |
| return -EIO; |
| } |
| |
| irq_ptr->qdioac=qdio_check_siga_needs(irq_ptr->irq); |
| /* if this gets set once, we're running under VM and can omit SVSes */ |
| if (irq_ptr->qdioac&CHSC_FLAG_SIGA_SYNC_NECESSARY) |
| omit_svs=1; |
| |
| sprintf(dbf_text,"qdioac%2x",irq_ptr->qdioac); |
| QDIO_DBF_TEXT2(0,setup,dbf_text); |
| |
| sprintf(dbf_text,"qib ac%2x",irq_ptr->qib.ac); |
| QDIO_DBF_TEXT2(0,setup,dbf_text); |
| |
| irq_ptr->hydra_gives_outbound_pcis= |
| irq_ptr->qib.ac&QIB_AC_OUTBOUND_PCI_SUPPORTED; |
| irq_ptr->sync_done_on_outb_pcis= |
| irq_ptr->qdioac&CHSC_FLAG_SIGA_SYNC_DONE_ON_OUTB_PCIS; |
| |
| qdio_initialize_set_siga_flags_input(irq_ptr); |
| qdio_initialize_set_siga_flags_output(irq_ptr); |
| |
| up(&irq_ptr->setting_up_sema); |
| |
| return result; |
| |
| } |
| |
| int |
| qdio_activate(struct ccw_device *cdev, int flags) |
| { |
| struct qdio_irq *irq_ptr; |
| int i,result=0,result2; |
| unsigned long saveflags; |
| char dbf_text[20]; /* see qdio_initialize */ |
| |
| irq_ptr = cdev->private->qdio_data; |
| if (!irq_ptr) |
| return -ENODEV; |
| |
| if (cdev->private->state != DEV_STATE_ONLINE) |
| return -EINVAL; |
| |
| down(&irq_ptr->setting_up_sema); |
| if (irq_ptr->state==QDIO_IRQ_STATE_INACTIVE) { |
| result=-EBUSY; |
| goto out; |
| } |
| |
| sprintf(dbf_text,"qact%4x", irq_ptr->irq); |
| QDIO_DBF_TEXT2(0,setup,dbf_text); |
| QDIO_DBF_TEXT2(0,trace,dbf_text); |
| |
| /* activate q */ |
| irq_ptr->ccw.cmd_code=irq_ptr->aqueue.cmd; |
| irq_ptr->ccw.flags=CCW_FLAG_SLI; |
| irq_ptr->ccw.count=irq_ptr->aqueue.count; |
| irq_ptr->ccw.cda=QDIO_GET_ADDR(0); |
| |
| spin_lock_irqsave(get_ccwdev_lock(cdev),saveflags); |
| |
| ccw_device_set_timeout(cdev, 0); |
| ccw_device_set_options(cdev, CCWDEV_REPORT_ALL); |
| result=ccw_device_start(cdev,&irq_ptr->ccw,QDIO_DOING_ACTIVATE, |
| 0, DOIO_DENY_PREFETCH); |
| if (result) { |
| result2=ccw_device_start(cdev,&irq_ptr->ccw, |
| QDIO_DOING_ACTIVATE,0,0); |
| sprintf(dbf_text,"aq:io%4x",result); |
| QDIO_DBF_TEXT2(1,setup,dbf_text); |
| if (result2) { |
| sprintf(dbf_text,"aq:io%4x",result); |
| QDIO_DBF_TEXT2(1,setup,dbf_text); |
| } |
| QDIO_PRINT_WARN("activate queues on irq %04x: do_IO " \ |
| "returned %i, next try returned %i\n", |
| irq_ptr->irq,result,result2); |
| result=result2; |
| } |
| |
| spin_unlock_irqrestore(get_ccwdev_lock(cdev),saveflags); |
| if (result) |
| goto out; |
| |
| for (i=0;i<irq_ptr->no_input_qs;i++) { |
| if (irq_ptr->is_thinint_irq) { |
| /* |
| * that way we know, that, if we will get interrupted |
| * by tiqdio_inbound_processing, qdio_unmark_q will |
| * not be called |
| */ |
| qdio_reserve_q(irq_ptr->input_qs[i]); |
| qdio_mark_tiq(irq_ptr->input_qs[i]); |
| qdio_release_q(irq_ptr->input_qs[i]); |
| } |
| } |
| |
| if (flags&QDIO_FLAG_NO_INPUT_INTERRUPT_CONTEXT) { |
| for (i=0;i<irq_ptr->no_input_qs;i++) { |
| irq_ptr->input_qs[i]->is_input_q|= |
| QDIO_FLAG_NO_INPUT_INTERRUPT_CONTEXT; |
| } |
| } |
| |
| wait_event_interruptible_timeout(cdev->private->wait_q, |
| ((irq_ptr->state == |
| QDIO_IRQ_STATE_STOPPED) || |
| (irq_ptr->state == |
| QDIO_IRQ_STATE_ERR)), |
| QDIO_ACTIVATE_TIMEOUT); |
| |
| switch (irq_ptr->state) { |
| case QDIO_IRQ_STATE_STOPPED: |
| case QDIO_IRQ_STATE_ERR: |
| up(&irq_ptr->setting_up_sema); |
| qdio_shutdown(cdev, QDIO_FLAG_CLEANUP_USING_CLEAR); |
| down(&irq_ptr->setting_up_sema); |
| result = -EIO; |
| break; |
| default: |
| qdio_set_state(irq_ptr, QDIO_IRQ_STATE_ACTIVE); |
| result = 0; |
| } |
| out: |
| up(&irq_ptr->setting_up_sema); |
| |
| return result; |
| } |
| |
| /* buffers filled forwards again to make Rick happy */ |
| static inline void |
| qdio_do_qdio_fill_input(struct qdio_q *q, unsigned int qidx, |
| unsigned int count, struct qdio_buffer *buffers) |
| { |
| for (;;) { |
| set_slsb(&q->slsb.acc.val[qidx],SLSB_CU_INPUT_EMPTY); |
| count--; |
| if (!count) break; |
| qidx=(qidx+1)&(QDIO_MAX_BUFFERS_PER_Q-1); |
| } |
| |
| /* not necessary, as the queues are synced during the SIGA read */ |
| /*SYNC_MEMORY;*/ |
| } |
| |
| static inline void |
| qdio_do_qdio_fill_output(struct qdio_q *q, unsigned int qidx, |
| unsigned int count, struct qdio_buffer *buffers) |
| { |
| for (;;) { |
| set_slsb(&q->slsb.acc.val[qidx],SLSB_CU_OUTPUT_PRIMED); |
| count--; |
| if (!count) break; |
| qidx=(qidx+1)&(QDIO_MAX_BUFFERS_PER_Q-1); |
| } |
| |
| /* SIGA write will sync the queues */ |
| /*SYNC_MEMORY;*/ |
| } |
| |
| static inline void |
| do_qdio_handle_inbound(struct qdio_q *q, unsigned int callflags, |
| unsigned int qidx, unsigned int count, |
| struct qdio_buffer *buffers) |
| { |
| int used_elements; |
| |
| /* This is the inbound handling of queues */ |
| used_elements=atomic_add_return(count, &q->number_of_buffers_used) - count; |
| |
| qdio_do_qdio_fill_input(q,qidx,count,buffers); |
| |
| if ((used_elements+count==QDIO_MAX_BUFFERS_PER_Q)&& |
| (callflags&QDIO_FLAG_UNDER_INTERRUPT)) |
| atomic_swap(&q->polling,0); |
| |
| if (used_elements) |
| return; |
| if (callflags&QDIO_FLAG_DONT_SIGA) |
| return; |
| if (q->siga_in) { |
| int result; |
| |
| result=qdio_siga_input(q); |
| if (result) { |
| if (q->siga_error) |
| q->error_status_flags|= |
| QDIO_STATUS_MORE_THAN_ONE_SIGA_ERROR; |
| q->error_status_flags|=QDIO_STATUS_LOOK_FOR_ERROR; |
| q->siga_error=result; |
| } |
| } |
| |
| qdio_mark_q(q); |
| } |
| |
| static inline void |
| do_qdio_handle_outbound(struct qdio_q *q, unsigned int callflags, |
| unsigned int qidx, unsigned int count, |
| struct qdio_buffer *buffers) |
| { |
| int used_elements; |
| |
| /* This is the outbound handling of queues */ |
| #ifdef QDIO_PERFORMANCE_STATS |
| perf_stats.start_time_outbound=NOW; |
| #endif /* QDIO_PERFORMANCE_STATS */ |
| |
| qdio_do_qdio_fill_output(q,qidx,count,buffers); |
| |
| used_elements=atomic_add_return(count, &q->number_of_buffers_used) - count; |
| |
| if (callflags&QDIO_FLAG_DONT_SIGA) { |
| #ifdef QDIO_PERFORMANCE_STATS |
| perf_stats.outbound_time+=NOW-perf_stats.start_time_outbound; |
| perf_stats.outbound_cnt++; |
| #endif /* QDIO_PERFORMANCE_STATS */ |
| return; |
| } |
| if (q->is_iqdio_q) { |
| /* one siga for every sbal */ |
| while (count--) |
| qdio_kick_outbound_q(q); |
| |
| __qdio_outbound_processing(q); |
| } else { |
| /* under VM, we do a SIGA sync unconditionally */ |
| SYNC_MEMORY; |
| else { |
| /* |
| * w/o shadow queues (else branch of |
| * SYNC_MEMORY :-/ ), we try to |
| * fast-requeue buffers |
| */ |
| if (q->slsb.acc.val[(qidx+QDIO_MAX_BUFFERS_PER_Q-1) |
| &(QDIO_MAX_BUFFERS_PER_Q-1)]!= |
| SLSB_CU_OUTPUT_PRIMED) { |
| qdio_kick_outbound_q(q); |
| } else { |
| QDIO_DBF_TEXT3(0,trace, "fast-req"); |
| #ifdef QDIO_PERFORMANCE_STATS |
| perf_stats.fast_reqs++; |
| #endif /* QDIO_PERFORMANCE_STATS */ |
| } |
| } |
| /* |
| * only marking the q could take too long, |
| * the upper layer module could do a lot of |
| * traffic in that time |
| */ |
| __qdio_outbound_processing(q); |
| } |
| |
| #ifdef QDIO_PERFORMANCE_STATS |
| perf_stats.outbound_time+=NOW-perf_stats.start_time_outbound; |
| perf_stats.outbound_cnt++; |
| #endif /* QDIO_PERFORMANCE_STATS */ |
| } |
| |
| /* count must be 1 in iqdio */ |
| int |
| do_QDIO(struct ccw_device *cdev,unsigned int callflags, |
| unsigned int queue_number, unsigned int qidx, |
| unsigned int count,struct qdio_buffer *buffers) |
| { |
| struct qdio_irq *irq_ptr; |
| #ifdef CONFIG_QDIO_DEBUG |
| char dbf_text[20]; |
| |
| sprintf(dbf_text,"doQD%04x",cdev->private->irq); |
| QDIO_DBF_TEXT3(0,trace,dbf_text); |
| #endif /* CONFIG_QDIO_DEBUG */ |
| |
| if ( (qidx>QDIO_MAX_BUFFERS_PER_Q) || |
| (count>QDIO_MAX_BUFFERS_PER_Q) || |
| (queue_number>QDIO_MAX_QUEUES_PER_IRQ) ) |
| return -EINVAL; |
| |
| if (count==0) |
| return 0; |
| |
| irq_ptr = cdev->private->qdio_data; |
| if (!irq_ptr) |
| return -ENODEV; |
| |
| #ifdef CONFIG_QDIO_DEBUG |
| if (callflags&QDIO_FLAG_SYNC_INPUT) |
| QDIO_DBF_HEX3(0,trace,&irq_ptr->input_qs[queue_number], |
| sizeof(void*)); |
| else |
| QDIO_DBF_HEX3(0,trace,&irq_ptr->output_qs[queue_number], |
| sizeof(void*)); |
| sprintf(dbf_text,"flag%04x",callflags); |
| QDIO_DBF_TEXT3(0,trace,dbf_text); |
| sprintf(dbf_text,"qi%02xct%02x",qidx,count); |
| QDIO_DBF_TEXT3(0,trace,dbf_text); |
| #endif /* CONFIG_QDIO_DEBUG */ |
| |
| if (irq_ptr->state!=QDIO_IRQ_STATE_ACTIVE) |
| return -EBUSY; |
| |
| if (callflags&QDIO_FLAG_SYNC_INPUT) |
| do_qdio_handle_inbound(irq_ptr->input_qs[queue_number], |
| callflags, qidx, count, buffers); |
| else if (callflags&QDIO_FLAG_SYNC_OUTPUT) |
| do_qdio_handle_outbound(irq_ptr->output_qs[queue_number], |
| callflags, qidx, count, buffers); |
| else { |
| QDIO_DBF_TEXT3(1,trace,"doQD:inv"); |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| #ifdef QDIO_PERFORMANCE_STATS |
| static int |
| qdio_perf_procfile_read(char *buffer, char **buffer_location, off_t offset, |
| int buffer_length, int *eof, void *data) |
| { |
| int c=0; |
| |
| /* we are always called with buffer_length=4k, so we all |
| deliver on the first read */ |
| if (offset>0) |
| return 0; |
| |
| #define _OUTP_IT(x...) c+=sprintf(buffer+c,x) |
| _OUTP_IT("i_p_nc/c=%lu/%lu\n",i_p_nc,i_p_c); |
| _OUTP_IT("ii_p_nc/c=%lu/%lu\n",ii_p_nc,ii_p_c); |
| _OUTP_IT("o_p_nc/c=%lu/%lu\n",o_p_nc,o_p_c); |
| _OUTP_IT("Number of tasklet runs (total) : %u\n", |
| perf_stats.tl_runs); |
| _OUTP_IT("\n"); |
| _OUTP_IT("Number of SIGA sync's issued : %u\n", |
| perf_stats.siga_syncs); |
| _OUTP_IT("Number of SIGA in's issued : %u\n", |
| perf_stats.siga_ins); |
| _OUTP_IT("Number of SIGA out's issued : %u\n", |
| perf_stats.siga_outs); |
| _OUTP_IT("Number of PCIs caught : %u\n", |
| perf_stats.pcis); |
| _OUTP_IT("Number of adapter interrupts caught : %u\n", |
| perf_stats.thinints); |
| _OUTP_IT("Number of fast requeues (outg. SBALs w/o SIGA) : %u\n", |
| perf_stats.fast_reqs); |
| _OUTP_IT("\n"); |
| _OUTP_IT("Total time of all inbound actions (us) incl. UL : %u\n", |
| perf_stats.inbound_time); |
| _OUTP_IT("Number of inbound transfers : %u\n", |
| perf_stats.inbound_cnt); |
| _OUTP_IT("Total time of all outbound do_QDIOs (us) : %u\n", |
| perf_stats.outbound_time); |
| _OUTP_IT("Number of do_QDIOs outbound : %u\n", |
| perf_stats.outbound_cnt); |
| _OUTP_IT("\n"); |
| |
| return c; |
| } |
| |
| static struct proc_dir_entry *qdio_perf_proc_file; |
| #endif /* QDIO_PERFORMANCE_STATS */ |
| |
| static void |
| qdio_add_procfs_entry(void) |
| { |
| #ifdef QDIO_PERFORMANCE_STATS |
| proc_perf_file_registration=0; |
| qdio_perf_proc_file=create_proc_entry(QDIO_PERF, |
| S_IFREG|0444,&proc_root); |
| if (qdio_perf_proc_file) { |
| qdio_perf_proc_file->read_proc=&qdio_perf_procfile_read; |
| } else proc_perf_file_registration=-1; |
| |
| if (proc_perf_file_registration) |
| QDIO_PRINT_WARN("was not able to register perf. " \ |
| "proc-file (%i).\n", |
| proc_perf_file_registration); |
| #endif /* QDIO_PERFORMANCE_STATS */ |
| } |
| |
| static void |
| qdio_remove_procfs_entry(void) |
| { |
| #ifdef QDIO_PERFORMANCE_STATS |
| perf_stats.tl_runs=0; |
| |
| if (!proc_perf_file_registration) /* means if it went ok earlier */ |
| remove_proc_entry(QDIO_PERF,&proc_root); |
| #endif /* QDIO_PERFORMANCE_STATS */ |
| } |
| |
| static void |
| tiqdio_register_thinints(void) |
| { |
| char dbf_text[20]; |
| register_thinint_result= |
| s390_register_adapter_interrupt(&tiqdio_thinint_handler); |
| if (register_thinint_result) { |
| sprintf(dbf_text,"regthn%x",(register_thinint_result&0xff)); |
| QDIO_DBF_TEXT0(0,setup,dbf_text); |
| QDIO_PRINT_ERR("failed to register adapter handler " \ |
| "(rc=%i).\nAdapter interrupts might " \ |
| "not work. Continuing.\n", |
| register_thinint_result); |
| } |
| } |
| |
| static void |
| tiqdio_unregister_thinints(void) |
| { |
| if (!register_thinint_result) |
| s390_unregister_adapter_interrupt(&tiqdio_thinint_handler); |
| } |
| |
| static int |
| qdio_get_qdio_memory(void) |
| { |
| int i; |
| indicator_used[0]=1; |
| |
| for (i=1;i<INDICATORS_PER_CACHELINE;i++) |
| indicator_used[i]=0; |
| indicators=(__u32*)kmalloc(sizeof(__u32)*(INDICATORS_PER_CACHELINE), |
| GFP_KERNEL); |
| if (!indicators) return -ENOMEM; |
| memset(indicators,0,sizeof(__u32)*(INDICATORS_PER_CACHELINE)); |
| return 0; |
| } |
| |
| static void |
| qdio_release_qdio_memory(void) |
| { |
| if (indicators) |
| kfree(indicators); |
| } |
| |
| static void |
| qdio_unregister_dbf_views(void) |
| { |
| if (qdio_dbf_setup) |
| debug_unregister(qdio_dbf_setup); |
| if (qdio_dbf_sbal) |
| debug_unregister(qdio_dbf_sbal); |
| if (qdio_dbf_sense) |
| debug_unregister(qdio_dbf_sense); |
| if (qdio_dbf_trace) |
| debug_unregister(qdio_dbf_trace); |
| #ifdef CONFIG_QDIO_DEBUG |
| if (qdio_dbf_slsb_out) |
| debug_unregister(qdio_dbf_slsb_out); |
| if (qdio_dbf_slsb_in) |
| debug_unregister(qdio_dbf_slsb_in); |
| #endif /* CONFIG_QDIO_DEBUG */ |
| } |
| |
| static int |
| qdio_register_dbf_views(void) |
| { |
| qdio_dbf_setup=debug_register(QDIO_DBF_SETUP_NAME, |
| QDIO_DBF_SETUP_PAGES, |
| QDIO_DBF_SETUP_NR_AREAS, |
| QDIO_DBF_SETUP_LEN); |
| if (!qdio_dbf_setup) |
| goto oom; |
| debug_register_view(qdio_dbf_setup,&debug_hex_ascii_view); |
| debug_set_level(qdio_dbf_setup,QDIO_DBF_SETUP_LEVEL); |
| |
| qdio_dbf_sbal=debug_register(QDIO_DBF_SBAL_NAME, |
| QDIO_DBF_SBAL_PAGES, |
| QDIO_DBF_SBAL_NR_AREAS, |
| QDIO_DBF_SBAL_LEN); |
| if (!qdio_dbf_sbal) |
| goto oom; |
| |
| debug_register_view(qdio_dbf_sbal,&debug_hex_ascii_view); |
| debug_set_level(qdio_dbf_sbal,QDIO_DBF_SBAL_LEVEL); |
| |
| qdio_dbf_sense=debug_register(QDIO_DBF_SENSE_NAME, |
| QDIO_DBF_SENSE_PAGES, |
| QDIO_DBF_SENSE_NR_AREAS, |
| QDIO_DBF_SENSE_LEN); |
| if (!qdio_dbf_sense) |
| goto oom; |
| |
| debug_register_view(qdio_dbf_sense,&debug_hex_ascii_view); |
| debug_set_level(qdio_dbf_sense,QDIO_DBF_SENSE_LEVEL); |
| |
| qdio_dbf_trace=debug_register(QDIO_DBF_TRACE_NAME, |
| QDIO_DBF_TRACE_PAGES, |
| QDIO_DBF_TRACE_NR_AREAS, |
| QDIO_DBF_TRACE_LEN); |
| if (!qdio_dbf_trace) |
| goto oom; |
| |
| debug_register_view(qdio_dbf_trace,&debug_hex_ascii_view); |
| debug_set_level(qdio_dbf_trace,QDIO_DBF_TRACE_LEVEL); |
| |
| #ifdef CONFIG_QDIO_DEBUG |
| qdio_dbf_slsb_out=debug_register(QDIO_DBF_SLSB_OUT_NAME, |
| QDIO_DBF_SLSB_OUT_PAGES, |
| QDIO_DBF_SLSB_OUT_NR_AREAS, |
| QDIO_DBF_SLSB_OUT_LEN); |
| if (!qdio_dbf_slsb_out) |
| goto oom; |
| debug_register_view(qdio_dbf_slsb_out,&debug_hex_ascii_view); |
| debug_set_level(qdio_dbf_slsb_out,QDIO_DBF_SLSB_OUT_LEVEL); |
| |
| qdio_dbf_slsb_in=debug_register(QDIO_DBF_SLSB_IN_NAME, |
| QDIO_DBF_SLSB_IN_PAGES, |
| QDIO_DBF_SLSB_IN_NR_AREAS, |
| QDIO_DBF_SLSB_IN_LEN); |
| if (!qdio_dbf_slsb_in) |
| goto oom; |
| debug_register_view(qdio_dbf_slsb_in,&debug_hex_ascii_view); |
| debug_set_level(qdio_dbf_slsb_in,QDIO_DBF_SLSB_IN_LEVEL); |
| #endif /* CONFIG_QDIO_DEBUG */ |
| return 0; |
| oom: |
| QDIO_PRINT_ERR("not enough memory for dbf.\n"); |
| qdio_unregister_dbf_views(); |
| return -ENOMEM; |
| } |
| |
| static int __init |
| init_QDIO(void) |
| { |
| int res; |
| #ifdef QDIO_PERFORMANCE_STATS |
| void *ptr; |
| #endif /* QDIO_PERFORMANCE_STATS */ |
| |
| printk("qdio: loading %s\n",version); |
| |
| res=qdio_get_qdio_memory(); |
| if (res) |
| return res; |
| |
| res = qdio_register_dbf_views(); |
| if (res) |
| return res; |
| |
| QDIO_DBF_TEXT0(0,setup,"initQDIO"); |
| |
| #ifdef QDIO_PERFORMANCE_STATS |
| memset((void*)&perf_stats,0,sizeof(perf_stats)); |
| QDIO_DBF_TEXT0(0,setup,"perfstat"); |
| ptr=&perf_stats; |
| QDIO_DBF_HEX0(0,setup,&ptr,sizeof(void*)); |
| #endif /* QDIO_PERFORMANCE_STATS */ |
| |
| qdio_add_procfs_entry(); |
| |
| if (tiqdio_check_chsc_availability()) |
| QDIO_PRINT_ERR("Not all CHSCs supported. Continuing.\n"); |
| |
| tiqdio_register_thinints(); |
| |
| return 0; |
| } |
| |
| static void __exit |
| cleanup_QDIO(void) |
| { |
| tiqdio_unregister_thinints(); |
| qdio_remove_procfs_entry(); |
| qdio_release_qdio_memory(); |
| qdio_unregister_dbf_views(); |
| |
| printk("qdio: %s: module removed\n",version); |
| } |
| |
| module_init(init_QDIO); |
| module_exit(cleanup_QDIO); |
| |
| EXPORT_SYMBOL(qdio_allocate); |
| EXPORT_SYMBOL(qdio_establish); |
| EXPORT_SYMBOL(qdio_initialize); |
| EXPORT_SYMBOL(qdio_activate); |
| EXPORT_SYMBOL(do_QDIO); |
| EXPORT_SYMBOL(qdio_shutdown); |
| EXPORT_SYMBOL(qdio_free); |
| EXPORT_SYMBOL(qdio_cleanup); |
| EXPORT_SYMBOL(qdio_synchronize); |