]> git.proxmox.com Git - mirror_edk2.git/blobdiff - StdLib/LibC/Uefi/Devices/Console/daConsole.c
StdLib: Add terminal type line editing (Interactive IO) for console devices.
[mirror_edk2.git] / StdLib / LibC / Uefi / Devices / Console / daConsole.c
index 4897a2e56e639476a7c041d5b686f6bdce2c4f24..927ec944eaeafa5d444322d5bc3cd72c82089209 100644 (file)
@@ -3,6 +3,13 @@
 \r
   Manipulates abstractions for stdin, stdout, stderr.\r
 \r
+  This device is a WIDE device and this driver returns WIDE\r
+  characters.  It this the responsibility of the caller to convert between\r
+  narrow and wide characters in order to perform the desired operations.\r
+\r
+  The devices status as a wide device is indicatd by _S_IWTTY being set in\r
+  f_iflags.\r
+\r
   Copyright (c) 2010 - 2012, Intel Corporation. All rights reserved.<BR>\r
   This program and the accompanying materials are licensed and made available under\r
   the terms and conditions of the BSD License that accompanies this distribution.\r
@@ -30,6 +37,7 @@
 #include  <unistd.h>\r
 #include  <kfile.h>\r
 #include  <Device/Device.h>\r
+#include  <Device/IIO.h>\r
 #include  <MainData.h>\r
 \r
 static const CHAR16* const\r
@@ -46,7 +54,7 @@ static const int stdioFlags[NUM_SPECIAL] = {
 static DeviceNode    *ConNode[NUM_SPECIAL];\r
 static ConInstance   *ConInstanceList;\r
 \r
-static wchar_t       *ConReadBuf;\r
+static cIIO          *IIO;\r
 \r
 /* Flags settable by Ioctl */\r
 static BOOLEAN        TtyCooked;\r
@@ -58,10 +66,10 @@ static BOOLEAN        TtyEcho;
     large enough to hold the converted results.  It is guaranteed\r
     that there will be fewer than n characters placed in dest.\r
 \r
-    @param  dest    WCS buffer to receive the converted string.\r
-    @param  buf     MBCS string to convert to WCS.\r
-    @param  n       Number of BYTES contained in buf.\r
-    @param  Cs      Pointer to the character state object for this stream\r
+    @param[out]     dest    WCS buffer to receive the converted string.\r
+    @param[in]      buf     MBCS string to convert to WCS.\r
+    @param[in]      n       Number of BYTES contained in buf.\r
+    @param[in,out]  Cs      Pointer to the character state object for this stream\r
 \r
     @return   The number of BYTES consumed from buf.\r
 **/\r
@@ -94,6 +102,13 @@ WideTtyCvt( CHAR16 *dest, const char *buf, ssize_t n, mbstate_t *Cs)
   return i;\r
 }\r
 \r
+/** Close an open file.\r
+\r
+    @param[in]  filp    Pointer to the file descriptor structure for this file.\r
+\r
+    @retval   0     The file has been successfully closed.\r
+    @retval   -1    filp does not point to a valid console descriptor.\r
+**/\r
 static\r
 int\r
 EFIAPI\r
@@ -106,13 +121,25 @@ da_ConClose(
   Stream = BASE_CR(filp->f_ops, ConInstance, Abstraction);\r
   // Quick check to see if Stream looks reasonable\r
   if(Stream->Cookie != CON_COOKIE) {    // Cookie == 'IoAb'\r
+    errno     = EINVAL;\r
     EFIerrno = RETURN_INVALID_PARAMETER;\r
     return -1;    // Looks like a bad File Descriptor pointer\r
   }\r
   gMD->StdIo[Stream->InstanceNum] = NULL;   // Mark the stream as closed\r
-  return RETURN_SUCCESS;\r
+  return 0;\r
 }\r
 \r
