text: #ifndef _S390_TLBFLUSH_H
  #define _S390_TLBFLUSH_H
  
  #include <linux/mm.h>
  #include <asm/processor.h>
  #include <asm/pgalloc.h>
  
  /*
   * TLB flushing:
   *
   *  - flush_tlb() flushes the current mm struct TLBs
   *  - flush_tlb_all() flushes all processes TLBs 
   *  - flush_tlb_mm(mm) flushes the specified mm context TLB's
   *  - flush_tlb_page(vma, vmaddr) flushes one page
   *  - flush_tlb_range(vma, start, end) flushes a range of pages
   *  - flush_tlb_kernel_range(start, end) flushes a range of kernel pages
   *  - flush_tlb_pgtables(mm, start, end) flushes a range of page tables
   */
  
  /*
   * S/390 has three ways of flushing TLBs
   * 'ptlb' does a flush of the local processor
   * 'csp' flushes the TLBs on all PUs of a SMP
   * 'ipte' invalidates a pte in a page table and flushes that out of
   * the TLBs of all PUs of a SMP
   */
  
  #define local_flush_tlb() \
  do {  asm volatile("ptlb": : :"memory"); } while (0)
  
  #ifndef CONFIG_SMP
  
  /*
   * We always need to flush, since s390 does not flush tlb
   * on each context switch
   */
  
  static inline void flush_tlb(void)
  {
  	local_flush_tlb();
  }
  static inline void flush_tlb_all(void)
  {
  	local_flush_tlb();
  }
  static inline void flush_tlb_mm(struct mm_struct *mm) 
  {
  	local_flush_tlb();
  }
  static inline void flush_tlb_page(struct vm_area_struct *vma,
  				  unsigned long addr)
  {
  	local_flush_tlb();
  }
  static inline void flush_tlb_range(struct vm_area_struct *vma,
  				   unsigned long start, unsigned long end)
  {
  	local_flush_tlb();
  }
  
  #define flush_tlb_kernel_range(start, end) \
  	local_flush_tlb();
  
  #else
  
  #include <asm/smp.h>
  
  extern void smp_ptlb_all(void);
  
  static inline void global_flush_tlb(void)
  {
  	register unsigned long reg2 asm("2");
  	register unsigned long reg3 asm("3");
  	register unsigned long reg4 asm("4");
  	long dummy;
  
  #ifndef __s390x__
  	if (!MACHINE_HAS_CSP) {
  		smp_ptlb_all();
  		return;
  	}
  #endif /* __s390x__ */
  
  	dummy = 0;
  	reg2 = reg3 = 0;
  	reg4 = ((unsigned long) &dummy) + 1;
  	asm volatile(
  		"	csp	%0,%2"
  		: : "d" (reg2), "d" (reg3), "d" (reg4), "m" (dummy) : "cc" );
  }
  
  /*
   * We only have to do global flush of tlb if process run since last
   * flush on any other pu than current. 
   * If we have threads (mm->count > 1) we always do a global flush, 
   * since the process runs on more than one processor at the same time.
   */
  
  static inline void __flush_tlb_mm(struct mm_struct * mm)
  {
  	cpumask_t local_cpumask;
  
  	if (unlikely(cpus_empty(mm->cpu_vm_mask)))
  		return;
  	if (MACHINE_HAS_IDTE) {
  		pgd_t *shadow_pgd = get_shadow_pgd(mm->pgd);
  
  		if (shadow_pgd) {
  			asm volatile(
  				"	.insn	rrf,0xb98e0000,0,%0,%1,0"
  				: : "a" (2048),
  				"a" (__pa(shadow_pgd) & PAGE_MASK) : "cc" );
  		}
  		asm volatile(
  			"	.insn	rrf,0xb98e0000,0,%0,%1,0"
  			: : "a" (2048), "a" (__pa(mm->pgd)&PAGE_MASK) : "cc");
  		return;
  	}
  	preempt_disable();
  	local_cpumask = cpumask_of_cpu(smp_processor_id());
  	if (cpus_equal(mm->cpu_vm_mask, local_cpumask))
  		local_flush_tlb();
  	else
  		global_flush_tlb();
  	preempt_enable();
  }
  
  static inline void flush_tlb(void)
  {
  	__flush_tlb_mm(current->mm);
  }
  static inline void flush_tlb_all(void)
  {
  	global_flush_tlb();
  }
  static inline void flush_tlb_mm(struct mm_struct *mm) 
  {
  	__flush_tlb_mm(mm); 
  }
  static inline void flush_tlb_page(struct vm_area_struct *vma,
  				  unsigned long addr)
  {
  	__flush_tlb_mm(vma->vm_mm);
  }
  static inline void flush_tlb_range(struct vm_area_struct *vma,
  				   unsigned long start, unsigned long end)
  {
  	__flush_tlb_mm(vma->vm_mm); 
  }
  
  #define flush_tlb_kernel_range(start, end) global_flush_tlb()
  
  #endif
  
  static inline void flush_tlb_pgtables(struct mm_struct *mm,
                                        unsigned long start, unsigned long end)
  {
          /* S/390 does not keep any page table caches in TLB */
  }
  
  #endif /* _S390_TLBFLUSH_H */
  