aboutsummaryrefslogblamecommitdiffstats
path: root/kernel/softirq.c
blob: bd89bc4eb0b9c0baa8070678d3cb099a8b40bb1c (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16















                                                                                
                          

                           
                      
                       












































                                                                        


                                            
                            














                                                






                                                       








































                                                                              
                            


                               
      

                                      
                            
                              
      














                                                                              
                            
                                 
      





                                         
                            




                               
      














                                                                 
                            
                                 
      




                                  

















                                                           

                                      

                                                                       
 


                                                            
                               
 
                           











                                              
                            







                                          
                             

                                      
                           





















                                          

      




                              




                                           

 











                                                                      
                             


                                                       





                                                                               






















                                                             














                                                                             











































































































































                                                                                        




























































































                                                                            
                                                             







                                                       
                                   








                                                                            
                               



                                                            
                                    

                                                
                                                       

                                                             
                      


                                                                               

                                                  
                                                          


                                          
         




                               
                                                      





                                                     


                                                              



                                                


















                                                                           
/*
 *	linux/kernel/softirq.c
 *
 *	Copyright (C) 1992 Linus Torvalds
 *
 * Rewritten. Old one was good in 2.2, but in 2.3 it was immoral. --ANK (990903)
 */

#include <linux/module.h>
#include <linux/kernel_stat.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/notifier.h>
#include <linux/percpu.h>
#include <linux/cpu.h>
#include <linux/freezer.h>
#include <linux/kthread.h>
#include <linux/rcupdate.h>
#include <linux/smp.h>
#include <linux/tick.h>

#include <asm/irq.h>
/*
   - No shared variables, all the data are CPU local.
   - If a softirq needs serialization, let it serialize itself
     by its own spinlocks.
   - Even if softirq is serialized, only local cpu is marked for
     execution. Hence, we get something sort of weak cpu binding.
     Though it is still not clear, will it result in better locality
     or will not.

   Examples:
   - NET RX softirq. It is multithreaded and does not require
     any global serialization.
   - NET TX softirq. It kicks software netdevice queues, hence
     it is logically serialized per device, but this serialization
     is invisible to common code.
   - Tasklets: serialized wrt itself.
 */

#ifndef __ARCH_IRQ_STAT
irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;
EXPORT_SYMBOL(irq_stat);
#endif

static struct softirq_action softirq_vec[32] __cacheline_aligned_in_smp;

static DEFINE_PER_CPU(struct task_struct *, ksoftirqd);

/*
 * we cannot loop indefinitely here to avoid userspace starvation,
 * but we also don't want to introduce a worst case 1/HZ latency
 * to the pending events, so lets the scheduler to balance
 * the softirq load for us.
 */
static inline void wakeup_softirqd(void)
{
	/* Interrupts are disabled: no need to stop preemption */
	struct task_struct *tsk = __get_cpu_var(ksoftirqd);

	if (tsk && tsk->state != TASK_RUNNING)
		wake_up_process(tsk);
}

/*
 * This one is for softirq.c-internal use,
 * where hardirqs are disabled legitimately:
 */
#ifdef CONFIG_TRACE_IRQFLAGS
static void __local_bh_disable(unsigned long ip)
{
	unsigned long flags;

	WARN_ON_ONCE(in_irq());

	raw_local_irq_save(flags);
	add_preempt_count(SOFTIRQ_OFFSET);
	/*
	 * Were softirqs turned off above:
	 */
	if (softirq_count() == SOFTIRQ_OFFSET)
		trace_softirqs_off(ip);
	raw_local_irq_restore(flags);
}
#else /* !CONFIG_TRACE_IRQFLAGS */
static inline void __local_bh_disable(unsigned long ip)
{
	add_preempt_count(SOFTIRQ_OFFSET);
	barrier();
}
#endif /* CONFIG_TRACE_IRQFLAGS */

void local_bh_disable(void)
{
	__local_bh_disable((unsigned long)__builtin_return_address(0));
}

EXPORT_SYMBOL(local_bh_disable);

void __local_bh_enable(void)
{
	WARN_ON_ONCE(in_irq());

	/*
	 * softirqs should never be enabled by __local_bh_enable(),
	 * it always nests inside local_bh_enable() sections:
	 */
	WARN_ON_ONCE(softirq_count() == SOFTIRQ_OFFSET);

	sub_preempt_count(SOFTIRQ_OFFSET);
}
EXPORT_SYMBOL_GPL(__local_bh_enable);

/*
 * Special-case - softirqs can safely be enabled in
 * cond_resched_softirq(), or by __do_softirq(),
 * without processing still-pending softirqs:
 */
void _local_bh_enable(void)
{
	WARN_ON_ONCE(in_irq());
	WARN_ON_ONCE(!irqs_disabled());

	if (softirq_count() == SOFTIRQ_OFFSET)
		trace_softirqs_on((unsigned long)__builtin_return_address(0));
	sub_preempt_count(SOFTIRQ_OFFSET);
}

EXPORT_SYMBOL(_local_bh_enable);

void local_bh_enable(void)
{
#ifdef CONFIG_TRACE_IRQFLAGS
	unsigned long flags;

	WARN_ON_ONCE(in_irq());
#endif
	WARN_ON_ONCE(irqs_disabled());

#ifdef CONFIG_TRACE_IRQFLAGS
	local_irq_save(flags);
#endif
	/*
	 * Are softirqs going to be turned on now:
	 */
	if (softirq_count() == SOFTIRQ_OFFSET)
		trace_softirqs_on((unsigned long)__builtin_return_address(0));
	/*
	 * Keep preemption disabled until we are done with
	 * softirq processing:
 	 */
 	sub_preempt_count(SOFTIRQ_OFFSET - 1);

	if (unlikely(!in_interrupt() && local_softirq_pending()))
		do_softirq();

	dec_preempt_count();
#ifdef CONFIG_TRACE_IRQFLAGS
	local_irq_restore(flags);
#endif
	preempt_check_resched();
}
EXPORT_SYMBOL(local_bh_enable);

void local_bh_enable_ip(unsigned long ip)
{
#ifdef CONFIG_TRACE_IRQFLAGS
	unsigned long flags;

	WARN_ON_ONCE(in_irq());

	local_irq_save(flags);
#endif
	/*
	 * Are softirqs going to be turned on now:
	 */
	if (softirq_count() == SOFTIRQ_OFFSET)
		trace_softirqs_on(ip);
	/*
	 * Keep preemption disabled until we are done with
	 * softirq processing:
 	 */
 	sub_preempt_count(SOFTIRQ_OFFSET - 1);

	if (unlikely(!in_interrupt() && local_softirq_pending()))
		do_softirq();

	dec_preempt_count();
#ifdef CONFIG_TRACE_IRQFLAGS
	local_irq_restore(flags);
#endif
	preempt_check_resched();
}
EXPORT_SYMBOL(local_bh_enable_ip);

/*
 * We restart softirq processing MAX_SOFTIRQ_RESTART times,
 * and we fall back to softirqd after that.
 *
 * This number has been established via experimentation.
 * The two things to balance is latency against fairness -
 * we want to handle softirqs as soon as possible, but they
 * should not be able to lock up the box.
 */
#define MAX_SOFTIRQ_RESTART 10

asmlinkage void __do_softirq(void)
{
	struct softirq_action *h;
	__u32 pending;
	int max_restart = MAX_SOFTIRQ_RESTART;
	int cpu;

	pending = local_softirq_pending();
	account_system_vtime(current);

	__local_bh_disable((unsigned long)__builtin_return_address(0));
	trace_softirq_enter();

	cpu = smp_processor_id();
restart:
	/* Reset the pending bitmask before enabling irqs */
	set_softirq_pending(0);

	local_irq_enable();

	h = softirq_vec;

	do {
		if (pending & 1) {
			h->action(h);
			rcu_bh_qsctr_inc(cpu);
		}
		h++;
		pending >>= 1;
	} while (pending);

	local_irq_disable();

	pending = local_softirq_pending();
	if (pending && --max_restart)
		goto restart;

	if (pending)
		wakeup_softirqd();

	trace_softirq_exit();

	account_system_vtime(current);
	_local_bh_enable();
}

#ifndef __ARCH_HAS_DO_SOFTIRQ

asmlinkage void do_softirq(void)
{
	__u32 pending;
	unsigned long flags;

	if (in_interrupt())
		return;

	local_irq_save(flags);

	pending = local_softirq_pending();

	if (pending)
		__do_softirq();

	local_irq_restore(flags);
}

#endif

/*
 * Enter an interrupt context.
 */
void irq_enter(void)
{
	__irq_enter();
#ifdef CONFIG_NO_HZ
	if (idle_cpu(smp_processor_id()))
		tick_nohz_update_jiffies();
#endif
}

#ifdef __ARCH_IRQ_EXIT_IRQS_DISABLED
# define invoke_softirq()	__do_softirq()
#else
# define invoke_softirq()	do_softirq()
#endif

/*
 * Exit an interrupt context. Process softirqs if needed and possible:
 */
void irq_exit(void)
{
	account_system_vtime(current);
	trace_hardirq_exit();
	sub_preempt_count(IRQ_EXIT_OFFSET);
	if (!in_interrupt() && local_softirq_pending())
		invoke_softirq();

#ifdef CONFIG_NO_HZ
	/* Make sure that timer wheel updates are propagated */
	if (!in_interrupt() && idle_cpu(smp_processor_id()) && !need_resched())
		tick_nohz_stop_sched_tick();
#endif
	preempt_enable_no_resched();
}

/*
 * This function must run with irqs disabled!
 */
inline fastcall void raise_softirq_irqoff(unsigned int nr)
{
	__raise_softirq_irqoff(nr);

	/*
	 * If we're in an interrupt or softirq, we're done
	 * (this also catches softirq-disabled code). We will
	 * actually run the softirq once we return from
	 * the irq or softirq.
	 *
	 * Otherwise we wake up ksoftirqd to make sure we
	 * schedule the softirq soon.
	 */
	if (!in_interrupt())
		wakeup_softirqd();
}

void fastcall raise_softirq(unsigned int nr)
{
	unsigned long flags;

	local_irq_save(flags);
	raise_softirq_irqoff(nr);
	local_irq_restore(flags);
}

void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
{
	softirq_vec[nr].data = data;
	softirq_vec[nr].action = action;
}

/* Tasklets */
struct tasklet_head
{
	struct tasklet_struct *list;
};

/* Some compilers disobey section attribute on statics when not
   initialized -- RR */
static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec) = { NULL };
static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec) = { NULL };

