OpenBMC开发之obmc-ikmv键鼠功能

OpenBMC开发之obmc-ikvm服务介绍

1. 初始化步骤

  • obmc-ikvm.cpp执行main()函数,创建ikvm::Manager实例并执行manager.run()
  • ikvm_manager.hpp中创建Input input实例,并在Manager析构函数中初始化input(args.getKeyboardPath(), args.getPointerPath(), args.getUdcName())
  • 创建Input实例时,析构函数中打开键鼠UDC设备文件:hidUdcPath = “/sys/kernel/config/usb_gadget/obmc_hid/UDC”
/* @brief Handle of the HID gadget UDC */
std::ofstream hidUdcStream;

Input::Input(const std::string& kbdPath, const std::string& ptrPath,
             const std::string& udc) :
    keyboardFd(-1), pointerFd(-1), keyboardReport{0}, pointerReport{0},
    keyboardPath(kbdPath), pointerPath(ptrPath), udcName(udc)
{
    hidUdcStream.exceptions(std::ofstream::failbit | std::ofstream::badbit);
    hidUdcStream.open(hidUdcPath, std::ios::out | std::ios::app);
}
  • 当没有任何KVM会话的时候,键鼠HID链路是断开的。

  • 只有当第一个KVM客户端打开时,也就是server->numClients = 0时,才进行HID链路的建联,调用 server->input.connect()函数进行USBGadget配置,同时,server->frameCounter = 0。

enum rfbNewClientAction Server::newClient(rfbClientPtr cl)
{
  Server* server = (Server*)cl->screen->screenData;
  cl->clientData =new ClientData(server->video.getFrameRate(), &server->input);
  cl->clientGoneHook = clientGone;
  cl->clientFramebufferUpdateRequestHook = clientFramebufferUpdateRequest;
  if (!server->numClients++)
  {
      server->input.connect();
      server->pendingResize = false;  
      server->frameCounter = 0;
  }
  return RFB_CLIENT_ACCEPT;
}
  • Input::connect()函数主要功能为获取USB端口号ID,写入hidUdcStream文件,以此配置键鼠的USBGadget
  • 如果入参udcName指定,则直接写入: hidUdcStream << udcName << std::endl;
  • 如果入参为空,则自动从路径/sys/bus/platform/devices/1e6a0000.usb-vhub中获取,获取规则为/sys/bus/platform/devices/1e6a0000.usb-vhub/1e6a0000.usb-vhub:pX/目录下包含gadget文件,且不存在suspended文件
void Input::connect()
for (const auto& port : fs::directory_iterator(usbVirtualHubPath))
{
    // port=/sys/bus/platform/devices/1e6a0000.usb-vhub/1e6a0000.usb-vhub:pX
    // 确认该路径为目录,而非链接文件
    if (fs::is_directory(port) && !fs::is_symlink(port))
    {
        // 遍历/sys/bus/platform/devices/1e6a0000.usb-vhub/1e6a0000.usb-vhub:pX下的所有文件
        // gadget=/sys/bus/platform/devices/1e6a0000.usb-vhub/1e6a0000.usb-vhub:pX/gadget.Y
        for (const auto& gadget:fs::directory_iterator(port.path()))
        {
            // Kernel 6.0:
            // /sys/.../1e6a0000.usb-vhub:pX/gadget.Y/suspended
            // Kernel 5.15:
            // /sys/.../1e6a0000.usb-vhub:pX/gadget/suspended
            // 确认gadget路径为目录,而非链接文件,且路径中包含gadget,且不存在suspended文件
            if (fs::is_directory(gadget) &&gadget.path().string().find("gadget") !=std::string::npos &&!fs::exists(gadget.path() / "suspended"))
            {
                const std::string portId = port.path().filename();
                hidUdcStream << portId << std::endl;
                found = true;
                break;
            }
        
        }
	}
}
  • USBGadget配置后在设备终端的配置效果如下,UDC控制已成功赋值,相当于执行:echo “1e6a0000.usb-vhub:p6”>/sys/kernel/config/usb_gadget/obmc_hid/UDC
