// 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 #include #include #include #include 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;ilastSeq=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>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;jprevIds[j]<0)continue; if(slotprevIds[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(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;}