The Pedigree Project 0.1
VirtualTerminal.cc
1/*
2 * Copyright (c) 2008-2014, Pedigree Developers
3 *
4 * Please see the CONTRIB file in the root of the source tree for a full
5 * list of contributors.
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20#include "modules/subsys/posix/VirtualTerminal.h"
21#include "pedigree/kernel/LockGuard.h"
22#include "pedigree/kernel/machine/Machine.h"
23#include "pedigree/kernel/machine/Vga.h"
24#include "pedigree/kernel/process/Process.h"
25#include "pedigree/kernel/process/Scheduler.h"
26
27#include "modules/subsys/posix/DevFs.h"
28#include "modules/subsys/posix/PosixSubsystem.h"
29#include "modules/system/console/Console.h"
30
31extern DevFs* g_pDevFs;
32
33VirtualTerminalManager::VirtualTerminalManager(DevFsDirectory* parentDir)
34 : m_pTty(nullptr),
35 m_CurrentTty(0),
36 m_WantedTty(~size_t(0)),
37 m_NumTtys(0),
38 m_ParentDir(parentDir),
39 m_bSwitchingLocked(false) {
40 for (size_t i = 0; i < MAX_VT; ++i) {
41 m_Terminals[i].textio = nullptr;
42 m_Terminals[i].file = nullptr;
43#if THREADS
44 m_Terminals[i].owner = ~size_t(0);
45 m_Terminals[i].ownerThread = ~size_t(0);
46#endif
47
48 ByteSet(&m_Terminals[i].mode, 0, sizeof(m_Terminals[i].mode));
49 m_Terminals[i].mode.mode = VT_AUTO;
50 m_Terminals[i].systemMode = Text;
51 }
52}
53
54VirtualTerminalManager::~VirtualTerminalManager() {
55 // The DevFs directory owns textui and every ConsolePhysicalFile wrapper.
56 // Secondary TextIO backends are not directory entries, so retire them
57 // here while their workers and input callbacks can still drain.
58 for (size_t i = 1; i < MAX_VT; ++i) {
59 delete m_Terminals[i].textio;
60 m_Terminals[i].textio = nullptr;
61 m_Terminals[i].file = nullptr;
62#if THREADS
63 m_Terminals[i].owner = ~size_t(0);
64 m_Terminals[i].ownerThread = ~size_t(0);
65#endif
66 }
67
68 m_Terminals[0].textio = nullptr;
69 m_Terminals[0].file = nullptr;
70 m_pTty = nullptr;
71}
72
73bool VirtualTerminalManager::initialise() {
74 // Create /dev/textui for the text-only UI device.
75 m_pTty = new TextIO(String("textui"), g_pDevFs->getNextInode(), g_pDevFs, m_ParentDir);
76 m_pTty->markPrimary();
77 if (m_pTty->initialise(false)) {
78 m_ParentDir->addEntry(m_pTty->getName(), m_pTty);
79 } else {
80 WARNING(
81 "POSIX: no /dev/tty - VirtualTerminalManager failed to "
82 "initialise.");
83 g_pDevFs->revertInode();
84 delete m_pTty;
85 m_pTty = nullptr;
86
87 return false;
88 }
89
90 // set up tty1
91 ConsolePhysicalFile* pTty1 = new ConsolePhysicalFile(0, m_pTty, String("tty1"), g_pDevFs);
92 m_ParentDir->addEntry(pTty1->getName(), pTty1);
93
94 m_Terminals[0].textio = m_pTty;
95 m_Terminals[0].file = pTty1;
96
97 // create tty2-6 as non-overloaded TextIO instances
98 for (size_t i = 1; i < 8; ++i) {
99 String ttyname;
100 ttyname.Format("tty%u", i + 1);
101
102 TextIO* tio = new TextIO(ttyname, g_pDevFs->getNextInode(), g_pDevFs, m_ParentDir);
103 if (tio->initialise(true)) {
104 ConsolePhysicalFile* file = new ConsolePhysicalFile(i, tio, ttyname, g_pDevFs);
105 m_ParentDir->addEntry(ttyname, file);
106
107 m_Terminals[i].textio = tio;
108 m_Terminals[i].file = file;
109
110 // activate the terminal by performing an empty write, which will
111 // ensure users switching to the terminal see a blank screen if
112 // nothing has actually opened it - this is better than seeing the
113 // previous tty's output...
114 tio->writeStr("", 0);
115 } else {
116 WARNING("POSIX: failed to create " << ttyname);
117 g_pDevFs->revertInode();
118 delete tio;
119 }
120 }
121
122 return true;
123}
124
125bool VirtualTerminalManager::isTerminal(size_t n) const {
126 return n < MAX_VT && m_Terminals[n].textio;
127}
128
129void VirtualTerminalManager::switchTerminal(size_t n) {
130 m_Terminals[m_CurrentTty].textio->unmarkPrimary();
131 m_CurrentTty = n;
132 m_WantedTty = ~size_t(0);
133 if (m_Terminals[n].systemMode == Text) {
134 if (Machine::instance().getNumVga()) {
135 Machine::instance().getVga(0)->setLargestTextMode();
136 }
137 m_Terminals[n].textio->markPrimary();
138 }
139 if (!sendSignal(n, true) && m_Terminals[n].mode.mode == VT_PROCESS) {
140 m_Terminals[n].mode.mode = VT_AUTO;
141 m_Terminals[n].systemMode = Text;
142 if (Machine::instance().getNumVga()) {
143 Machine::instance().getVga(0)->setLargestTextMode();
144 }
145 m_Terminals[n].textio->markPrimary();
146 }
147}
148
150 LockGuard<Mutex> guard(m_Lock);
151 if (!isTerminal(n) || m_bSwitchingLocked) {
152 return false;
153 }
154 if (n == m_CurrentTty) {
155 return true;
156 }
157 if (m_WantedTty != ~size_t(0)) {
158 if (sendSignal(m_CurrentTty, false)) {
159 return false;
160 }
161 m_WantedTty = ~size_t(0);
162 }
163 if (m_Terminals[m_CurrentTty].mode.mode == VT_PROCESS) {
164 m_WantedTty = n;
165 if (sendSignal(m_CurrentTty, false)) {
166 return true;
167 }
168 // An exited controller cannot acknowledge release.
169 m_Terminals[m_CurrentTty].mode.mode = VT_AUTO;
170 m_Terminals[m_CurrentTty].systemMode = Text;
171 }
172 switchTerminal(n);
173 return true;
174}
175
176bool VirtualTerminalManager::reportPermission(size_t n, SwitchPermission perm) {
177 LockGuard<Mutex> guard(m_Lock);
178 if (n != m_CurrentTty || m_WantedTty == ~size_t(0)) {
179 return false;
180 }
181#if THREADS
182 if (Processor::information().getCurrentThread()->getParent()->getId() != m_Terminals[n].owner) {
183 return false;
184 }
185#endif
186 if (perm == Disallowed) {
187 m_WantedTty = ~size_t(0);
188 } else {
189 switchTerminal(m_WantedTty);
190 }
191 return true;
192}
193
194bool VirtualTerminalManager::acknowledgeAcquire(size_t n) {
195 LockGuard<Mutex> guard(m_Lock);
196 if (n != m_CurrentTty || m_Terminals[n].mode.mode != VT_PROCESS) {
197 return false;
198 }
199#if THREADS
200 return Processor::information().getCurrentThread()->getParent()->getId() == m_Terminals[n].owner;
201#else
202 return true;
203#endif
204}
205
207 LockGuard<Mutex> guard(m_Lock);
208 for (size_t i = 0; i < MAX_VT; ++i) {
209 if (m_Terminals[i].textio == nullptr) {
210 NOTICE("VirtualTerminalManager: opening inactive VT #" << i);
211
212 String ttyname;
213 ttyname.Format("tty%u", i + 1);
214
215 TextIO* tio = new TextIO(ttyname, g_pDevFs->getNextInode(), g_pDevFs, m_ParentDir);
216 if (tio->initialise(true)) {
217 ConsolePhysicalFile* file = new ConsolePhysicalFile(i, tio, ttyname, g_pDevFs);
218 m_ParentDir->addEntry(tio->getName(), file);
219
220 m_Terminals[i].textio = tio;
221 m_Terminals[i].file = file;
222
223 // activate the terminal by performing an empty write, which
224 // will ensure users switching to the terminal see a blank
225 // screen if nothing has actually opened it - this is better
226 // than seeing the previous tty's output...
227 tio->writeStr("", 0);
228
229 return i;
230 } else {
231 WARNING("POSIX: failed to create " << ttyname);
232 g_pDevFs->revertInode();
233 delete tio;
234 }
235 }
236 }
237
238 return ~0;
239}
240
242 LockGuard<Mutex> guard(m_Lock);
243 m_bSwitchingLocked = locked;
244}
245
246size_t VirtualTerminalManager::getCurrentTerminalNumber() const {
247 return m_CurrentTty;
248}
249
250TextIO* VirtualTerminalManager::getCurrentTerminal() const {
251 return m_Terminals[m_CurrentTty].textio;
252}
253
254File* VirtualTerminalManager::getCurrentTerminalFile() const {
255 return m_Terminals[m_CurrentTty].file;
256}
257
258struct vt_mode VirtualTerminalManager::getTerminalMode(size_t n) const {
259 LockGuard<Mutex> guard(m_Lock);
260 return m_Terminals[n].mode;
261}
262
263bool VirtualTerminalManager::setTerminalMode(size_t n, struct vt_mode mode) {
264 LockGuard<Mutex> guard(m_Lock);
265 if (!isTerminal(n) || (mode.mode != VT_AUTO && mode.mode != VT_PROCESS) ||
266 (mode.mode == VT_PROCESS &&
267 (mode.relsig <= 0 || mode.acqsig <= 0 ||
268 static_cast<size_t>(mode.relsig) > PosixSubsystem::MaximumSupportedSignal ||
269 static_cast<size_t>(mode.acqsig) > PosixSubsystem::MaximumSupportedSignal))) {
270 return false;
271 }
272 m_Terminals[n].mode = mode;
273#if THREADS
274 Thread* thread = Processor::information().getCurrentThread();
275 m_Terminals[n].owner = mode.mode == VT_PROCESS ? thread->getParent()->getId() : ~size_t(0);
276 m_Terminals[n].ownerThread = mode.mode == VT_PROCESS ? thread->getId() : ~size_t(0);
277#endif
278 if (n == m_CurrentTty && mode.mode == VT_AUTO && m_WantedTty != ~size_t(0)) {
279 switchTerminal(m_WantedTty);
280 }
281 return true;
282}
283
284struct vt_stat VirtualTerminalManager::getState() const {
285 LockGuard<Mutex> guard(m_Lock);
286 struct vt_stat state;
287
288 state.v_active = m_CurrentTty + 1;
289 state.v_signal = 0;
290 state.v_state = 1; // VT 0 == current, always
291 for (size_t i = 0; i < 15; ++i) {
292 if (m_Terminals[i].textio != nullptr) {
293 state.v_state |= (1 << (i + 1));
294 }
295 }
296
297 NOTICE("getState:");
298 NOTICE(" -> active = " << state.v_active);
299 NOTICE(" -> state = " << Hex << state.v_state);
300
301 return state;
302}
303
304void VirtualTerminalManager::setSystemMode(size_t n, SystemMode mode) {
305 LockGuard<Mutex> guard(m_Lock);
306 m_Terminals[n].systemMode = mode;
307 if (n != m_CurrentTty) {
308 return;
309 }
310 if (mode == Graphics) {
311 m_Terminals[n].textio->unmarkPrimary();
312 } else {
313 if (Machine::instance().getNumVga()) {
314 Machine::instance().getVga(0)->setLargestTextMode();
315 }
316 m_Terminals[n].textio->markPrimary();
317 }
318}
319
320VirtualTerminalManager::SystemMode VirtualTerminalManager::getSystemMode(size_t n) const {
321 LockGuard<Mutex> guard(m_Lock);
322 return m_Terminals[n].systemMode;
323}
324
325void VirtualTerminalManager::setInputMode(size_t n, TextIO::InputMode newMode) {
326 if (!m_Terminals[n].textio) {
327 NOTICE("VirtualTerminalManager: can't set mode of VT #" << n << " as it is inactive");
328 return;
329 }
330
331 m_Terminals[n].textio->setMode(newMode);
332}
333
334TextIO::InputMode VirtualTerminalManager::getInputMode(size_t n) const {
335 if (!m_Terminals[n].textio) {
336 NOTICE("VirtualTerminalManager: can't get mode of VT #" << n << " as it is inactive");
337 return TextIO::Standard;
338 }
339
340 return m_Terminals[n].textio->getMode();
341}
342
343bool VirtualTerminalManager::sendSignal(size_t n, bool acq) {
344 auto& terminal = m_Terminals[n];
345 if (terminal.mode.mode != VT_PROCESS) {
346 return false;
347 }
348#if THREADS
350 if (!Scheduler::instance().acquireProcessById(process, terminal.owner) ||
351 process->getState() >= Process::Terminating) {
352 return false;
353 }
354 PosixSubsystem* subsystem = static_cast<PosixSubsystem*>(process->getSubsystem());
356 if (!subsystem || !process->acquireThreadById(target, terminal.ownerThread) ||
357 !target->acceptingEvents()) {
358 return false;
359 }
360 // The controller masks these signals while presenting. Another thread must
361 // not acknowledge release while that presentation is still writing scanout.
362 subsystem->sendSignal(target.get(), acq ? terminal.mode.acqsig : terminal.mode.relsig, false,
363 false);
364 return true;
365#else
366 return false;
367#endif
368}
Definition DevFs.h:269
Definition File.h:75
String getName() const
Definition File.cc:782
virtual Vga * getVga(size_t n)=0
virtual void sendSignal(Thread *pThread, int signal, bool yield=true, bool processDirected=false)
size_t getId()
Definition Process.h:499
MUST_USE_RESULT bool acquireThreadById(ThreadLease &lease, size_t id)
Definition Process.cc:1446
static ProcessorInformation & information()
static Scheduler & instance()
Definition Scheduler.h:96
bool initialise(bool bClear=true)
Definition TextIO.cc:139
void unmarkPrimary()
Definition TextIO.cc:2254
InputMode getMode() const
Definition TextIO.cc:2267
void writeStr(const char *s, size_t len)
Definition TextIO.cc:492
void markPrimary()
Definition TextIO.cc:2240
void setMode(InputMode mode)
Definition TextIO.cc:2263
Process * getParent() const
Definition Thread.h:340
size_t getId()
Definition Thread.h:465
virtual bool setLargestTextMode()=0
void lockSwitching(bool locked)
bool reportPermission(size_t n, SwitchPermission perm)
bool activate(size_t n)
Starts the process of activating the given tty.
@ Hex
Definition Log.h:124