193 lines
11 KiB
C++
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;}
|