summaryrefslogtreecommitdiff
path: root/device/intr.c
blob: cf5d93f62444c93d36bce0076b429f623ba3ba93 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
#include <device/intr.h>
#include <device/ds_routines.h>
#include <kern/queue.h>
#include <kern/printf.h>

#ifndef MACH_XEN
// TODO this is only for x86 system
#define sti() __asm__ __volatile__ ("sti": : :"memory")
#define cli() __asm__ __volatile__ ("cli": : :"memory")

static boolean_t deliver_intr (int line, ipc_port_t dest_port);

struct intr_entry
{
  queue_chain_t chain;
  ipc_port_t dest;
  int line;
  /* The number of interrupts occur since last run of intr_thread. */
  int interrupts;
};

static queue_head_t intr_queue;
/* The total number of unprocessed interrupts. */
static int tot_num_intr;

static struct intr_entry *
search_intr (int line, ipc_port_t dest)
{
  struct intr_entry *e;
  queue_iterate (&intr_queue, e, struct intr_entry *, chain)
    {
      if (e->dest == dest && e->line == line)
	return e;
    }
  return NULL;
}

/* This function can only be used in the interrupt handler. */
void
queue_intr (int line, ipc_port_t dest)
{
  extern void intr_thread ();
  struct intr_entry *e;
  
  cli ();
  e = search_intr (line, dest);
  assert (e);
  e->interrupts++;
  tot_num_intr++;
  sti ();

  thread_wakeup ((event_t) &intr_thread);
}

/* insert an interrupt entry in the queue.
 * This entry exists in the queue until
 * the corresponding interrupt port is removed.*/
int
insert_intr_entry (int line, ipc_port_t dest)
{
  int err = 0;
  struct intr_entry *e, *new;
  int free = 0;

  new = (struct intr_entry *) kalloc (sizeof (*new));
  if (new == NULL)
    return D_NO_MEMORY;

  /* check whether the intr entry has been in the queue. */
  cli ();
  e = search_intr (line, dest);
  if (e)
    {
      printf ("the interrupt entry for line %d and port %p has been inserted\n",
	  line, dest);
      free = 1;
      err = D_ALREADY_OPEN;
      goto out;
    }
  new->line = line;
  new->dest = dest;
  new->interrupts = 0;
  queue_enter (&intr_queue, new, struct intr_entry *, chain);
out:
  sti ();
  if (free)
    kfree ((vm_offset_t) new, sizeof (*new));
  return err;
}

/* this function should be called when line is disabled. */
void mark_intr_removed (int line, ipc_port_t dest)
{
  struct intr_entry *e;

  e = search_intr (line, dest);
  if (e)
    e->dest = NULL;
}

void
intr_thread ()
{
  struct intr_entry *e;
  int line;
  ipc_port_t dest;
  queue_init (&intr_queue);
  
  for (;;)
    {
      assert_wait ((event_t) &intr_thread, FALSE);
      cli ();
      while (tot_num_intr)
	{
	  int del = 0;

	  queue_iterate (&intr_queue, e, struct intr_entry *, chain)
	    {
	      /* if an entry doesn't have dest port,
	       * we should remove it. */
	      if (e->dest == NULL)
		{
		  clear_wait (current_thread (), 0, 0);
		  del = 1;
		  break;
		}

	      if (e->interrupts)
		{
		  clear_wait (current_thread (), 0, 0);
		  line = e->line;
		  dest = e->dest;
		  e->interrupts--;
		  tot_num_intr--;

		  sti ();
		  deliver_intr (line, dest);
		  cli ();
		}
	    }

	  /* remove the entry without dest port from the queue and free it. */
	  if (del)
	    {
	      assert (!queue_empty (&intr_queue));
	      queue_remove (&intr_queue, e, struct intr_entry *, chain);
	      sti ();
	      kfree ((vm_offset_t) e, sizeof (*e));
	      cli ();
	    }
	}
      sti ();
      thread_block (NULL);
    }
}

static boolean_t
deliver_intr (int line, ipc_port_t dest_port)
{
  ipc_kmsg_t kmsg;
  mach_intr_notification_t *n;
  mach_port_t dest = (mach_port_t) dest_port;

  if (dest == MACH_PORT_NULL)
    return FALSE;

  kmsg = ikm_alloc(sizeof *n);
  if (kmsg == IKM_NULL) 
    return FALSE;

  ikm_init(kmsg, sizeof *n);
  n = (mach_intr_notification_t *) &kmsg->ikm_header;

  mach_msg_header_t *m = &n->intr_header;
  mach_msg_type_t *t = &n->intr_type;

  m->msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND, 0);
  m->msgh_size = sizeof *n;
  m->msgh_seqno = INTR_NOTIFY_MSGH_SEQNO;
  m->msgh_local_port = MACH_PORT_NULL;
  m->msgh_remote_port = MACH_PORT_NULL;
  m->msgh_id = MACH_INTR_NOTIFY;

  t->msgt_name = MACH_MSG_TYPE_INTEGER_32;
  t->msgt_size = 32;
  t->msgt_number = 1;
  t->msgt_inline = TRUE;
  t->msgt_longform = FALSE;
  t->msgt_deallocate = FALSE;
  t->msgt_unused = 0;

  n->intr_header.msgh_remote_port = dest;
  n->line = line;

  ipc_port_copy_send (dest_port);
  ipc_mqueue_send_always(kmsg);

  return TRUE;
}
#endif	/* MACH_XEN */