summaryrefslogtreecommitdiffstats
path: root/src/lib/kernel/console.c
blob: 0d031b5b4bdd6a2257f416bdf1fc1ffdbbb4398e (plain) (blame)
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
#include <console.h>
#include <stdarg.h>
#include <stdio.h>
#include "devices/serial.h"
#include "devices/vga.h"
#include "threads/init.h"
#include "threads/interrupt.h"
#include "threads/synch.h"

static void vprintf_helper (char, void *);
static void putchar_have_lock (uint8_t c);

/* The console lock.
   Both the vga and serial layers do their own locking, so it's
   safe to call them at any time.
   But this lock is useful to prevent simultaneous printf() calls
   from mixing their output, which looks confusing. */
static struct lock console_lock;

/* True in ordinary circumstances: we want to use the console
   lock to avoid mixing output between threads, as explained
   above.

   False in early boot before the point that locks are functional
   or the console lock has been initialized, or after a kernel
   panics.  In the former case, taking the lock would cause an
   assertion failure, which in turn would cause a panic, turning
   it into the latter case.  In the latter case, if it is a buggy
   lock_acquire() implementation that caused the panic, we'll
   likely just recurse. */
static bool use_console_lock;

/* It's possible, if you add enough debug output to Pintos, to
   try to recursively grab console_lock from a single thread.  As
   a real example, I added a printf() call to palloc_free().
   Here's a real backtrace that resulted:

   lock_console()
   vprintf()
   printf()             - palloc() tries to grab the lock again
   palloc_free()        
   schedule_tail()      - another thread dying as we switch threads
   schedule()
   thread_yield()
   intr_handler()       - timer interrupt
   intr_set_level()
   serial_putc()
   putchar_have_lock()
   putbuf()
   sys_write()          - one process writing to the console
   syscall_handler()
   intr_handler()

   This kind of thing is very difficult to debug, so we avoid the
   problem by simulating a recursive lock with a depth
   counter. */
static int console_lock_depth;

/* Number of characters written to console. */
static int64_t write_cnt;

/* Enable console locking. */
void
console_init (void) 
{
  lock_init (&console_lock);
  use_console_lock = true;
}

/* Notifies the console that a kernel panic is underway,
   which warns it to avoid trying to take the console lock from
   now on. */
void
console_panic (void) 
{
  use_console_lock = false;
}

/* Prints console statistics. */
void
console_print_stats (void) 
{
  printf ("Console: %lld characters output\n", write_cnt);
}

/* Acquires the console lock. */
static void
acquire_console (void) 
{
  if (!intr_context () && use_console_lock) 
    {
      if (lock_held_by_current_thread (&console_lock)) 
        console_lock_depth++; 
      else
        lock_acquire (&console_lock); 
    }
}

/* Releases the console lock. */
static void
release_console (void) 
{
  if (!intr_context () && use_console_lock) 
    {
      if (console_lock_depth > 0)
        console_lock_depth--;
      else
        lock_release (&console_lock); 
    }
}

/* Returns true if the current thread has the console lock,
   false otherwise. */
static bool
console_locked_by_current_thread (void) 
{
  return (intr_context ()
          || !use_console_lock
          || lock_held_by_current_thread (&console_lock));
}

/* The standard vprintf() function,
   which is like printf() but uses a va_list.
   Writes its output to both vga display and serial port. */
int
vprintf (const char *format, va_list args) 
{
  int char_cnt = 0;

  acquire_console ();
  __vprintf (format, args, vprintf_helper, &char_cnt);
  release_console ();

  return char_cnt;
}

/* Writes string S to the console, followed by a new-line
   character. */
int
puts (const char *s) 
{
  acquire_console ();
  while (*s != '\0')
    putchar_have_lock (*s++);
  putchar_have_lock ('\n');
  release_console ();

  return 0;
}

/* Writes the N characters in BUFFER to the console. */
void
putbuf (const char *buffer, size_t n) 
{
  acquire_console ();
  while (n-- > 0)
    putchar_have_lock (*buffer++);
  release_console ();
}

/* Writes C to the vga display and serial port. */
int
putchar (int c) 
{
  acquire_console ();
  putchar_have_lock (c);
  release_console ();
  
  return c;
}

/* Helper function for vprintf(). */
static void
vprintf_helper (char c, void *char_cnt_) 
{
  int *char_cnt = char_cnt_;
  (*char_cnt)++;
  putchar_have_lock (c);
}

/* Writes C to the vga display and serial port.
   The caller has already acquired the console lock if
   appropriate. */
static void
putchar_have_lock (uint8_t c) 
{
  ASSERT (console_locked_by_current_thread ());
  write_cnt++;
  serial_putc (c);
  vga_putc (c);
}