3 * - By John Hodge (thePowersGang)
6 * - Virtual Terminal - Initialisation and VFS Interface
12 #include <api_drv_keyboard.h>
13 #include <api_drv_video.h>
15 #include <semaphore.h>
18 #define VERSION ((0<<8)|(50))
21 //#define DEFAULT_OUTPUT "BochsGA"
22 #define DEFAULT_OUTPUT "Vesa"
23 #define FALLBACK_OUTPUT "x86_VGAText"
24 #define DEFAULT_INPUT "Keyboard"
25 #define DEFAULT_WIDTH 640
26 #define DEFAULT_HEIGHT 480
27 #define DEFAULT_SCROLLBACK 4 // 2 Screens of text + current screen
28 //#define DEFAULT_SCROLLBACK 0
33 extern void Debug_SetKTerminal(const char *File);
36 int VT_Install(char **Arguments);
37 int VT_ReadDir(tVFS_Node *Node, int Pos, char Dest[FILENAME_MAX]);
38 tVFS_Node *VT_FindDir(tVFS_Node *Node, const char *Name, Uint Flags);
39 int VT_Root_IOCtl(tVFS_Node *Node, int Id, void *Data);
40 size_t VT_Read(tVFS_Node *Node, off_t Offset, size_t Length, void *Buffer, Uint Flags);
41 size_t VT_Write(tVFS_Node *Node, off_t Offset, size_t Length, const void *Buffer, Uint Flags);
42 int VT_Terminal_IOCtl(tVFS_Node *Node, int Id, void *Data);
43 void VT_Terminal_Reference(tVFS_Node *Node);
44 void VT_Terminal_Close(tVFS_Node *Node);
45 //void VT_SetTerminal(int Term);
50 MODULE_DEFINE(0, VERSION, VTerm, VT_Install, NULL, NULL);
51 tVFS_NodeType gVT_RootNodeType = {
52 .TypeName = "VTerm Root",
53 .ReadDir = VT_ReadDir,
54 .FindDir = VT_FindDir,
55 .IOCtl = VT_Root_IOCtl
57 tVFS_NodeType gVT_TermNodeType = {
61 .IOCtl = VT_Terminal_IOCtl
63 tDevFS_Driver gVT_DrvInfo = {
66 .Flags = VFS_FFLAG_DIRECTORY,
70 .Type = &gVT_RootNodeType
74 tVTerm gVT_Terminals[NUM_VTS];
75 int giVT_CurrentTerminal = 0;
76 tVTerm *gpVT_CurTerm = &gVT_Terminals[0];
77 // --- Video State ---
78 short giVT_RealWidth = DEFAULT_WIDTH; //!< Screen Width
79 short giVT_RealHeight = DEFAULT_HEIGHT; //!< Screen Height
80 int giVT_Scrollback = DEFAULT_SCROLLBACK;
81 // --- Driver Handles ---
82 char *gsVT_OutputDevice = NULL;
83 char *gsVT_InputDevice = NULL;
84 int giVT_OutputDevHandle = -2;
85 int giVT_InputDevHandle = -2;
89 * \fn int VT_Install(char **Arguments)
90 * \brief Installs the Virtual Terminal Driver
92 int VT_Install(char **Arguments)
101 for(args = Arguments; (arg = *args); args++ )
103 char data[strlen(arg)+1];
107 val = strchr(arg, '=');
110 data[ val - arg ] = '\0';
113 Log_Debug("VTerm", "Argument '%s'", arg);
115 if( strcmp(opt, "Video") == 0 ) {
116 if( !gsVT_OutputDevice )
117 gsVT_OutputDevice = val;
119 else if( strcmp(opt, "Input") == 0 ) {
120 if( !gsVT_InputDevice )
121 gsVT_InputDevice = val;
123 else if( strcmp(opt, "Width") == 0 ) {
124 giVT_RealWidth = atoi( val );
126 else if( strcmp(opt, "Height") == 0 ) {
127 giVT_RealHeight = atoi( val );
129 else if( strcmp(opt, "Scrollback") == 0 ) {
130 giVT_Scrollback = atoi( val );
133 Log_Notice("VTerm", "Unknown option '%s'", opt);
139 if(!gsVT_OutputDevice) gsVT_OutputDevice = (char*)DEFAULT_OUTPUT;
140 else if( Module_EnsureLoaded( gsVT_OutputDevice ) ) gsVT_OutputDevice = (char*)DEFAULT_OUTPUT;
141 if( Module_EnsureLoaded( gsVT_OutputDevice ) ) gsVT_OutputDevice = (char*)FALLBACK_OUTPUT;
142 if( Module_EnsureLoaded( gsVT_OutputDevice ) ) {
143 Log_Error("VTerm", "Fallback video '%s' is not avaliable, giving up", FALLBACK_OUTPUT);
144 return MODULE_ERR_MISC;
147 if(!gsVT_InputDevice) gsVT_InputDevice = (char*)DEFAULT_INPUT;
148 else if( Module_EnsureLoaded( gsVT_InputDevice ) ) gsVT_InputDevice = (char*)DEFAULT_INPUT;
149 if( Module_EnsureLoaded( gsVT_InputDevice ) ) {
150 Log_Error("VTerm", "Fallback input '%s' is not avaliable, input will not be avaliable", DEFAULT_INPUT);
153 // Create device paths
156 tmp = malloc( 9 + strlen(gsVT_OutputDevice) + 1 );
157 strcpy(tmp, "/Devices/");
158 strcpy(&tmp[9], gsVT_OutputDevice);
159 gsVT_OutputDevice = tmp;
161 tmp = malloc( 9 + strlen(gsVT_InputDevice) + 1 );
162 strcpy(tmp, "/Devices/");
163 strcpy(&tmp[9], gsVT_InputDevice);
164 gsVT_InputDevice = tmp;
167 Log_Log("VTerm", "Using '%s' as output", gsVT_OutputDevice);
168 Log_Log("VTerm", "Using '%s' as input", gsVT_InputDevice);
175 Log_Debug("VTerm", "Initialising nodes (and creating buffers)");
176 for( i = 0; i < NUM_VTS; i++ )
178 gVT_Terminals[i].Mode = TERM_MODE_TEXT;
179 gVT_Terminals[i].Flags = 0;
180 // gVT_Terminals[i].Flags = VT_FLAG_HIDECSR; //HACK - Stop all those memcpy calls
181 gVT_Terminals[i].CurColour = DEFAULT_COLOUR;
182 gVT_Terminals[i].WritePos = 0;
183 gVT_Terminals[i].AltWritePos = 0;
184 gVT_Terminals[i].ViewPos = 0;
185 gVT_Terminals[i].ReadingThread = -1;
186 gVT_Terminals[i].ScrollHeight = 0;
189 VT_int_ChangeMode( &gVT_Terminals[i],
190 TERM_MODE_TEXT, giVT_RealWidth, giVT_RealHeight );
192 gVT_Terminals[i].Name[0] = '0'+i;
193 gVT_Terminals[i].Name[1] = '\0';
194 gVT_Terminals[i].Node.Inode = i;
195 gVT_Terminals[i].Node.ImplPtr = &gVT_Terminals[i];
196 gVT_Terminals[i].Node.NumACLs = 0; // Only root can open virtual terminals
198 gVT_Terminals[i].Node.Type = &gVT_TermNodeType;
199 // Semaphore_Init(&gVT_Terminals[i].InputSemaphore, 0, MAX_INPUT_CHARS8, "VTerm", gVT_Terminals[i].Name);
203 DevFS_AddDevice( &gVT_DrvInfo );
205 // Set kernel output to VT0
206 Log_Debug("VTerm", "Setting kernel output to VT#0");
207 Debug_SetKTerminal("/Devices/VTerm/0");
209 return MODULE_ERR_OK;
213 * \brief Set the video resolution
214 * \param Width New screen width
215 * \param Height New screen height
217 void VT_SetResolution(int Width, int Height)
219 tVideo_IOCtl_Mode mode = {0};
223 // Create the video mode
225 mode.height = Height;
230 VFS_IOCtl( giVT_OutputDevHandle, VIDEO_IOCTL_FINDMODE, &mode );
232 if( Width != mode.width || Height != mode.height )
235 "Selected resolution (%ix%i) is not supported by the device, using (%ix%i)",
236 giVT_RealWidth, giVT_RealHeight,
237 mode.width, mode.height
239 giVT_RealWidth = mode.width;
240 giVT_RealHeight = mode.height;
242 VFS_IOCtl( giVT_OutputDevHandle, VIDEO_IOCTL_GETSETMODE, &tmp );
244 // Resize text terminals if needed
245 if( gVT_Terminals[0].Text && (giVT_RealWidth != mode.width || giVT_RealHeight != mode.height) )
247 int newBufSize = (giVT_RealWidth/giVT_CharWidth)
248 *(giVT_RealHeight/giVT_CharHeight)
249 *(giVT_Scrollback+1);
251 // Resize the text terminals
252 Log_Debug("VTerm", "Resizing terminals to %ix%i",
253 giVT_RealWidth/giVT_CharWidth, giVT_RealHeight/giVT_CharHeight);
254 for( i = 0; i < NUM_VTS; i ++ )
256 if( gVT_Terminals[i].Mode != TERM_MODE_TEXT ) continue;
258 gVT_Terminals[i].TextWidth = giVT_RealWidth/giVT_CharWidth;
259 gVT_Terminals[i].TextHeight = giVT_RealHeight/giVT_CharHeight;
260 gVT_Terminals[i].ScrollHeight = gVT_Terminals[i].TextHeight;
262 gVT_Terminals[i].Text = realloc(
263 gVT_Terminals[i].Text,
264 newBufSize*sizeof(tVT_Char)
271 * \fn char *VT_ReadDir(tVFS_Node *Node, int Pos)
272 * \brief Read from the VTerm Directory
274 int VT_ReadDir(tVFS_Node *Node, int Pos, char Dest[FILENAME_MAX])
276 if(Pos < 0) return -EINVAL;
277 if(Pos >= NUM_VTS) return -EINVAL;
278 strncpy(Dest, gVT_Terminals[Pos].Name, FILENAME_MAX);
283 * \fn tVFS_Node *VT_FindDir(tVFS_Node *Node, const char *Name)
284 * \brief Find an item in the VTerm directory
285 * \param Node Root node
286 * \param Name Name (number) of the terminal
288 tVFS_Node *VT_FindDir(tVFS_Node *Node, const char *Name, Uint Flags)
292 ENTER("pNode sName", Node, Name);
294 // Open the input and output files if needed
295 if(giVT_OutputDevHandle == -2) VT_InitOutput();
296 if(giVT_InputDevHandle == -2) VT_InitInput();
299 if(Name[0] < '0' || Name[0] > '9' || Name[1] != '\0') {
310 LEAVE('p', &gVT_Terminals[num].Node);
311 return &gVT_Terminals[num].Node;
315 * \fn int VT_Root_IOCtl(tVFS_Node *Node, int Id, void *Data)
316 * \brief Control the VTerm Driver
318 int VT_Root_IOCtl(tVFS_Node *Node, int Id, void *Data)
323 case DRV_IOCTL_TYPE: return DRV_TYPE_MISC;
324 case DRV_IOCTL_IDENT: memcpy(Data, "VT\0\0", 4); return 0;
325 case DRV_IOCTL_VERSION: return VERSION;
326 case DRV_IOCTL_LOOKUP: return 0;
328 case 4: // Get Video Driver
329 if(Data) strcpy(Data, gsVT_OutputDevice);
330 return strlen(gsVT_OutputDevice);
332 case 5: // Set Video Driver
333 if(!Data) return -EINVAL;
334 if(Threads_GetUID() != 0) return -EACCES;
338 // TODO: Check if the string used is a heap string
340 free(gsVT_OutputDevice);
342 gsVT_OutputDevice = malloc(len+1);
343 strcpy(gsVT_OutputDevice, Data);
345 VFS_Close(giVT_OutputDevHandle);
346 giVT_OutputDevHandle = -1;
355 * \brief Read from a virtual terminal
357 size_t VT_Read(tVFS_Node *Node, off_t Offset, size_t Length, void *Buffer, Uint Flags)
360 tVTerm *term = &gVT_Terminals[ Node->Inode ];
361 Uint32 *codepoint_buf = Buffer;
362 Uint32 *codepoint_in;
363 tTime timeout_zero = 0;
365 Mutex_Acquire( &term->ReadingLock );
367 // Check current mode
372 VT_int_UpdateCursor(term, 1);
374 rv = VFS_SelectNode(Node, VFS_SELECT_READ,
375 (Flags & VFS_IOFLAG_NOBLOCK ? &timeout_zero : NULL), "VT_Read (UTF-8)");
377 errno = (Flags & VFS_IOFLAG_NOBLOCK) ? EWOULDBLOCK : EINTR;
381 avail = term->InputWrite - term->InputRead;
383 avail += MAX_INPUT_CHARS8;
390 ((char*)Buffer)[pos] = term->InputBuffer[term->InputRead];
393 while(term->InputRead >= MAX_INPUT_CHARS8)
394 term->InputRead -= MAX_INPUT_CHARS8;
401 rv = VFS_SelectNode(Node, VFS_SELECT_READ,
402 (Flags & VFS_IOFLAG_NOBLOCK ? &timeout_zero : NULL), "VT_Read (UCS-4)");
404 errno = (Flags & VFS_IOFLAG_NOBLOCK) ? EWOULDBLOCK : EINTR;
408 avail = term->InputWrite - term->InputRead;
410 avail += MAX_INPUT_CHARS32;
415 codepoint_in = (void*)term->InputBuffer;
416 codepoint_buf = Buffer;
421 codepoint_buf[pos] = codepoint_in[term->InputRead];
424 while(term->InputRead >= MAX_INPUT_CHARS32)
425 term->InputRead -= MAX_INPUT_CHARS32;
431 // Mark none avaliable if buffer empty
432 if( term->InputRead == term->InputWrite )
433 VFS_MarkAvaliable(&term->Node, 0);
435 term->ReadingThread = -1;
437 // VT_int_UpdateCursor(term, term->Mode == TERM_MODE_TEXT);
439 Mutex_Release( &term->ReadingLock );
445 * \brief Write to a virtual terminal
447 size_t VT_Write(tVFS_Node *Node, off_t Offset, size_t Length, const void *Buffer, Uint Flags)
449 tVTerm *term = &gVT_Terminals[ Node->Inode ];
457 VT_int_PutString(term, Buffer, Length);
463 size = term->Width*term->Height*4;
464 if( Offset > size ) {
465 Log_Notice("VTerm", "VT_Write: %i Offset (0x%llx) > FBSize (0x%x)",
466 (int)Node->Inode, Offset, size);
469 if( Offset + Length > size ) {
470 Log_Notice("VTerm", "VT_Write: Offset+Length (0x%llx) > FBSize (0x%x)",
471 Offset+Length, size);
472 Length = size - Offset;
475 // Update screen if needed
476 if( Node->Inode == giVT_CurrentTerminal )
478 if( giVT_RealHeight > term->Height )
479 Offset += (giVT_RealHeight - term->Height) / 2 * term->Width * 4;
480 // Handle undersized virtual terminals
481 if( giVT_RealWidth > term->Width )
483 // No? :( Well, just center it
486 // TODO: Fix to handle the final line correctly?
487 x = Offset/4; y = x / term->Width; x %= term->Width;
488 w = Length/4+x; h = w / term->Width; w %= term->Width;
491 x += (giVT_RealWidth - term->Width) / 2;
492 dst_ofs = (x + y * giVT_RealWidth) * 4;
495 VFS_WriteAt( giVT_OutputDevHandle,
500 Buffer = (void*)( (Uint)Buffer + term->Width*4 );
501 dst_ofs += giVT_RealWidth * 4;
507 return VFS_WriteAt( giVT_OutputDevHandle, Offset, Length, Buffer );
513 term->Buffer = malloc( term->Width * term->Height * 4 );
514 // Copy to the local cache
515 memcpy( (char*)term->Buffer + (Uint)Offset, Buffer, Length );
518 // Just pass on (for now)
519 // TODO: Handle locally too to ensure no information is lost on
520 // VT Switch (and to isolate terminals from each other)
521 case TERM_MODE_2DACCEL:
522 //case TERM_MODE_3DACCEL:
523 if( Node->Inode == giVT_CurrentTerminal )
525 VFS_Write( giVT_OutputDevHandle, Length, Buffer );
534 * \fn int VT_Terminal_IOCtl(tVFS_Node *Node, int Id, void *Data)
535 * \brief Call an IO Control on a virtual terminal
537 int VT_Terminal_IOCtl(tVFS_Node *Node, int Id, void *Data)
541 tVTerm *term = Node->ImplPtr;
542 ENTER("pNode iId pData", Node, Id, Data);
544 if(Id >= DRV_IOCTL_LOOKUP) {
545 // Only root can fiddle with graphics modes
546 // TODO: Remove this and replace with user ownership
547 if( Threads_GetUID() != 0 ) return -1;
554 LEAVE('i', DRV_TYPE_TERMINAL);
555 return DRV_TYPE_TERMINAL;
556 case DRV_IOCTL_IDENT:
557 memcpy(Data, "VT\0\0", 4);
560 case DRV_IOCTL_VERSION:
563 case DRV_IOCTL_LOOKUP:
567 // Get/Set the mode (and apply any changes)
568 case TERM_IOCTL_MODETYPE:
571 if( CheckMem(Data, sizeof(int)) == 0 ) {
575 Log_Log("VTerm", "VTerm %i mode set to %i", (int)Node->Inode, *iData);
577 // Update mode if needed
578 if( term->Mode != *iData || term->NewWidth || term->NewHeight)
580 // Adjust for text mode
581 if( *iData == TERM_MODE_TEXT ) {
582 term->NewHeight *= giVT_CharHeight;
583 term->NewWidth *= giVT_CharWidth;
585 // Fill unchanged dimensions
586 if(term->NewHeight == 0) term->NewHeight = term->Height;
587 if(term->NewWidth == 0) term->NewWidth = term->Width;
589 VT_int_ChangeMode(term, *iData, term->NewWidth, term->NewHeight);
590 // Clear unapplied dimensions
595 // Update the screen dimensions
596 if(Node->Inode == giVT_CurrentTerminal)
597 VT_SetTerminal( giVT_CurrentTerminal );
599 LEAVE('i', term->Mode);
602 // Get/set the terminal width
603 case TERM_IOCTL_WIDTH:
605 if( CheckMem(Data, sizeof(int)) == 0 ) {
609 term->NewWidth = *iData;
612 ret = term->NewWidth;
613 else if( term->Mode == TERM_MODE_TEXT )
614 ret = term->TextWidth;
620 // Get/set the terminal height
621 case TERM_IOCTL_HEIGHT:
623 if( CheckMem(Data, sizeof(int)) == 0 ) {
627 term->NewHeight = *iData;
629 if( term->NewHeight )
630 ret = term->NewHeight;
631 else if( term->Mode == TERM_MODE_TEXT )
632 ret = term->TextHeight;
638 case TERM_IOCTL_FORCESHOW:
639 Log_Log("VTerm", "Thread %i forced VTerm %i to be shown",
640 Threads_GetTID(), (int)Node->Inode);
641 VT_SetTerminal( Node->Inode );
645 case TERM_IOCTL_GETSETCURSOR:
648 tVideo_IOCtl_Pos *pos = Data;
649 if( !CheckMem(Data, sizeof(*pos)) ) {
655 if( term->Mode == TERM_MODE_TEXT )
657 if(term->Flags & VT_FLAG_ALTBUF)
658 term->AltWritePos = pos->x + pos->y * term->TextWidth;
660 term->WritePos = pos->x + pos->y * term->TextWidth + term->ViewPos;
661 VT_int_UpdateCursor(term, 0);
665 term->VideoCursorX = pos->x;
666 term->VideoCursorY = pos->y;
667 VT_int_UpdateCursor(term, 1);
670 ret = (term->Flags & VT_FLAG_ALTBUF) ? term->AltWritePos : term->WritePos-term->ViewPos;
674 case TERM_IOCTL_SETCURSORBITMAP: {
675 tVideo_IOCtl_Bitmap *bmp = Data;
678 free( term->VideoCursor );
679 term->VideoCursor = NULL;
684 // Sanity check bitmap
685 if( !CheckMem(bmp, sizeof(tVideo_IOCtl_Bitmap)) ) {
686 Log_Notice("VTerm", "%p in TERM_IOCTL_SETCURSORBITMAP invalid", bmp);
690 if( !CheckMem(bmp->Data, bmp->W*bmp->H*sizeof(Uint32)) ) {
691 Log_Notice("VTerm", "%p in TERM_IOCTL_SETCURSORBITMAP invalid", bmp);
696 // Reallocate if needed
697 if(term->VideoCursor)
699 if(bmp->W * bmp->H != term->VideoCursor->W * term->VideoCursor->H) {
700 free(term->VideoCursor);
701 term->VideoCursor = NULL;
704 if(!term->VideoCursor) {
705 term->VideoCursor = malloc(sizeof(tVideo_IOCtl_Pos) + bmp->W*bmp->H*sizeof(Uint32));
706 if(!term->VideoCursor) {
707 Log_Error("VTerm", "Unable to allocate memory for cursor");
713 memcpy(term->VideoCursor, bmp, sizeof(tVideo_IOCtl_Pos) + bmp->W*bmp->H*sizeof(Uint32));
715 Log_Debug("VTerm", "Set VT%i's cursor to %p %ix%i",
716 (int)term->Node.Inode, bmp, bmp->W, bmp->H);
718 if(gpVT_CurTerm == term)
719 VFS_IOCtl(giVT_OutputDevHandle, VIDEO_IOCTL_SETCURSORBITMAP, term->VideoCursor);
728 void VT_Terminal_Reference(tVFS_Node *Node)
730 // Append PID to list
733 void VT_Terminal_Close(tVFS_Node *Node)
735 // Remove PID from list
739 * \fn void VT_SetTerminal(int ID)
740 * \brief Set the current terminal
742 void VT_SetTerminal(int ID)
744 // Copy the screen state
745 if( ID != giVT_CurrentTerminal && gpVT_CurTerm->Mode != TERM_MODE_TEXT )
747 if( !gpVT_CurTerm->Buffer )
748 gpVT_CurTerm->Buffer = malloc( gpVT_CurTerm->Width*gpVT_CurTerm->Height*4 );
749 if( gpVT_CurTerm->Width < giVT_RealWidth )
752 Uint32 *dest = gpVT_CurTerm->Buffer;
753 // Slower scanline copy
754 for( int line = 0; line < gpVT_CurTerm->Height; line ++ )
756 VFS_ReadAt(giVT_OutputDevHandle, ofs, gpVT_CurTerm->Width*4, dest);
757 ofs += giVT_RealWidth * 4;
758 dest += gpVT_CurTerm->Width;
763 VFS_ReadAt(giVT_OutputDevHandle,
764 0, gpVT_CurTerm->Height*giVT_RealWidth*4,
768 LOG("Cached screen contents");
771 // Update current terminal ID
772 Log_Log("VTerm", "Changed terminal from %i to %i", giVT_CurrentTerminal, ID);
773 giVT_CurrentTerminal = ID;
774 gpVT_CurTerm = &gVT_Terminals[ID];
776 LOG("Attempting VT_SetMode");
778 if( gpVT_CurTerm->Mode == TERM_MODE_TEXT )
780 VT_SetMode( VIDEO_BUFFMT_TEXT );
784 // Update the cursor image
785 if(gpVT_CurTerm->VideoCursor)
786 VFS_IOCtl(giVT_OutputDevHandle, VIDEO_IOCTL_SETCURSORBITMAP, gpVT_CurTerm->VideoCursor);
787 VT_SetMode( VIDEO_BUFFMT_FRAMEBUFFER );
792 if(gpVT_CurTerm->Buffer)
794 // TODO: Handle non equal sized
796 giVT_OutputDevHandle,
798 gpVT_CurTerm->Width*gpVT_CurTerm->Height*sizeof(Uint32),
801 LOG("Updated screen contents");
804 VT_int_UpdateCursor(gpVT_CurTerm, 1);
806 VT_int_UpdateScreen(gpVT_CurTerm, 1);