void fastcall __tasklet_schedule(struct tasklet_struct *t)
{
	unsigned long flags;

	local_irq_save(flags);
	t->next = __get_cpu_var(tasklet_vec).list;
	__get_cpu_var(tasklet_vec).list = t;
	raise_softirq_irqoff(TASKLET_SOFTIRQ);
	local_irq_restore(flags);
}

EXPORT_SYMBOL(__tasklet_schedule);

void fastcall __tasklet_hi_schedule(struct tasklet_struct *t)
{
	unsigned long flags;

	local_irq_save(flags);
	t->next = __get_cpu_var(tasklet_hi_vec).list;
	__get_cpu_var(tasklet_hi_vec).list = t;
	raise_softirq_irqoff(HI_SOFTIRQ);
	local_irq_restore(flags);
}

EXPORT_SYMBOL(__tasklet_hi_schedule);

static void tasklet_action(struct softirq_action *a)
{
	struct tasklet_struct *list;

	local_irq_disable();
	list = __get_cpu_var(tasklet_vec).list;
	__get_cpu_var(tasklet_vec).list = NULL;
	local_irq_enable();

	while (list) {
		struct tasklet_struct *t = list;

		list = list->next;

		if (tasklet_trylock(t)) {
			if (!atomic_read(&t->count)) {
				if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
					BUG();
				t->func(t->data);
				tasklet_unlock(t);
				continue;
			}
			tasklet_unlock(t);
		}

		local_irq_disable();
		t->next = __get_cpu_var(tasklet_vec).list;
		__get_cpu_var(tasklet_vec).list = t;
		__raise_softirq_irqoff(TASKLET_SOFTIRQ);
		local_irq_enable();
	}
}

static void tasklet_hi_action(struct softirq_action *a)
{
	struct tasklet_struct *list;

	local_irq_disable();
	list = __get_cpu_var(tasklet_hi_vec).list;
	__get_cpu_var(tasklet_hi_vec).list = NULL;
	local_irq_enable();

	while (list) {
		struct tasklet_struct *t = list;

		list = list->next;

		if (tasklet_trylock(t)) {
			if (!atomic_read(&t->count)) {
				if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
					BUG();
				t->func(t->data);
				tasklet_unlock(t);
				continue;
			}
			tasklet_unlock(t);
		}

		local_irq_disable();
		t->next = __get_cpu_var(tasklet_hi_vec).list;
		__get_cpu_var(tasklet_hi_vec).list = t;
		__raise_softirq_irqoff(HI_SOFTIRQ);
		local_irq_enable();
	}
}


void tasklet_init(struct tasklet_struct *t,
		  void (*func)(unsigned long), unsigned long data)
{
	t->next = NULL;
	t->state = 0;
	atomic_set(&t->count, 0);
	t->func = func;
	t->data = data;
}

EXPORT_SYMBOL(tasklet_init);

