2 * Acess2 Virtual Terminal Driver
8 #include <api_drv_keyboard.h>
9 #include <api_drv_video.h>
11 #include <semaphore.h>
14 #define VERSION ((0<<8)|(50))
17 //#define DEFAULT_OUTPUT "BochsGA"
18 #define DEFAULT_OUTPUT "Vesa"
19 #define FALLBACK_OUTPUT "x86_VGAText"
20 #define DEFAULT_INPUT "PS2Keyboard"
21 #define DEFAULT_WIDTH 640
22 #define DEFAULT_HEIGHT 480
23 #define DEFAULT_SCROLLBACK 2 // 2 Screens of text + current screen
24 //#define DEFAULT_SCROLLBACK 0
29 extern void Debug_SetKTerminal(const char *File);
32 int VT_Install(char **Arguments);
33 char *VT_ReadDir(tVFS_Node *Node, int Pos);
34 tVFS_Node *VT_FindDir(tVFS_Node *Node, const char *Name);
35 int VT_Root_IOCtl(tVFS_Node *Node, int Id, void *Data);
36 Uint64 VT_Read(tVFS_Node *Node, Uint64 Offset, Uint64 Length, void *Buffer);
37 Uint64 VT_Write(tVFS_Node *Node, Uint64 Offset, Uint64 Length, const void *Buffer);
38 int VT_Terminal_IOCtl(tVFS_Node *Node, int Id, void *Data);
43 MODULE_DEFINE(0, VERSION, VTerm, VT_Install, NULL, DEFAULT_INPUT, NULL);
44 tVFS_NodeType gVT_RootNodeType = {
45 .TypeName = "VTerm Root",
46 .ReadDir = VT_ReadDir,
47 .FindDir = VT_FindDir,
48 .IOCtl = VT_Root_IOCtl
50 tVFS_NodeType gVT_TermNodeType = {
54 .IOCtl = VT_Terminal_IOCtl
56 tDevFS_Driver gVT_DrvInfo = {
59 .Flags = VFS_FFLAG_DIRECTORY,
63 .Type = &gVT_RootNodeType
67 tVTerm gVT_Terminals[NUM_VTS];
68 int giVT_CurrentTerminal = 0;
69 tVTerm *gpVT_CurTerm = &gVT_Terminals[0];
70 // --- Video State ---
71 short giVT_RealWidth = DEFAULT_WIDTH; //!< Screen Width
72 short giVT_RealHeight = DEFAULT_HEIGHT; //!< Screen Height
73 int giVT_Scrollback = DEFAULT_SCROLLBACK;
74 // --- Driver Handles ---
75 char *gsVT_OutputDevice = NULL;
76 char *gsVT_InputDevice = NULL;
77 int giVT_OutputDevHandle = -2;
78 int giVT_InputDevHandle = -2;
82 * \fn int VT_Install(char **Arguments)
83 * \brief Installs the Virtual Terminal Driver
85 int VT_Install(char **Arguments)
94 for(args = Arguments; (arg = *args); args++ )
96 char data[strlen(arg)+1];
100 val = strchr(arg, '=');
103 data[ val - arg ] = '\0';
106 Log_Debug("VTerm", "Argument '%s'", arg);
108 if( strcmp(opt, "Video") == 0 ) {
109 if( !gsVT_OutputDevice )
110 gsVT_OutputDevice = strdup(val);
112 else if( strcmp(opt, "Input") == 0 ) {
113 if( !gsVT_InputDevice )
114 gsVT_InputDevice = strdup(val);
116 else if( strcmp(opt, "Width") == 0 ) {
117 giVT_RealWidth = atoi( val );
119 else if( strcmp(opt, "Height") == 0 ) {
120 giVT_RealHeight = atoi( val );
122 else if( strcmp(opt, "Scrollback") == 0 ) {
123 giVT_Scrollback = atoi( val );
129 if(!gsVT_OutputDevice) gsVT_OutputDevice = (char*)DEFAULT_OUTPUT;
130 else if( Module_EnsureLoaded( gsVT_OutputDevice ) ) gsVT_OutputDevice = (char*)DEFAULT_OUTPUT;
131 if( Module_EnsureLoaded( gsVT_OutputDevice ) ) gsVT_OutputDevice = (char*)FALLBACK_OUTPUT;
132 if( Module_EnsureLoaded( gsVT_OutputDevice ) ) {
133 Log_Error("VTerm", "Fallback video '%s' is not avaliable, giving up", FALLBACK_OUTPUT);
134 return MODULE_ERR_MISC;
137 if(!gsVT_InputDevice) gsVT_InputDevice = (char*)DEFAULT_INPUT;
138 else if( Module_EnsureLoaded( gsVT_InputDevice ) ) gsVT_InputDevice = (char*)DEFAULT_INPUT;
140 // Create device paths
143 tmp = malloc( 9 + strlen(gsVT_OutputDevice) + 1 );
144 strcpy(tmp, "/Devices/");
145 strcpy(&tmp[9], gsVT_OutputDevice);
146 gsVT_OutputDevice = tmp;
148 tmp = malloc( 9 + strlen(gsVT_InputDevice) + 1 );
149 strcpy(tmp, "/Devices/");
150 strcpy(&tmp[9], gsVT_InputDevice);
151 gsVT_InputDevice = tmp;
154 Log_Log("VTerm", "Using '%s' as output", gsVT_OutputDevice);
155 Log_Log("VTerm", "Using '%s' as input", gsVT_InputDevice);
161 for( i = 0; i < NUM_VTS; i++ )
163 gVT_Terminals[i].Mode = TERM_MODE_TEXT;
164 gVT_Terminals[i].Flags = 0;
165 // gVT_Terminals[i].Flags = VT_FLAG_HIDECSR; //HACK - Stop all those memcpy calls
166 gVT_Terminals[i].CurColour = DEFAULT_COLOUR;
167 gVT_Terminals[i].WritePos = 0;
168 gVT_Terminals[i].AltWritePos = 0;
169 gVT_Terminals[i].ViewPos = 0;
170 gVT_Terminals[i].ReadingThread = -1;
171 gVT_Terminals[i].ScrollHeight = 0;
174 VT_int_ChangeMode( &gVT_Terminals[i],
175 TERM_MODE_TEXT, giVT_RealWidth, giVT_RealHeight );
177 gVT_Terminals[i].Name[0] = '0'+i;
178 gVT_Terminals[i].Name[1] = '\0';
179 gVT_Terminals[i].Node.Inode = i;
180 gVT_Terminals[i].Node.ImplPtr = &gVT_Terminals[i];
181 gVT_Terminals[i].Node.NumACLs = 0; // Only root can open virtual terminals
183 gVT_Terminals[i].Node.Type = &gVT_TermNodeType;
184 // Semaphore_Init(&gVT_Terminals[i].InputSemaphore, 0, MAX_INPUT_CHARS8, "VTerm", gVT_Terminals[i].Name);
188 DevFS_AddDevice( &gVT_DrvInfo );
190 // Set kernel output to VT0
191 Debug_SetKTerminal("/Devices/VTerm/0");
193 return MODULE_ERR_OK;
197 * \brief Set the video resolution
198 * \param Width New screen width
199 * \param Height New screen height
201 void VT_SetResolution(int Width, int Height)
203 tVideo_IOCtl_Mode mode = {0};
207 // Create the video mode
209 mode.height = Height;
214 VFS_IOCtl( giVT_OutputDevHandle, VIDEO_IOCTL_FINDMODE, &mode );
216 if( Width != mode.width || Height != mode.height )
219 "Selected resolution (%ix%i is not supported) by the device, using (%ix%i)",
220 giVT_RealWidth, giVT_RealHeight,
221 mode.width, mode.height
223 giVT_RealWidth = mode.width;
224 giVT_RealHeight = mode.height;
226 VFS_IOCtl( giVT_OutputDevHandle, VIDEO_IOCTL_GETSETMODE, &tmp );
228 // Resize text terminals if needed
229 if( gVT_Terminals[0].Text && (giVT_RealWidth != mode.width || giVT_RealHeight != mode.height) )
231 int newBufSize = (giVT_RealWidth/giVT_CharWidth)
232 *(giVT_RealHeight/giVT_CharHeight)
233 *(giVT_Scrollback+1);
235 // Resize the text terminals
236 Log_Debug("VTerm", "Resizing terminals to %ix%i",
237 giVT_RealWidth/giVT_CharWidth, giVT_RealHeight/giVT_CharHeight);
238 for( i = 0; i < NUM_VTS; i ++ )
240 if( gVT_Terminals[i].Mode != TERM_MODE_TEXT ) continue;
242 gVT_Terminals[i].TextWidth = giVT_RealWidth/giVT_CharWidth;
243 gVT_Terminals[i].TextHeight = giVT_RealHeight/giVT_CharHeight;
244 gVT_Terminals[i].ScrollHeight = gVT_Terminals[i].TextHeight;
246 gVT_Terminals[i].Text = realloc(
247 gVT_Terminals[i].Text,
248 newBufSize*sizeof(tVT_Char)
255 * \fn char *VT_ReadDir(tVFS_Node *Node, int Pos)
256 * \brief Read from the VTerm Directory
258 char *VT_ReadDir(tVFS_Node *Node, int Pos)
260 if(Pos < 0) return NULL;
261 if(Pos >= NUM_VTS) return NULL;
262 return strdup( gVT_Terminals[Pos].Name );
266 * \fn tVFS_Node *VT_FindDir(tVFS_Node *Node, const char *Name)
267 * \brief Find an item in the VTerm directory
268 * \param Node Root node
269 * \param Name Name (number) of the terminal
271 tVFS_Node *VT_FindDir(tVFS_Node *Node, const char *Name)
275 ENTER("pNode sName", Node, Name);
277 // Open the input and output files if needed
278 if(giVT_OutputDevHandle == -2) VT_InitOutput();
279 if(giVT_InputDevHandle == -2) VT_InitInput();
282 if(Name[0] < '0' || Name[0] > '9' || Name[1] != '\0') {
293 LEAVE('p', &gVT_Terminals[num].Node);
294 return &gVT_Terminals[num].Node;
298 * \fn int VT_Root_IOCtl(tVFS_Node *Node, int Id, void *Data)
299 * \brief Control the VTerm Driver
301 int VT_Root_IOCtl(tVFS_Node *Node, int Id, void *Data)
306 case DRV_IOCTL_TYPE: return DRV_TYPE_MISC;
307 case DRV_IOCTL_IDENT: memcpy(Data, "VT\0\0", 4); return 0;
308 case DRV_IOCTL_VERSION: return VERSION;
309 case DRV_IOCTL_LOOKUP: return 0;
311 case 4: // Get Video Driver
312 if(Data) strcpy(Data, gsVT_OutputDevice);
313 return strlen(gsVT_OutputDevice);
315 case 5: // Set Video Driver
316 if(!Data) return -EINVAL;
317 if(Threads_GetUID() != 0) return -EACCES;
321 // TODO: Check if the string used is a heap string
323 free(gsVT_OutputDevice);
325 gsVT_OutputDevice = malloc(len+1);
326 strcpy(gsVT_OutputDevice, Data);
328 VFS_Close(giVT_OutputDevHandle);
329 giVT_OutputDevHandle = -1;
338 * \brief Read from a virtual terminal
340 Uint64 VT_Read(tVFS_Node *Node, Uint64 Offset, Uint64 Length, void *Buffer)
344 tVTerm *term = &gVT_Terminals[ Node->Inode ];
345 Uint32 *codepoint_buf = Buffer;
346 Uint32 *codepoint_in;
348 Mutex_Acquire( &term->ReadingLock );
350 // Check current mode
355 VT_int_UpdateCursor(term, 1);
357 VFS_SelectNode(Node, VFS_SELECT_READ, NULL, "VT_Read (UTF-8)");
359 avail = term->InputWrite - term->InputRead;
361 avail += MAX_INPUT_CHARS8;
362 if(avail > Length - pos)
363 avail = Length - pos;
367 ((char*)Buffer)[pos] = term->InputBuffer[term->InputRead];
370 while(term->InputRead >= MAX_INPUT_CHARS8)
371 term->InputRead -= MAX_INPUT_CHARS8;
378 VFS_SelectNode(Node, VFS_SELECT_READ, NULL, "VT_Read (UCS-4)");
380 avail = term->InputWrite - term->InputRead;
382 avail += MAX_INPUT_CHARS32;
384 if(avail > Length - pos)
385 avail = Length - pos;
387 codepoint_in = (void*)term->InputBuffer;
388 codepoint_buf = Buffer;
392 codepoint_buf[pos] = codepoint_in[term->InputRead];
395 while(term->InputRead >= MAX_INPUT_CHARS32)
396 term->InputRead -= MAX_INPUT_CHARS32;
402 // Mark none avaliable if buffer empty
403 if( term->InputRead == term->InputWrite )
404 VFS_MarkAvaliable(&term->Node, 0);
406 term->ReadingThread = -1;
408 // VT_int_UpdateCursor(term, term->Mode == TERM_MODE_TEXT);
410 Mutex_Release( &term->ReadingLock );
416 * \fn Uint64 VT_Write(tVFS_Node *Node, Uint64 Offset, Uint64 Length, const void *Buffer)
417 * \brief Write to a virtual terminal
419 Uint64 VT_Write(tVFS_Node *Node, Uint64 Offset, Uint64 Length, const void *Buffer)
421 tVTerm *term = &gVT_Terminals[ Node->Inode ];
429 VT_int_PutString(term, Buffer, Length);
435 size = term->Width*term->Height*4;
436 if( Offset > size ) {
437 Log_Notice("VTerm", "VT_Write: Offset (0x%llx) > FBSize (0x%x)",
441 if( Offset + Length > size ) {
442 Log_Notice("VTerm", "VT_Write: Offset+Length (0x%llx) > FBSize (0x%x)",
443 Offset+Length, size);
444 Length = size - Offset;
447 // Update screen if needed
448 if( Node->Inode == giVT_CurrentTerminal )
450 if( giVT_RealHeight > term->Height )
451 Offset += (giVT_RealHeight - term->Height) / 2 * term->Width * 4;
452 // Handle undersized virtual terminals
453 if( giVT_RealWidth > term->Width )
455 // No? :( Well, just center it
458 // TODO: Fix to handle the final line correctly?
459 x = Offset/4; y = x / term->Width; x %= term->Width;
460 w = Length/4+x; h = w / term->Width; w %= term->Width;
463 x += (giVT_RealWidth - term->Width) / 2;
464 dst_ofs = (x + y * giVT_RealWidth) * 4;
467 VFS_WriteAt( giVT_OutputDevHandle,
472 Buffer = (void*)( (Uint)Buffer + term->Width*4 );
473 dst_ofs += giVT_RealWidth * 4;
479 return VFS_WriteAt( giVT_OutputDevHandle, Offset, Length, Buffer );
485 term->Buffer = malloc( term->Width * term->Height * 4 );
486 // Copy to the local cache
487 memcpy( (char*)term->Buffer + (Uint)Offset, Buffer, Length );
490 // Just pass on (for now)
491 // TODO: Handle locally too to ensure no information is lost on
492 // VT Switch (and to isolate terminals from each other)
493 case TERM_MODE_2DACCEL:
494 //case TERM_MODE_3DACCEL:
495 if( Node->Inode == giVT_CurrentTerminal )
497 VFS_Write( giVT_OutputDevHandle, Length, Buffer );
506 * \fn int VT_Terminal_IOCtl(tVFS_Node *Node, int Id, void *Data)
507 * \brief Call an IO Control on a virtual terminal
509 int VT_Terminal_IOCtl(tVFS_Node *Node, int Id, void *Data)
513 tVTerm *term = Node->ImplPtr;
514 ENTER("pNode iId pData", Node, Id, Data);
516 if(Id >= DRV_IOCTL_LOOKUP) {
517 // Only root can fiddle with graphics modes
518 // TODO: Remove this and replace with user ownership
519 if( Threads_GetUID() != 0 ) return -1;
526 LEAVE('i', DRV_TYPE_TERMINAL);
527 return DRV_TYPE_TERMINAL;
528 case DRV_IOCTL_IDENT:
529 memcpy(Data, "VT\0\0", 4);
532 case DRV_IOCTL_VERSION:
535 case DRV_IOCTL_LOOKUP:
539 // Get/Set the mode (and apply any changes)
540 case TERM_IOCTL_MODETYPE:
543 if( CheckMem(Data, sizeof(int)) == 0 ) {
547 Log_Log("VTerm", "VTerm %i mode set to %i", (int)Node->Inode, *iData);
549 // Update mode if needed
550 if( term->Mode != *iData || term->NewWidth || term->NewHeight)
552 // Adjust for text mode
553 if( *iData == TERM_MODE_TEXT ) {
554 term->NewHeight *= giVT_CharHeight;
555 term->NewWidth *= giVT_CharWidth;
557 // Fill unchanged dimensions
558 if(term->NewHeight == 0) term->NewHeight = term->Height;
559 if(term->NewWidth == 0) term->NewWidth = term->Width;
561 VT_int_ChangeMode(term, *iData, term->NewWidth, term->NewHeight);
562 // Clear unapplied dimensions
567 // Update the screen dimensions
568 if(Node->Inode == giVT_CurrentTerminal)
569 VT_SetTerminal( giVT_CurrentTerminal );
571 LEAVE('i', term->Mode);
574 // Get/set the terminal width
575 case TERM_IOCTL_WIDTH:
577 if( CheckMem(Data, sizeof(int)) == 0 ) {
581 term->NewWidth = *iData;
584 ret = term->NewWidth;
585 else if( term->Mode == TERM_MODE_TEXT )
586 ret = term->TextWidth;
592 // Get/set the terminal height
593 case TERM_IOCTL_HEIGHT:
595 if( CheckMem(Data, sizeof(int)) == 0 ) {
599 term->NewHeight = *iData;
601 if( term->NewHeight )
602 ret = term->NewHeight;
603 else if( term->Mode == TERM_MODE_TEXT )
604 ret = term->TextHeight;
610 case TERM_IOCTL_FORCESHOW:
611 Log_Log("VTerm", "Thread %i forced VTerm %i to be shown",
612 Threads_GetTID(), (int)Node->Inode);
613 VT_SetTerminal( Node->Inode );
617 case TERM_IOCTL_GETSETCURSOR:
620 tVideo_IOCtl_Pos *pos = Data;
621 if( !CheckMem(Data, sizeof(*pos)) ) {
627 if( term->Mode == TERM_MODE_TEXT )
629 if(term->Flags & VT_FLAG_ALTBUF)
630 term->AltWritePos = pos->x + pos->y * term->TextWidth;
632 term->WritePos = pos->x + pos->y * term->TextWidth + term->ViewPos;
633 VT_int_UpdateCursor(term, 0);
637 term->VideoCursorX = pos->x;
638 term->VideoCursorY = pos->y;
639 VT_int_UpdateCursor(term, 1);
642 ret = (term->Flags & VT_FLAG_ALTBUF) ? term->AltWritePos : term->WritePos-term->ViewPos;
646 case TERM_IOCTL_SETCURSORBITMAP: {
647 tVideo_IOCtl_Bitmap *bmp = Data;
650 free( term->VideoCursor );
651 term->VideoCursor = NULL;
656 // Sanity check bitmap
657 if( !CheckMem(bmp, sizeof(tVideo_IOCtl_Bitmap)) ) {
658 Log_Notice("VTerm", "%p in TERM_IOCTL_SETCURSORBITMAP invalid", bmp);
662 if( !CheckMem(bmp->Data, bmp->W*bmp->H*sizeof(Uint32)) ) {
663 Log_Notice("VTerm", "%p in TERM_IOCTL_SETCURSORBITMAP invalid", bmp);
668 // Reallocate if needed
669 if(term->VideoCursor)
671 if(bmp->W * bmp->H != term->VideoCursor->W * term->VideoCursor->H) {
672 free(term->VideoCursor);
673 term->VideoCursor = NULL;
676 if(!term->VideoCursor) {
677 term->VideoCursor = malloc(sizeof(tVideo_IOCtl_Pos) + bmp->W*bmp->H*sizeof(Uint32));
678 if(!term->VideoCursor) {
679 Log_Error("VTerm", "Unable to allocate memory for cursor");
685 memcpy(term->VideoCursor, bmp, sizeof(tVideo_IOCtl_Pos) + bmp->W*bmp->H*sizeof(Uint32));
687 Log_Debug("VTerm", "Set VT%i's cursor to %p %ix%i",
688 (int)term->Node.Inode, bmp, bmp->W, bmp->H);
690 if(gpVT_CurTerm == term)
691 VFS_IOCtl(giVT_OutputDevHandle, VIDEO_IOCTL_SETCURSORBITMAP, term->VideoCursor);
701 * \fn void VT_SetTerminal(int ID)
702 * \brief Set the current terminal
704 void VT_SetTerminal(int ID)
706 // Copy the screen state
707 if( ID != giVT_CurrentTerminal && gpVT_CurTerm->Mode != TERM_MODE_TEXT )
709 if( !gpVT_CurTerm->Buffer )
710 gpVT_CurTerm->Buffer = malloc( gpVT_CurTerm->Width*gpVT_CurTerm->Height*4 );
711 if( gpVT_CurTerm->Width < giVT_RealWidth )
715 Uint32 *dest = gpVT_CurTerm->Buffer;
716 // Slower scanline copy
717 for( line = 0; line < gpVT_CurTerm->Height; line ++ )
719 VFS_ReadAt(giVT_OutputDevHandle, ofs, gpVT_CurTerm->Width*4, dest);
720 ofs += giVT_RealWidth * 4;
721 dest += gpVT_CurTerm->Width;
726 VFS_ReadAt(giVT_OutputDevHandle,
727 0, gpVT_CurTerm->Height*giVT_RealWidth*4,
733 // Update current terminal ID
734 Log_Log("VTerm", "Changed terminal from %i to %i", giVT_CurrentTerminal, ID);
735 giVT_CurrentTerminal = ID;
736 gpVT_CurTerm = &gVT_Terminals[ID];
738 if( gpVT_CurTerm->Mode == TERM_MODE_TEXT )
740 VT_SetMode( VIDEO_BUFFMT_TEXT );
744 // Update the cursor image
745 if(gpVT_CurTerm->VideoCursor)
746 VFS_IOCtl(giVT_OutputDevHandle, VIDEO_IOCTL_SETCURSORBITMAP, gpVT_CurTerm->VideoCursor);
747 VT_SetMode( VIDEO_BUFFMT_FRAMEBUFFER );
750 if(gpVT_CurTerm->Buffer)
752 // TODO: Handle non equal sized
754 giVT_OutputDevHandle,
756 gpVT_CurTerm->Width*gpVT_CurTerm->Height*sizeof(Uint32),
761 VT_int_UpdateCursor(gpVT_CurTerm, 1);
763 VT_int_UpdateScreen(gpVT_CurTerm, 1);