summaryrefslogtreecommitdiff
path: root/libdiskfs/=exc.c
blob: ced9c5b9e40d6592a3a5a4a6186c1682165155de (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
/* 
   Copyright (C) 1994, 1995 Free Software Foundation

   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 "priv.h"

/* List of preempters that are around. */
struct preempt_record
{
  struct preempt_record *next;
  struct hurd_signal_preempt preempter1, preempter2;
  struct pager *p;
  vm_address_t off;
  void *addr;
  long len;
};

static struct preempt_record *preempt_list;
static spin_lock_t preempt_list_lock = SPIN_LOCK_INITIALIZER;

/* This special signal handler is run anytime we have taken a fault on
   a page that we map (registered through
   diskfs_register_memory_fault_area).  What we do is just longjmp to the
   context saved on the stack by diskfs_catch_exception.  */
static void
special_segv_handler (int code, int subcode, int error)
{
  struct preempt_record *rec;
  struct thread_stuff *stack_record;

  /* SUBCODE is the address and ERROR is the error returned by the
     pager through the kernel.  But because there is an annoying
     kernel bug (or rather unimplemented feature) the errors are not
     actually passed back.  So we have to scan the list of preempt
     records and find the one that got us here, and then query the
     pager to find out what error it gave the kernel. */
  spin_lock (&preempt_list_lock);
  for (rec = preempt_list; rec; rec = rec->next)
    if ((void *)subcode >= rec->addr
	&& (void *)subcode < rec->addr + rec->len)
      break;
  assert (rec);
  spin_unlock (&preempt_list_lock);
  error = pager_get_error (rec->p, rec->off + (void *)subcode - rec->addr);
  
  /* Now look up the stack record left by diskfs_catch_exception, consuming it
     on the way */
  stack_record = (struct thread_stuff *) cthread_data (cthread_self ());
  assert (stack_record);
  cthread_set_data (cthread_self (), (any_t)stack_record->link);
  
  /* And return to it... */
  longjmp (&stack_record->buf, error);

  abort ();
}

/* Return a signal handler for a thread which has faulted inside a
   region registered as expecting such faults.  This routine runs
   inside the library's signal thread, and accordingly must be
   careful.  */
static sighandler_t
segv_preempter (thread_t thread, int signo,
		long int sigcode, int sigerror)
{
  /* Just assume that everything is cool (how could it not be?)
     and return our special handler above. */
  return special_segv_handler;
}

/* Mark the memory at ADDR continuing for LEN bytes as mapped from pager P
   at offset OFF.  Call when vm_map-ing part of the disk.  */
void
diskfs_register_memory_fault_area (struct pager *p,
				   vm_address_t off,
				   void *addr, 
				   long len)
{
  struct preempt_record *rec = malloc (sizeof (struct preempt_record));

  hurd_preempt_signals (&rec->preempter1, SIGSEGV, addr, addr + len,
			segv_preempter);
  hurd_preempt_signals (&rec->preempter2, SIGBUS, addr, addr + len,
			segv_preempter);
  rec->p = p;
  rec->off = off;
  rec->addr = addr;
  rec->len = len;

  spin_lock (&preempt_list_lock);
  rec->next = preempt_list;
  preempt_list = rec;
  spin_unlock (&preempt_list_lock);
}

/* Mark the memory at ADDR continuing for LEN bytes as no longer
   mapped from the disk.  Call when vm_unmap-ing part of the disk.  */
void
diskfs_unregister_memory_fault_area (void *addr,
				     long len)
{
  struct preempt_record *rec, **prevp;
  
  spin_lock (&preempt_list_lock);
  for (rec = preempt_list, prevp = &preempt_list; 
       rec; 
       rec = rec->next, prevp = &rec->next)
    if (rec->addr == addr && rec->len == len)
      {
	/* This is it, make it go away. */
	*prevp = rec->next;
	spin_unlock (&preempt_list_lock);
	hurd_unpreempt_signals (&rec->preempter1, SIGSEGV);
	hurd_unpreempt_signals (&rec->preempter2, SIGBUS);
	free (rec);
	return;
      }
  spin_unlock (&preempt_list_lock);
  
  /* Not found */
  assert (0);
}

/* Set up the exception handling system.  */
void
init_exceptions ()
{
}