Files
UniXNU/drivers/NativeHidInput/NativeHidInput.cpp
T
2026-10-19 18:49:31 +08:00

193 lines
11 KiB
C++

// SPDX-License-Identifier: BSD-2-Clause-Patent
// Explicit offline keyboard/consumer input source. Uses the unchanged original
// HID parser and event dispatch; no user-client entitlement is substituted.
#include "OriginalHidDeviceABI.h"
#include <IOKit/IOBufferMemoryDescriptor.h>
#include <IOKit/IOWorkLoop.h>
#include <IOKit/IOTimerEventSource.h>
#include <libkern/c++/OSString.h>
#include <libkern/c++/OSIterator.h>
extern "C" void native_hid_input_observed(unsigned,int);
extern "C" void native_hid_live_checkpoint(IOService *);
extern "C" bool native_hid_input_requested(void);
// Optional touch digitizer fed by the board touch probe (kits/sheng/TouchProbe.cpp): finger set published with a seqlock.
extern "C" {
extern volatile uint32_t native_touch_seq, native_touch_count, native_touch_hid_reports;
extern volatile int native_touch_xs[12], native_touch_ys[12], native_touch_ids[12];
}
// Keyboard Application collection ensures the original generic keyboard
// personality can match. Report 2 carries Consumer Menu, not a claimed result
// that the system accepted Home. Report 1 is an ordinary boot keyboard report.
static const unsigned char descriptor[]={
0x05,0x01,0x09,0x06,0xa1,0x01,0x85,0x01,
0x05,0x07,0x19,0xe0,0x29,0xe7,0x15,0x00,0x25,0x01,
0x75,0x01,0x95,0x08,0x81,0x02,
0x75,0x08,0x95,0x01,0x81,0x03,
0x19,0x00,0x29,0x65,0x15,0x00,0x25,0x65,
0x75,0x08,0x95,0x06,0x81,0x00,
0x85,0x02,0x05,0x0c,0x09,0x40,0x15,0x00,0x25,0x01,
0x75,0x01,0x95,0x01,0x81,0x02,
0x75,0x07,0x95,0x01,0x81,0x03,0xc0
};
// Report 3: ten-finger Touch Screen collection (Tip Switch, Untouch (set when the tip is up), Contact Identifier, X, Y per finger, then Contact Count).
// Coordinates are normalised to 0..32767 on both axes by the producer.
#define FINGER_DESC \
0x09,0x22,0xa1,0x02, 0x09,0x42,0x15,0x00,0x25,0x01,0x75,0x01,0x95,0x01,0x81,0x02, 0x09,0x47,0x81,0x02, 0x75,0x06,0x95,0x01,0x81,0x03, \
0x09,0x51,0x75,0x08,0x95,0x01,0x15,0x00,0x26,0xff,0x00,0x81,0x02, \
0x05,0x01,0x09,0x30,0x15,0x00,0x26,0xff,0x7f,0x75,0x10,0x95,0x01,0x81,0x02, 0x09,0x31,0x81,0x02, 0x05,0x0d, 0xc0,
static const unsigned char touchDescriptor[]={
0x05,0x0d,0x09,0x04,0xa1,0x01,0x85,0x03,
FINGER_DESC FINGER_DESC FINGER_DESC FINGER_DESC FINGER_DESC
FINGER_DESC FINGER_DESC FINGER_DESC FINGER_DESC FINGER_DESC
0x09,0x54,0x75,0x08,0x95,0x01,0x15,0x00,0x26,0xff,0x00,0x81,0x02,0xc0
};
#define TOUCH_FINGERS 10
class NativeHidInput : public IOHIDDevice {
OSDeclareDefaultStructors(NativeHidInput)
IOWorkLoop *inputLoop=nullptr;
IOTimerEventSource *timer=nullptr;
IOTimerEventSource *touchTimer=nullptr;
bool down=false;
bool touchEnabled=false;
// The digitizer is a separate service from the keyboard: backboardd matches digitizers by primary usage page/usage
// (Digitizer/Touch Screen) and the Built-In flag, which a combined keyboard-first descriptor would not provide.
bool touchOnly=false;
bool eventDriverFound=false;
uint32_t lastSeq=0;
// Fingers sent as down in the previous report; a vanished identifier gets one explicit tip-up report.
int prevIds[TOUCH_FINGERS]={-1,-1,-1,-1,-1,-1,-1,-1,-1,-1};
int prevX[TOUCH_FINGERS]={},prevY[TOUCH_FINGERS]={};
static unsigned transformCoordinate(int value,int maximum) {
if(value<0)value=0;if(value>=maximum)value=maximum-1;
return (unsigned)((long long)value*32767/(maximum-1));
}
static void touchTick(OSObject *owner,IOTimerEventSource *source) {
NativeHidInput *self=OSDynamicCast(NativeHidInput,owner);if(!self)return;
source->setTimeoutMS(8);
if(!self->hasEventDriverCached())return;
uint32_t before=native_touch_seq;
if((before&1) || before==self->lastSeq)return;
__asm__ volatile("dmb ish":::"memory");
unsigned count=native_touch_count;if(count>TOUCH_FINGERS)count=TOUCH_FINGERS;
int ids[TOUCH_FINGERS],xs[TOUCH_FINGERS],ys[TOUCH_FINGERS];
for(unsigned i=0;i<count;++i){ids[i]=native_touch_ids[i];xs[i]=native_touch_xs[i];ys[i]=native_touch_ys[i];}
__asm__ volatile("dmb ish":::"memory");
if(native_touch_seq!=before)return; // torn read: retry on the next tick
self->lastSeq=before;
// Sizes follow the axis maxima of the touch core: 30480 long, 20320 short, mapped to the same 0..32767 range.
unsigned char report[1+TOUCH_FINGERS*6+1]={3};
unsigned slot=0;
for(unsigned i=0;i<count && slot<TOUCH_FINGERS;++i,++slot) {
unsigned x=transformCoordinate(xs[i],30480),y=transformCoordinate(ys[i],20320);
unsigned char *f=report+1+slot*6;
f[0]=1;f[1]=(unsigned char)(ids[i]&0xff);f[2]=(unsigned char)x;f[3]=(unsigned char)(x>>8);f[4]=(unsigned char)y;f[5]=(unsigned char)(y>>8);
self->prevIds[slot]=ids[i];self->prevX[slot]=(int)x;self->prevY[slot]=(int)y;
}
unsigned downCount=slot;
// Identifiers that were down and are gone: tip up at the last known position.
for(unsigned j=downCount;j<TOUCH_FINGERS;++j) {
if(self->prevIds[j]<0)continue;
if(slot<TOUCH_FINGERS) {
unsigned char *f=report+1+slot*6;
f[0]=2;f[1]=(unsigned char)(self->prevIds[j]&0xff);
f[2]=(unsigned char)self->prevX[j];f[3]=(unsigned char)(self->prevX[j]>>8);
f[4]=(unsigned char)self->prevY[j];f[5]=(unsigned char)(self->prevY[j]>>8);
++slot;
}
self->prevIds[j]=-1;
}
if(!slot)return;
report[1+TOUCH_FINGERS*6]=(unsigned char)slot;
IOBufferMemoryDescriptor *buffer=IOBufferMemoryDescriptor::withBytes(report,sizeof(report),kIODirectionNone);
IOReturn result=buffer?self->handleReport(buffer,0,0):kIOReturnNoMemory;
OSSafeReleaseNULL(buffer);
native_hid_input_observed(6,result);
native_touch_hid_reports=native_touch_hid_reports+1;
}
bool hasEventDriverCached() {
if(!eventDriverFound)eventDriverFound=hasEventDriver(this,4);
return eventDriverFound;
}
static bool hasEventDriver(IORegistryEntry *parent,unsigned depth) {
if(!depth)return false;
OSIterator *it=parent->getChildIterator(gIOServicePlane);if(!it)return false;
bool found=false;
while(OSObject *child=it->getNextObject()) {
IORegistryEntry *entry=OSDynamicCast(IORegistryEntry,child);
if(entry && (entry->metaCast("IOHIDEventDriver") || hasEventDriver(entry,depth-1))) {found=true;break;}
}
it->release();return found;
}
static void tick(OSObject *owner,IOTimerEventSource *source) {
NativeHidInput *self=OSDynamicCast(NativeHidInput,owner);if(!self)return;
native_hid_live_checkpoint(self);
if(self->touchOnly){source->setTimeoutMS(1000);return;}
if(!self->down && !hasEventDriver(self,4)) {
native_hid_input_observed(3,0);
source->setTimeoutMS(1000);
return;
}
native_hid_input_observed(3,1);
if(!self->down && !native_hid_input_requested()) {source->setTimeoutMS(1000);return;}
unsigned char report[]={2,static_cast<unsigned char>(self->down?0:1)};
IOBufferMemoryDescriptor *buffer=IOBufferMemoryDescriptor::withBytes(report,sizeof(report),kIODirectionNone);
IOReturn result=buffer?self->handleReport(buffer,0,0):kIOReturnNoMemory;
OSSafeReleaseNULL(buffer);
native_hid_input_observed(self->down?5:4,result);
// Always attempt release, even if down returned an error: input must
// never be left pressed by an ambiguous report completion.
if(!self->down) {self->down=true;source->setTimeoutMS(100);}
else {self->down=false;source->setTimeoutMS(1000);}
}
public:
void setTouchOnly() {touchOnly=true;}
IOWorkLoop *getWorkLoop() const override {return inputLoop;}
OSString *newTransportString() const override {return OSString::withCString(touchOnly?"SPI":"Virtual");}
OSString *newProductString() const override {return OSString::withCString(touchOnly?"Offline touch digitizer":"Offline HID input probe");}
IOReturn newReportDescriptor(IOMemoryDescriptor **out) const override {
if(!out)return kIOReturnBadArgument;
unsigned char combined[sizeof(descriptor)+sizeof(touchDescriptor)];
size_t size;
if(touchOnly){size=sizeof(touchDescriptor);memcpy(combined,touchDescriptor,size);}
else{
size=sizeof(descriptor);memcpy(combined,descriptor,size);
// Default: one device, keyboard collection first, digitizer second (the arrangement whose event driver
// and user client attach on hardware); the separate digitizer device is the opt-in sheng-touch-split.
if(touchEnabled){memcpy(combined+size,touchDescriptor,sizeof(touchDescriptor));size+=sizeof(touchDescriptor);}
}
*out=IOBufferMemoryDescriptor::withBytes(combined,size,kIODirectionNone);
return *out?kIOReturnSuccess:kIOReturnNoMemory;
}
bool start(IOService *provider) override {
inputLoop=IOWorkLoop::workLoop();if(!inputLoop)return false;
// Request the normal generic HID event personality for this owned
// keyboard. This is a matching property, not a client entitlement.
if(!setProperty("HIDDefaultBehavior",true))return false;
unsigned touch=0;
touchEnabled=PE_parse_boot_argn("sheng-touch-hid",&touch,sizeof(touch)) && touch==1;
if(touchOnly||touchEnabled){setProperty("Built-In",true);setProperty("DisplayIntegrated",true);}
if(!IOHIDDevice::start(provider))return false;
timer=IOTimerEventSource::timerEventSource(this,tick);
if(!timer || inputLoop->addEventSource(timer)!=kIOReturnSuccess) {
OSSafeReleaseNULL(timer);IOHIDDevice::stop(provider);return false;
}
if(touchOnly||touchEnabled) {
touchTimer=IOTimerEventSource::timerEventSource(this,touchTick);
if(touchTimer && inputLoop->addEventSource(touchTimer)==kIOReturnSuccess)touchTimer->setTimeoutMS(1000);
else OSSafeReleaseNULL(touchTimer);
}
setProperty("OfflineInputProbe",true);
native_hid_input_observed(2,0);
timer->setTimeoutMS(1000);return true;
}
void stop(IOService *provider) override {
if(touchTimer){touchTimer->cancelTimeout();inputLoop->removeEventSource(touchTimer);OSSafeReleaseNULL(touchTimer);}
if(timer){timer->cancelTimeout();inputLoop->removeEventSource(timer);OSSafeReleaseNULL(timer);}
IOHIDDevice::stop(provider);
}
void free() override {OSSafeReleaseNULL(inputLoop);IOHIDDevice::free();}
};
OSDefineMetaClassAndStructors(NativeHidInput,IOHIDDevice)
extern "C" IOService *native_hid_input_allocate(void) {return new NativeHidInput;}
extern "C" IOService *native_hid_touch_allocate(void) {NativeHidInput *device=new NativeHidInput;if(device)device->setTouchOnly();return device;}