+/** Position the console cursor to the coordinates specified by Position.\r
+\r
+    @param[in]  filp      Pointer to the file descriptor structure for this file.\r
+    @param[in]  Position  A value containing the target X and Y coordinates.\r
+    @param[in]  whence    Ignored by the Console device.\r
+\r
+    @retval   Position    Success.  Returns a copy of the Position argument.\r
+    @retval   -1          filp is not associated with a valid console stream.\r
+    @retval   -1          This console stream is attached to stdin.\r
+    @retval   -1          The SetCursorPosition operation failed.\r
+**/\r
 static\r
 off_t\r
 EFIAPI\r
@@ -155,11 +182,14 @@ da_ConSeek(
 \r
 /* Write a NULL terminated WCS to the EFI console.\r
 \r
-  @param[in,out]  BufferSize  Number of bytes in Buffer.  Set to zero if\r
-                              the string couldn't be displayed.\r
+  NOTE: The UEFI Console is a wide device, _S_IWTTY, so characters received\r
+        by da_ConWrite are WIDE characters.  It is the responsibility of the\r
+        higher-level function(s) to perform any necessary conversions.\r
+\r
+    @param[in,out]  BufferSize  Number of characters in Buffer.\r
   @param[in]      Buffer      The WCS string to be displayed\r
 \r
-  @return   The number of BYTES written.  Because of MBCS, this may be more than number of characters.\r
+    @return   The number of Characters written.\r
 */\r
 static\r
 ssize_t\r
@@ -174,8 +204,10 @@ da_ConWrite(
   EFI_STATUS                          Status;\r
   EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL    *Proto;\r
   ConInstance                        *Stream;\r
-  ssize_t                             NumBytes;\r
+  ssize_t                             NumChar;\r
+  XY_OFFSET                          CursorPos;\r
 \r
+  NumChar = -1;\r
   Stream = BASE_CR(filp->f_ops, ConInstance, Abstraction);\r
   // Quick check to see if Stream looks reasonable\r
   if(Stream->Cookie != CON_COOKIE) {    // Cookie == 'IoAb'\r
@@ -190,35 +222,45 @@ da_ConWrite(
   // Everything is OK to do the write.\r
   Proto = (EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *)Stream->Dev;\r
 \r
-  // Convert string from MBCS to WCS and translate \n to \r\n.\r
-  NumBytes = WideTtyCvt(gMD->UString, (const char *)Buffer, (ssize_t)BufferSize, &Stream->CharState);\r
-  BufferSize = NumBytes;\r
+  Status = EFI_SUCCESS;\r
+  if(Position != NULL) {\r
+    CursorPos.Offset = *Position;\r
 \r
+    Status = Proto->SetCursorPosition(Proto,\r
+                                      (INTN)CursorPos.XYpos.Column,\r
+                                      (INTN)CursorPos.XYpos.Row);\r
 \r
+  }\r
+  if(!RETURN_ERROR(Status)) {\r
   // Send the Unicode buffer to the console\r
-  Status = Proto->OutputString( Proto, gMD->UString);\r
-  // Depending on status, update BufferSize and return\r
-  if(RETURN_ERROR(Status)) {\r
-    BufferSize = 0;     // We don't really know how many characters made it out\r
+    Status = Proto->OutputString( Proto, (CHAR16 *)Buffer);\r
   }\r
-  else {\r
-    //BufferSize = NumBytes;\r
-    Stream->NumWritten += NumBytes;\r
+\r
+  // Depending on status, update BufferSize and return\r
+  if(!RETURN_ERROR(Status)) {\r
+    //BufferSize = NumChar;\r
+    NumChar = BufferSize;\r
+    Stream->NumWritten += NumChar;\r
   }\r
   EFIerrno = Status;      // Make error reason available to caller\r
-  return BufferSize;\r
+  return NumChar;\r
 }\r
 \r
-/** Read characters from the console input device.\r
+/** Read a wide character from the console input device.\r
+\r
+  NOTE: The UEFI Console is a wide device, _S_IWTTY, so characters returned\r
+        by da_ConRead are WIDE characters.  It is the responsibility of the\r
+        higher-level function(s) to perform any necessary conversions.\r
 \r
-    @param[in,out]  filp          Pointer to file descriptor for this file.\r
-    @param[in,out]  offset        Ignored.\r
+    @param[in,out]  BufferSize  Number of characters in Buffer.\r
+    @param[in]      filp          Pointer to file descriptor for this file.\r
+    @param[in]      offset        Ignored.\r
     @param[in]      BufferSize    Buffer size, in bytes.\r
     @param[out]     Buffer        Buffer in which to place the read characters.\r
 \r
-    @return     Number of bytes actually placed into Buffer.\r
-\r
-    @todo       Handle encodings other than ASCII-7 and UEFI.\r
+    @retval    -1   An error has occurred.  Reason in errno and EFIerrno.\r
+    @retval    -1   No data is available.  errno is set to EAGAIN\r
+    @retval     1   One wide character has been placed in Buffer\r
 **/\r
 static\r
 ssize_t\r
@@ -232,84 +274,80 @@ da_ConRead(
 {\r
   EFI_SIMPLE_TEXT_INPUT_PROTOCOL   *Proto;\r
   ConInstance                      *Stream;\r
-  wchar_t                          *OutPtr;\r
-  EFI_INPUT_KEY                     Key;\r
-  UINTN                             NumChar;\r
-  UINTN                             Edex;\r
+  cIIO                              *Self;\r
+  EFI_INPUT_KEY                     Key = {0,0};\r
   EFI_STATUS                        Status = RETURN_SUCCESS;\r
-  UINTN                             i;\r
-  char                              EchoBuff[MB_CUR_MAX + 1];\r
-  int                               NumEcho;\r
+  UINTN                             Edex;\r
+  ssize_t                           NumRead;\r
+  int                               Flags;\r
+  wchar_t                           RetChar;   // Default to No Data\r
 \r
-  Stream = BASE_CR(filp->f_ops, ConInstance, Abstraction);\r
-  // Quick check to see if Stream looks reasonable\r
-  if(Stream->Cookie != CON_COOKIE) {    // Cookie == 'IoAb'\r
-    EFIerrno = RETURN_INVALID_PARAMETER;\r
-    return -1;    // Looks like a bad This pointer\r
-  }\r
-  if(Stream->InstanceNum != STDIN_FILENO) {\r
-    // Read only valid for stdin\r
-    EFIerrno = RETURN_UNSUPPORTED;\r
-    return -1;\r
+  NumRead = -1;\r
+  if(BufferSize < sizeof(wchar_t)) {\r
+    errno = EINVAL;     // Buffer is too small to hold one character\r
   }\r
-  // It looks like things are OK for trying to read\r
-  // We will accumulate *BufferSize characters or until we encounter\r
-  // an "activation" character.  Currently any control character.\r
+  else {\r
+    Self = (cIIO *)filp->devdata;\r
+  Stream = BASE_CR(filp->f_ops, ConInstance, Abstraction);\r
   Proto = (EFI_SIMPLE_TEXT_INPUT_PROTOCOL *)Stream->Dev;\r
-  OutPtr = ConReadBuf;\r
-  NumChar = (BufferSize > MAX_INPUT)? MAX_INPUT : BufferSize;\r
-  i = 0;\r
-  do {\r
+    Flags = filp->Oflags;\r
     if((Stream->UnGetKey.UnicodeChar == CHAR_NULL) && (Stream->UnGetKey.ScanCode == SCAN_NULL)) {\r
+      // No data pending in the Un-get buffer.  Get a char from the hardware.\r
+      if((Flags & O_NONBLOCK) == 0) {\r
+        // Read a byte in Blocking mode\r
       Status = gBS->WaitForEvent( 1, &Proto->WaitForKey, &Edex);\r
-      if(Status != RETURN_SUCCESS) {\r
-        break;\r
+        EFIerrno = Status;\r
+        if(Status != EFI_SUCCESS) {\r
+          errno = EINVAL;\r
       }\r
+        else {\r
       Status = Proto->ReadKeyStroke(Proto, &Key);\r
-      if(Status != RETURN_SUCCESS) {\r
-        break;\r
+          if(Status == EFI_SUCCESS) {\r
+            NumRead = 1;   // Indicate that Key holds the data\r
+          }\r
+          else {\r
+            errno = EIO;\r
+          }\r
+        }\r
+      }\r
+      else {\r
+        // Read a byte in Non-Blocking mode\r
+      Status = Proto->ReadKeyStroke(Proto, &Key);\r
+        EFIerrno = Status;\r
+        if(Status == EFI_SUCCESS) {\r
+          // Got a keystroke.\r
+          NumRead = 1;   // Indicate that Key holds the data\r
+        }\r
+        else if(Status == EFI_NOT_READY) {\r
+          // Keystroke data is not available\r
+          errno = EAGAIN;\r
+        }\r
+        else {\r
+          // Hardware error\r
+          errno = EIO;\r
+        }\r
       }\r
     }\r
     else {\r
+      // Use the data in the Un-get buffer\r
       Key.ScanCode          = Stream->UnGetKey.ScanCode;\r
       Key.UnicodeChar       = Stream->UnGetKey.UnicodeChar;\r
       Stream->UnGetKey.ScanCode     = SCAN_NULL;\r
       Stream->UnGetKey.UnicodeChar  = CHAR_NULL;\r
+      NumRead = 1;   // Indicate that Key holds the data\r
     }\r
-    if(Key.ScanCode == SCAN_NULL) {\r
-      NumEcho = 0;\r
-      if(TtyCooked && (Key.UnicodeChar == CHAR_CARRIAGE_RETURN)) {\r
-        *OutPtr++ = CHAR_LINEFEED;\r
-        NumEcho = wctomb(EchoBuff, CHAR_LINEFEED);\r
-      }\r
-      else {\r
-        *OutPtr++ = Key.UnicodeChar;\r
-        NumEcho = wctomb(EchoBuff, Key.UnicodeChar);\r
+    // If we have data, prepare it for return.\r
+    if(NumRead == 1) {\r
+      RetChar = Key.UnicodeChar;\r
+      if((RetChar == 0) && ((Self->Termio.c_iflag & IGNSPEC) == 0)) {\r
+        // Must be a control, function, or other non-printable key.\r
+        // Map it into the Platform portion of the Unicode private use area\r
+        RetChar = (Key.ScanCode == 0) ? 0 : 0xF900U - Key.ScanCode;\r
       }\r
-      ++i;\r
-      EchoBuff[NumEcho] = 0;  /* Terminate the Echo buffer */\r
-      if(TtyEcho) {\r
-        /* Echo the character just input */\r
-        da_ConWrite(&gMD->fdarray[STDOUT_FILENO], NULL, 2, EchoBuff);\r
+      *((wchar_t *)Buffer) = RetChar;\r
       }\r
     }\r
-    if(iswcntrl(Key.UnicodeChar)) {    // If a control character, or a scan code\r
-      break;\r
-    }\r
-  } while(i < NumChar);\r
-\r
-  *OutPtr = L'\0';    // Terminate the input buffer\r
-\r
-  /*  Convert the input buffer and place in Buffer.\r
-      If the fully converted input buffer won't fit, write what will and\r
-      leave the rest in ConReadBuf with ConReadLeft indicating how many\r
-      unconverted characters remain in ConReadBuf.\r
-  */\r
-  NumEcho = (int)wcstombs(Buffer, ConReadBuf, BufferSize);   /* Re-use NumEcho to hold number of bytes in Buffer */\r
-  /* More work needs to be done before locales other than C can be supported. */\r
-\r
-  EFIerrno = Status;\r
-  return (ssize_t)NumEcho;  // Will be 0 if we didn't get a key\r
+  return NumRead;\r
 }\r
 \r
 /** Console-specific helper function for the fstat() function.\r
@@ -320,6 +358,14 @@ da_ConRead(
     st_blksize    Set to 1 since this is a character device\r
 \r
     All other members of the stat structure are left unchanged.\r
+\r
+    @param[in]      filp          Pointer to file descriptor for this file.\r
+    @param[out]     Buffer        Pointer to a stat structure to receive the information.\r
+    @param[in,out]  Something     Ignored.\r
+\r
+    @retval   0   Successful completion.\r
+    @retval   -1  Either filp is not associated with a console stream, or\r
+                  Buffer is NULL.  errno is set to EINVAL.\r
 **/\r
 static\r
 int\r
@@ -343,6 +389,7 @@ da_ConStat(
   if ((Stream->Cookie != CON_COOKIE) ||    // Cookie == 'IoAb'\r
       (Buffer == NULL))\r
   {\r
+    errno     = EINVAL;\r
     EFIerrno = RETURN_INVALID_PARAMETER;\r
     return -1;\r
   }\r
@@ -378,6 +425,14 @@ da_ConStat(
   return 0;\r
 }\r
 \r
+/** Console-specific helper for the ioctl system call.\r
+\r
+    The console device does not directly participate in ioctl operations.\r
+    This function completes the device abstraction and returns an error value\r
+    to indicate that the function is not supported for this device.\r
+\r
+    @retval   -1    Function is not supported for this device.\r
+**/\r
 static\r
 int\r
 EFIAPI\r
@@ -387,10 +442,21 @@ da_ConIoctl(
   va_list             argp\r
   )\r
 {\r
-  return -EPERM;\r
+  errno   = ENODEV;\r
+  return  -1;\r
 }\r
 \r
 /** Open an abstract Console Device.\r
+\r
+    @param[in]    DevNode       Pointer to the Device control structure for this stream.\r
+    @param[in]    filp          Pointer to the new file control structure for this stream.\r
+    @param[in]    DevInstance   Not used for the console device.\r
+    @param[in]    Path          Not used for the console device.\r
+    @param[in]    MPath         Not used for the console device.\r
+\r
+    @retval   0   This console stream has been successfully opened.\r
+    @retval   -1  The DevNode or filp pointer is NULL.\r
+    @retval   -1  DevNode does not point to a valid console stream device.\r
 **/\r
 int\r
 EFIAPI\r
@@ -403,36 +469,57 @@ da_ConOpen(
   )\r
 {\r
   ConInstance                      *Stream;\r
+  UINT32          Instance;\r
+  int             RetVal = -1;\r
 \r
-  if((filp == NULL)           ||\r
-     (DevNode  == NULL))\r
+  if((filp    != NULL)    &&\r
+      (DevNode != NULL))\r
   {\r
-    EFIerrno = RETURN_INVALID_PARAMETER;\r
-    errno = EINVAL;\r
-    return -1;\r
-  }\r
   Stream = (ConInstance *)DevNode->InstanceList;\r
   // Quick check to see if Stream looks reasonable\r
-  if(Stream->Cookie != CON_COOKIE) {    // Cookie == 'IoAb'\r
+    if(Stream->Cookie == CON_COOKIE)\r
+    {\r
+      Instance = Stream->InstanceNum;\r
+      if(Instance < NUM_SPECIAL) {\r
+        gMD->StdIo[Instance] = Stream;\r
+        filp->f_iflags |= (_S_IFCHR | _S_ITTY | _S_IWTTY | _S_ICONSOLE);\r
+        filp->f_offset = 0;\r
+        filp->f_ops = &Stream->Abstraction;\r
+        filp->devdata = (void *)IIO;\r
+        RetVal = 0;\r
+      }\r
+    }\r
+  }\r
+  if (RetVal < 0) {\r
     EFIerrno = RETURN_INVALID_PARAMETER;\r
     errno = EINVAL;\r
-    return -1;    // Looks like a bad This pointer\r
   }\r
-  gMD->StdIo[Stream->InstanceNum] = Stream;\r
-  filp->f_iflags |= (S_IFREG | _S_IFCHR | _S_ICONSOLE);\r
-  filp->f_offset = 0;\r
-  filp->f_ops = &Stream->Abstraction;\r
+  return RetVal;\r
 \r
-  return 0;\r
 }\r
 \r
 #include  <sys/poll.h>\r
 /*  Returns a bit mask describing which operations could be completed immediately.\r
 \r
+    Testable Events for this device are:\r
     (POLLIN | POLLRDNORM)   A Unicode character is available to read\r
     (POLLIN)                A ScanCode is ready.\r
     (POLLOUT)               The device is ready for output - always set on stdout and stderr.\r
 \r
+    Non-testable Events which are only valid in return values are:\r
+      POLLERR                 The specified device is not one of stdin, stdout, or stderr.\r
+      POLLHUP                 The specified stream has been disconnected\r
+      POLLNVAL                da_ConPoll was called with an invalid parameter.\r
+\r
+  NOTE: The "Events" handled by this function are not UEFI events.\r
+\r
+    @param[in]  filp      Pointer to the file control structure for this stream.\r
+    @param[in]  events    A bit mask identifying the events to be examined\r
+                          for this device.\r
+\r
+    @return   Returns a bit mask comprised of both testable and non-testable\r
+              event codes indicating both the state of the operation and the\r
+              status of the device.\r
 */\r
 static\r
 short\r
@@ -450,6 +537,7 @@ da_ConPoll(
   Stream = BASE_CR(filp->f_ops, ConInstance, Abstraction);\r
   // Quick check to see if Stream looks reasonable\r
   if(Stream->Cookie != CON_COOKIE) {    // Cookie == 'IoAb'\r
+    errno     = EINVAL;\r
     EFIerrno = RETURN_INVALID_PARAMETER;\r
     return POLLNVAL;    // Looks like a bad filp pointer\r
   }\r
@@ -495,15 +583,18 @@ __Cons_construct(
 )\r
 {\r
   ConInstance    *Stream;\r
-  RETURN_STATUS   Status = RETURN_SUCCESS;\r
+  RETURN_STATUS   Status;\r
   int             i;\r
 \r
+  Status = RETURN_OUT_OF_RESOURCES;\r
   ConInstanceList = (ConInstance *)AllocateZeroPool(NUM_SPECIAL * sizeof(ConInstance));\r
-  ConReadBuf      = (wchar_t *)AllocateZeroPool((MAX_INPUT + 1) * sizeof(wchar_t));\r
-  if((ConInstanceList == NULL) || (ConReadBuf == NULL))  {\r
-    return RETURN_OUT_OF_RESOURCES;\r
+  if(ConInstanceList != NULL) {\r
+    IIO = New_cIIO();\r
+    if(IIO == NULL) {\r
+      FreePool(ConInstanceList);\r
   }\r
-\r
+    else {\r
+      Status = RETURN_SUCCESS;\r
   for( i = 0; i < NUM_SPECIAL; ++i) {\r
     // Get pointer to instance.\r
     Stream = &ConInstanceList[i];\r
@@ -553,9 +644,10 @@ __Cons_construct(
     if(Stream->Dev == NULL) {\r
       continue;                 // No device for this stream.\r
     }\r
-    ConNode[i] = __DevRegister(stdioNames[i], NULL, &da_ConOpen, Stream, 1, sizeof(ConInstance), stdioFlags[i]);\r
+        ConNode[i] = __DevRegister(stdioNames[i], NULL, &da_ConOpen, Stream,\r
+                                   1, sizeof(ConInstance), stdioFlags[i]);\r
     if(ConNode[i] == NULL) {\r
-      Status = EFIerrno;\r
+          Status = EFIerrno;    // Grab error code that DevRegister produced.\r
       break;\r
     }\r
     Stream->Parent = ConNode[i];\r
@@ -563,7 +655,8 @@ __Cons_construct(
   /* Initialize Ioctl flags until Ioctl is really implemented. */\r
   TtyCooked = TRUE;\r
   TtyEcho   = TRUE;\r
-\r
+    }\r
+  }\r
   return  Status;\r
 }\r
 \r
@@ -584,15 +677,16 @@ __Cons_deconstruct(
   if(ConInstanceList != NULL) {\r
     FreePool(ConInstanceList);\r
   }\r
-  if(ConReadBuf != NULL) {\r
-    FreePool(ConReadBuf);\r
+  if(IIO != NULL) {\r
+    IIO->Delete(IIO);\r
+    IIO = NULL;\r
   }\r
 \r
   return RETURN_SUCCESS;\r
 }\r
 \r
 /* ######################################################################### */\r
-#if 0 /* Not implemented for Console */\r
+#if 0 /* Not implemented (yet?) for Console */\r
 \r
 static\r
 int\r