void tasklet_kill(struct tasklet_struct *t)
{
	if (in_interrupt())
		printk("Attempt to kill tasklet from interrupt\n");

	while (test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {
		do
			yield();
		while (test_bit(TASKLET_STATE_SCHED, &t->state));
	}
	tasklet_unlock_wait(t);
	clear_bit(TASKLET_STATE_SCHED, &t->state);
}

EXPORT_SYMBOL(tasklet_kill);

void __init softirq_init(void)
{
	open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
	open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
}

static int ksoftirqd(void * __bind_cpu)
{
	set_current_state(TASK_INTERRUPTIBLE);

	while (!kthread_should_stop()) {
		preempt_disable();
		if (!local_softirq_pending()) {
			preempt_enable_no_resched();
			schedule();
			preempt_disable();
		}

		__set_current_state(TASK_RUNNING);

		while (local_softirq_pending()) {
			/* Preempt disable stops cpu going offline.
			   If already offline, we'll be on wrong CPU:
			   don't process */
			if (cpu_is_offline((long)__bind_cpu))
				goto wait_to_die;
			do_softirq();
			preempt_enable_no_resched();
			cond_resched();
			preempt_disable();
		}
		preempt_enable();
		set_current_state(TASK_INTERRUPTIBLE);
	}
	__set_current_state(TASK_RUNNING);
	return 0;

wait_to_die:
	preempt_enable();
	/* Wait for kthread_stop */
	set_current_state(TASK_INTERRUPTIBLE);
	while (!kthread_should_stop()) {
		schedule();
		set_current_state(TASK_INTERRUPTIBLE);
	}
	__set_current_state(TASK_RUNNING);
	return 0;
}

#ifdef CONFIG_HOTPLUG_CPU
/*
 * tasklet_kill_immediate is called to remove a tasklet which can already be
 * scheduled for execution on @cpu.
 *
 * Unlike tasklet_kill, this function removes the tasklet
 * _immediately_, even if the tasklet is in TASKLET_STATE_SCHED state.
 *
 * When this function is called, @cpu must be in the CPU_DEAD state.
 */
void tasklet_kill_immediate(struct tasklet_struct *t, unsigned int cpu)
{
	struct tasklet_struct **i;

	BUG_ON(cpu_online(cpu));
	BUG_ON(test_bit(TASKLET_STATE_RUN, &t->state));

	if (!test_bit(TASKLET_STATE_SCHED, &t->state))
		return;

	/* CPU is dead, so no lock needed. */
	for (i = &per_cpu(tasklet_vec, cpu).list; *i; i = &(*i)->next) {
		if (*i == t) {
			*i = t->next;
			return;
		}
	}
	BUG();
}

static void takeover_tasklets(unsigned int cpu)
{
	struct tasklet_struct **i;

	/* CPU is dead, so no lock needed. */
	local_irq_disable();

	/* Find end, append list for that CPU. */
	for (i = &__get_cpu_var(tasklet_vec).list; *i; i = &(*i)->next);
	*i = per_cpu(tasklet_vec, cpu).list;
	per_cpu(tasklet_vec, cpu).list = NULL;
	raise_softirq_irqoff(TASKLET_SOFTIRQ);

	for (i = &__get_cpu_var(tasklet_hi_vec).list; *i; i = &(*i)->next);
	*i = per_cpu(tasklet_hi_vec, cpu).list;
	per_cpu(tasklet_hi_vec, cpu).list = NULL;
	raise_softirq_irqoff(HI_SOFTIRQ);

	local_irq_enable();
}
#endif /* CONFIG_HOTPLUG_CPU */

static int __cpuinit cpu_callback(struct notifier_block *nfb,
				  unsigned long action,
				  void *hcpu)
{
	int hotcpu = (unsigned long)hcpu;
	struct task_struct *p;

	switch (action) {
	case CPU_UP_PREPARE:
	case CPU_UP_PREPARE_FROZEN:
		p = kthread_create(ksoftirqd, hcpu, "ksoftirqd/%d", hotcpu);
		if (IS_ERR(p)) {
			printk("ksoftirqd for %i failed\n", hotcpu);
			return NOTIFY_BAD;
		}
		kthread_bind(p, hotcpu);
  		per_cpu(ksoftirqd, hotcpu) = p;
 		break;
	case CPU_ONLINE:
	case CPU_ONLINE_FROZEN:
		wake_up_process(per_cpu(ksoftirqd, hotcpu));
		break;
#ifdef CONFIG_HOTPLUG_CPU
	case CPU_UP_CANCELED:
	case CPU_UP_CANCELED_FROZEN:
		if (!per_cpu(ksoftirqd, hotcpu))
			break;
		/* Unbind so it can run.  Fall thru. */
		kthread_bind(per_cpu(ksoftirqd, hotcpu),
			     any_online_cpu(cpu_online_map));
	case CPU_DEAD:
	case CPU_DEAD_FROZEN: {
		struct sched_param param = { .sched_priority = MAX_RT_PRIO-1 };

		p = per_cpu(ksoftirqd, hotcpu);
		per_cpu(ksoftirqd, hotcpu) = NULL;
		sched_setscheduler(p, SCHED_FIFO, &param);
		kthread_stop(p);
		takeover_tasklets(hotcpu);
		break;
	}
#endif /* CONFIG_HOTPLUG_CPU */
 	}
	return NOTIFY_OK;
}

static struct notifier_block __cpuinitdata cpu_nfb = {
	.notifier_call = cpu_callback
};

__init int spawn_ksoftirqd(void)
{
	void *cpu = (void *)(long)smp_processor_id();
	int err = cpu_callback(&cpu_nfb, CPU_UP_PREPARE, cpu);

	BUG_ON(err == NOTIFY_BAD);
	cpu_callback(&cpu_nfb, CPU_ONLINE, cpu);
	register_cpu_notifier(&cpu_nfb);
	return 0;
}

#ifdef CONFIG_SMP
/*
 * Call a function on all processors
 */
int on_each_cpu(void (*func) (void *info), void *info, int retry, int wait)
{
	int ret = 0;

	preempt_disable();
	ret = smp_call_function(func, info, retry, wait);
	local_irq_disable();
	func(info);
	local_irq_enable();
	preempt_enable();
	return ret;
}
EXPORT_SYMBOL(on_each_cpu);
#endif
an class="hl opt">= 0; dlog(ms, "start cmd=%x", (int) cmd); #endif /* Off we go */ dlog(ms, "about to arb, intr/exc/err/fc=%.8x", MKWORD(mr->interrupt, mr->exception, mr->error, mr->fifo_count)); out_8(&mr->interrupt, INT_CMDDONE); out_8(&mr->sequence, SEQ_ENBRESEL); mesh_flush_io(mr); udelay(1); if (in_8(&mr->bus_status1) & (BS1_BSY | BS1_SEL)) { /* * Some other device has the bus or is arbitrating for it - * probably a target which is about to reselect us. */ dlog(ms, "busy b4 arb, intr/exc/err/fc=%.8x", MKWORD(mr->interrupt, mr->exception, mr->error, mr->fifo_count)); for (t = 100; t > 0; --t) { if ((in_8(&mr->bus_status1) & (BS1_BSY | BS1_SEL)) == 0) break; if (in_8(&mr->interrupt) != 0) { dlog(ms, "intr b4 arb, intr/exc/err/fc=%.8x", MKWORD(mr->interrupt, mr->exception, mr->error, mr->fifo_count)); mesh_interrupt(ms); if (ms->phase != arbitrating) return; } udelay(1); } if (in_8(&mr->bus_status1) & (BS1_BSY | BS1_SEL)) { /* XXX should try again in a little while */ ms->stat = DID_BUS_BUSY; ms->phase = idle; mesh_done(ms, 0); return; } } /* * Apparently the mesh has a bug where it will assert both its * own bit and the target's bit on the bus during arbitration. */ out_8(&mr->dest_id, mr->source_id); /* * There appears to be a race with reselection sometimes, * where a target reselects us just as we issue the * arbitrate command. It seems that then the arbitrate * command just hangs waiting for the bus to be free * without giving us a reselection exception. * The only way I have found to get it to respond correctly * is this: disable reselection before issuing the arbitrate * command, then after issuing it, if it looks like a target * is trying to reselect us, reset the mesh and then enable * reselection. */ out_8(&mr->sequence, SEQ_DISRESEL); if (in_8(&mr->interrupt) != 0) { dlog(ms, "intr after disresel, intr/exc/err/fc=%.8x", MKWORD(mr->interrupt, mr->exception, mr->error, mr->fifo_count)); mesh_interrupt(ms); if (ms->phase != arbitrating) return; dlog(ms, "after intr after disresel, intr/exc/err/fc=%.8x", MKWORD(mr->interrupt, mr->exception, mr->error, mr->fifo_count)); } out_8(&mr->sequence, SEQ_ARBITRATE); for (t = 230; t > 0; --t) { if (in_8(&mr->interrupt) != 0) break; udelay(1); } dlog(ms, "after arb, intr/exc/err/fc=%.8x", MKWORD(mr->interrupt, mr->exception, mr->error, mr->fifo_count)); if (in_8(&mr->interrupt) == 0 && (in_8(&mr->bus_status1) & BS1_SEL) && (in_8(&mr->bus_status0) & BS0_IO)) { /* looks like a reselection - try resetting the mesh */ dlog(ms, "resel? after arb, intr/exc/err/fc=%.8x", MKWORD(mr->interrupt, mr->exception, mr->error, mr->fifo_count)); out_8(&mr->sequence, SEQ_RESETMESH); mesh_flush_io(mr); udelay(10); out_8(&mr->interrupt, INT_ERROR | INT_EXCEPTION | INT_CMDDONE); out_8(&mr->intr_mask, INT_ERROR | INT_EXCEPTION | INT_CMDDONE); out_8(&mr->sequence, SEQ_ENBRESEL); mesh_flush_io(mr); for (t = 10; t > 0 && in_8(&mr->interrupt) == 0; --t) udelay(1); dlog(ms, "tried reset after arb, intr/exc/err/fc=%.8x", MKWORD(mr->interrupt, mr->exception, mr->error, mr->fifo_count)); #ifndef MESH_MULTIPLE_HOSTS if (in_8(&mr->interrupt) == 0 && (in_8(&mr->bus_status1) & BS1_SEL) && (in_8(&mr->bus_status0) & BS0_IO)) { printk(KERN_ERR "mesh: controller not responding" " to reselection!\n"); /* * If this is a target reselecting us, and the * mesh isn't responding, the higher levels of * the scsi code will eventually time out and * reset the bus. */ } #endif } } /* * Start the next command for a MESH. * Should be called with interrupts disabled. */ static void mesh_start(struct mesh_state *ms) { struct scsi_cmnd *cmd, *prev, *next; if (ms->phase != idle || ms->current_req != NULL) { printk(KERN_ERR "inappropriate mesh_start (phase=%d, ms=%p)", ms->phase, ms); return; } while (ms->phase == idle) { prev = NULL; for (cmd = ms->request_q; ; cmd = (struct scsi_cmnd *) cmd->host_scribble) { if (cmd == NULL) return; if (ms->tgts[cmd->device->id].current_req == NULL) break; prev = cmd; } next = (struct scsi_cmnd *) cmd->host_scribble; if (prev == NULL) ms->request_q = next; else prev->host_scribble = (void *) next; if (next == NULL) ms->request_qtail = prev; mesh_start_cmd(ms, cmd); } } static void mesh_done(struct mesh_state *ms, int start_next) { struct scsi_cmnd *cmd; struct mesh_target *tp = &ms->tgts[ms->conn_tgt]; cmd = ms->current_req; ms->current_req = NULL; tp->current_req = NULL; if (cmd) { cmd->result = (ms->stat << 16) + cmd->SCp.Status; if (ms->stat == DID_OK) cmd->result += (cmd->SCp.Message << 8); if (DEBUG_TARGET(cmd)) { printk(KERN_DEBUG "mesh_done: result = %x, data_ptr=%d, buflen=%d\n", cmd->result, ms->data_ptr, scsi_bufflen(cmd)); #if 0 /* needs to use sg? */ if ((cmd->cmnd[0] == 0 || cmd->cmnd[0] == 0x12 || cmd->cmnd[0] == 3) && cmd->request_buffer != 0) { unsigned char *b = cmd->request_buffer; printk(KERN_DEBUG "buffer = %x %x %x %x %x %x %x %x\n", b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]); } #endif } cmd->SCp.this_residual -= ms->data_ptr; mesh_completed(ms, cmd); } if (start_next) { out_8(&ms->mesh->sequence, SEQ_ENBRESEL); mesh_flush_io(ms->mesh); udelay(1); ms->phase = idle; mesh_start(ms); } } static inline void add_sdtr_msg(struct mesh_state *ms) { int i = ms->n_msgout; ms->msgout[i] = EXTENDED_MESSAGE; ms->msgout[i+1] = 3; ms->msgout[i+2] = EXTENDED_SDTR; ms->msgout[i+3] = mesh_sync_period/4; ms->msgout[i+4] = (ALLOW_SYNC(ms->conn_tgt)? mesh_sync_offset: 0); ms->n_msgout = i + 5; } static void set_sdtr(struct mesh_state *ms, int period, int offset) { struct mesh_target *tp = &ms->tgts[ms->conn_tgt]; volatile struct mesh_regs __iomem *mr = ms->mesh; int v, tr; tp->sdtr_state = sdtr_done; if (offset == 0) { /* asynchronous */ if (SYNC_OFF(tp->sync_params)) printk(KERN_INFO "mesh: target %d now asynchronous\n", ms->conn_tgt); tp->sync_params = ASYNC_PARAMS; out_8(&mr->sync_params, ASYNC_PARAMS); return; } /* * We need to compute ceil(clk_freq * period / 500e6) - 2 * without incurring overflow. */ v = (ms->clk_freq / 5000) * period; if (v <= 250000) { /* special case: sync_period == 5 * clk_period */ v = 0; /* units of tr are 100kB/s */ tr = (ms->clk_freq + 250000) / 500000; } else { /* sync_period == (v + 2) * 2 * clk_period */ v = (v + 99999) / 100000 - 2; if (v > 15) v = 15; /* oops */ tr = ((ms->clk_freq / (v + 2)) + 199999) / 200000; } if (offset > 15) offset = 15; /* can't happen */ tp->sync_params = SYNC_PARAMS(offset, v); out_8(&mr->sync_params, tp->sync_params); printk(KERN_INFO "mesh: target %d synchronous at %d.%d MB/s\n", ms->conn_tgt, tr/10, tr%10); } static void start_phase(struct mesh_state *ms) { int i, seq, nb; volatile struct mesh_regs __iomem *mr = ms->mesh; volatile struct dbdma_regs __iomem *md = ms->dma; struct scsi_cmnd *cmd = ms->current_req; struct mesh_target *tp = &ms->tgts[ms->conn_tgt]; dlog(ms, "start_phase nmo/exc/fc/seq = %.8x", MKWORD(ms->n_msgout, mr->exception, mr->fifo_count, mr->sequence)); out_8(&mr->interrupt, INT_ERROR | INT_EXCEPTION | INT_CMDDONE); seq = use_active_neg + (ms->n_msgout? SEQ_ATN: 0); switch (ms->msgphase) { case msg_none: break; case msg_in: out_8(&mr->count_hi, 0); out_8(&mr->count_lo, 1); out_8(&mr->sequence, SEQ_MSGIN + seq); ms->n_msgin = 0; return; case msg_out: /* * To make sure ATN drops before we assert ACK for * the last byte of the message, we have to do the * last byte specially. */ if (ms->n_msgout <= 0) { printk(KERN_ERR "mesh: msg_out but n_msgout=%d\n", ms->n_msgout); mesh_dump_regs(ms); ms->msgphase = msg_none; break; } if (ALLOW_DEBUG(ms->conn_tgt)) { printk(KERN_DEBUG "mesh: sending %d msg bytes:", ms->n_msgout); for (i = 0; i < ms->n_msgout; ++i) printk(" %x", ms->msgout[i]); printk("\n"); } dlog(ms, "msgout msg=%.8x", MKWORD(ms->n_msgout, ms->msgout[0], ms->msgout[1], ms->msgout[2])); out_8(&mr->count_hi, 0); out_8(&mr->sequence, SEQ_FLUSHFIFO); mesh_flush_io(mr); udelay(1); /* * If ATN is not already asserted, we assert it, then * issue a SEQ_MSGOUT to get the mesh to drop ACK. */ if ((in_8(&mr->bus_status0) & BS0_ATN) == 0) { dlog(ms, "bus0 was %.2x explicitly asserting ATN", mr->bus_status0); out_8(&mr->bus_status0, BS0_ATN); /* explicit ATN */ mesh_flush_io(mr); udelay(1); out_8(&mr->count_lo, 1); out_8(&mr->sequence, SEQ_MSGOUT + seq); out_8(&mr->bus_status0, 0); /* release explicit ATN */ dlog(ms,"hace: after explicit ATN bus0=%.2x",mr->bus_status0); } if (ms->n_msgout == 1) { /* * We can't issue the SEQ_MSGOUT without ATN * until the target has asserted REQ. The logic * in cmd_complete handles both situations: * REQ already asserted or not. */ cmd_complete(ms); } else { out_8(&mr->count_lo, ms->n_msgout - 1); out_8(&mr->sequence, SEQ_MSGOUT + seq); for (i = 0; i < ms->n_msgout - 1; ++i) out_8(&mr->fifo, ms->msgout[i]); } return; default: printk(KERN_ERR "mesh bug: start_phase msgphase=%d\n", ms->msgphase); } switch (ms->phase) { case selecting: out_8(&mr->dest_id, ms->conn_tgt); out_8(&mr->sequence, SEQ_SELECT + SEQ_ATN); break; case commanding: out_8(&mr->sync_params, tp->sync_params); out_8(&mr->count_hi, 0); if (cmd) { out_8(&mr->count_lo, cmd->cmd_len); out_8(&mr->sequence, SEQ_COMMAND + seq); for (i = 0; i < cmd->cmd_len; ++i) out_8(&mr->fifo, cmd->cmnd[i]); } else { out_8(&mr->count_lo, 6); out_8(&mr->sequence, SEQ_COMMAND + seq); for (i = 0; i < 6; ++i) out_8(&mr->fifo, 0); } break; case dataing: /* transfer data, if any */ if (!ms->dma_started) { set_dma_cmds(ms, cmd); out_le32(&md->cmdptr, virt_to_phys(ms->dma_cmds)); out_le32(&md->control, (RUN << 16) | RUN); ms->dma_started = 1; } nb = ms->dma_count; if (nb > 0xfff0) nb = 0xfff0; ms->dma_count -= nb; ms->data_ptr += nb; out_8(&mr->count_lo, nb); out_8(&mr->count_hi, nb >> 8); out_8(&mr->sequence, (tp->data_goes_out? SEQ_DATAOUT: SEQ_DATAIN) + SEQ_DMA_MODE + seq); break; case statusing: out_8(&mr->count_hi, 0); out_8(&mr->count_lo, 1); out_8(&mr->sequence, SEQ_STATUS + seq); break; case busfreeing: case disconnecting: out_8(&mr->sequence, SEQ_ENBRESEL); mesh_flush_io(mr); udelay(1); dlog(ms, "enbresel intr/exc/err/fc=%.8x", MKWORD(mr->interrupt, mr->exception, mr->error, mr->fifo_count)); out_8(&mr->sequence, SEQ_BUSFREE); break; default: printk(KERN_ERR "mesh: start_phase called with phase=%d\n", ms->phase); dumpslog(ms); } } static inline void get_msgin(struct mesh_state *ms) { volatile struct mesh_regs __iomem *mr = ms->mesh; int i, n; n = mr->fifo_count; if (n != 0) { i = ms->n_msgin; ms->n_msgin = i + n; for (; n > 0; --n) ms->msgin[i++] = in_8(&mr->fifo); } } static inline int msgin_length(struct mesh_state *ms) { int b, n; n = 1; if (ms->n_msgin > 0) { b = ms->msgin[0]; if (b == 1) { /* extended message */ n = ms->n_msgin < 2? 2: ms->msgin[1] + 2; } else if (0x20 <= b && b <= 0x2f) { /* 2-byte message */ n = 2; } } return n; } static void reselected(struct mesh_state *ms) { volatile struct mesh_regs __iomem *mr = ms->mesh; struct scsi_cmnd *cmd; struct mesh_target *tp; int b, t, prev; switch (ms->phase) { case idle: break; case arbitrating: if ((cmd = ms->current_req) != NULL) { /* put the command back on the queue */ cmd->host_scribble = (void *) ms->request_q; if (ms->request_q == NULL) ms->request_qtail = cmd; ms->request_q = cmd; tp = &ms->tgts[cmd->device->id]; tp->current_req = NULL; } break; case busfreeing: ms->phase = reselecting; mesh_done(ms, 0); break; case disconnecting: break; default: printk(KERN_ERR "mesh: reselected in phase %d/%d tgt %d\n", ms->msgphase, ms->phase, ms->conn_tgt); dumplog(ms, ms->conn_tgt); dumpslog(ms); } if (ms->dma_started) { printk(KERN_ERR "mesh: reselected with DMA started !\n"); halt_dma(ms); } ms->current_req = NULL; ms->phase = dataing; ms->msgphase = msg_in; ms->n_msgout = 0; ms->last_n_msgout = 0; prev = ms->conn_tgt; /* * We seem to get abortive reselections sometimes. */ while ((in_8(&mr->bus_status1) & BS1_BSY) == 0) { static int mesh_aborted_resels; mesh_aborted_resels++; out_8(&mr->interrupt, INT_ERROR | INT_EXCEPTION | INT_CMDDONE); mesh_flush_io(mr); udelay(1); out_8(&mr->sequence, SEQ_ENBRESEL); mesh_flush_io(mr); udelay(5); dlog(ms, "extra resel err/exc/fc = %.6x", MKWORD(0, mr->error, mr->exception, mr->fifo_count)); } out_8(&mr->interrupt, INT_ERROR | INT_EXCEPTION | INT_CMDDONE); mesh_flush_io(mr); udelay(1); out_8(&mr->sequence, SEQ_ENBRESEL); mesh_flush_io(mr); udelay(1); out_8(&mr->sync_params, ASYNC_PARAMS); /* * Find out who reselected us. */ if (in_8(&mr->fifo_count) == 0) { printk(KERN_ERR "mesh: reselection but nothing in fifo?\n"); ms->conn_tgt = ms->host->this_id; goto bogus; } /* get the last byte in the fifo */ do { b = in_8(&mr->fifo); dlog(ms, "reseldata %x", b); } while (in_8(&mr->fifo_count)); for (t = 0; t < 8; ++t) if ((b & (1 << t)) != 0 && t != ms->host->this_id) break; if (b != (1 << t) + (1 << ms->host->this_id)) { printk(KERN_ERR "mesh: bad reselection data %x\n", b); ms->conn_tgt = ms->host->this_id; goto bogus; } /* * Set up to continue with that target's transfer. */ ms->conn_tgt = t; tp = &ms->tgts[t]; out_8(&mr->sync_params, tp->sync_params); if (ALLOW_DEBUG(t)) { printk(KERN_DEBUG "mesh: reselected by target %d\n", t); printk(KERN_DEBUG "mesh: saved_ptr=%x goes_out=%d cmd=%p\n", tp->saved_ptr, tp->data_goes_out, tp->current_req); } ms->current_req = tp->current_req; if (tp->current_req == NULL) { printk(KERN_ERR "mesh: reselected by tgt %d but no cmd!\n", t); goto bogus; } ms->data_ptr = tp->saved_ptr; dlog(ms, "resel prev tgt=%d", prev); dlog(ms, "resel err/exc=%.4x", MKWORD(0, 0, mr->error, mr->exception)); start_phase(ms); return; bogus: dumplog(ms, ms->conn_tgt); dumpslog(ms); ms->data_ptr = 0; ms->aborting = 1; start_phase(ms); } static void do_abort(struct mesh_state *ms) { ms->msgout[0] = ABORT; ms->n_msgout = 1; ms->aborting = 1; ms->stat = DID_ABORT; dlog(ms, "abort", 0); } static void handle_reset(struct mesh_state *ms) { int tgt; struct mesh_target *tp; struct scsi_cmnd *cmd; volatile struct mesh_regs __iomem *mr = ms->mesh; for (tgt = 0; tgt < 8; ++tgt) { tp = &ms->tgts[tgt]; if ((cmd = tp->current_req) != NULL) { cmd->result = DID_RESET << 16; tp->current_req = NULL; mesh_completed(ms, cmd); } ms->tgts[tgt].sdtr_state = do_sdtr; ms->tgts[tgt].sync_params = ASYNC_PARAMS; } ms->current_req = NULL; while ((cmd = ms->request_q) != NULL) { ms->request_q = (struct scsi_cmnd *) cmd->host_scribble; cmd->result = DID_RESET << 16; mesh_completed(ms, cmd); } ms->phase = idle; ms->msgphase = msg_none; out_8(&mr->interrupt, INT_ERROR | INT_EXCEPTION | INT_CMDDONE); out_8(&mr->sequence, SEQ_FLUSHFIFO); mesh_flush_io(mr); udelay(1); out_8(&mr->sync_params, ASYNC_PARAMS); out_8(&mr->sequence, SEQ_ENBRESEL); } static irqreturn_t do_mesh_interrupt(int irq, void *dev_id) { unsigned long flags; struct mesh_state *ms = dev_id; struct Scsi_Host *dev = ms->host; spin_lock_irqsave(dev->host_lock, flags); mesh_interrupt(ms); spin_unlock_irqrestore(dev->host_lock, flags); return IRQ_HANDLED; } static void handle_error(struct mesh_state *ms) { int err, exc, count; volatile struct mesh_regs __iomem *mr = ms->mesh; err = in_8(&mr->error); exc = in_8(&mr->exception); out_8(&mr->interrupt, INT_ERROR | INT_EXCEPTION | INT_CMDDONE); dlog(ms, "error err/exc/fc/cl=%.8x", MKWORD(err, exc, mr->fifo_count, mr->count_lo)); if (err & ERR_SCSIRESET) { /* SCSI bus was reset */ printk(KERN_INFO "mesh: SCSI bus reset detected: " "waiting for end..."); while ((in_8(&mr->bus_status1) & BS1_RST) != 0) udelay(1); printk("done\n"); handle_reset(ms); /* request_q is empty, no point in mesh_start() */ return; } if (err & ERR_UNEXPDISC) { /* Unexpected disconnect */ if (exc & EXC_RESELECTED) { reselected(ms); return; } if (!ms->aborting) { printk(KERN_WARNING "mesh: target %d aborted\n", ms->conn_tgt); dumplog(ms, ms->conn_tgt); dumpslog(ms); } out_8(&mr->interrupt, INT_CMDDONE); ms->stat = DID_ABORT; mesh_done(ms, 1); return; } if (err & ERR_PARITY) { if (ms->msgphase == msg_in) { printk(KERN_ERR "mesh: msg parity error, target %d\n", ms->conn_tgt); ms->msgout[0] = MSG_PARITY_ERROR; ms->n_msgout = 1; ms->msgphase = msg_in_bad; cmd_complete(ms); return; } if (ms->stat == DID_OK) { printk(KERN_ERR "mesh: parity error, target %d\n", ms->conn_tgt); ms->stat = DID_PARITY; } count = (mr->count_hi << 8) + mr->count_lo; if (count == 0) { cmd_complete(ms); } else { /* reissue the data transfer command */ out_8(&mr->sequence, mr->sequence); } return; } if (err & ERR_SEQERR) { if (exc & EXC_RESELECTED) { /* This can happen if we issue a command to get the bus just after the target reselects us. */ static int mesh_resel_seqerr; mesh_resel_seqerr++; reselected(ms); return; } if (exc == EXC_PHASEMM) { static int mesh_phasemm_seqerr; mesh_phasemm_seqerr++; phase_mismatch(ms); return; } printk(KERN_ERR "mesh: sequence error (err=%x exc=%x)\n", err, exc); } else { printk(KERN_ERR "mesh: unknown error %x (exc=%x)\n", err, exc); } mesh_dump_regs(ms); dumplog(ms, ms->conn_tgt); if (ms->phase > selecting && (in_8(&mr->bus_status1) & BS1_BSY)) { /* try to do what the target wants */ do_abort(ms); phase_mismatch(ms); return; } ms->stat = DID_ERROR; mesh_done(ms, 1); } static void handle_exception(struct mesh_state *ms) { int exc; volatile struct mesh_regs __iomem *mr = ms->mesh; exc = in_8(&mr->exception); out_8(&mr->interrupt, INT_EXCEPTION | INT_CMDDONE); if (exc & EXC_RESELECTED) { static int mesh_resel_exc; mesh_resel_exc++; reselected(ms); } else if (exc == EXC_ARBLOST) { printk(KERN_DEBUG "mesh: lost arbitration\n"); ms->stat = DID_BUS_BUSY; mesh_done(ms, 1); } else if (exc == EXC_SELTO) { /* selection timed out */ ms->stat = DID_BAD_TARGET; mesh_done(ms, 1); } else if (exc == EXC_PHASEMM) { /* target wants to do something different: find out what it wants and do it. */ phase_mismatch(ms); } else { printk(KERN_ERR "mesh: can't cope with exception %x\n", exc); mesh_dump_regs(ms); dumplog(ms, ms->conn_tgt); do_abort(ms); phase_mismatch(ms); } } static void handle_msgin(struct mesh_state *ms) { int i, code; struct scsi_cmnd *cmd = ms->current_req; struct mesh_target *tp = &ms->tgts[ms->conn_tgt]; if (ms->n_msgin == 0) return; code = ms->msgin[0]; if (ALLOW_DEBUG(ms->conn_tgt)) { printk(KERN_DEBUG "got %d message bytes:", ms->n_msgin); for (i = 0; i < ms->n_msgin; ++i) printk(" %x", ms->msgin[i]); printk("\n"); } dlog(ms, "msgin msg=%.8x", MKWORD(ms->n_msgin, code, ms->msgin[1], ms->msgin[2])); ms->expect_reply = 0; ms->n_msgout = 0; if (ms->n_msgin < msgin_length(ms)) goto reject; if (cmd) cmd->SCp.Message = code; switch (code) { case COMMAND_COMPLETE: break; case EXTENDED_MESSAGE: switch (ms->msgin[2]) { case EXTENDED_MODIFY_DATA_POINTER: ms->data_ptr += (ms->msgin[3] << 24) + ms->msgin[6] + (ms->msgin[4] << 16) + (ms->msgin[5] << 8); break; case EXTENDED_SDTR: if (tp->sdtr_state != sdtr_sent) { /* reply with an SDTR */ add_sdtr_msg(ms); /* limit period to at least his value, offset to no more than his */ if (ms->msgout[3] < ms->msgin[3]) ms->msgout[3] = ms->msgin[3]; if (ms->msgout[4] > ms->msgin[4]) ms->msgout[4] = ms->msgin[4]; set_sdtr(ms, ms->msgout[3], ms->msgout[4]); ms->msgphase = msg_out; } else { set_sdtr(ms, ms->msgin[3], ms->msgin[4]); } break; default: goto reject; } break; case SAVE_POINTERS: tp->saved_ptr = ms->data_ptr; break; case RESTORE_POINTERS: ms->data_ptr = tp->saved_ptr; break; case DISCONNECT: ms->phase = disconnecting; break; case ABORT: break; case MESSAGE_REJECT: if (tp->sdtr_state == sdtr_sent) set_sdtr(ms, 0, 0); break; case NOP: break; default: if (IDENTIFY_BASE <= code && code <= IDENTIFY_BASE + 7) { if (cmd == NULL) { do_abort(ms); ms->msgphase = msg_out; } else if (code != cmd->device->lun + IDENTIFY_BASE) { printk(KERN_WARNING "mesh: lun mismatch " "(%d != %d) on reselection from " "target %d\n", code - IDENTIFY_BASE, cmd->device->lun, ms->conn_tgt); } break; } goto reject; } return; reject: printk(KERN_WARNING "mesh: rejecting message from target %d:", ms->conn_tgt); for (i = 0; i < ms->n_msgin; ++i) printk(" %x", ms->msgin[i]); printk("\n"); ms->msgout[0] = MESSAGE_REJECT; ms->n_msgout = 1; ms->msgphase = msg_out; } /* * Set up DMA commands for transferring data. */ static void set_dma_cmds(struct mesh_state *ms, struct scsi_cmnd *cmd) { int i, dma_cmd, total, off, dtot; struct scatterlist *scl; struct dbdma_cmd *dcmds; dma_cmd = ms->tgts[ms->conn_tgt].data_goes_out? OUTPUT_MORE: INPUT_MORE; dcmds = ms->dma_cmds; dtot = 0; if (cmd) { int nseg; cmd->SCp.this_residual = scsi_bufflen(cmd); nseg = scsi_dma_map(cmd); BUG_ON(nseg < 0); if (nseg) { total = 0; off = ms->data_ptr; scsi_for_each_sg(cmd, scl, nseg, i) { u32 dma_addr = sg_dma_address(scl); u32 dma_len = sg_dma_len(scl); total += scl->length; if (off >= dma_len) { off -= dma_len; continue; } if (dma_len > 0xffff) panic("mesh: scatterlist element >= 64k"); st_le16(&dcmds->req_count, dma_len - off); st_le16(&dcmds->command, dma_cmd); st_le32(&dcmds->phy_addr, dma_addr + off); dcmds->xfer_status = 0; ++dcmds; dtot += dma_len - off; off = 0; } } } if (dtot == 0) { /* Either the target has overrun our buffer, or the caller didn't provide a buffer. */ static char mesh_extra_buf[64]; dtot = sizeof(mesh_extra_buf); st_le16(&dcmds->req_count, dtot); st_le32(&dcmds->phy_addr, virt_to_phys(mesh_extra_buf)); dcmds->xfer_status = 0; ++dcmds; } dma_cmd += OUTPUT_LAST - OUTPUT_MORE; st_le16(&dcmds[-1].command, dma_cmd); memset(dcmds, 0, sizeof(*dcmds)); st_le16(&dcmds->command, DBDMA_STOP); ms->dma_count = dtot; } static void halt_dma(struct mesh_state *ms) { volatile struct dbdma_regs __iomem *md = ms->dma; volatile struct mesh_regs __iomem *mr = ms->mesh; struct scsi_cmnd *cmd = ms->current_req; int t, nb; if (!ms->tgts[ms->conn_tgt].data_goes_out) { /* wait a little while until the fifo drains */ t = 50; while (t > 0 && in_8(&mr->fifo_count) != 0 && (in_le32(&md->status) & ACTIVE) != 0) { --t; udelay(1); } } out_le32(&md->control, RUN << 16); /* turn off RUN bit */ nb = (mr->count_hi << 8) + mr->count_lo; dlog(ms, "halt_dma fc/count=%.6x", MKWORD(0, mr->fifo_count, 0, nb)); if (ms->tgts[ms->conn_tgt].data_goes_out) nb += mr->fifo_count; /* nb is the number of bytes not yet transferred to/from the target. */ ms->data_ptr -= nb; dlog(ms, "data_ptr %x", ms->data_ptr); if (ms->data_ptr < 0) { printk(KERN_ERR "mesh: halt_dma: data_ptr=%d (nb=%d, ms=%p)\n", ms->data_ptr, nb, ms); ms->data_ptr = 0; #ifdef MESH_DBG dumplog(ms, ms->conn_tgt); dumpslog(ms); #endif /* MESH_DBG */ } else if (cmd && scsi_bufflen(cmd) && ms->data_ptr > scsi_bufflen(cmd)) { printk(KERN_DEBUG "mesh: target %d overrun, " "data_ptr=%x total=%x goes_out=%d\n", ms->conn_tgt, ms->data_ptr, scsi_bufflen(cmd), ms->tgts[ms->conn_tgt].data_goes_out); } scsi_dma_unmap(cmd); ms->dma_started = 0; } static void phase_mismatch(struct mesh_state *ms) { volatile struct mesh_regs __iomem *mr = ms->mesh; int phase; dlog(ms, "phasemm ch/cl/seq/fc=%.8x", MKWORD(mr->count_hi, mr->count_lo, mr->sequence, mr->fifo_count)); phase = in_8(&mr->bus_status0) & BS0_PHASE; if (ms->msgphase == msg_out_xxx && phase == BP_MSGOUT) { /* output the last byte of the message, without ATN */ out_8(&mr->count_lo, 1); out_8(&mr->sequence, SEQ_MSGOUT + use_active_neg); mesh_flush_io(mr); udelay(1); out_8(&mr->fifo, ms->msgout[ms->n_msgout-1]); ms->msgphase = msg_out_last; return; } if (ms->msgphase == msg_in) { get_msgin(ms); if (ms->n_msgin) handle_msgin(ms); } if (ms->dma_started) halt_dma(ms); if (mr->fifo_count) { out_8(&mr->sequence, SEQ_FLUSHFIFO); mesh_flush_io(mr); udelay(1); } ms->msgphase = msg_none; switch (phase) { case BP_DATAIN: ms->tgts[ms->conn_tgt].data_goes_out = 0; ms->phase = dataing; break; case BP_DATAOUT: ms->tgts[ms->conn_tgt].data_goes_out = 1; ms->phase = dataing; break; case BP_COMMAND: ms->phase = commanding; break; case BP_STATUS: ms->phase = statusing; break; case BP_MSGIN: ms->msgphase = msg_in; ms->n_msgin = 0; break; case BP_MSGOUT: ms->msgphase = msg_out; if (ms->n_msgout == 0) { if (ms->aborting) { do_abort(ms); } else { if (ms->last_n_msgout == 0) { printk(KERN_DEBUG "mesh: no msg to repeat\n"); ms->msgout[0] = NOP; ms->last_n_msgout = 1; } ms->n_msgout = ms->last_n_msgout; } } break; default: printk(KERN_DEBUG "mesh: unknown scsi phase %x\n", phase); ms->stat = DID_ERROR; mesh_done(ms, 1); return; } start_phase(ms); } static void cmd_complete(struct mesh_state *ms) { volatile struct mesh_regs __iomem *mr = ms->mesh; struct scsi_cmnd *cmd = ms->current_req; struct mesh_target *tp = &ms->tgts[ms->conn_tgt]; int seq, n, t; dlog(ms, "cmd_complete fc=%x", mr->fifo_count); seq = use_active_neg + (ms->n_msgout? SEQ_ATN: 0); switch (ms->msgphase) { case msg_out_xxx: /* huh? we expected a phase mismatch */ ms->n_msgin = 0; ms->msgphase = msg_in; /* fall through */ case msg_in: /* should have some message bytes in fifo */ get_msgin(ms); n = msgin_length(ms); if (ms->n_msgin < n) { out_8(&mr->count_lo, n - ms->n_msgin); out_8(&mr->sequence, SEQ_MSGIN + seq); } else { ms->msgphase = msg_none; handle_msgin(ms); start_phase(ms); } break; case msg_in_bad: out_8(&mr->sequence, SEQ_FLUSHFIFO); mesh_flush_io(mr); udelay(1); out_8(&mr->count_lo, 1); out_8(&mr->sequence, SEQ_MSGIN + SEQ_ATN + use_active_neg); break; case msg_out: /* * To get the right timing on ATN wrt ACK, we have * to get the MESH to drop ACK, wait until REQ gets * asserted, then drop ATN. To do this we first * issue a SEQ_MSGOUT with ATN and wait for REQ, * then change the command to a SEQ_MSGOUT w/o ATN. * If we don't see REQ in a reasonable time, we * change the command to SEQ_MSGIN with ATN, * wait for the phase mismatch interrupt, then * issue the SEQ_MSGOUT without ATN. */ out_8(&mr->count_lo, 1); out_8(&mr->sequence, SEQ_MSGOUT + use_active_neg + SEQ_ATN); t = 30; /* wait up to 30us */ while ((in_8(&mr->bus_status0) & BS0_REQ) == 0 && --t >= 0) udelay(1); dlog(ms, "last_mbyte err/exc/fc/cl=%.8x", MKWORD(mr->error, mr->exception, mr->fifo_count, mr->count_lo)); if (in_8(&mr->interrupt) & (INT_ERROR | INT_EXCEPTION)) { /* whoops, target didn't do what we expected */ ms->last_n_msgout = ms->n_msgout; ms->n_msgout = 0; if (in_8(&mr->interrupt) & INT_ERROR) { printk(KERN_ERR "mesh: error %x in msg_out\n", in_8(&mr->error)); handle_error(ms); return; } if (in_8(&mr->exception) != EXC_PHASEMM) printk(KERN_ERR "mesh: exc %x in msg_out\n", in_8(&mr->exception)); else printk(KERN_DEBUG "mesh: bs0=%x in msg_out\n", in_8(&mr->bus_status0)); handle_exception(ms); return; } if (in_8(&mr->bus_status0) & BS0_REQ) { out_8(&mr->sequence, SEQ_MSGOUT + use_active_neg); mesh_flush_io(mr); udelay(1); out_8(&mr->fifo, ms->msgout[ms->n_msgout-1]); ms->msgphase = msg_out_last; } else { out_8(&mr->sequence, SEQ_MSGIN + use_active_neg + SEQ_ATN); ms->msgphase = msg_out_xxx; } break; case msg_out_last: ms->last_n_msgout = ms->n_msgout; ms->n_msgout = 0; ms->msgphase = ms->expect_reply? msg_in: msg_none; start_phase(ms); break; case msg_none: switch (ms->phase) { case idle: printk(KERN_ERR "mesh: interrupt in idle phase?\n"); dumpslog(ms); return; case selecting: dlog(ms, "Selecting phase at command completion",0); ms->msgout[0] = IDENTIFY(ALLOW_RESEL(ms->conn_tgt), (cmd? cmd->device->lun: 0)); ms->n_msgout = 1; ms->expect_reply = 0; if (ms->aborting) { ms->msgout[0] = ABORT; ms->n_msgout++; } else if (tp->sdtr_state == do_sdtr) { /* add SDTR message */ add_sdtr_msg(ms); ms->expect_reply = 1; tp->sdtr_state = sdtr_sent; } ms->msgphase = msg_out; /* * We need to wait for REQ before dropping ATN. * We wait for at most 30us, then fall back to * a scheme where we issue a SEQ_COMMAND with ATN, * which will give us a phase mismatch interrupt * when REQ does come, and then we send the message. */ t = 230; /* wait up to 230us */ while ((in_8(&mr->bus_status0) & BS0_REQ) == 0) { if (--t < 0) { dlog(ms, "impatient for req", ms->n_msgout); ms->msgphase = msg_none; break; } udelay(1); } break; case dataing: if (ms->dma_count != 0) { start_phase(ms); return; } /* * We can get a phase mismatch here if the target * changes to the status phase, even though we have * had a command complete interrupt. Then, if we * issue the SEQ_STATUS command, we'll get a sequence * error interrupt. Which isn't so bad except that * occasionally the mesh actually executes the * SEQ_STATUS *as well as* giving us the sequence * error and phase mismatch exception. */ out_8(&mr->sequence, 0); out_8(&mr->interrupt, INT_ERROR | INT_EXCEPTION | INT_CMDDONE); halt_dma(ms); break; case statusing: if (cmd) { cmd->SCp.Status = mr->fifo; if (DEBUG_TARGET(cmd)) printk(KERN_DEBUG "mesh: status is %x\n", cmd->SCp.Status); } ms->msgphase = msg_in; break; case busfreeing: mesh_done(ms, 1); return; case disconnecting: ms->current_req = NULL; ms->phase = idle; mesh_start(ms); return; default: break; } ++ms->phase; start_phase(ms); break; } } /* * Called by midlayer with host locked to queue a new * request */ static int mesh_queue(struct scsi_cmnd *cmd, void (*done)(struct scsi_cmnd *)) { struct mesh_state *ms; cmd->scsi_done = done; cmd->host_scribble = NULL; ms = (struct mesh_state *) cmd->device->host->hostdata; if (ms->request_q == NULL) ms->request_q = cmd; else ms->request_qtail->host_scribble = (void *) cmd; ms->request_qtail = cmd; if (ms->phase == idle) mesh_start(ms); return 0; } /* * Called to handle interrupts, either call by the interrupt * handler (do_mesh_interrupt) or by other functions in * exceptional circumstances */ static void mesh_interrupt(struct mesh_state *ms) { volatile struct mesh_regs __iomem *mr = ms->mesh; int intr; #if 0 if (ALLOW_DEBUG(ms->conn_tgt)) printk(KERN_DEBUG "mesh_intr, bs0=%x int=%x exc=%x err=%x " "phase=%d msgphase=%d\n", mr->bus_status0, mr->interrupt, mr->exception, mr->error, ms->phase, ms->msgphase); #endif while ((intr = in_8(&mr->interrupt)) != 0) { dlog(ms, "interrupt intr/err/exc/seq=%.8x", MKWORD(intr, mr->error, mr->exception, mr->sequence)); if (intr & INT_ERROR) { handle_error(ms); } else if (intr & INT_EXCEPTION) { handle_exception(ms); } else if (intr & INT_CMDDONE) { out_8(&mr->interrupt, INT_CMDDONE); cmd_complete(ms); } } } /* Todo: here we can at least try to remove the command from the * queue if it isn't connected yet, and for pending command, assert * ATN until the bus gets freed. */ static int mesh_abort(struct scsi_cmnd *cmd) { struct mesh_state *ms = (struct mesh_state *) cmd->device->host->hostdata; printk(KERN_DEBUG "mesh_abort(%p)\n", cmd); mesh_dump_regs(ms); dumplog(ms, cmd->device->id); dumpslog(ms); return FAILED; } /* * Called by the midlayer with the lock held to reset the * SCSI host and bus. * The midlayer will wait for devices to come back, we don't need * to do that ourselves */ static int mesh_host_reset(struct scsi_cmnd *cmd) { struct mesh_state *ms = (struct mesh_state *) cmd->device->host->hostdata; volatile struct mesh_regs __iomem *mr = ms->mesh; volatile struct dbdma_regs __iomem *md = ms->dma; unsigned long flags; printk(KERN_DEBUG "mesh_host_reset\n"); spin_lock_irqsave(ms->host->host_lock, flags); /* Reset the controller & dbdma channel */ out_le32(&md->control, (RUN|PAUSE|FLUSH|WAKE) << 16); /* stop dma */ out_8(&mr->exception, 0xff); /* clear all exception bits */ out_8(&mr->error, 0xff); /* clear all error bits */ out_8(&mr->sequence, SEQ_RESETMESH); mesh_flush_io(mr); udelay(1); out_8(&mr->intr_mask, INT_ERROR | INT_EXCEPTION | INT_CMDDONE); out_8(&mr->source_id, ms->host->this_id); out_8(&mr->sel_timeout, 25); /* 250ms */ out_8(&mr->sync_params, ASYNC_PARAMS); /* Reset the bus */ out_8(&mr->bus_status1, BS1_RST); /* assert RST */ mesh_flush_io(mr); udelay(30); /* leave it on for >= 25us */ out_8(&mr->bus_status1, 0); /* negate RST */ /* Complete pending commands */ handle_reset(ms); spin_unlock_irqrestore(ms->host->host_lock, flags); return SUCCESS; } static void set_mesh_power(struct mesh_state *ms, int state) { if (!machine_is(powermac)) return; if (state) { pmac_call_feature(PMAC_FTR_MESH_ENABLE, macio_get_of_node(ms->mdev), 0, 1); msleep(200); } else { pmac_call_feature(PMAC_FTR_MESH_ENABLE, macio_get_of_node(ms->mdev), 0, 0); msleep(10); } } #ifdef CONFIG_PM static int mesh_suspend(struct macio_dev *mdev, pm_message_t mesg) { struct mesh_state *ms = (struct mesh_state *)macio_get_drvdata(mdev); unsigned long flags; switch (mesg.event) { case PM_EVENT_SUSPEND: case PM_EVENT_HIBERNATE: case PM_EVENT_FREEZE: break; default: return 0; } if (ms->phase == sleeping) return 0; scsi_block_requests(ms->host); spin_lock_irqsave(ms->host->host_lock, flags); while(ms->phase != idle) { spin_unlock_irqrestore(ms->host->host_lock, flags); msleep(10); spin_lock_irqsave(ms->host->host_lock, flags); } ms->phase = sleeping; spin_unlock_irqrestore(ms->host->host_lock, flags); disable_irq(ms->meshintr); set_mesh_power(ms, 0); return 0; } static int mesh_resume(struct macio_dev *mdev) { struct mesh_state *ms = (struct mesh_state *)macio_get_drvdata(mdev); unsigned long flags; if (ms->phase != sleeping) return 0; set_mesh_power(ms, 1); mesh_init(ms); spin_lock_irqsave(ms->host->host_lock, flags); mesh_start(ms); spin_unlock_irqrestore(ms->host->host_lock, flags); enable_irq(ms->meshintr); scsi_unblock_requests(ms->host); return 0; } #endif /* CONFIG_PM */ /* * If we leave drives set for synchronous transfers (especially * CDROMs), and reboot to MacOS, it gets confused, poor thing. * So, on reboot we reset the SCSI bus. */ static int mesh_shutdown(struct macio_dev *mdev) { struct mesh_state *ms = (struct mesh_state *)macio_get_drvdata(mdev); volatile struct mesh_regs __iomem *mr; unsigned long flags; printk(KERN_INFO "resetting MESH scsi bus(es)\n"); spin_lock_irqsave(ms->host->host_lock, flags); mr = ms->mesh; out_8(&mr->intr_mask, 0); out_8(&mr->interrupt, INT_ERROR | INT_EXCEPTION | INT_CMDDONE); out_8(&mr->bus_status1, BS1_RST); mesh_flush_io(mr); udelay(30); out_8(&mr->bus_status1, 0); spin_unlock_irqrestore(ms->host->host_lock, flags); return 0; } static struct scsi_host_template mesh_template = { .proc_name = "mesh", .name = "MESH", .queuecommand = mesh_queue, .eh_abort_handler = mesh_abort, .eh_host_reset_handler = mesh_host_reset, .can_queue = 20, .this_id = 7, .sg_tablesize = SG_ALL, .cmd_per_lun = 2, .use_clustering = DISABLE_CLUSTERING, }; static int mesh_probe(struct macio_dev *mdev, const struct of_device_id *match) { struct device_node *mesh = macio_get_of_node(mdev); struct pci_dev* pdev = macio_get_pci_dev(mdev); int tgt, minper; const int *cfp; struct mesh_state *ms; struct Scsi_Host *mesh_host; void *dma_cmd_space; dma_addr_t dma_cmd_bus; switch (mdev->bus->chip->type) { case macio_heathrow: case macio_gatwick: case macio_paddington: use_active_neg = 0; break; default: use_active_neg = SEQ_ACTIVE_NEG; } if (macio_resource_count(mdev) != 2 || macio_irq_count(mdev) != 2) { printk(KERN_ERR "mesh: expected 2 addrs and 2 intrs" " (got %d,%d)\n", macio_resource_count(mdev), macio_irq_count(mdev)); return -ENODEV; } if (macio_request_resources(mdev, "mesh") != 0) { printk(KERN_ERR "mesh: unable to request memory resources"); return -EBUSY; } mesh_host = scsi_host_alloc(&mesh_template, sizeof(struct mesh_state)); if (mesh_host == NULL) { printk(KERN_ERR "mesh: couldn't register host"); goto out_release; } /* Old junk for root discovery, that will die ultimately */ #if !defined(MODULE) note_scsi_host(mesh, mesh_host); #endif mesh_host->base = macio_resource_start(mdev, 0); mesh_host->irq = macio_irq(mdev, 0); ms = (struct mesh_state *) mesh_host->hostdata; macio_set_drvdata(mdev, ms); ms->host = mesh_host; ms->mdev = mdev; ms->pdev = pdev; ms->mesh = ioremap(macio_resource_start(mdev, 0), 0x1000); if (ms->mesh == NULL) { printk(KERN_ERR "mesh: can't map registers\n"); goto out_free; } ms->dma = ioremap(macio_resource_start(mdev, 1), 0x1000); if (ms->dma == NULL) { printk(KERN_ERR "mesh: can't map registers\n"); iounmap(ms->mesh); goto out_free; } ms->meshintr = macio_irq(mdev, 0); ms->dmaintr = macio_irq(mdev, 1); /* Space for dma command list: +1 for stop command, * +1 to allow for aligning. */ ms->dma_cmd_size = (mesh_host->sg_tablesize + 2) * sizeof(struct dbdma_cmd); /* We use the PCI APIs for now until the generic one gets fixed * enough or until we get some macio-specific versions */ dma_cmd_space = pci_alloc_consistent(macio_get_pci_dev(mdev), ms->dma_cmd_size, &dma_cmd_bus); if (dma_cmd_space == NULL) { printk(KERN_ERR "mesh: can't allocate DMA table\n"); goto out_unmap; } memset(dma_cmd_space, 0, ms->dma_cmd_size); ms->dma_cmds = (struct dbdma_cmd *) DBDMA_ALIGN(dma_cmd_space); ms->dma_cmd_space = dma_cmd_space; ms->dma_cmd_bus = dma_cmd_bus + ((unsigned long)ms->dma_cmds) - (unsigned long)dma_cmd_space; ms->current_req = NULL; for (tgt = 0; tgt < 8; ++tgt) { ms->tgts[tgt].sdtr_state = do_sdtr; ms->tgts[tgt].sync_params = ASYNC_PARAMS; ms->tgts[tgt].current_req = NULL; } if ((cfp = of_get_property(mesh, "clock-frequency", NULL))) ms->clk_freq = *cfp; else { printk(KERN_INFO "mesh: assuming 50MHz clock frequency\n"); ms->clk_freq = 50000000; } /* The maximum sync rate is clock / 5; increase * mesh_sync_period if necessary. */ minper = 1000000000 / (ms->clk_freq / 5); /* ns */ if (mesh_sync_period < minper) mesh_sync_period = minper; /* Power up the chip */ set_mesh_power(ms, 1); /* Set it up */ mesh_init(ms); /* Request interrupt */ if (request_irq(ms->meshintr, do_mesh_interrupt, 0, "MESH", ms)) { printk(KERN_ERR "MESH: can't get irq %d\n", ms->meshintr); goto out_shutdown; } /* Add scsi host & scan */ if (scsi_add_host(mesh_host, &mdev->ofdev.dev)) goto out_release_irq; scsi_scan_host(mesh_host); return 0; out_release_irq: free_irq(ms->meshintr, ms); out_shutdown: /* shutdown & reset bus in case of error or macos can be confused * at reboot if the bus was set to synchronous mode already */ mesh_shutdown(mdev); set_mesh_power(ms, 0); pci_free_consistent(macio_get_pci_dev(mdev), ms->dma_cmd_size, ms->dma_cmd_space, ms->dma_cmd_bus); out_unmap: iounmap(ms->dma); iounmap(ms->mesh); out_free: scsi_host_put(mesh_host); out_release: macio_release_resources(mdev); return -ENODEV; } static int mesh_remove(struct macio_dev *mdev) { struct mesh_state *ms = (struct mesh_state *)macio_get_drvdata(mdev); struct Scsi_Host *mesh_host = ms->host; scsi_remove_host(mesh_host); free_irq(ms->meshintr, ms); /* Reset scsi bus */ mesh_shutdown(mdev); /* Shut down chip & termination */ set_mesh_power(ms, 0); /* Unmap registers & dma controller */ iounmap(ms->mesh); iounmap(ms->dma); /* Free DMA commands memory */ pci_free_consistent(macio_get_pci_dev(mdev), ms->dma_cmd_size, ms->dma_cmd_space, ms->dma_cmd_bus); /* Release memory resources */ macio_release_resources(mdev); scsi_host_put(mesh_host); return 0; } static struct of_device_id mesh_match[] = { { .name = "mesh", }, { .type = "scsi", .compatible = "chrp,mesh0" }, {}, }; MODULE_DEVICE_TABLE (of, mesh_match); static struct macio_driver mesh_driver = { .name = "mesh", .match_table = mesh_match, .probe = mesh_probe, .remove = mesh_remove, .shutdown = mesh_shutdown, #ifdef CONFIG_PM .suspend = mesh_suspend, .resume = mesh_resume, #endif }; static int __init init_mesh(void) { /* Calculate sync rate from module parameters */ if (sync_rate > 10) sync_rate = 10; if (sync_rate > 0) { printk(KERN_INFO "mesh: configured for synchronous %d MB/s\n", sync_rate); mesh_sync_period = 1000 / sync_rate; /* ns */ mesh_sync_offset = 15; } else printk(KERN_INFO "mesh: configured for asynchronous\n"); return macio_register_driver(&mesh_driver); } static void __exit exit_mesh(void) { return macio_unregister_driver(&mesh_driver); } module_init(init_mesh); module_exit(exit_mesh);