root@10020220507ABCD:/# cat /sys/kernel/config/usb_gadget/obmc_hid/UDC 
1e6a0000.usb-vhub:p6
root@10020220507ABCD:/# ls -l /sys/kernel/config/usb_gadget/obmc_hid/
-rw-r--r--    1 root     root          4096 Nov 17 14:17 UDC
-rw-r--r--    1 root     root          4096 Nov 19 09:05 bDeviceClass
-rw-r--r--    1 root     root          4096 Nov 19 09:05 bDeviceProtocol
-rw-r--r--    1 root     root          4096 Nov 19 09:05 bDeviceSubClass
-rw-r--r--    1 root     root          4096 Nov 19 09:05 bMaxPacketSize0
-rw-r--r--    1 root     root          4096 Nov 17 10:32 bcdDevice
-rw-r--r--    1 root     root          4096 Nov 17 10:32 bcdUSB
drwxr-xr-x    3 root     root             0 Nov 17 10:32 configs
drwxr-xr-x    4 root     root             0 Nov 17 10:32 functions
-rw-r--r--    1 root     root          4096 Nov 17 10:32 idProduct
-rw-r--r--    1 root     root          4096 Nov 17 10:32 idVendor
-rw-r--r--    1 root     root          4096 Nov 19 09:05 max_speed
drwxr-xr-x    2 root     root             0 Nov 17 10:32 os_desc
drwxr-xr-x    3 root     root             0 Nov 17 10:32 strings
  • void Input::connect()函数中USBGadget配置OK后,接着open键盘设备/dev/hidg0以及鼠标设备/dev/hidg1,获取对应描述符,用于键鼠的读写操作
if (!keyboardPath.empty())
{
    keyboardFd =
    open(keyboardPath.c_str(), O_RDWR | O_CLOEXEC | O_NONBLOCK);
    if (keyboardFd < 0)
    {
        log<level::ERR>("Failed to open input device",entry("PATH=%s", keyboardPath.c_str()),entry("ERROR=%s", strerror(errno)));
        elog<Open>(xyz::openbmc_project::Common::File::Open::ERRNO(errno),xyz::openbmc_project::Common::File::Open::PATH(
        keyboardPath.c_str()));
    }
}

if (!pointerPath.empty())
{
    pointerFd = open(pointerPath.c_str(), O_RDWR | O_CLOEXEC | O_NONBLOCK);
    if (pointerFd < 0)
    {
        log<level::ERR>("Failed to open input device",entry("PATH=%s", pointerPath.c_str()),entry("ERROR=%s", strerror(errno)));
        elog<Open>(xyz::openbmc_project::Common::File::Open::ERRNO(errno),xyz::openbmc_project::Common::File::Open::PATH(
        pointerPath.c_str()));
    }
}
  • 键鼠写操作API函数如下
/* @brief File descriptor for the USB keyboard device */
int keyboardFd;
/* @brief File descriptor for the USB mouse device */
int pointerFd;
/* @brief Data for keyboard report */
uint8_t keyboardReport[KEY_REPORT_LENGTH];
/* @brief Data for pointer report */
uint8_t pointerReport[PTR_REPORT_LENGTH];

2. 卸载步骤

  • 当关闭KVM会话时,会首先检测是不是最后一个会话,如果是,则执行server->input.disconnect()函数断开USB设备
void Server::clientGone(rfbClientPtr cl)
{
    Server* server = (Server*)cl->screen->screenData;

    delete (ClientData*)cl->clientData;
    cl->clientData = nullptr;
    //后置操作:numClients返回当前值判断是否==1,然后才递减,当最后一个会话时,执行断开操作
    if (server->numClients-- == 1)
    {
        server->input.disconnect();
        rfbMarkRectAsModified(server->server, 0, 0, server->video.getWidth(),
                              server->video.getHeight());
    }
}
  • 而Input::disconnect()函数中,通过将键鼠设备的UDC置空关闭USBGadget,相当于执行:echo “”>/sys/kernel/config/usb_gadget/obmc_hid/UDC
void Input::disconnect()
{
    if (keyboardFd >= 0)
    {
        close(keyboardFd);
        keyboardFd = -1;
    }
    if (pointerFd >= 0)
    {
        close(pointerFd);
        pointerFd = -1;
    }

    try
    {
        hidUdcStream << "" << std::endl; //
    }
    catch (std::ofstream::failure& e)
    {
        log<level::ERR>("Failed to disconnect HID gadget",entry("ERROR=%s", e.what()));
    }
}

3. 数据读写操作

  • 键盘和鼠标数据长度分别为8Bytes/6Bytes,对应的写操作函数分别在ikvm_input.cpp中
  • 两个写函数中分别引入了Retry机制,会尝试5次重写,间隔10ms
  • 实测中发现10ms并不是一个合适的值,当连续发送失败时,可能会导入KVM视频帧率下降,从而引起KVM画面卡顿问题
static constexpr int KEY_REPORT_LENGTH = 8;
static constexpr int PTR_REPORT_LENGTH = 6;
 static constexpr int HID_REPORT_RETRY_MAX = 5;

bool Input::writeKeyboard(const uint8_t* report)
void Input::writePointer(const uint8_t* report)
    
bool Input::writeKeyboard(const uint8_t* report)
{
    std::unique_lock<std::mutex> lk(keyMutex);
    uint retryCount = HID_REPORT_RETRY_MAX;

    while (retryCount > 0)
    {
        if (write(keyboardFd, report, KEY_REPORT_LENGTH) == KEY_REPORT_LENGTH)
        {
            return true;
        }

        if (errno != EAGAIN)
        {
            if (errno != ESHUTDOWN)
            {
                log<level::ERR>("Failed to write keyboard report",entry("ERROR=%s", strerror(errno)));
            }

            break;
        }

        lk.unlock();
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
        lk.lock();
        retryCount--;
    }

    return false;
}

4. 缺陷分析

  • 当前OpenBMC架构键鼠Write()重发超时时间是10ms,这会造成Bios界面快速晃动鼠标情况下,KVM帧率降低,界面卡顿问题;
  • 当前OpenBMC架构键鼠Write()操作比较暴力直接,尤其是定义Fd为O_NONBLOCK情况下,大量的数据冲击会造成Driver USB传输异常,从而引起键鼠无效;
  • 比较好的解决方法是:调整重发间隔,并增加发送前的Poll机制(驱动提供该定义,但obmc-ikvm未调用)
static __poll_t f_hidg_poll(struct file *file, poll_table *wait)
{
	struct f_hidg	*hidg  = file->private_data;
	__poll_t	ret = 0;

	poll_wait(file, &hidg->read_queue, wait);
	poll_wait(file, &hidg->write_queue, wait);

	if (WRITE_COND)
		ret |= EPOLLOUT | EPOLLWRNORM;

	if (hidg->use_out_ep) {
		if (READ_COND_INTOUT)
			ret |= EPOLLIN | EPOLLRDNORM;
	} else {
		if (READ_COND_SSREPORT)
			ret |= EPOLLIN | EPOLLRDNORM;
	}

	return ret;
}

bool Input::writeKeyboard()
{
    struct pollfd fds[1];
    
    fds[0] = keyboardFd;
    fds[1].events = POLLOUT;
    
    while(retryCount > 0)
    {
        ret = poll(fds, 1, 200)
        if(ret == 0)
        {
            log<level::ERR>("Write keyboard report timeout!");
		}
        else if(ret < 0)
        }
            log<level::ERR>("Write keyboard report error!");
		}  
		else if(fds[0].revents & POLLOUT)
        {
            if (write(keyboardFd, report, KEY_REPORT_LENGTH) == KEY_REPORT_LENGTH)
            {
                return true;
            }
            ...............
        }
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柠檬恋上雨

打赏助力,共创精彩博客内容!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值