123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239 |
- From e4addc12f76b5625d0a2c48459709b9be269f5df Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Fri, 12 Feb 2021 12:54:34 +0100
- Subject: [PATCH] platform/surface: Set up Surface Aggregator device registry
- The Surface System Aggregator Module (SSAM) subsystem provides various
- functionalities, which are separated by spreading them across multiple
- devices and corresponding drivers. Parts of that functionality / some of
- those devices, however, can (as far as we currently know) not be
- auto-detected by conventional means. While older (specifically 5th- and
- 6th-)generation models do advertise most of their functionality via
- standard platform devices in ACPI, newer generations do not.
- As we are currently also not aware of any feasible way to query said
- functionalities dynamically, this poses a problem. There is, however, a
- device in ACPI that seems to be used by Windows for identifying
- different Surface models: The Windows Surface Integration Device (WSID).
- This device seems to have a HID corresponding to the overall set of
- functionalities SSAM provides for the associated model.
- This commit introduces a registry providing non-detectable device
- information via software nodes. In addition, a SSAM platform hub driver
- is introduced, which takes care of creating and managing the SSAM
- devices specified in this registry. This approach allows for a
- hierarchical setup akin to ACPI and is easily extendable, e.g. via
- firmware node properties.
- Note that this commit only provides the basis for the platform hub and
- registry, and does not add any content to it. The registry will be
- expanded in subsequent commits.
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Link: https://lore.kernel.org/r/20210212115439.1525216-2-luzmaximilian@gmail.com
- Signed-off-by: Hans de Goede <hdegoede@redhat.com>
- Patchset: surface-sam
- ---
- MAINTAINERS | 1 +
- drivers/platform/surface/Kconfig | 27 ++
- drivers/platform/surface/Makefile | 1 +
- .../surface/surface_aggregator_registry.c | 284 ++++++++++++++++++
- 4 files changed, 313 insertions(+)
- create mode 100644 drivers/platform/surface/surface_aggregator_registry.c
- diff --git a/MAINTAINERS b/MAINTAINERS
- index 9450e052f1b1..f6c524630575 100644
- --- a/MAINTAINERS
- +++ b/MAINTAINERS
- @@ -11904,6 +11904,7 @@ F: Documentation/driver-api/surface_aggregator/
- F: drivers/platform/surface/aggregator/
- F: drivers/platform/surface/surface_acpi_notify.c
- F: drivers/platform/surface/surface_aggregator_cdev.c
- +F: drivers/platform/surface/surface_aggregator_registry.c
- F: include/linux/surface_acpi_notify.h
- F: include/linux/surface_aggregator/
- F: include/uapi/linux/surface_aggregator/
- diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig
- index fd45940ab6ce..c51c55204b5f 100644
- --- a/drivers/platform/surface/Kconfig
- +++ b/drivers/platform/surface/Kconfig
- @@ -77,6 +77,33 @@ config SURFACE_AGGREGATOR_CDEV
- The provided interface is intended for debugging and development only,
- and should not be used otherwise.
-
- +config SURFACE_AGGREGATOR_REGISTRY
- + tristate "Surface System Aggregator Module Device Registry"
- + depends on SURFACE_AGGREGATOR
- + depends on SURFACE_AGGREGATOR_BUS
- + help
- + Device-registry and device-hubs for Surface System Aggregator Module
- + (SSAM) devices.
- +
- + Provides a module and driver which act as a device-registry for SSAM
- + client devices that cannot be detected automatically, e.g. via ACPI.
- + Such devices are instead provided via this registry and attached via
- + device hubs, also provided in this module.
- +
- + Devices provided via this registry are:
- + - Platform profile (performance-/cooling-mode) device (5th- and later
- + generations).
- + - Battery/AC devices (7th-generation).
- + - HID input devices (7th-generation).
- +
- + Select M (recommended) or Y here if you want support for the above
- + mentioned devices on the corresponding Surface models. Without this
- + module, the respective devices will not be instantiated and thus any
- + functionality provided by them will be missing, even when drivers for
- + these devices are present. In other words, this module only provides
- + the respective client devices. Drivers for these devices still need to
- + be selected via the other options.
- +
- config SURFACE_BOOK1_DGPU_SWITCH
- tristate "Surface Book 1 dGPU Switch Driver"
- depends on SYSFS
- diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile
- index 6b69175598ab..ed12676f06e6 100644
- --- a/drivers/platform/surface/Makefile
- +++ b/drivers/platform/surface/Makefile
- @@ -10,6 +10,7 @@ obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o
- obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o
- obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/
- obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o
- +obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o
- obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o
- obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o
- obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o
- diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
- new file mode 100644
- index 000000000000..a051d941ad96
- --- /dev/null
- +++ b/drivers/platform/surface/surface_aggregator_registry.c
- @@ -0,0 +1,284 @@
- +// SPDX-License-Identifier: GPL-2.0+
- +/*
- + * Surface System Aggregator Module (SSAM) client device registry.
- + *
- + * Registry for non-platform/non-ACPI SSAM client devices, i.e. devices that
- + * cannot be auto-detected. Provides device-hubs and performs instantiation
- + * for these devices.
- + *
- + * Copyright (C) 2020-2021 Maximilian Luz <luzmaximilian@gmail.com>
- + */
- +
- +#include <linux/acpi.h>
- +#include <linux/kernel.h>
- +#include <linux/module.h>
- +#include <linux/platform_device.h>
- +#include <linux/property.h>
- +
- +#include <linux/surface_aggregator/controller.h>
- +#include <linux/surface_aggregator/device.h>
- +
- +
- +/* -- Device registry. ------------------------------------------------------ */
- +
- +/*
- + * SSAM device names follow the SSAM module alias, meaning they are prefixed
- + * with 'ssam:', followed by domain, category, target ID, instance ID, and
- + * function, each encoded as two-digit hexadecimal, separated by ':'. In other
- + * words, it follows the scheme
- + *
- + * ssam:dd:cc:tt:ii:ff
- + *
- + * Where, 'dd', 'cc', 'tt', 'ii', and 'ff' are the two-digit hexadecimal
- + * values mentioned above, respectively.
- + */
- +
- +/* Root node. */
- +static const struct software_node ssam_node_root = {
- + .name = "ssam_platform_hub",
- +};
- +
- +/* Devices for Surface Book 2. */
- +static const struct software_node *ssam_node_group_sb2[] = {
- + &ssam_node_root,
- + NULL,
- +};
- +
- +/* Devices for Surface Book 3. */
- +static const struct software_node *ssam_node_group_sb3[] = {
- + &ssam_node_root,
- + NULL,
- +};
- +
- +/* Devices for Surface Laptop 1. */
- +static const struct software_node *ssam_node_group_sl1[] = {
- + &ssam_node_root,
- + NULL,
- +};
- +
- +/* Devices for Surface Laptop 2. */
- +static const struct software_node *ssam_node_group_sl2[] = {
- + &ssam_node_root,
- + NULL,
- +};
- +
- +/* Devices for Surface Laptop 3. */
- +static const struct software_node *ssam_node_group_sl3[] = {
- + &ssam_node_root,
- + NULL,
- +};
- +
- +/* Devices for Surface Laptop Go. */
- +static const struct software_node *ssam_node_group_slg1[] = {
- + &ssam_node_root,
- + NULL,
- +};
- +
- +/* Devices for Surface Pro 5. */
- +static const struct software_node *ssam_node_group_sp5[] = {
- + &ssam_node_root,
- + NULL,
- +};
- +
- +/* Devices for Surface Pro 6. */
- +static const struct software_node *ssam_node_group_sp6[] = {
- + &ssam_node_root,
- + NULL,
- +};
- +
- +/* Devices for Surface Pro 7. */
- +static const struct software_node *ssam_node_group_sp7[] = {
- + &ssam_node_root,
- + NULL,
- +};
- +
- +
- +/* -- Device registry helper functions. ------------------------------------- */
- +
- +static int ssam_uid_from_string(const char *str, struct ssam_device_uid *uid)
- +{
- + u8 d, tc, tid, iid, fn;
- + int n;
- +
- + n = sscanf(str, "ssam:%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn);
- + if (n != 5)
- + return -EINVAL;
- +
- + uid->domain = d;
- + uid->category = tc;
- + uid->target = tid;
- + uid->instance = iid;
- + uid->function = fn;
- +
- + return 0;
- +}
- +
- +static int ssam_hub_remove_devices_fn(struct device *dev, void *data)
- +{
- + if (!is_ssam_device(dev))
- + return 0;
- +
- + ssam_device_remove(to_ssam_device(dev));
- + return 0;
- +}
- +
- +static void ssam_hub_remove_devices(struct device *parent)
- +{
- + device_for_each_child_reverse(parent, NULL, ssam_hub_remove_devices_fn);
- +}
- +
- +static int ssam_hub_add_device(struct device *parent, struct ssam_controller *ctrl,
- + struct fwnode_handle *node)
- +{
- + struct ssam_device_uid uid;
- + struct ssam_device *sdev;
- + int status;
- +
- + status = ssam_uid_from_string(fwnode_get_name(node), &uid);
- + if (status)
- + return status;
- +
- + sdev = ssam_device_alloc(ctrl, uid);
- + if (!sdev)
- + return -ENOMEM;
- +
- + sdev->dev.parent = parent;
- + sdev->dev.fwnode = node;
- +
- + status = ssam_device_add(sdev);
- + if (status)
- + ssam_device_put(sdev);
- +
- + return status;
- +}
- +
- +static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *ctrl,
- + struct fwnode_handle *node)
- +{
- + struct fwnode_handle *child;
- + int status;
- +
- + fwnode_for_each_child_node(node, child) {
- + /*
- + * Try to add the device specified in the firmware node. If
- + * this fails with -EINVAL, the node does not specify any SSAM
- + * device, so ignore it and continue with the next one.
- + */
- +
- + status = ssam_hub_add_device(parent, ctrl, child);
- + if (status && status != -EINVAL)
- + goto err;
- + }
- +
- + return 0;
- +err:
- + ssam_hub_remove_devices(parent);
- + return status;
- +}
- +
- +
- +/* -- SSAM platform/meta-hub driver. ---------------------------------------- */
- +
- +static const struct acpi_device_id ssam_platform_hub_match[] = {
- + /* Surface Pro 4, 5, and 6 (OMBR < 0x10) */
- + { "MSHW0081", (unsigned long)ssam_node_group_sp5 },
- +
- + /* Surface Pro 6 (OMBR >= 0x10) */
- + { "MSHW0111", (unsigned long)ssam_node_group_sp6 },
- +
- + /* Surface Pro 7 */
- + { "MSHW0116", (unsigned long)ssam_node_group_sp7 },
- +
- + /* Surface Book 2 */
- + { "MSHW0107", (unsigned long)ssam_node_group_sb2 },
- +
- + /* Surface Book 3 */
- + { "MSHW0117", (unsigned long)ssam_node_group_sb3 },
- +
- + /* Surface Laptop 1 */
- + { "MSHW0086", (unsigned long)ssam_node_group_sl1 },
- +
- + /* Surface Laptop 2 */
- + { "MSHW0112", (unsigned long)ssam_node_group_sl2 },
- +
- + /* Surface Laptop 3 (13", Intel) */
- + { "MSHW0114", (unsigned long)ssam_node_group_sl3 },
- +
- + /* Surface Laptop 3 (15", AMD) */
- + { "MSHW0110", (unsigned long)ssam_node_group_sl3 },
- +
- + /* Surface Laptop Go 1 */
- + { "MSHW0118", (unsigned long)ssam_node_group_slg1 },
- +
- + { },
- +};
- +MODULE_DEVICE_TABLE(acpi, ssam_platform_hub_match);
- +
- +static int ssam_platform_hub_probe(struct platform_device *pdev)
- +{
- + const struct software_node **nodes;
- + struct ssam_controller *ctrl;
- + struct fwnode_handle *root;
- + int status;
- +
- + nodes = (const struct software_node **)acpi_device_get_match_data(&pdev->dev);
- + if (!nodes)
- + return -ENODEV;
- +
- + /*
- + * As we're adding the SSAM client devices as children under this device
- + * and not the SSAM controller, we need to add a device link to the
- + * controller to ensure that we remove all of our devices before the
- + * controller is removed. This also guarantees proper ordering for
- + * suspend/resume of the devices on this hub.
- + */
- + ctrl = ssam_client_bind(&pdev->dev);
- + if (IS_ERR(ctrl))
- + return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl);
- +
- + status = software_node_register_node_group(nodes);
- + if (status)
- + return status;
- +
- + root = software_node_fwnode(&ssam_node_root);
- + if (!root) {
- + software_node_unregister_node_group(nodes);
- + return -ENOENT;
- + }
- +
- + set_secondary_fwnode(&pdev->dev, root);
- +
- + status = ssam_hub_add_devices(&pdev->dev, ctrl, root);
- + if (status) {
- + set_secondary_fwnode(&pdev->dev, NULL);
- + software_node_unregister_node_group(nodes);
- + }
- +
- + platform_set_drvdata(pdev, nodes);
- + return status;
- +}
- +
- +static int ssam_platform_hub_remove(struct platform_device *pdev)
- +{
- + const struct software_node **nodes = platform_get_drvdata(pdev);
- +
- + ssam_hub_remove_devices(&pdev->dev);
- + set_secondary_fwnode(&pdev->dev, NULL);
- + software_node_unregister_node_group(nodes);
- + return 0;
- +}
- +
- +static struct platform_driver ssam_platform_hub_driver = {
- + .probe = ssam_platform_hub_probe,
- + .remove = ssam_platform_hub_remove,
- + .driver = {
- + .name = "surface_aggregator_platform_hub",
- + .acpi_match_table = ssam_platform_hub_match,
- + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
- + },
- +};
- +module_platform_driver(ssam_platform_hub_driver);
- +
- +MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
- +MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module");
- +MODULE_LICENSE("GPL");
- --
- 2.32.0
- From c70ad8f475c65c0000e4d08acdc1861ff557fe01 Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Fri, 12 Feb 2021 12:54:35 +0100
- Subject: [PATCH] platform/surface: aggregator_registry: Add base device hub
- The Surface Book 3 has a detachable base part. While the top part
- (so-called clipboard) contains the CPU, touchscreen, and primary
- battery, the base contains, among other things, a keyboard, touchpad,
- and secondary battery.
- Those devices do not react well to being accessed when the base part is
- detached and should thus be removed and added in sync with the base. To
- facilitate this, we introduce a virtual base device hub, which
- automatically removes or adds the devices registered under it.
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Link: https://lore.kernel.org/r/20210212115439.1525216-3-luzmaximilian@gmail.com
- Signed-off-by: Hans de Goede <hdegoede@redhat.com>
- Patchset: surface-sam
- ---
- .../surface/surface_aggregator_registry.c | 261 +++++++++++++++++-
- 1 file changed, 260 insertions(+), 1 deletion(-)
- diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
- index a051d941ad96..6c23d75a044c 100644
- --- a/drivers/platform/surface/surface_aggregator_registry.c
- +++ b/drivers/platform/surface/surface_aggregator_registry.c
- @@ -11,9 +11,12 @@
-
- #include <linux/acpi.h>
- #include <linux/kernel.h>
- +#include <linux/limits.h>
- #include <linux/module.h>
- +#include <linux/mutex.h>
- #include <linux/platform_device.h>
- #include <linux/property.h>
- +#include <linux/types.h>
-
- #include <linux/surface_aggregator/controller.h>
- #include <linux/surface_aggregator/device.h>
- @@ -38,6 +41,12 @@ static const struct software_node ssam_node_root = {
- .name = "ssam_platform_hub",
- };
-
- +/* Base device hub (devices attached to Surface Book 3 base). */
- +static const struct software_node ssam_node_hub_base = {
- + .name = "ssam:00:00:02:00:00",
- + .parent = &ssam_node_root,
- +};
- +
- /* Devices for Surface Book 2. */
- static const struct software_node *ssam_node_group_sb2[] = {
- &ssam_node_root,
- @@ -47,6 +56,7 @@ static const struct software_node *ssam_node_group_sb2[] = {
- /* Devices for Surface Book 3. */
- static const struct software_node *ssam_node_group_sb3[] = {
- &ssam_node_root,
- + &ssam_node_hub_base,
- NULL,
- };
-
- @@ -177,6 +187,230 @@ static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *c
- }
-
-
- +/* -- SSAM base-hub driver. ------------------------------------------------- */
- +
- +enum ssam_base_hub_state {
- + SSAM_BASE_HUB_UNINITIALIZED,
- + SSAM_BASE_HUB_CONNECTED,
- + SSAM_BASE_HUB_DISCONNECTED,
- +};
- +
- +struct ssam_base_hub {
- + struct ssam_device *sdev;
- +
- + struct mutex lock; /* Guards state update checks and transitions. */
- + enum ssam_base_hub_state state;
- +
- + struct ssam_event_notifier notif;
- +};
- +
- +static SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, {
- + .target_category = SSAM_SSH_TC_BAS,
- + .target_id = 0x01,
- + .command_id = 0x0d,
- + .instance_id = 0x00,
- +});
- +
- +#define SSAM_BAS_OPMODE_TABLET 0x00
- +#define SSAM_EVENT_BAS_CID_CONNECTION 0x0c
- +
- +static int ssam_base_hub_query_state(struct ssam_base_hub *hub, enum ssam_base_hub_state *state)
- +{
- + u8 opmode;
- + int status;
- +
- + status = ssam_retry(ssam_bas_query_opmode, hub->sdev->ctrl, &opmode);
- + if (status < 0) {
- + dev_err(&hub->sdev->dev, "failed to query base state: %d\n", status);
- + return status;
- + }
- +
- + if (opmode != SSAM_BAS_OPMODE_TABLET)
- + *state = SSAM_BASE_HUB_CONNECTED;
- + else
- + *state = SSAM_BASE_HUB_DISCONNECTED;
- +
- + return 0;
- +}
- +
- +static ssize_t ssam_base_hub_state_show(struct device *dev, struct device_attribute *attr,
- + char *buf)
- +{
- + struct ssam_base_hub *hub = dev_get_drvdata(dev);
- + bool connected;
- +
- + mutex_lock(&hub->lock);
- + connected = hub->state == SSAM_BASE_HUB_CONNECTED;
- + mutex_unlock(&hub->lock);
- +
- + return sysfs_emit(buf, "%d\n", connected);
- +}
- +
- +static struct device_attribute ssam_base_hub_attr_state =
- + __ATTR(state, 0444, ssam_base_hub_state_show, NULL);
- +
- +static struct attribute *ssam_base_hub_attrs[] = {
- + &ssam_base_hub_attr_state.attr,
- + NULL,
- +};
- +
- +const struct attribute_group ssam_base_hub_group = {
- + .attrs = ssam_base_hub_attrs,
- +};
- +
- +static int __ssam_base_hub_update(struct ssam_base_hub *hub, enum ssam_base_hub_state new)
- +{
- + struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev);
- + int status = 0;
- +
- + lockdep_assert_held(&hub->lock);
- +
- + if (hub->state == new)
- + return 0;
- + hub->state = new;
- +
- + if (hub->state == SSAM_BASE_HUB_CONNECTED)
- + status = ssam_hub_add_devices(&hub->sdev->dev, hub->sdev->ctrl, node);
- + else
- + ssam_hub_remove_devices(&hub->sdev->dev);
- +
- + if (status)
- + dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status);
- +
- + return status;
- +}
- +
- +static int ssam_base_hub_update(struct ssam_base_hub *hub)
- +{
- + enum ssam_base_hub_state state;
- + int status;
- +
- + mutex_lock(&hub->lock);
- +
- + status = ssam_base_hub_query_state(hub, &state);
- + if (!status)
- + status = __ssam_base_hub_update(hub, state);
- +
- + mutex_unlock(&hub->lock);
- + return status;
- +}
- +
- +static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event)
- +{
- + struct ssam_base_hub *hub;
- + struct ssam_device *sdev;
- + enum ssam_base_hub_state new;
- +
- + hub = container_of(nf, struct ssam_base_hub, notif);
- + sdev = hub->sdev;
- +
- + if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION)
- + return 0;
- +
- + if (event->length < 1) {
- + dev_err(&sdev->dev, "unexpected payload size: %u\n",
- + event->length);
- + return 0;
- + }
- +
- + if (event->data[0])
- + new = SSAM_BASE_HUB_CONNECTED;
- + else
- + new = SSAM_BASE_HUB_DISCONNECTED;
- +
- + mutex_lock(&hub->lock);
- + __ssam_base_hub_update(hub, new);
- + mutex_unlock(&hub->lock);
- +
- + /*
- + * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and
- + * consumed by the detachment system driver. We're just a (more or less)
- + * silent observer.
- + */
- + return 0;
- +}
- +
- +static int __maybe_unused ssam_base_hub_resume(struct device *dev)
- +{
- + return ssam_base_hub_update(dev_get_drvdata(dev));
- +}
- +static SIMPLE_DEV_PM_OPS(ssam_base_hub_pm_ops, NULL, ssam_base_hub_resume);
- +
- +static int ssam_base_hub_probe(struct ssam_device *sdev)
- +{
- + struct ssam_base_hub *hub;
- + int status;
- +
- + hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL);
- + if (!hub)
- + return -ENOMEM;
- +
- + mutex_init(&hub->lock);
- +
- + hub->sdev = sdev;
- + hub->state = SSAM_BASE_HUB_UNINITIALIZED;
- +
- + hub->notif.base.priority = INT_MAX; /* This notifier should run first. */
- + hub->notif.base.fn = ssam_base_hub_notif;
- + hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM;
- + hub->notif.event.id.target_category = SSAM_SSH_TC_BAS,
- + hub->notif.event.id.instance = 0,
- + hub->notif.event.mask = SSAM_EVENT_MASK_NONE;
- + hub->notif.event.flags = SSAM_EVENT_SEQUENCED;
- +
- + ssam_device_set_drvdata(sdev, hub);
- +
- + status = ssam_notifier_register(sdev->ctrl, &hub->notif);
- + if (status)
- + goto err_register;
- +
- + status = ssam_base_hub_update(hub);
- + if (status)
- + goto err_update;
- +
- + status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group);
- + if (status)
- + goto err_update;
- +
- + return 0;
- +
- +err_update:
- + ssam_notifier_unregister(sdev->ctrl, &hub->notif);
- + ssam_hub_remove_devices(&sdev->dev);
- +err_register:
- + mutex_destroy(&hub->lock);
- + return status;
- +}
- +
- +static void ssam_base_hub_remove(struct ssam_device *sdev)
- +{
- + struct ssam_base_hub *hub = ssam_device_get_drvdata(sdev);
- +
- + sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group);
- +
- + ssam_notifier_unregister(sdev->ctrl, &hub->notif);
- + ssam_hub_remove_devices(&sdev->dev);
- +
- + mutex_destroy(&hub->lock);
- +}
- +
- +static const struct ssam_device_id ssam_base_hub_match[] = {
- + { SSAM_VDEV(HUB, 0x02, SSAM_ANY_IID, 0x00) },
- + { },
- +};
- +
- +static struct ssam_device_driver ssam_base_hub_driver = {
- + .probe = ssam_base_hub_probe,
- + .remove = ssam_base_hub_remove,
- + .match_table = ssam_base_hub_match,
- + .driver = {
- + .name = "surface_aggregator_base_hub",
- + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
- + .pm = &ssam_base_hub_pm_ops,
- + },
- +};
- +
- +
- /* -- SSAM platform/meta-hub driver. ---------------------------------------- */
-
- static const struct acpi_device_id ssam_platform_hub_match[] = {
- @@ -277,7 +511,32 @@ static struct platform_driver ssam_platform_hub_driver = {
- .probe_type = PROBE_PREFER_ASYNCHRONOUS,
- },
- };
- -module_platform_driver(ssam_platform_hub_driver);
- +
- +
- +/* -- Module initialization. ------------------------------------------------ */
- +
- +static int __init ssam_device_hub_init(void)
- +{
- + int status;
- +
- + status = platform_driver_register(&ssam_platform_hub_driver);
- + if (status)
- + return status;
- +
- + status = ssam_device_driver_register(&ssam_base_hub_driver);
- + if (status)
- + platform_driver_unregister(&ssam_platform_hub_driver);
- +
- + return status;
- +}
- +module_init(ssam_device_hub_init);
- +
- +static void __exit ssam_device_hub_exit(void)
- +{
- + ssam_device_driver_unregister(&ssam_base_hub_driver);
- + platform_driver_unregister(&ssam_platform_hub_driver);
- +}
- +module_exit(ssam_device_hub_exit);
-
- MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
- MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module");
- --
- 2.32.0
- From 9652c52f812afe1f5a75508d4fe077cf73d71e3f Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Fri, 12 Feb 2021 12:54:36 +0100
- Subject: [PATCH] platform/surface: aggregator_registry: Add battery subsystem
- devices
- Add battery subsystem (TC=0x02) devices (battery and AC) to the SSAM
- device registry. These devices need to be registered for 7th-generation
- Surface models. On 5th- and 6th-generation models, these devices are
- handled via the standard ACPI battery/AC interface, which in turn
- accesses the same SSAM interface via the Surface ACPI Notify (SAN)
- driver.
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Link: https://lore.kernel.org/r/20210212115439.1525216-4-luzmaximilian@gmail.com
- Signed-off-by: Hans de Goede <hdegoede@redhat.com>
- Patchset: surface-sam
- ---
- .../surface/surface_aggregator_registry.c | 27 +++++++++++++++++++
- 1 file changed, 27 insertions(+)
- diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
- index 6c23d75a044c..cde279692842 100644
- --- a/drivers/platform/surface/surface_aggregator_registry.c
- +++ b/drivers/platform/surface/surface_aggregator_registry.c
- @@ -47,6 +47,24 @@ static const struct software_node ssam_node_hub_base = {
- .parent = &ssam_node_root,
- };
-
- +/* AC adapter. */
- +static const struct software_node ssam_node_bat_ac = {
- + .name = "ssam:01:02:01:01:01",
- + .parent = &ssam_node_root,
- +};
- +
- +/* Primary battery. */
- +static const struct software_node ssam_node_bat_main = {
- + .name = "ssam:01:02:01:01:00",
- + .parent = &ssam_node_root,
- +};
- +
- +/* Secondary battery (Surface Book 3). */
- +static const struct software_node ssam_node_bat_sb3base = {
- + .name = "ssam:01:02:02:01:00",
- + .parent = &ssam_node_hub_base,
- +};
- +
- /* Devices for Surface Book 2. */
- static const struct software_node *ssam_node_group_sb2[] = {
- &ssam_node_root,
- @@ -57,6 +75,9 @@ static const struct software_node *ssam_node_group_sb2[] = {
- static const struct software_node *ssam_node_group_sb3[] = {
- &ssam_node_root,
- &ssam_node_hub_base,
- + &ssam_node_bat_ac,
- + &ssam_node_bat_main,
- + &ssam_node_bat_sb3base,
- NULL,
- };
-
- @@ -75,12 +96,16 @@ static const struct software_node *ssam_node_group_sl2[] = {
- /* Devices for Surface Laptop 3. */
- static const struct software_node *ssam_node_group_sl3[] = {
- &ssam_node_root,
- + &ssam_node_bat_ac,
- + &ssam_node_bat_main,
- NULL,
- };
-
- /* Devices for Surface Laptop Go. */
- static const struct software_node *ssam_node_group_slg1[] = {
- &ssam_node_root,
- + &ssam_node_bat_ac,
- + &ssam_node_bat_main,
- NULL,
- };
-
- @@ -99,6 +124,8 @@ static const struct software_node *ssam_node_group_sp6[] = {
- /* Devices for Surface Pro 7. */
- static const struct software_node *ssam_node_group_sp7[] = {
- &ssam_node_root,
- + &ssam_node_bat_ac,
- + &ssam_node_bat_main,
- NULL,
- };
-
- --
- 2.32.0
- From 4d0bc54f28f710ec656a063bf3c5265a57d3b13d Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Fri, 12 Feb 2021 12:54:37 +0100
- Subject: [PATCH] platform/surface: aggregator_registry: Add platform profile
- device
- Add the SSAM platform profile device to the SSAM device registry. This
- device is accessible under the thermal subsystem (TC=0x03) and needs to
- be registered for all Surface models.
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Link: https://lore.kernel.org/r/20210212115439.1525216-5-luzmaximilian@gmail.com
- Signed-off-by: Hans de Goede <hdegoede@redhat.com>
- Patchset: surface-sam
- ---
- .../surface/surface_aggregator_registry.c | 15 +++++++++++++++
- 1 file changed, 15 insertions(+)
- diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
- index cde279692842..33904613dd4b 100644
- --- a/drivers/platform/surface/surface_aggregator_registry.c
- +++ b/drivers/platform/surface/surface_aggregator_registry.c
- @@ -65,9 +65,16 @@ static const struct software_node ssam_node_bat_sb3base = {
- .parent = &ssam_node_hub_base,
- };
-
- +/* Platform profile / performance-mode device. */
- +static const struct software_node ssam_node_tmp_pprof = {
- + .name = "ssam:01:03:01:00:01",
- + .parent = &ssam_node_root,
- +};
- +
- /* Devices for Surface Book 2. */
- static const struct software_node *ssam_node_group_sb2[] = {
- &ssam_node_root,
- + &ssam_node_tmp_pprof,
- NULL,
- };
-
- @@ -78,18 +85,21 @@ static const struct software_node *ssam_node_group_sb3[] = {
- &ssam_node_bat_ac,
- &ssam_node_bat_main,
- &ssam_node_bat_sb3base,
- + &ssam_node_tmp_pprof,
- NULL,
- };
-
- /* Devices for Surface Laptop 1. */
- static const struct software_node *ssam_node_group_sl1[] = {
- &ssam_node_root,
- + &ssam_node_tmp_pprof,
- NULL,
- };
-
- /* Devices for Surface Laptop 2. */
- static const struct software_node *ssam_node_group_sl2[] = {
- &ssam_node_root,
- + &ssam_node_tmp_pprof,
- NULL,
- };
-
- @@ -98,6 +108,7 @@ static const struct software_node *ssam_node_group_sl3[] = {
- &ssam_node_root,
- &ssam_node_bat_ac,
- &ssam_node_bat_main,
- + &ssam_node_tmp_pprof,
- NULL,
- };
-
- @@ -106,18 +117,21 @@ static const struct software_node *ssam_node_group_slg1[] = {
- &ssam_node_root,
- &ssam_node_bat_ac,
- &ssam_node_bat_main,
- + &ssam_node_tmp_pprof,
- NULL,
- };
-
- /* Devices for Surface Pro 5. */
- static const struct software_node *ssam_node_group_sp5[] = {
- &ssam_node_root,
- + &ssam_node_tmp_pprof,
- NULL,
- };
-
- /* Devices for Surface Pro 6. */
- static const struct software_node *ssam_node_group_sp6[] = {
- &ssam_node_root,
- + &ssam_node_tmp_pprof,
- NULL,
- };
-
- @@ -126,6 +140,7 @@ static const struct software_node *ssam_node_group_sp7[] = {
- &ssam_node_root,
- &ssam_node_bat_ac,
- &ssam_node_bat_main,
- + &ssam_node_tmp_pprof,
- NULL,
- };
-
- --
- 2.32.0
- From d7dedaa2b75bb813a9fdd78d7a08827f67f0ea31 Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Fri, 12 Feb 2021 12:54:38 +0100
- Subject: [PATCH] platform/surface: aggregator_registry: Add DTX device
- Add the detachment system (DTX) SSAM device for the Surface Book 3. This
- device is accessible under the base (TC=0x11) subsystem.
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Link: https://lore.kernel.org/r/20210212115439.1525216-6-luzmaximilian@gmail.com
- Signed-off-by: Hans de Goede <hdegoede@redhat.com>
- Patchset: surface-sam
- ---
- drivers/platform/surface/surface_aggregator_registry.c | 7 +++++++
- 1 file changed, 7 insertions(+)
- diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
- index 33904613dd4b..dc044d06828b 100644
- --- a/drivers/platform/surface/surface_aggregator_registry.c
- +++ b/drivers/platform/surface/surface_aggregator_registry.c
- @@ -71,6 +71,12 @@ static const struct software_node ssam_node_tmp_pprof = {
- .parent = &ssam_node_root,
- };
-
- +/* DTX / detachment-system device (Surface Book 3). */
- +static const struct software_node ssam_node_bas_dtx = {
- + .name = "ssam:01:11:01:00:00",
- + .parent = &ssam_node_root,
- +};
- +
- /* Devices for Surface Book 2. */
- static const struct software_node *ssam_node_group_sb2[] = {
- &ssam_node_root,
- @@ -86,6 +92,7 @@ static const struct software_node *ssam_node_group_sb3[] = {
- &ssam_node_bat_main,
- &ssam_node_bat_sb3base,
- &ssam_node_tmp_pprof,
- + &ssam_node_bas_dtx,
- NULL,
- };
-
- --
- 2.32.0
- From 48e36688edec92d06ede33ddb78c708adbe4179f Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Fri, 12 Feb 2021 12:54:39 +0100
- Subject: [PATCH] platform/surface: aggregator_registry: Add HID subsystem
- devices
- Add HID subsystem (TC=0x15) devices. These devices need to be registered
- for 7th-generation Surface models. On previous generations, these
- devices are either provided as platform devices via ACPI (Surface Laptop
- 1 and 2) or implemented as standard USB device.
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Link: https://lore.kernel.org/r/20210212115439.1525216-7-luzmaximilian@gmail.com
- Signed-off-by: Hans de Goede <hdegoede@redhat.com>
- Patchset: surface-sam
- ---
- .../surface/surface_aggregator_registry.c | 49 +++++++++++++++++++
- 1 file changed, 49 insertions(+)
- diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
- index dc044d06828b..caee90d135c5 100644
- --- a/drivers/platform/surface/surface_aggregator_registry.c
- +++ b/drivers/platform/surface/surface_aggregator_registry.c
- @@ -77,6 +77,48 @@ static const struct software_node ssam_node_bas_dtx = {
- .parent = &ssam_node_root,
- };
-
- +/* HID keyboard. */
- +static const struct software_node ssam_node_hid_main_keyboard = {
- + .name = "ssam:01:15:02:01:00",
- + .parent = &ssam_node_root,
- +};
- +
- +/* HID touchpad. */
- +static const struct software_node ssam_node_hid_main_touchpad = {
- + .name = "ssam:01:15:02:03:00",
- + .parent = &ssam_node_root,
- +};
- +
- +/* HID device instance 5 (unknown HID device). */
- +static const struct software_node ssam_node_hid_main_iid5 = {
- + .name = "ssam:01:15:02:05:00",
- + .parent = &ssam_node_root,
- +};
- +
- +/* HID keyboard (base hub). */
- +static const struct software_node ssam_node_hid_base_keyboard = {
- + .name = "ssam:01:15:02:01:00",
- + .parent = &ssam_node_hub_base,
- +};
- +
- +/* HID touchpad (base hub). */
- +static const struct software_node ssam_node_hid_base_touchpad = {
- + .name = "ssam:01:15:02:03:00",
- + .parent = &ssam_node_hub_base,
- +};
- +
- +/* HID device instance 5 (unknown HID device, base hub). */
- +static const struct software_node ssam_node_hid_base_iid5 = {
- + .name = "ssam:01:15:02:05:00",
- + .parent = &ssam_node_hub_base,
- +};
- +
- +/* HID device instance 6 (unknown HID device, base hub). */
- +static const struct software_node ssam_node_hid_base_iid6 = {
- + .name = "ssam:01:15:02:06:00",
- + .parent = &ssam_node_hub_base,
- +};
- +
- /* Devices for Surface Book 2. */
- static const struct software_node *ssam_node_group_sb2[] = {
- &ssam_node_root,
- @@ -93,6 +135,10 @@ static const struct software_node *ssam_node_group_sb3[] = {
- &ssam_node_bat_sb3base,
- &ssam_node_tmp_pprof,
- &ssam_node_bas_dtx,
- + &ssam_node_hid_base_keyboard,
- + &ssam_node_hid_base_touchpad,
- + &ssam_node_hid_base_iid5,
- + &ssam_node_hid_base_iid6,
- NULL,
- };
-
- @@ -116,6 +162,9 @@ static const struct software_node *ssam_node_group_sl3[] = {
- &ssam_node_bat_ac,
- &ssam_node_bat_main,
- &ssam_node_tmp_pprof,
- + &ssam_node_hid_main_keyboard,
- + &ssam_node_hid_main_touchpad,
- + &ssam_node_hid_main_iid5,
- NULL,
- };
-
- --
- 2.32.0
- From 7178cfe4327a0e70115450be0fe768f5023fe1c3 Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Thu, 11 Feb 2021 21:17:03 +0100
- Subject: [PATCH] platform/surface: Add platform profile driver
- Add a driver to provide platform profile support on 5th- and later
- generation Microsoft Surface devices with a Surface System Aggregator
- Module. On those devices, the platform profile can be used to influence
- cooling behavior and power consumption.
- For example, the default 'quiet' profile limits fan noise and in turn
- sacrifices performance of the discrete GPU found on Surface Books. Its
- full performance can only be unlocked on the 'performance' profile.
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Reviewed-by: Hans de Goede <hdegoede@redhat.com>
- Link: https://lore.kernel.org/r/20210211201703.658240-5-luzmaximilian@gmail.com
- Signed-off-by: Hans de Goede <hdegoede@redhat.com>
- Patchset: surface-sam
- ---
- MAINTAINERS | 6 +
- drivers/platform/surface/Kconfig | 22 ++
- drivers/platform/surface/Makefile | 1 +
- .../surface/surface_platform_profile.c | 190 ++++++++++++++++++
- 4 files changed, 219 insertions(+)
- create mode 100644 drivers/platform/surface/surface_platform_profile.c
- diff --git a/MAINTAINERS b/MAINTAINERS
- index f6c524630575..fce5cdcefc0b 100644
- --- a/MAINTAINERS
- +++ b/MAINTAINERS
- @@ -11889,6 +11889,12 @@ L: platform-driver-x86@vger.kernel.org
- S: Maintained
- F: drivers/platform/surface/surface_hotplug.c
-
- +MICROSOFT SURFACE PLATFORM PROFILE DRIVER
- +M: Maximilian Luz <luzmaximilian@gmail.com>
- +L: platform-driver-x86@vger.kernel.org
- +S: Maintained
- +F: drivers/platform/surface/surface_platform_profile.c
- +
- MICROSOFT SURFACE PRO 3 BUTTON DRIVER
- M: Chen Yu <yu.c.chen@intel.com>
- L: platform-driver-x86@vger.kernel.org
- diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig
- index c51c55204b5f..6fb304da845f 100644
- --- a/drivers/platform/surface/Kconfig
- +++ b/drivers/platform/surface/Kconfig
- @@ -139,6 +139,28 @@ config SURFACE_HOTPLUG
- Select M or Y here, if you want to (fully) support hot-plugging of
- dGPU devices on the Surface Book 2 and/or 3 during D3cold.
-
- +config SURFACE_PLATFORM_PROFILE
- + tristate "Surface Platform Profile Driver"
- + depends on SURFACE_AGGREGATOR_REGISTRY
- + select ACPI_PLATFORM_PROFILE
- + help
- + Provides support for the ACPI platform profile on 5th- and later
- + generation Microsoft Surface devices.
- +
- + More specifically, this driver provides ACPI platform profile support
- + on Microsoft Surface devices with a Surface System Aggregator Module
- + (SSAM) connected via the Surface Serial Hub (SSH / SAM-over-SSH). In
- + other words, this driver provides platform profile support on the
- + Surface Pro 5, Surface Book 2, Surface Laptop, Surface Laptop Go and
- + later. On those devices, the platform profile can significantly
- + influence cooling behavior, e.g. setting it to 'quiet' (default) or
- + 'low-power' can significantly limit performance of the discrete GPU on
- + Surface Books, while in turn leading to lower power consumption and/or
- + less fan noise.
- +
- + Select M or Y here, if you want to include ACPI platform profile
- + support on the above mentioned devices.
- +
- config SURFACE_PRO3_BUTTON
- tristate "Power/home/volume buttons driver for Microsoft Surface Pro 3/4 tablet"
- depends on INPUT
- diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile
- index ed12676f06e6..f7187bae1729 100644
- --- a/drivers/platform/surface/Makefile
- +++ b/drivers/platform/surface/Makefile
- @@ -14,4 +14,5 @@ obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o
- obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o
- obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o
- obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o
- +obj-$(CONFIG_SURFACE_PLATFORM_PROFILE) += surface_platform_profile.o
- obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o
- diff --git a/drivers/platform/surface/surface_platform_profile.c b/drivers/platform/surface/surface_platform_profile.c
- new file mode 100644
- index 000000000000..0081b01a5b0f
- --- /dev/null
- +++ b/drivers/platform/surface/surface_platform_profile.c
- @@ -0,0 +1,190 @@
- +// SPDX-License-Identifier: GPL-2.0+
- +/*
- + * Surface Platform Profile / Performance Mode driver for Surface System
- + * Aggregator Module (thermal subsystem).
- + *
- + * Copyright (C) 2021 Maximilian Luz <luzmaximilian@gmail.com>
- + */
- +
- +#include <asm/unaligned.h>
- +#include <linux/kernel.h>
- +#include <linux/module.h>
- +#include <linux/platform_profile.h>
- +#include <linux/types.h>
- +
- +#include <linux/surface_aggregator/device.h>
- +
- +enum ssam_tmp_profile {
- + SSAM_TMP_PROFILE_NORMAL = 1,
- + SSAM_TMP_PROFILE_BATTERY_SAVER = 2,
- + SSAM_TMP_PROFILE_BETTER_PERFORMANCE = 3,
- + SSAM_TMP_PROFILE_BEST_PERFORMANCE = 4,
- +};
- +
- +struct ssam_tmp_profile_info {
- + __le32 profile;
- + __le16 unknown1;
- + __le16 unknown2;
- +} __packed;
- +
- +struct ssam_tmp_profile_device {
- + struct ssam_device *sdev;
- + struct platform_profile_handler handler;
- +};
- +
- +static SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, {
- + .target_category = SSAM_SSH_TC_TMP,
- + .command_id = 0x02,
- +});
- +
- +static SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, {
- + .target_category = SSAM_SSH_TC_TMP,
- + .command_id = 0x03,
- +});
- +
- +static int ssam_tmp_profile_get(struct ssam_device *sdev, enum ssam_tmp_profile *p)
- +{
- + struct ssam_tmp_profile_info info;
- + int status;
- +
- + status = ssam_retry(__ssam_tmp_profile_get, sdev, &info);
- + if (status < 0)
- + return status;
- +
- + *p = le32_to_cpu(info.profile);
- + return 0;
- +}
- +
- +static int ssam_tmp_profile_set(struct ssam_device *sdev, enum ssam_tmp_profile p)
- +{
- + __le32 profile_le = cpu_to_le32(p);
- +
- + return ssam_retry(__ssam_tmp_profile_set, sdev, &profile_le);
- +}
- +
- +static int convert_ssam_to_profile(struct ssam_device *sdev, enum ssam_tmp_profile p)
- +{
- + switch (p) {
- + case SSAM_TMP_PROFILE_NORMAL:
- + return PLATFORM_PROFILE_BALANCED;
- +
- + case SSAM_TMP_PROFILE_BATTERY_SAVER:
- + return PLATFORM_PROFILE_LOW_POWER;
- +
- + case SSAM_TMP_PROFILE_BETTER_PERFORMANCE:
- + return PLATFORM_PROFILE_BALANCED_PERFORMANCE;
- +
- + case SSAM_TMP_PROFILE_BEST_PERFORMANCE:
- + return PLATFORM_PROFILE_PERFORMANCE;
- +
- + default:
- + dev_err(&sdev->dev, "invalid performance profile: %d", p);
- + return -EINVAL;
- + }
- +}
- +
- +static int convert_profile_to_ssam(struct ssam_device *sdev, enum platform_profile_option p)
- +{
- + switch (p) {
- + case PLATFORM_PROFILE_LOW_POWER:
- + return SSAM_TMP_PROFILE_BATTERY_SAVER;
- +
- + case PLATFORM_PROFILE_BALANCED:
- + return SSAM_TMP_PROFILE_NORMAL;
- +
- + case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
- + return SSAM_TMP_PROFILE_BETTER_PERFORMANCE;
- +
- + case PLATFORM_PROFILE_PERFORMANCE:
- + return SSAM_TMP_PROFILE_BEST_PERFORMANCE;
- +
- + default:
- + /* This should have already been caught by platform_profile_store(). */
- + WARN(true, "unsupported platform profile");
- + return -EOPNOTSUPP;
- + }
- +}
- +
- +static int ssam_platform_profile_get(struct platform_profile_handler *pprof,
- + enum platform_profile_option *profile)
- +{
- + struct ssam_tmp_profile_device *tpd;
- + enum ssam_tmp_profile tp;
- + int status;
- +
- + tpd = container_of(pprof, struct ssam_tmp_profile_device, handler);
- +
- + status = ssam_tmp_profile_get(tpd->sdev, &tp);
- + if (status)
- + return status;
- +
- + status = convert_ssam_to_profile(tpd->sdev, tp);
- + if (status < 0)
- + return status;
- +
- + *profile = status;
- + return 0;
- +}
- +
- +static int ssam_platform_profile_set(struct platform_profile_handler *pprof,
- + enum platform_profile_option profile)
- +{
- + struct ssam_tmp_profile_device *tpd;
- + int tp;
- +
- + tpd = container_of(pprof, struct ssam_tmp_profile_device, handler);
- +
- + tp = convert_profile_to_ssam(tpd->sdev, profile);
- + if (tp < 0)
- + return tp;
- +
- + return ssam_tmp_profile_set(tpd->sdev, tp);
- +}
- +
- +static int surface_platform_profile_probe(struct ssam_device *sdev)
- +{
- + struct ssam_tmp_profile_device *tpd;
- +
- + tpd = devm_kzalloc(&sdev->dev, sizeof(*tpd), GFP_KERNEL);
- + if (!tpd)
- + return -ENOMEM;
- +
- + tpd->sdev = sdev;
- +
- + tpd->handler.profile_get = ssam_platform_profile_get;
- + tpd->handler.profile_set = ssam_platform_profile_set;
- +
- + set_bit(PLATFORM_PROFILE_LOW_POWER, tpd->handler.choices);
- + set_bit(PLATFORM_PROFILE_BALANCED, tpd->handler.choices);
- + set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, tpd->handler.choices);
- + set_bit(PLATFORM_PROFILE_PERFORMANCE, tpd->handler.choices);
- +
- + platform_profile_register(&tpd->handler);
- + return 0;
- +}
- +
- +static void surface_platform_profile_remove(struct ssam_device *sdev)
- +{
- + platform_profile_remove();
- +}
- +
- +static const struct ssam_device_id ssam_platform_profile_match[] = {
- + { SSAM_SDEV(TMP, 0x01, 0x00, 0x01) },
- + { },
- +};
- +MODULE_DEVICE_TABLE(ssam, ssam_platform_profile_match);
- +
- +static struct ssam_device_driver surface_platform_profile = {
- + .probe = surface_platform_profile_probe,
- + .remove = surface_platform_profile_remove,
- + .match_table = ssam_platform_profile_match,
- + .driver = {
- + .name = "surface_platform_profile",
- + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
- + },
- +};
- +module_ssam_device_driver(surface_platform_profile);
- +
- +MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
- +MODULE_DESCRIPTION("Platform Profile Support for Surface System Aggregator Module");
- +MODULE_LICENSE("GPL");
- --
- 2.32.0
- From 9b2de618ae2ae75163dd8ae3a77704a58aaad345 Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Thu, 4 Mar 2021 20:05:24 +0100
- Subject: [PATCH] platform/surface: aggregator: Make SSAM_DEFINE_SYNC_REQUEST_x
- define static functions
- The SSAM_DEFINE_SYNC_REQUEST_x() macros are intended to reduce
- boiler-plate code for SSAM request definitions by defining a wrapper
- function for the specified request. The client device variants of those
- macros, i.e. SSAM_DEFINE_SYNC_REQUEST_CL_x() in particular rely on the
- multi-device (MD) variants, e.g.:
- #define SSAM_DEFINE_SYNC_REQUEST_CL_R(name, rtype, spec...) \
- SSAM_DEFINE_SYNC_REQUEST_MD_R(__raw_##name, rtype, spec) \
- int name(struct ssam_device *sdev, rtype *ret) \
- { \
- return __raw_##name(sdev->ctrl, sdev->uid.target, \
- sdev->uid.instance, ret); \
- }
- This now creates the problem that it is not possible to declare the
- generated functions static via
- static SSAM_DEFINE_SYNC_REQUEST_CL_R(...)
- as this will only apply to the function defined by the multi-device
- macro, i.e. SSAM_DEFINE_SYNC_REQUEST_MD_R(). Thus compiling with
- `-Wmissing-prototypes' rightfully complains that there is a 'static'
- keyword missing.
- To solve this, make all SSAM_DEFINE_SYNC_REQUEST_x() macros define
- static functions. Non-client-device macros are also changed for
- consistency. In general, we expect those functions to be only used
- locally in the respective drivers for the corresponding interfaces, so
- having to define a wrapper function to be able to export this should be
- the odd case out.
- Reported-by: kernel test robot <lkp@intel.com>
- Fixes: b78b4982d763 ("platform/surface: Add platform profile driver")
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Link: https://lore.kernel.org/r/20210304190524.1172197-1-luzmaximilian@gmail.com
- Signed-off-by: Hans de Goede <hdegoede@redhat.com>
- Patchset: surface-sam
- ---
- .../driver-api/surface_aggregator/client.rst | 4 +-
- .../platform/surface/aggregator/controller.c | 10 +--
- .../surface/surface_aggregator_registry.c | 2 +-
- .../surface/surface_platform_profile.c | 4 +-
- include/linux/surface_aggregator/controller.h | 74 +++++++++----------
- include/linux/surface_aggregator/device.h | 31 ++++----
- 6 files changed, 63 insertions(+), 62 deletions(-)
- diff --git a/Documentation/driver-api/surface_aggregator/client.rst b/Documentation/driver-api/surface_aggregator/client.rst
- index 26d13085a117..e519d374c378 100644
- --- a/Documentation/driver-api/surface_aggregator/client.rst
- +++ b/Documentation/driver-api/surface_aggregator/client.rst
- @@ -248,7 +248,7 @@ This example defines a function
-
- .. code-block:: c
-
- - int __ssam_tmp_perf_mode_set(struct ssam_controller *ctrl, const __le32 *arg);
- + static int __ssam_tmp_perf_mode_set(struct ssam_controller *ctrl, const __le32 *arg);
-
- executing the specified request, with the controller passed in when calling
- said function. In this example, the argument is provided via the ``arg``
- @@ -296,7 +296,7 @@ This invocation of the macro defines a function
-
- .. code-block:: c
-
- - int ssam_bat_get_sta(struct ssam_device *sdev, __le32 *ret);
- + static int ssam_bat_get_sta(struct ssam_device *sdev, __le32 *ret);
-
- executing the specified request, using the device IDs and controller given
- in the client device. The full list of such macros for client devices is:
- diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c
- index d68d51fb24ff..d006a36b2924 100644
- --- a/drivers/platform/surface/aggregator/controller.c
- +++ b/drivers/platform/surface/aggregator/controller.c
- @@ -1750,35 +1750,35 @@ EXPORT_SYMBOL_GPL(ssam_request_sync_with_buffer);
-
- /* -- Internal SAM requests. ------------------------------------------------ */
-
- -static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_get_firmware_version, __le32, {
- +SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_get_firmware_version, __le32, {
- .target_category = SSAM_SSH_TC_SAM,
- .target_id = 0x01,
- .command_id = 0x13,
- .instance_id = 0x00,
- });
-
- -static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_off, u8, {
- +SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_off, u8, {
- .target_category = SSAM_SSH_TC_SAM,
- .target_id = 0x01,
- .command_id = 0x15,
- .instance_id = 0x00,
- });
-
- -static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_on, u8, {
- +SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_on, u8, {
- .target_category = SSAM_SSH_TC_SAM,
- .target_id = 0x01,
- .command_id = 0x16,
- .instance_id = 0x00,
- });
-
- -static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_exit, u8, {
- +SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_exit, u8, {
- .target_category = SSAM_SSH_TC_SAM,
- .target_id = 0x01,
- .command_id = 0x33,
- .instance_id = 0x00,
- });
-
- -static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_entry, u8, {
- +SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_entry, u8, {
- .target_category = SSAM_SSH_TC_SAM,
- .target_id = 0x01,
- .command_id = 0x34,
- diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
- index caee90d135c5..cdb4a95af3e8 100644
- --- a/drivers/platform/surface/surface_aggregator_registry.c
- +++ b/drivers/platform/surface/surface_aggregator_registry.c
- @@ -302,7 +302,7 @@ struct ssam_base_hub {
- struct ssam_event_notifier notif;
- };
-
- -static SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, {
- +SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, {
- .target_category = SSAM_SSH_TC_BAS,
- .target_id = 0x01,
- .command_id = 0x0d,
- diff --git a/drivers/platform/surface/surface_platform_profile.c b/drivers/platform/surface/surface_platform_profile.c
- index 0081b01a5b0f..6373d3b5eb7f 100644
- --- a/drivers/platform/surface/surface_platform_profile.c
- +++ b/drivers/platform/surface/surface_platform_profile.c
- @@ -32,12 +32,12 @@ struct ssam_tmp_profile_device {
- struct platform_profile_handler handler;
- };
-
- -static SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, {
- +SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, {
- .target_category = SSAM_SSH_TC_TMP,
- .command_id = 0x02,
- });
-
- -static SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, {
- +SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, {
- .target_category = SSAM_SSH_TC_TMP,
- .command_id = 0x03,
- });
- diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h
- index f4b1ba887384..0806796eabcb 100644
- --- a/include/linux/surface_aggregator/controller.h
- +++ b/include/linux/surface_aggregator/controller.h
- @@ -344,16 +344,16 @@ struct ssam_request_spec_md {
- * request has been fully completed. The required transport buffer will be
- * allocated on the stack.
- *
- - * The generated function is defined as ``int name(struct ssam_controller
- - * *ctrl)``, returning the status of the request, which is zero on success and
- - * negative on failure. The ``ctrl`` parameter is the controller via which the
- - * request is being sent.
- + * The generated function is defined as ``static int name(struct
- + * ssam_controller *ctrl)``, returning the status of the request, which is
- + * zero on success and negative on failure. The ``ctrl`` parameter is the
- + * controller via which the request is being sent.
- *
- * Refer to ssam_request_sync_onstack() for more details on the behavior of
- * the generated function.
- */
- #define SSAM_DEFINE_SYNC_REQUEST_N(name, spec...) \
- - int name(struct ssam_controller *ctrl) \
- + static int name(struct ssam_controller *ctrl) \
- { \
- struct ssam_request_spec s = (struct ssam_request_spec)spec; \
- struct ssam_request rqst; \
- @@ -383,17 +383,17 @@ struct ssam_request_spec_md {
- * returning once the request has been fully completed. The required transport
- * buffer will be allocated on the stack.
- *
- - * The generated function is defined as ``int name(struct ssam_controller
- - * *ctrl, const atype *arg)``, returning the status of the request, which is
- - * zero on success and negative on failure. The ``ctrl`` parameter is the
- - * controller via which the request is sent. The request argument is specified
- - * via the ``arg`` pointer.
- + * The generated function is defined as ``static int name(struct
- + * ssam_controller *ctrl, const atype *arg)``, returning the status of the
- + * request, which is zero on success and negative on failure. The ``ctrl``
- + * parameter is the controller via which the request is sent. The request
- + * argument is specified via the ``arg`` pointer.
- *
- * Refer to ssam_request_sync_onstack() for more details on the behavior of
- * the generated function.
- */
- #define SSAM_DEFINE_SYNC_REQUEST_W(name, atype, spec...) \
- - int name(struct ssam_controller *ctrl, const atype *arg) \
- + static int name(struct ssam_controller *ctrl, const atype *arg) \
- { \
- struct ssam_request_spec s = (struct ssam_request_spec)spec; \
- struct ssam_request rqst; \
- @@ -424,17 +424,17 @@ struct ssam_request_spec_md {
- * request itself, returning once the request has been fully completed. The
- * required transport buffer will be allocated on the stack.
- *
- - * The generated function is defined as ``int name(struct ssam_controller
- - * *ctrl, rtype *ret)``, returning the status of the request, which is zero on
- - * success and negative on failure. The ``ctrl`` parameter is the controller
- - * via which the request is sent. The request's return value is written to the
- - * memory pointed to by the ``ret`` parameter.
- + * The generated function is defined as ``static int name(struct
- + * ssam_controller *ctrl, rtype *ret)``, returning the status of the request,
- + * which is zero on success and negative on failure. The ``ctrl`` parameter is
- + * the controller via which the request is sent. The request's return value is
- + * written to the memory pointed to by the ``ret`` parameter.
- *
- * Refer to ssam_request_sync_onstack() for more details on the behavior of
- * the generated function.
- */
- #define SSAM_DEFINE_SYNC_REQUEST_R(name, rtype, spec...) \
- - int name(struct ssam_controller *ctrl, rtype *ret) \
- + static int name(struct ssam_controller *ctrl, rtype *ret) \
- { \
- struct ssam_request_spec s = (struct ssam_request_spec)spec; \
- struct ssam_request rqst; \
- @@ -483,17 +483,17 @@ struct ssam_request_spec_md {
- * returning once the request has been fully completed. The required transport
- * buffer will be allocated on the stack.
- *
- - * The generated function is defined as ``int name(struct ssam_controller
- - * *ctrl, u8 tid, u8 iid)``, returning the status of the request, which is
- - * zero on success and negative on failure. The ``ctrl`` parameter is the
- - * controller via which the request is sent, ``tid`` the target ID for the
- - * request, and ``iid`` the instance ID.
- + * The generated function is defined as ``static int name(struct
- + * ssam_controller *ctrl, u8 tid, u8 iid)``, returning the status of the
- + * request, which is zero on success and negative on failure. The ``ctrl``
- + * parameter is the controller via which the request is sent, ``tid`` the
- + * target ID for the request, and ``iid`` the instance ID.
- *
- * Refer to ssam_request_sync_onstack() for more details on the behavior of
- * the generated function.
- */
- #define SSAM_DEFINE_SYNC_REQUEST_MD_N(name, spec...) \
- - int name(struct ssam_controller *ctrl, u8 tid, u8 iid) \
- + static int name(struct ssam_controller *ctrl, u8 tid, u8 iid) \
- { \
- struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \
- struct ssam_request rqst; \
- @@ -524,18 +524,18 @@ struct ssam_request_spec_md {
- * the request itself, returning once the request has been fully completed.
- * The required transport buffer will be allocated on the stack.
- *
- - * The generated function is defined as ``int name(struct ssam_controller
- - * *ctrl, u8 tid, u8 iid, const atype *arg)``, returning the status of the
- - * request, which is zero on success and negative on failure. The ``ctrl``
- - * parameter is the controller via which the request is sent, ``tid`` the
- - * target ID for the request, and ``iid`` the instance ID. The request argument
- - * is specified via the ``arg`` pointer.
- + * The generated function is defined as ``static int name(struct
- + * ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg)``, returning the
- + * status of the request, which is zero on success and negative on failure.
- + * The ``ctrl`` parameter is the controller via which the request is sent,
- + * ``tid`` the target ID for the request, and ``iid`` the instance ID. The
- + * request argument is specified via the ``arg`` pointer.
- *
- * Refer to ssam_request_sync_onstack() for more details on the behavior of
- * the generated function.
- */
- #define SSAM_DEFINE_SYNC_REQUEST_MD_W(name, atype, spec...) \
- - int name(struct ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg)\
- + static int name(struct ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg) \
- { \
- struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \
- struct ssam_request rqst; \
- @@ -567,18 +567,18 @@ struct ssam_request_spec_md {
- * execution of the request itself, returning once the request has been fully
- * completed. The required transport buffer will be allocated on the stack.
- *
- - * The generated function is defined as ``int name(struct ssam_controller
- - * *ctrl, u8 tid, u8 iid, rtype *ret)``, returning the status of the request,
- - * which is zero on success and negative on failure. The ``ctrl`` parameter is
- - * the controller via which the request is sent, ``tid`` the target ID for the
- - * request, and ``iid`` the instance ID. The request's return value is written
- - * to the memory pointed to by the ``ret`` parameter.
- + * The generated function is defined as ``static int name(struct
- + * ssam_controller *ctrl, u8 tid, u8 iid, rtype *ret)``, returning the status
- + * of the request, which is zero on success and negative on failure. The
- + * ``ctrl`` parameter is the controller via which the request is sent, ``tid``
- + * the target ID for the request, and ``iid`` the instance ID. The request's
- + * return value is written to the memory pointed to by the ``ret`` parameter.
- *
- * Refer to ssam_request_sync_onstack() for more details on the behavior of
- * the generated function.
- */
- #define SSAM_DEFINE_SYNC_REQUEST_MD_R(name, rtype, spec...) \
- - int name(struct ssam_controller *ctrl, u8 tid, u8 iid, rtype *ret) \
- + static int name(struct ssam_controller *ctrl, u8 tid, u8 iid, rtype *ret) \
- { \
- struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \
- struct ssam_request rqst; \
- diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h
- index 02f3e06c0a60..4441ad667c3f 100644
- --- a/include/linux/surface_aggregator/device.h
- +++ b/include/linux/surface_aggregator/device.h
- @@ -336,17 +336,18 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d);
- * request has been fully completed. The required transport buffer will be
- * allocated on the stack.
- *
- - * The generated function is defined as ``int name(struct ssam_device *sdev)``,
- - * returning the status of the request, which is zero on success and negative
- - * on failure. The ``sdev`` parameter specifies both the target device of the
- - * request and by association the controller via which the request is sent.
- + * The generated function is defined as ``static int name(struct ssam_device
- + * *sdev)``, returning the status of the request, which is zero on success and
- + * negative on failure. The ``sdev`` parameter specifies both the target
- + * device of the request and by association the controller via which the
- + * request is sent.
- *
- * Refer to ssam_request_sync_onstack() for more details on the behavior of
- * the generated function.
- */
- #define SSAM_DEFINE_SYNC_REQUEST_CL_N(name, spec...) \
- SSAM_DEFINE_SYNC_REQUEST_MD_N(__raw_##name, spec) \
- - int name(struct ssam_device *sdev) \
- + static int name(struct ssam_device *sdev) \
- { \
- return __raw_##name(sdev->ctrl, sdev->uid.target, \
- sdev->uid.instance); \
- @@ -368,19 +369,19 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d);
- * itself, returning once the request has been fully completed. The required
- * transport buffer will be allocated on the stack.
- *
- - * The generated function is defined as ``int name(struct ssam_device *sdev,
- - * const atype *arg)``, returning the status of the request, which is zero on
- - * success and negative on failure. The ``sdev`` parameter specifies both the
- - * target device of the request and by association the controller via which
- - * the request is sent. The request's argument is specified via the ``arg``
- - * pointer.
- + * The generated function is defined as ``static int name(struct ssam_device
- + * *sdev, const atype *arg)``, returning the status of the request, which is
- + * zero on success and negative on failure. The ``sdev`` parameter specifies
- + * both the target device of the request and by association the controller via
- + * which the request is sent. The request's argument is specified via the
- + * ``arg`` pointer.
- *
- * Refer to ssam_request_sync_onstack() for more details on the behavior of
- * the generated function.
- */
- #define SSAM_DEFINE_SYNC_REQUEST_CL_W(name, atype, spec...) \
- SSAM_DEFINE_SYNC_REQUEST_MD_W(__raw_##name, atype, spec) \
- - int name(struct ssam_device *sdev, const atype *arg) \
- + static int name(struct ssam_device *sdev, const atype *arg) \
- { \
- return __raw_##name(sdev->ctrl, sdev->uid.target, \
- sdev->uid.instance, arg); \
- @@ -402,8 +403,8 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d);
- * itself, returning once the request has been fully completed. The required
- * transport buffer will be allocated on the stack.
- *
- - * The generated function is defined as ``int name(struct ssam_device *sdev,
- - * rtype *ret)``, returning the status of the request, which is zero on
- + * The generated function is defined as ``static int name(struct ssam_device
- + * *sdev, rtype *ret)``, returning the status of the request, which is zero on
- * success and negative on failure. The ``sdev`` parameter specifies both the
- * target device of the request and by association the controller via which
- * the request is sent. The request's return value is written to the memory
- @@ -414,7 +415,7 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d);
- */
- #define SSAM_DEFINE_SYNC_REQUEST_CL_R(name, rtype, spec...) \
- SSAM_DEFINE_SYNC_REQUEST_MD_R(__raw_##name, rtype, spec) \
- - int name(struct ssam_device *sdev, rtype *ret) \
- + static int name(struct ssam_device *sdev, rtype *ret) \
- { \
- return __raw_##name(sdev->ctrl, sdev->uid.target, \
- sdev->uid.instance, ret); \
- --
- 2.32.0
- From 19c9c3b67fc1ea05aea51df4959ea59056dc6e02 Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Mon, 8 Mar 2021 19:48:17 +0100
- Subject: [PATCH] platform/surface: Add DTX driver
- The Microsoft Surface Book series devices consist of a so-called
- clipboard part (containing the CPU, touchscreen, and primary battery)
- and a base part (containing keyboard, secondary battery, and optional
- discrete GPU). These parts can be separated, i.e. the clipboard can be
- detached and used as tablet.
- This detachment process is initiated by pressing a button. On the
- Surface Book 2 and 3 (targeted with this commit), the Surface Aggregator
- Module (i.e. the embedded controller on those devices) attempts to send
- a notification to any listening client driver and waits for further
- instructions (i.e. whether the detachment process should continue or be
- aborted). If it does not receive a response in a certain time-frame, the
- detachment process (by default) continues and the clipboard can be
- physically separated. In other words, (by default and) without a driver,
- the detachment process takes about 10 seconds to complete.
- This commit introduces a driver for this detachment system (called DTX).
- This driver allows a user-space daemon to control and influence the
- detachment behavior. Specifically, it forwards any detachment requests
- to user-space, allows user-space to make such requests itself, and
- allows handling of those requests. Requests can be handled by either
- aborting, continuing/allowing, or delaying (i.e. resetting the timeout
- via a heartbeat commend). The user-space API is implemented via the
- /dev/surface/dtx miscdevice.
- In addition, user-space can change the default behavior on timeout from
- allowing detachment to disallowing it, which is useful if the (optional)
- discrete GPU is in use.
- Furthermore, this driver allows user-space to receive notifications
- about the state of the base, specifically when it is physically removed
- (as opposed to detachment requested), in what manner it is connected
- (i.e. in reverse-/tent-/studio- or laptop-mode), and what type of base
- is connected. Based on this information, the driver also provides a
- simple tablet-mode switch (aliasing all modes without keyboard access,
- i.e. tablet-mode and studio-mode to its reported tablet-mode).
- An implementation of such a user-space daemon, allowing configuration of
- detachment behavior via scripts (e.g. safely unmounting USB devices
- connected to the base before continuing) can be found at [1].
- [1]: https://github.com/linux-surface/surface-dtx-daemon
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Link: https://lore.kernel.org/r/20210308184819.437438-2-luzmaximilian@gmail.com
- Signed-off-by: Hans de Goede <hdegoede@redhat.com>
- Patchset: surface-sam
- ---
- .../userspace-api/ioctl/ioctl-number.rst | 2 +
- MAINTAINERS | 7 +
- drivers/platform/surface/Kconfig | 16 +
- drivers/platform/surface/Makefile | 1 +
- drivers/platform/surface/surface_dtx.c | 1201 +++++++++++++++++
- include/uapi/linux/surface_aggregator/dtx.h | 146 ++
- 6 files changed, 1373 insertions(+)
- create mode 100644 drivers/platform/surface/surface_dtx.c
- create mode 100644 include/uapi/linux/surface_aggregator/dtx.h
- diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
- index 599bd4493944..1c28b8ef6677 100644
- --- a/Documentation/userspace-api/ioctl/ioctl-number.rst
- +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
- @@ -327,6 +327,8 @@ Code Seq# Include File Comments
- 0xA4 00-1F uapi/asm/sgx.h <mailto:linux-sgx@vger.kernel.org>
- 0xA5 01 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator
- <mailto:luzmaximilian@gmail.com>
- +0xA5 20-2F linux/surface_aggregator/dtx.h Microsoft Surface DTX driver
- + <mailto:luzmaximilian@gmail.com>
- 0xAA 00-3F linux/uapi/linux/userfaultfd.h
- 0xAB 00-1F linux/nbd.h
- 0xAC 00-1F linux/raw.h
- diff --git a/MAINTAINERS b/MAINTAINERS
- index fce5cdcefc0b..3917e7363520 100644
- --- a/MAINTAINERS
- +++ b/MAINTAINERS
- @@ -11868,6 +11868,13 @@ F: drivers/scsi/smartpqi/smartpqi*.[ch]
- F: include/linux/cciss*.h
- F: include/uapi/linux/cciss*.h
-
- +MICROSOFT SURFACE DTX DRIVER
- +M: Maximilian Luz <luzmaximilian@gmail.com>
- +L: platform-driver-x86@vger.kernel.org
- +S: Maintained
- +F: drivers/platform/surface/surface_dtx.c
- +F: include/uapi/linux/surface_aggregator/dtx.h
- +
- MICROSOFT SURFACE GPE LID SUPPORT DRIVER
- M: Maximilian Luz <luzmaximilian@gmail.com>
- L: platform-driver-x86@vger.kernel.org
- diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig
- index 6fb304da845f..41d67bb250fd 100644
- --- a/drivers/platform/surface/Kconfig
- +++ b/drivers/platform/surface/Kconfig
- @@ -111,6 +111,22 @@ config SURFACE_BOOK1_DGPU_SWITCH
- This driver provides a sysfs switch to set the power-state of the
- discrete GPU found on the Microsoft Surface Book 1.
-
- +config SURFACE_DTX
- + tristate "Surface DTX (Detachment System) Driver"
- + depends on SURFACE_AGGREGATOR
- + depends on INPUT
- + help
- + Driver for the Surface Book clipboard detachment system (DTX).
- +
- + On the Surface Book series devices, the display part containing the
- + CPU (called the clipboard) can be detached from the base (containing a
- + battery, the keyboard, and, optionally, a discrete GPU) by (if
- + necessary) unlocking and opening the latch connecting both parts.
- +
- + This driver provides a user-space interface that can influence the
- + behavior of this process, which includes the option to abort it in
- + case the base is still in use or speed it up in case it is not.
- +
- config SURFACE_GPE
- tristate "Surface GPE/Lid Support Driver"
- depends on DMI
- diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile
- index f7187bae1729..0cc63440328d 100644
- --- a/drivers/platform/surface/Makefile
- +++ b/drivers/platform/surface/Makefile
- @@ -12,6 +12,7 @@ obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/
- obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o
- obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o
- obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o
- +obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o
- obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o
- obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o
- obj-$(CONFIG_SURFACE_PLATFORM_PROFILE) += surface_platform_profile.o
- diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c
- new file mode 100644
- index 000000000000..1301fab0ea14
- --- /dev/null
- +++ b/drivers/platform/surface/surface_dtx.c
- @@ -0,0 +1,1201 @@
- +// SPDX-License-Identifier: GPL-2.0+
- +/*
- + * Surface Book (gen. 2 and later) detachment system (DTX) driver.
- + *
- + * Provides a user-space interface to properly handle clipboard/tablet
- + * (containing screen and processor) detachment from the base of the device
- + * (containing the keyboard and optionally a discrete GPU). Allows to
- + * acknowledge (to speed things up), abort (e.g. in case the dGPU is still in
- + * use), or request detachment via user-space.
- + *
- + * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
- + */
- +
- +#include <linux/fs.h>
- +#include <linux/input.h>
- +#include <linux/ioctl.h>
- +#include <linux/kernel.h>
- +#include <linux/kfifo.h>
- +#include <linux/kref.h>
- +#include <linux/miscdevice.h>
- +#include <linux/module.h>
- +#include <linux/mutex.h>
- +#include <linux/platform_device.h>
- +#include <linux/poll.h>
- +#include <linux/rwsem.h>
- +#include <linux/slab.h>
- +#include <linux/workqueue.h>
- +
- +#include <linux/surface_aggregator/controller.h>
- +#include <linux/surface_aggregator/dtx.h>
- +
- +
- +/* -- SSAM interface. ------------------------------------------------------- */
- +
- +enum sam_event_cid_bas {
- + SAM_EVENT_CID_DTX_CONNECTION = 0x0c,
- + SAM_EVENT_CID_DTX_REQUEST = 0x0e,
- + SAM_EVENT_CID_DTX_CANCEL = 0x0f,
- + SAM_EVENT_CID_DTX_LATCH_STATUS = 0x11,
- +};
- +
- +enum ssam_bas_base_state {
- + SSAM_BAS_BASE_STATE_DETACH_SUCCESS = 0x00,
- + SSAM_BAS_BASE_STATE_ATTACHED = 0x01,
- + SSAM_BAS_BASE_STATE_NOT_FEASIBLE = 0x02,
- +};
- +
- +enum ssam_bas_latch_status {
- + SSAM_BAS_LATCH_STATUS_CLOSED = 0x00,
- + SSAM_BAS_LATCH_STATUS_OPENED = 0x01,
- + SSAM_BAS_LATCH_STATUS_FAILED_TO_OPEN = 0x02,
- + SSAM_BAS_LATCH_STATUS_FAILED_TO_REMAIN_OPEN = 0x03,
- + SSAM_BAS_LATCH_STATUS_FAILED_TO_CLOSE = 0x04,
- +};
- +
- +enum ssam_bas_cancel_reason {
- + SSAM_BAS_CANCEL_REASON_NOT_FEASIBLE = 0x00, /* Low battery. */
- + SSAM_BAS_CANCEL_REASON_TIMEOUT = 0x02,
- + SSAM_BAS_CANCEL_REASON_FAILED_TO_OPEN = 0x03,
- + SSAM_BAS_CANCEL_REASON_FAILED_TO_REMAIN_OPEN = 0x04,
- + SSAM_BAS_CANCEL_REASON_FAILED_TO_CLOSE = 0x05,
- +};
- +
- +struct ssam_bas_base_info {
- + u8 state;
- + u8 base_id;
- +} __packed;
- +
- +static_assert(sizeof(struct ssam_bas_base_info) == 2);
- +
- +SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_lock, {
- + .target_category = SSAM_SSH_TC_BAS,
- + .target_id = 0x01,
- + .command_id = 0x06,
- + .instance_id = 0x00,
- +});
- +
- +SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_unlock, {
- + .target_category = SSAM_SSH_TC_BAS,
- + .target_id = 0x01,
- + .command_id = 0x07,
- + .instance_id = 0x00,
- +});
- +
- +SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_request, {
- + .target_category = SSAM_SSH_TC_BAS,
- + .target_id = 0x01,
- + .command_id = 0x08,
- + .instance_id = 0x00,
- +});
- +
- +SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_confirm, {
- + .target_category = SSAM_SSH_TC_BAS,
- + .target_id = 0x01,
- + .command_id = 0x09,
- + .instance_id = 0x00,
- +});
- +
- +SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_heartbeat, {
- + .target_category = SSAM_SSH_TC_BAS,
- + .target_id = 0x01,
- + .command_id = 0x0a,
- + .instance_id = 0x00,
- +});
- +
- +SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_cancel, {
- + .target_category = SSAM_SSH_TC_BAS,
- + .target_id = 0x01,
- + .command_id = 0x0b,
- + .instance_id = 0x00,
- +});
- +
- +SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_base, struct ssam_bas_base_info, {
- + .target_category = SSAM_SSH_TC_BAS,
- + .target_id = 0x01,
- + .command_id = 0x0c,
- + .instance_id = 0x00,
- +});
- +
- +SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_device_mode, u8, {
- + .target_category = SSAM_SSH_TC_BAS,
- + .target_id = 0x01,
- + .command_id = 0x0d,
- + .instance_id = 0x00,
- +});
- +
- +SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_latch_status, u8, {
- + .target_category = SSAM_SSH_TC_BAS,
- + .target_id = 0x01,
- + .command_id = 0x11,
- + .instance_id = 0x00,
- +});
- +
- +
- +/* -- Main structures. ------------------------------------------------------ */
- +
- +enum sdtx_device_state {
- + SDTX_DEVICE_SHUTDOWN_BIT = BIT(0),
- + SDTX_DEVICE_DIRTY_BASE_BIT = BIT(1),
- + SDTX_DEVICE_DIRTY_MODE_BIT = BIT(2),
- + SDTX_DEVICE_DIRTY_LATCH_BIT = BIT(3),
- +};
- +
- +struct sdtx_device {
- + struct kref kref;
- + struct rw_semaphore lock; /* Guards device and controller reference. */
- +
- + struct device *dev;
- + struct ssam_controller *ctrl;
- + unsigned long flags;
- +
- + struct miscdevice mdev;
- + wait_queue_head_t waitq;
- + struct mutex write_lock; /* Guards order of events/notifications. */
- + struct rw_semaphore client_lock; /* Guards client list. */
- + struct list_head client_list;
- +
- + struct delayed_work state_work;
- + struct {
- + struct ssam_bas_base_info base;
- + u8 device_mode;
- + u8 latch_status;
- + } state;
- +
- + struct delayed_work mode_work;
- + struct input_dev *mode_switch;
- +
- + struct ssam_event_notifier notif;
- +};
- +
- +enum sdtx_client_state {
- + SDTX_CLIENT_EVENTS_ENABLED_BIT = BIT(0),
- +};
- +
- +struct sdtx_client {
- + struct sdtx_device *ddev;
- + struct list_head node;
- + unsigned long flags;
- +
- + struct fasync_struct *fasync;
- +
- + struct mutex read_lock; /* Guards FIFO buffer read access. */
- + DECLARE_KFIFO(buffer, u8, 512);
- +};
- +
- +static void __sdtx_device_release(struct kref *kref)
- +{
- + struct sdtx_device *ddev = container_of(kref, struct sdtx_device, kref);
- +
- + mutex_destroy(&ddev->write_lock);
- + kfree(ddev);
- +}
- +
- +static struct sdtx_device *sdtx_device_get(struct sdtx_device *ddev)
- +{
- + if (ddev)
- + kref_get(&ddev->kref);
- +
- + return ddev;
- +}
- +
- +static void sdtx_device_put(struct sdtx_device *ddev)
- +{
- + if (ddev)
- + kref_put(&ddev->kref, __sdtx_device_release);
- +}
- +
- +
- +/* -- Firmware value translations. ------------------------------------------ */
- +
- +static u16 sdtx_translate_base_state(struct sdtx_device *ddev, u8 state)
- +{
- + switch (state) {
- + case SSAM_BAS_BASE_STATE_ATTACHED:
- + return SDTX_BASE_ATTACHED;
- +
- + case SSAM_BAS_BASE_STATE_DETACH_SUCCESS:
- + return SDTX_BASE_DETACHED;
- +
- + case SSAM_BAS_BASE_STATE_NOT_FEASIBLE:
- + return SDTX_DETACH_NOT_FEASIBLE;
- +
- + default:
- + dev_err(ddev->dev, "unknown base state: %#04x\n", state);
- + return SDTX_UNKNOWN(state);
- + }
- +}
- +
- +static u16 sdtx_translate_latch_status(struct sdtx_device *ddev, u8 status)
- +{
- + switch (status) {
- + case SSAM_BAS_LATCH_STATUS_CLOSED:
- + return SDTX_LATCH_CLOSED;
- +
- + case SSAM_BAS_LATCH_STATUS_OPENED:
- + return SDTX_LATCH_OPENED;
- +
- + case SSAM_BAS_LATCH_STATUS_FAILED_TO_OPEN:
- + return SDTX_ERR_FAILED_TO_OPEN;
- +
- + case SSAM_BAS_LATCH_STATUS_FAILED_TO_REMAIN_OPEN:
- + return SDTX_ERR_FAILED_TO_REMAIN_OPEN;
- +
- + case SSAM_BAS_LATCH_STATUS_FAILED_TO_CLOSE:
- + return SDTX_ERR_FAILED_TO_CLOSE;
- +
- + default:
- + dev_err(ddev->dev, "unknown latch status: %#04x\n", status);
- + return SDTX_UNKNOWN(status);
- + }
- +}
- +
- +static u16 sdtx_translate_cancel_reason(struct sdtx_device *ddev, u8 reason)
- +{
- + switch (reason) {
- + case SSAM_BAS_CANCEL_REASON_NOT_FEASIBLE:
- + return SDTX_DETACH_NOT_FEASIBLE;
- +
- + case SSAM_BAS_CANCEL_REASON_TIMEOUT:
- + return SDTX_DETACH_TIMEDOUT;
- +
- + case SSAM_BAS_CANCEL_REASON_FAILED_TO_OPEN:
- + return SDTX_ERR_FAILED_TO_OPEN;
- +
- + case SSAM_BAS_CANCEL_REASON_FAILED_TO_REMAIN_OPEN:
- + return SDTX_ERR_FAILED_TO_REMAIN_OPEN;
- +
- + case SSAM_BAS_CANCEL_REASON_FAILED_TO_CLOSE:
- + return SDTX_ERR_FAILED_TO_CLOSE;
- +
- + default:
- + dev_err(ddev->dev, "unknown cancel reason: %#04x\n", reason);
- + return SDTX_UNKNOWN(reason);
- + }
- +}
- +
- +
- +/* -- IOCTLs. --------------------------------------------------------------- */
- +
- +static int sdtx_ioctl_get_base_info(struct sdtx_device *ddev,
- + struct sdtx_base_info __user *buf)
- +{
- + struct ssam_bas_base_info raw;
- + struct sdtx_base_info info;
- + int status;
- +
- + lockdep_assert_held_read(&ddev->lock);
- +
- + status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &raw);
- + if (status < 0)
- + return status;
- +
- + info.state = sdtx_translate_base_state(ddev, raw.state);
- + info.base_id = SDTX_BASE_TYPE_SSH(raw.base_id);
- +
- + if (copy_to_user(buf, &info, sizeof(info)))
- + return -EFAULT;
- +
- + return 0;
- +}
- +
- +static int sdtx_ioctl_get_device_mode(struct sdtx_device *ddev, u16 __user *buf)
- +{
- + u8 mode;
- + int status;
- +
- + lockdep_assert_held_read(&ddev->lock);
- +
- + status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode);
- + if (status < 0)
- + return status;
- +
- + return put_user(mode, buf);
- +}
- +
- +static int sdtx_ioctl_get_latch_status(struct sdtx_device *ddev, u16 __user *buf)
- +{
- + u8 latch;
- + int status;
- +
- + lockdep_assert_held_read(&ddev->lock);
- +
- + status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &latch);
- + if (status < 0)
- + return status;
- +
- + return put_user(sdtx_translate_latch_status(ddev, latch), buf);
- +}
- +
- +static long __surface_dtx_ioctl(struct sdtx_client *client, unsigned int cmd, unsigned long arg)
- +{
- + struct sdtx_device *ddev = client->ddev;
- +
- + lockdep_assert_held_read(&ddev->lock);
- +
- + switch (cmd) {
- + case SDTX_IOCTL_EVENTS_ENABLE:
- + set_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags);
- + return 0;
- +
- + case SDTX_IOCTL_EVENTS_DISABLE:
- + clear_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags);
- + return 0;
- +
- + case SDTX_IOCTL_LATCH_LOCK:
- + return ssam_retry(ssam_bas_latch_lock, ddev->ctrl);
- +
- + case SDTX_IOCTL_LATCH_UNLOCK:
- + return ssam_retry(ssam_bas_latch_unlock, ddev->ctrl);
- +
- + case SDTX_IOCTL_LATCH_REQUEST:
- + return ssam_retry(ssam_bas_latch_request, ddev->ctrl);
- +
- + case SDTX_IOCTL_LATCH_CONFIRM:
- + return ssam_retry(ssam_bas_latch_confirm, ddev->ctrl);
- +
- + case SDTX_IOCTL_LATCH_HEARTBEAT:
- + return ssam_retry(ssam_bas_latch_heartbeat, ddev->ctrl);
- +
- + case SDTX_IOCTL_LATCH_CANCEL:
- + return ssam_retry(ssam_bas_latch_cancel, ddev->ctrl);
- +
- + case SDTX_IOCTL_GET_BASE_INFO:
- + return sdtx_ioctl_get_base_info(ddev, (struct sdtx_base_info __user *)arg);
- +
- + case SDTX_IOCTL_GET_DEVICE_MODE:
- + return sdtx_ioctl_get_device_mode(ddev, (u16 __user *)arg);
- +
- + case SDTX_IOCTL_GET_LATCH_STATUS:
- + return sdtx_ioctl_get_latch_status(ddev, (u16 __user *)arg);
- +
- + default:
- + return -EINVAL;
- + }
- +}
- +
- +static long surface_dtx_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
- +{
- + struct sdtx_client *client = file->private_data;
- + long status;
- +
- + if (down_read_killable(&client->ddev->lock))
- + return -ERESTARTSYS;
- +
- + if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags)) {
- + up_read(&client->ddev->lock);
- + return -ENODEV;
- + }
- +
- + status = __surface_dtx_ioctl(client, cmd, arg);
- +
- + up_read(&client->ddev->lock);
- + return status;
- +}
- +
- +
- +/* -- File operations. ------------------------------------------------------ */
- +
- +static int surface_dtx_open(struct inode *inode, struct file *file)
- +{
- + struct sdtx_device *ddev = container_of(file->private_data, struct sdtx_device, mdev);
- + struct sdtx_client *client;
- +
- + /* Initialize client. */
- + client = kzalloc(sizeof(*client), GFP_KERNEL);
- + if (!client)
- + return -ENOMEM;
- +
- + client->ddev = sdtx_device_get(ddev);
- +
- + INIT_LIST_HEAD(&client->node);
- +
- + mutex_init(&client->read_lock);
- + INIT_KFIFO(client->buffer);
- +
- + file->private_data = client;
- +
- + /* Attach client. */
- + down_write(&ddev->client_lock);
- +
- + /*
- + * Do not add a new client if the device has been shut down. Note that
- + * it's enough to hold the client_lock here as, during shutdown, we
- + * only acquire that lock and remove clients after marking the device
- + * as shut down.
- + */
- + if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) {
- + up_write(&ddev->client_lock);
- + sdtx_device_put(client->ddev);
- + kfree(client);
- + return -ENODEV;
- + }
- +
- + list_add_tail(&client->node, &ddev->client_list);
- + up_write(&ddev->client_lock);
- +
- + stream_open(inode, file);
- + return 0;
- +}
- +
- +static int surface_dtx_release(struct inode *inode, struct file *file)
- +{
- + struct sdtx_client *client = file->private_data;
- +
- + /* Detach client. */
- + down_write(&client->ddev->client_lock);
- + list_del(&client->node);
- + up_write(&client->ddev->client_lock);
- +
- + /* Free client. */
- + sdtx_device_put(client->ddev);
- + mutex_destroy(&client->read_lock);
- + kfree(client);
- +
- + return 0;
- +}
- +
- +static ssize_t surface_dtx_read(struct file *file, char __user *buf, size_t count, loff_t *offs)
- +{
- + struct sdtx_client *client = file->private_data;
- + struct sdtx_device *ddev = client->ddev;
- + unsigned int copied;
- + int status = 0;
- +
- + if (down_read_killable(&ddev->lock))
- + return -ERESTARTSYS;
- +
- + /* Make sure we're not shut down. */
- + if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) {
- + up_read(&ddev->lock);
- + return -ENODEV;
- + }
- +
- + do {
- + /* Check availability, wait if necessary. */
- + if (kfifo_is_empty(&client->buffer)) {
- + up_read(&ddev->lock);
- +
- + if (file->f_flags & O_NONBLOCK)
- + return -EAGAIN;
- +
- + status = wait_event_interruptible(ddev->waitq,
- + !kfifo_is_empty(&client->buffer) ||
- + test_bit(SDTX_DEVICE_SHUTDOWN_BIT,
- + &ddev->flags));
- + if (status < 0)
- + return status;
- +
- + if (down_read_killable(&client->ddev->lock))
- + return -ERESTARTSYS;
- +
- + /* Need to check that we're not shut down again. */
- + if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) {
- + up_read(&ddev->lock);
- + return -ENODEV;
- + }
- + }
- +
- + /* Try to read from FIFO. */
- + if (mutex_lock_interruptible(&client->read_lock)) {
- + up_read(&ddev->lock);
- + return -ERESTARTSYS;
- + }
- +
- + status = kfifo_to_user(&client->buffer, buf, count, &copied);
- + mutex_unlock(&client->read_lock);
- +
- + if (status < 0) {
- + up_read(&ddev->lock);
- + return status;
- + }
- +
- + /* We might not have gotten anything, check this here. */
- + if (copied == 0 && (file->f_flags & O_NONBLOCK)) {
- + up_read(&ddev->lock);
- + return -EAGAIN;
- + }
- + } while (copied == 0);
- +
- + up_read(&ddev->lock);
- + return copied;
- +}
- +
- +static __poll_t surface_dtx_poll(struct file *file, struct poll_table_struct *pt)
- +{
- + struct sdtx_client *client = file->private_data;
- + __poll_t events = 0;
- +
- + if (down_read_killable(&client->ddev->lock))
- + return -ERESTARTSYS;
- +
- + if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags)) {
- + up_read(&client->ddev->lock);
- + return EPOLLHUP | EPOLLERR;
- + }
- +
- + poll_wait(file, &client->ddev->waitq, pt);
- +
- + if (!kfifo_is_empty(&client->buffer))
- + events |= EPOLLIN | EPOLLRDNORM;
- +
- + up_read(&client->ddev->lock);
- + return events;
- +}
- +
- +static int surface_dtx_fasync(int fd, struct file *file, int on)
- +{
- + struct sdtx_client *client = file->private_data;
- +
- + return fasync_helper(fd, file, on, &client->fasync);
- +}
- +
- +static const struct file_operations surface_dtx_fops = {
- + .owner = THIS_MODULE,
- + .open = surface_dtx_open,
- + .release = surface_dtx_release,
- + .read = surface_dtx_read,
- + .poll = surface_dtx_poll,
- + .fasync = surface_dtx_fasync,
- + .unlocked_ioctl = surface_dtx_ioctl,
- + .compat_ioctl = surface_dtx_ioctl,
- + .llseek = no_llseek,
- +};
- +
- +
- +/* -- Event handling/forwarding. -------------------------------------------- */
- +
- +/*
- + * The device operation mode is not immediately updated on the EC when the
- + * base has been connected, i.e. querying the device mode inside the
- + * connection event callback yields an outdated value. Thus, we can only
- + * determine the new tablet-mode switch and device mode values after some
- + * time.
- + *
- + * These delays have been chosen by experimenting. We first delay on connect
- + * events, then check and validate the device mode against the base state and
- + * if invalid delay again by the "recheck" delay.
- + */
- +#define SDTX_DEVICE_MODE_DELAY_CONNECT msecs_to_jiffies(100)
- +#define SDTX_DEVICE_MODE_DELAY_RECHECK msecs_to_jiffies(100)
- +
- +struct sdtx_status_event {
- + struct sdtx_event e;
- + __u16 v;
- +} __packed;
- +
- +struct sdtx_base_info_event {
- + struct sdtx_event e;
- + struct sdtx_base_info v;
- +} __packed;
- +
- +union sdtx_generic_event {
- + struct sdtx_event common;
- + struct sdtx_status_event status;
- + struct sdtx_base_info_event base;
- +};
- +
- +static void sdtx_update_device_mode(struct sdtx_device *ddev, unsigned long delay);
- +
- +/* Must be executed with ddev->write_lock held. */
- +static void sdtx_push_event(struct sdtx_device *ddev, struct sdtx_event *evt)
- +{
- + const size_t len = sizeof(struct sdtx_event) + evt->length;
- + struct sdtx_client *client;
- +
- + lockdep_assert_held(&ddev->write_lock);
- +
- + down_read(&ddev->client_lock);
- + list_for_each_entry(client, &ddev->client_list, node) {
- + if (!test_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags))
- + continue;
- +
- + if (likely(kfifo_avail(&client->buffer) >= len))
- + kfifo_in(&client->buffer, (const u8 *)evt, len);
- + else
- + dev_warn(ddev->dev, "event buffer overrun\n");
- +
- + kill_fasync(&client->fasync, SIGIO, POLL_IN);
- + }
- + up_read(&ddev->client_lock);
- +
- + wake_up_interruptible(&ddev->waitq);
- +}
- +
- +static u32 sdtx_notifier(struct ssam_event_notifier *nf, const struct ssam_event *in)
- +{
- + struct sdtx_device *ddev = container_of(nf, struct sdtx_device, notif);
- + union sdtx_generic_event event;
- + size_t len;
- +
- + /* Validate event payload length. */
- + switch (in->command_id) {
- + case SAM_EVENT_CID_DTX_CONNECTION:
- + len = 2 * sizeof(u8);
- + break;
- +
- + case SAM_EVENT_CID_DTX_REQUEST:
- + len = 0;
- + break;
- +
- + case SAM_EVENT_CID_DTX_CANCEL:
- + len = sizeof(u8);
- + break;
- +
- + case SAM_EVENT_CID_DTX_LATCH_STATUS:
- + len = sizeof(u8);
- + break;
- +
- + default:
- + return 0;
- + };
- +
- + if (in->length != len) {
- + dev_err(ddev->dev,
- + "unexpected payload size for event %#04x: got %u, expected %zu\n",
- + in->command_id, in->length, len);
- + return 0;
- + }
- +
- + mutex_lock(&ddev->write_lock);
- +
- + /* Translate event. */
- + switch (in->command_id) {
- + case SAM_EVENT_CID_DTX_CONNECTION:
- + clear_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags);
- +
- + /* If state has not changed: do not send new event. */
- + if (ddev->state.base.state == in->data[0] &&
- + ddev->state.base.base_id == in->data[1])
- + goto out;
- +
- + ddev->state.base.state = in->data[0];
- + ddev->state.base.base_id = in->data[1];
- +
- + event.base.e.length = sizeof(struct sdtx_base_info);
- + event.base.e.code = SDTX_EVENT_BASE_CONNECTION;
- + event.base.v.state = sdtx_translate_base_state(ddev, in->data[0]);
- + event.base.v.base_id = SDTX_BASE_TYPE_SSH(in->data[1]);
- + break;
- +
- + case SAM_EVENT_CID_DTX_REQUEST:
- + event.common.code = SDTX_EVENT_REQUEST;
- + event.common.length = 0;
- + break;
- +
- + case SAM_EVENT_CID_DTX_CANCEL:
- + event.status.e.length = sizeof(u16);
- + event.status.e.code = SDTX_EVENT_CANCEL;
- + event.status.v = sdtx_translate_cancel_reason(ddev, in->data[0]);
- + break;
- +
- + case SAM_EVENT_CID_DTX_LATCH_STATUS:
- + clear_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags);
- +
- + /* If state has not changed: do not send new event. */
- + if (ddev->state.latch_status == in->data[0])
- + goto out;
- +
- + ddev->state.latch_status = in->data[0];
- +
- + event.status.e.length = sizeof(u16);
- + event.status.e.code = SDTX_EVENT_LATCH_STATUS;
- + event.status.v = sdtx_translate_latch_status(ddev, in->data[0]);
- + break;
- + }
- +
- + sdtx_push_event(ddev, &event.common);
- +
- + /* Update device mode on base connection change. */
- + if (in->command_id == SAM_EVENT_CID_DTX_CONNECTION) {
- + unsigned long delay;
- +
- + delay = in->data[0] ? SDTX_DEVICE_MODE_DELAY_CONNECT : 0;
- + sdtx_update_device_mode(ddev, delay);
- + }
- +
- +out:
- + mutex_unlock(&ddev->write_lock);
- + return SSAM_NOTIF_HANDLED;
- +}
- +
- +
- +/* -- State update functions. ----------------------------------------------- */
- +
- +static bool sdtx_device_mode_invalid(u8 mode, u8 base_state)
- +{
- + return ((base_state == SSAM_BAS_BASE_STATE_ATTACHED) &&
- + (mode == SDTX_DEVICE_MODE_TABLET)) ||
- + ((base_state == SSAM_BAS_BASE_STATE_DETACH_SUCCESS) &&
- + (mode != SDTX_DEVICE_MODE_TABLET));
- +}
- +
- +static void sdtx_device_mode_workfn(struct work_struct *work)
- +{
- + struct sdtx_device *ddev = container_of(work, struct sdtx_device, mode_work.work);
- + struct sdtx_status_event event;
- + struct ssam_bas_base_info base;
- + int status, tablet;
- + u8 mode;
- +
- + /* Get operation mode. */
- + status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode);
- + if (status) {
- + dev_err(ddev->dev, "failed to get device mode: %d\n", status);
- + return;
- + }
- +
- + /* Get base info. */
- + status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &base);
- + if (status) {
- + dev_err(ddev->dev, "failed to get base info: %d\n", status);
- + return;
- + }
- +
- + /*
- + * In some cases (specifically when attaching the base), the device
- + * mode isn't updated right away. Thus we check if the device mode
- + * makes sense for the given base state and try again later if it
- + * doesn't.
- + */
- + if (sdtx_device_mode_invalid(mode, base.state)) {
- + dev_dbg(ddev->dev, "device mode is invalid, trying again\n");
- + sdtx_update_device_mode(ddev, SDTX_DEVICE_MODE_DELAY_RECHECK);
- + return;
- + }
- +
- + mutex_lock(&ddev->write_lock);
- + clear_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags);
- +
- + /* Avoid sending duplicate device-mode events. */
- + if (ddev->state.device_mode == mode) {
- + mutex_unlock(&ddev->write_lock);
- + return;
- + }
- +
- + ddev->state.device_mode = mode;
- +
- + event.e.length = sizeof(u16);
- + event.e.code = SDTX_EVENT_DEVICE_MODE;
- + event.v = mode;
- +
- + sdtx_push_event(ddev, &event.e);
- +
- + /* Send SW_TABLET_MODE event. */
- + tablet = mode != SDTX_DEVICE_MODE_LAPTOP;
- + input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet);
- + input_sync(ddev->mode_switch);
- +
- + mutex_unlock(&ddev->write_lock);
- +}
- +
- +static void sdtx_update_device_mode(struct sdtx_device *ddev, unsigned long delay)
- +{
- + schedule_delayed_work(&ddev->mode_work, delay);
- +}
- +
- +/* Must be executed with ddev->write_lock held. */
- +static void __sdtx_device_state_update_base(struct sdtx_device *ddev,
- + struct ssam_bas_base_info info)
- +{
- + struct sdtx_base_info_event event;
- +
- + lockdep_assert_held(&ddev->write_lock);
- +
- + /* Prevent duplicate events. */
- + if (ddev->state.base.state == info.state &&
- + ddev->state.base.base_id == info.base_id)
- + return;
- +
- + ddev->state.base = info;
- +
- + event.e.length = sizeof(struct sdtx_base_info);
- + event.e.code = SDTX_EVENT_BASE_CONNECTION;
- + event.v.state = sdtx_translate_base_state(ddev, info.state);
- + event.v.base_id = SDTX_BASE_TYPE_SSH(info.base_id);
- +
- + sdtx_push_event(ddev, &event.e);
- +}
- +
- +/* Must be executed with ddev->write_lock held. */
- +static void __sdtx_device_state_update_mode(struct sdtx_device *ddev, u8 mode)
- +{
- + struct sdtx_status_event event;
- + int tablet;
- +
- + /*
- + * Note: This function must be called after updating the base state
- + * via __sdtx_device_state_update_base(), as we rely on the updated
- + * base state value in the validity check below.
- + */
- +
- + lockdep_assert_held(&ddev->write_lock);
- +
- + if (sdtx_device_mode_invalid(mode, ddev->state.base.state)) {
- + dev_dbg(ddev->dev, "device mode is invalid, trying again\n");
- + sdtx_update_device_mode(ddev, SDTX_DEVICE_MODE_DELAY_RECHECK);
- + return;
- + }
- +
- + /* Prevent duplicate events. */
- + if (ddev->state.device_mode == mode)
- + return;
- +
- + ddev->state.device_mode = mode;
- +
- + /* Send event. */
- + event.e.length = sizeof(u16);
- + event.e.code = SDTX_EVENT_DEVICE_MODE;
- + event.v = mode;
- +
- + sdtx_push_event(ddev, &event.e);
- +
- + /* Send SW_TABLET_MODE event. */
- + tablet = mode != SDTX_DEVICE_MODE_LAPTOP;
- + input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet);
- + input_sync(ddev->mode_switch);
- +}
- +
- +/* Must be executed with ddev->write_lock held. */
- +static void __sdtx_device_state_update_latch(struct sdtx_device *ddev, u8 status)
- +{
- + struct sdtx_status_event event;
- +
- + lockdep_assert_held(&ddev->write_lock);
- +
- + /* Prevent duplicate events. */
- + if (ddev->state.latch_status == status)
- + return;
- +
- + ddev->state.latch_status = status;
- +
- + event.e.length = sizeof(struct sdtx_base_info);
- + event.e.code = SDTX_EVENT_BASE_CONNECTION;
- + event.v = sdtx_translate_latch_status(ddev, status);
- +
- + sdtx_push_event(ddev, &event.e);
- +}
- +
- +static void sdtx_device_state_workfn(struct work_struct *work)
- +{
- + struct sdtx_device *ddev = container_of(work, struct sdtx_device, state_work.work);
- + struct ssam_bas_base_info base;
- + u8 mode, latch;
- + int status;
- +
- + /* Mark everything as dirty. */
- + set_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags);
- + set_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags);
- + set_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags);
- +
- + /*
- + * Ensure that the state gets marked as dirty before continuing to
- + * query it. Necessary to ensure that clear_bit() calls in
- + * sdtx_notifier() and sdtx_device_mode_workfn() actually clear these
- + * bits if an event is received while updating the state here.
- + */
- + smp_mb__after_atomic();
- +
- + status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &base);
- + if (status) {
- + dev_err(ddev->dev, "failed to get base state: %d\n", status);
- + return;
- + }
- +
- + status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode);
- + if (status) {
- + dev_err(ddev->dev, "failed to get device mode: %d\n", status);
- + return;
- + }
- +
- + status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &latch);
- + if (status) {
- + dev_err(ddev->dev, "failed to get latch status: %d\n", status);
- + return;
- + }
- +
- + mutex_lock(&ddev->write_lock);
- +
- + /*
- + * If the respective dirty-bit has been cleared, an event has been
- + * received, updating this state. The queried state may thus be out of
- + * date. At this point, we can safely assume that the state provided
- + * by the event is either up to date, or we're about to receive
- + * another event updating it.
- + */
- +
- + if (test_and_clear_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags))
- + __sdtx_device_state_update_base(ddev, base);
- +
- + if (test_and_clear_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags))
- + __sdtx_device_state_update_mode(ddev, mode);
- +
- + if (test_and_clear_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags))
- + __sdtx_device_state_update_latch(ddev, latch);
- +
- + mutex_unlock(&ddev->write_lock);
- +}
- +
- +static void sdtx_update_device_state(struct sdtx_device *ddev, unsigned long delay)
- +{
- + schedule_delayed_work(&ddev->state_work, delay);
- +}
- +
- +
- +/* -- Common device initialization. ----------------------------------------- */
- +
- +static int sdtx_device_init(struct sdtx_device *ddev, struct device *dev,
- + struct ssam_controller *ctrl)
- +{
- + int status, tablet_mode;
- +
- + /* Basic initialization. */
- + kref_init(&ddev->kref);
- + init_rwsem(&ddev->lock);
- + ddev->dev = dev;
- + ddev->ctrl = ctrl;
- +
- + ddev->mdev.minor = MISC_DYNAMIC_MINOR;
- + ddev->mdev.name = "surface_dtx";
- + ddev->mdev.nodename = "surface/dtx";
- + ddev->mdev.fops = &surface_dtx_fops;
- +
- + ddev->notif.base.priority = 1;
- + ddev->notif.base.fn = sdtx_notifier;
- + ddev->notif.event.reg = SSAM_EVENT_REGISTRY_SAM;
- + ddev->notif.event.id.target_category = SSAM_SSH_TC_BAS;
- + ddev->notif.event.id.instance = 0;
- + ddev->notif.event.mask = SSAM_EVENT_MASK_NONE;
- + ddev->notif.event.flags = SSAM_EVENT_SEQUENCED;
- +
- + init_waitqueue_head(&ddev->waitq);
- + mutex_init(&ddev->write_lock);
- + init_rwsem(&ddev->client_lock);
- + INIT_LIST_HEAD(&ddev->client_list);
- +
- + INIT_DELAYED_WORK(&ddev->mode_work, sdtx_device_mode_workfn);
- + INIT_DELAYED_WORK(&ddev->state_work, sdtx_device_state_workfn);
- +
- + /*
- + * Get current device state. We want to guarantee that events are only
- + * sent when state actually changes. Thus we cannot use special
- + * "uninitialized" values, as that would cause problems when manually
- + * querying the state in surface_dtx_pm_complete(). I.e. we would not
- + * be able to detect state changes there if no change event has been
- + * received between driver initialization and first device suspension.
- + *
- + * Note that we also need to do this before registering the event
- + * notifier, as that may access the state values.
- + */
- + status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &ddev->state.base);
- + if (status)
- + return status;
- +
- + status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &ddev->state.device_mode);
- + if (status)
- + return status;
- +
- + status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &ddev->state.latch_status);
- + if (status)
- + return status;
- +
- + /* Set up tablet mode switch. */
- + ddev->mode_switch = input_allocate_device();
- + if (!ddev->mode_switch)
- + return -ENOMEM;
- +
- + ddev->mode_switch->name = "Microsoft Surface DTX Device Mode Switch";
- + ddev->mode_switch->phys = "ssam/01:11:01:00:00/input0";
- + ddev->mode_switch->id.bustype = BUS_HOST;
- + ddev->mode_switch->dev.parent = ddev->dev;
- +
- + tablet_mode = (ddev->state.device_mode != SDTX_DEVICE_MODE_LAPTOP);
- + input_set_capability(ddev->mode_switch, EV_SW, SW_TABLET_MODE);
- + input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet_mode);
- +
- + status = input_register_device(ddev->mode_switch);
- + if (status) {
- + input_free_device(ddev->mode_switch);
- + return status;
- + }
- +
- + /* Set up event notifier. */
- + status = ssam_notifier_register(ddev->ctrl, &ddev->notif);
- + if (status)
- + goto err_notif;
- +
- + /* Register miscdevice. */
- + status = misc_register(&ddev->mdev);
- + if (status)
- + goto err_mdev;
- +
- + /*
- + * Update device state in case it has changed between getting the
- + * initial mode and registering the event notifier.
- + */
- + sdtx_update_device_state(ddev, 0);
- + return 0;
- +
- +err_notif:
- + ssam_notifier_unregister(ddev->ctrl, &ddev->notif);
- + cancel_delayed_work_sync(&ddev->mode_work);
- +err_mdev:
- + input_unregister_device(ddev->mode_switch);
- + return status;
- +}
- +
- +static struct sdtx_device *sdtx_device_create(struct device *dev, struct ssam_controller *ctrl)
- +{
- + struct sdtx_device *ddev;
- + int status;
- +
- + ddev = kzalloc(sizeof(*ddev), GFP_KERNEL);
- + if (!ddev)
- + return ERR_PTR(-ENOMEM);
- +
- + status = sdtx_device_init(ddev, dev, ctrl);
- + if (status) {
- + sdtx_device_put(ddev);
- + return ERR_PTR(status);
- + }
- +
- + return ddev;
- +}
- +
- +static void sdtx_device_destroy(struct sdtx_device *ddev)
- +{
- + struct sdtx_client *client;
- +
- + /*
- + * Mark device as shut-down. Prevent new clients from being added and
- + * new operations from being executed.
- + */
- + set_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags);
- +
- + /* Disable notifiers, prevent new events from arriving. */
- + ssam_notifier_unregister(ddev->ctrl, &ddev->notif);
- +
- + /* Stop mode_work, prevent access to mode_switch. */
- + cancel_delayed_work_sync(&ddev->mode_work);
- +
- + /* Stop state_work. */
- + cancel_delayed_work_sync(&ddev->state_work);
- +
- + /* With mode_work canceled, we can unregister the mode_switch. */
- + input_unregister_device(ddev->mode_switch);
- +
- + /* Wake up async clients. */
- + down_write(&ddev->client_lock);
- + list_for_each_entry(client, &ddev->client_list, node) {
- + kill_fasync(&client->fasync, SIGIO, POLL_HUP);
- + }
- + up_write(&ddev->client_lock);
- +
- + /* Wake up blocking clients. */
- + wake_up_interruptible(&ddev->waitq);
- +
- + /*
- + * Wait for clients to finish their current operation. After this, the
- + * controller and device references are guaranteed to be no longer in
- + * use.
- + */
- + down_write(&ddev->lock);
- + ddev->dev = NULL;
- + ddev->ctrl = NULL;
- + up_write(&ddev->lock);
- +
- + /* Finally remove the misc-device. */
- + misc_deregister(&ddev->mdev);
- +
- + /*
- + * We're now guaranteed that sdtx_device_open() won't be called any
- + * more, so we can now drop out reference.
- + */
- + sdtx_device_put(ddev);
- +}
- +
- +
- +/* -- PM ops. --------------------------------------------------------------- */
- +
- +#ifdef CONFIG_PM_SLEEP
- +
- +static void surface_dtx_pm_complete(struct device *dev)
- +{
- + struct sdtx_device *ddev = dev_get_drvdata(dev);
- +
- + /*
- + * Normally, the EC will store events while suspended (i.e. in
- + * display-off state) and release them when resumed (i.e. transitioned
- + * to display-on state). During hibernation, however, the EC will be
- + * shut down and does not store events. Furthermore, events might be
- + * dropped during prolonged suspension (it is currently unknown how
- + * big this event buffer is and how it behaves on overruns).
- + *
- + * To prevent any problems, we update the device state here. We do
- + * this delayed to ensure that any events sent by the EC directly
- + * after resuming will be handled first. The delay below has been
- + * chosen (experimentally), so that there should be ample time for
- + * these events to be handled, before we check and, if necessary,
- + * update the state.
- + */
- + sdtx_update_device_state(ddev, msecs_to_jiffies(1000));
- +}
- +
- +static const struct dev_pm_ops surface_dtx_pm_ops = {
- + .complete = surface_dtx_pm_complete,
- +};
- +
- +#else /* CONFIG_PM_SLEEP */
- +
- +static const struct dev_pm_ops surface_dtx_pm_ops = {};
- +
- +#endif /* CONFIG_PM_SLEEP */
- +
- +
- +/* -- Platform driver. ------------------------------------------------------ */
- +
- +static int surface_dtx_platform_probe(struct platform_device *pdev)
- +{
- + struct ssam_controller *ctrl;
- + struct sdtx_device *ddev;
- +
- + /* Link to EC. */
- + ctrl = ssam_client_bind(&pdev->dev);
- + if (IS_ERR(ctrl))
- + return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl);
- +
- + ddev = sdtx_device_create(&pdev->dev, ctrl);
- + if (IS_ERR(ddev))
- + return PTR_ERR(ddev);
- +
- + platform_set_drvdata(pdev, ddev);
- + return 0;
- +}
- +
- +static int surface_dtx_platform_remove(struct platform_device *pdev)
- +{
- + sdtx_device_destroy(platform_get_drvdata(pdev));
- + return 0;
- +}
- +
- +static const struct acpi_device_id surface_dtx_acpi_match[] = {
- + { "MSHW0133", 0 },
- + { },
- +};
- +MODULE_DEVICE_TABLE(acpi, surface_dtx_acpi_match);
- +
- +static struct platform_driver surface_dtx_platform_driver = {
- + .probe = surface_dtx_platform_probe,
- + .remove = surface_dtx_platform_remove,
- + .driver = {
- + .name = "surface_dtx_pltf",
- + .acpi_match_table = surface_dtx_acpi_match,
- + .pm = &surface_dtx_pm_ops,
- + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
- + },
- +};
- +module_platform_driver(surface_dtx_platform_driver);
- +
- +MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
- +MODULE_DESCRIPTION("Detachment-system driver for Surface System Aggregator Module");
- +MODULE_LICENSE("GPL");
- diff --git a/include/uapi/linux/surface_aggregator/dtx.h b/include/uapi/linux/surface_aggregator/dtx.h
- new file mode 100644
- index 000000000000..0833aab0d819
- --- /dev/null
- +++ b/include/uapi/linux/surface_aggregator/dtx.h
- @@ -0,0 +1,146 @@
- +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
- +/*
- + * Surface DTX (clipboard detachment system driver) user-space interface.
- + *
- + * Definitions, structs, and IOCTLs for the /dev/surface/dtx misc device. This
- + * device allows user-space to control the clipboard detachment process on
- + * Surface Book series devices.
- + *
- + * Copyright (C) 2020-2021 Maximilian Luz <luzmaximilian@gmail.com>
- + */
- +
- +#ifndef _UAPI_LINUX_SURFACE_AGGREGATOR_DTX_H
- +#define _UAPI_LINUX_SURFACE_AGGREGATOR_DTX_H
- +
- +#include <linux/ioctl.h>
- +#include <linux/types.h>
- +
- +/* Status/error categories */
- +#define SDTX_CATEGORY_STATUS 0x0000
- +#define SDTX_CATEGORY_RUNTIME_ERROR 0x1000
- +#define SDTX_CATEGORY_HARDWARE_ERROR 0x2000
- +#define SDTX_CATEGORY_UNKNOWN 0xf000
- +
- +#define SDTX_CATEGORY_MASK 0xf000
- +#define SDTX_CATEGORY(value) ((value) & SDTX_CATEGORY_MASK)
- +
- +#define SDTX_STATUS(code) ((code) | SDTX_CATEGORY_STATUS)
- +#define SDTX_ERR_RT(code) ((code) | SDTX_CATEGORY_RUNTIME_ERROR)
- +#define SDTX_ERR_HW(code) ((code) | SDTX_CATEGORY_HARDWARE_ERROR)
- +#define SDTX_UNKNOWN(code) ((code) | SDTX_CATEGORY_UNKNOWN)
- +
- +#define SDTX_SUCCESS(value) (SDTX_CATEGORY(value) == SDTX_CATEGORY_STATUS)
- +
- +/* Latch status values */
- +#define SDTX_LATCH_CLOSED SDTX_STATUS(0x00)
- +#define SDTX_LATCH_OPENED SDTX_STATUS(0x01)
- +
- +/* Base state values */
- +#define SDTX_BASE_DETACHED SDTX_STATUS(0x00)
- +#define SDTX_BASE_ATTACHED SDTX_STATUS(0x01)
- +
- +/* Runtime errors (non-critical) */
- +#define SDTX_DETACH_NOT_FEASIBLE SDTX_ERR_RT(0x01)
- +#define SDTX_DETACH_TIMEDOUT SDTX_ERR_RT(0x02)
- +
- +/* Hardware errors (critical) */
- +#define SDTX_ERR_FAILED_TO_OPEN SDTX_ERR_HW(0x01)
- +#define SDTX_ERR_FAILED_TO_REMAIN_OPEN SDTX_ERR_HW(0x02)
- +#define SDTX_ERR_FAILED_TO_CLOSE SDTX_ERR_HW(0x03)
- +
- +/* Base types */
- +#define SDTX_DEVICE_TYPE_HID 0x0100
- +#define SDTX_DEVICE_TYPE_SSH 0x0200
- +
- +#define SDTX_DEVICE_TYPE_MASK 0x0f00
- +#define SDTX_DEVICE_TYPE(value) ((value) & SDTX_DEVICE_TYPE_MASK)
- +
- +#define SDTX_BASE_TYPE_HID(id) ((id) | SDTX_DEVICE_TYPE_HID)
- +#define SDTX_BASE_TYPE_SSH(id) ((id) | SDTX_DEVICE_TYPE_SSH)
- +
- +/**
- + * enum sdtx_device_mode - Mode describing how (and if) the clipboard is
- + * attached to the base of the device.
- + * @SDTX_DEVICE_MODE_TABLET: The clipboard is detached from the base and the
- + * device operates as tablet.
- + * @SDTX_DEVICE_MODE_LAPTOP: The clipboard is attached normally to the base
- + * and the device operates as laptop.
- + * @SDTX_DEVICE_MODE_STUDIO: The clipboard is attached to the base in reverse.
- + * The device operates as tablet with keyboard and
- + * touchpad deactivated, however, the base battery
- + * and, if present in the specific device model, dGPU
- + * are available to the system.
- + */
- +enum sdtx_device_mode {
- + SDTX_DEVICE_MODE_TABLET = 0x00,
- + SDTX_DEVICE_MODE_LAPTOP = 0x01,
- + SDTX_DEVICE_MODE_STUDIO = 0x02,
- +};
- +
- +/**
- + * struct sdtx_event - Event provided by reading from the DTX device file.
- + * @length: Length of the event payload, in bytes.
- + * @code: Event code, detailing what type of event this is.
- + * @data: Payload of the event, containing @length bytes.
- + *
- + * See &enum sdtx_event_code for currently valid event codes.
- + */
- +struct sdtx_event {
- + __u16 length;
- + __u16 code;
- + __u8 data[];
- +} __attribute__((__packed__));
- +
- +/**
- + * enum sdtx_event_code - Code describing the type of an event.
- + * @SDTX_EVENT_REQUEST: Detachment request event type.
- + * @SDTX_EVENT_CANCEL: Cancel detachment process event type.
- + * @SDTX_EVENT_BASE_CONNECTION: Base/clipboard connection change event type.
- + * @SDTX_EVENT_LATCH_STATUS: Latch status change event type.
- + * @SDTX_EVENT_DEVICE_MODE: Device mode change event type.
- + *
- + * Used in &struct sdtx_event to describe the type of the event. Further event
- + * codes are reserved for future use. Any event parser should be able to
- + * gracefully handle unknown events, i.e. by simply skipping them.
- + *
- + * Consult the DTX user-space interface documentation for details regarding
- + * the individual event types.
- + */
- +enum sdtx_event_code {
- + SDTX_EVENT_REQUEST = 1,
- + SDTX_EVENT_CANCEL = 2,
- + SDTX_EVENT_BASE_CONNECTION = 3,
- + SDTX_EVENT_LATCH_STATUS = 4,
- + SDTX_EVENT_DEVICE_MODE = 5,
- +};
- +
- +/**
- + * struct sdtx_base_info - Describes if and what type of base is connected.
- + * @state: The state of the connection. Valid values are %SDTX_BASE_DETACHED,
- + * %SDTX_BASE_ATTACHED, and %SDTX_DETACH_NOT_FEASIBLE (in case a base
- + * is attached but low clipboard battery prevents detachment). Other
- + * values are currently reserved.
- + * @base_id: The type of base connected. Zero if no base is connected.
- + */
- +struct sdtx_base_info {
- + __u16 state;
- + __u16 base_id;
- +} __attribute__((__packed__));
- +
- +/* IOCTLs */
- +#define SDTX_IOCTL_EVENTS_ENABLE _IO(0xa5, 0x21)
- +#define SDTX_IOCTL_EVENTS_DISABLE _IO(0xa5, 0x22)
- +
- +#define SDTX_IOCTL_LATCH_LOCK _IO(0xa5, 0x23)
- +#define SDTX_IOCTL_LATCH_UNLOCK _IO(0xa5, 0x24)
- +
- +#define SDTX_IOCTL_LATCH_REQUEST _IO(0xa5, 0x25)
- +#define SDTX_IOCTL_LATCH_CONFIRM _IO(0xa5, 0x26)
- +#define SDTX_IOCTL_LATCH_HEARTBEAT _IO(0xa5, 0x27)
- +#define SDTX_IOCTL_LATCH_CANCEL _IO(0xa5, 0x28)
- +
- +#define SDTX_IOCTL_GET_BASE_INFO _IOR(0xa5, 0x29, struct sdtx_base_info)
- +#define SDTX_IOCTL_GET_DEVICE_MODE _IOR(0xa5, 0x2a, __u16)
- +#define SDTX_IOCTL_GET_LATCH_STATUS _IOR(0xa5, 0x2b, __u16)
- +
- +#endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_DTX_H */
- --
- 2.32.0
- From 710cbcbc3064eb0815f9f82a67762e9438fc6f94 Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Mon, 8 Mar 2021 19:48:18 +0100
- Subject: [PATCH] platform/surface: dtx: Add support for native SSAM devices
- Add support for native SSAM devices to the DTX driver. This allows
- support for the Surface Book 3, on which the DTX device is not present
- in ACPI.
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Link: https://lore.kernel.org/r/20210308184819.437438-3-luzmaximilian@gmail.com
- Signed-off-by: Hans de Goede <hdegoede@redhat.com>
- Patchset: surface-sam
- ---
- drivers/platform/surface/Kconfig | 4 ++
- drivers/platform/surface/surface_dtx.c | 90 +++++++++++++++++++++++++-
- 2 files changed, 93 insertions(+), 1 deletion(-)
- diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig
- index 41d67bb250fd..53beaedefdd1 100644
- --- a/drivers/platform/surface/Kconfig
- +++ b/drivers/platform/surface/Kconfig
- @@ -127,6 +127,10 @@ config SURFACE_DTX
- behavior of this process, which includes the option to abort it in
- case the base is still in use or speed it up in case it is not.
-
- + Note that this module can be built without support for the Surface
- + Aggregator Bus (i.e. CONFIG_SURFACE_AGGREGATOR_BUS=n). In that case,
- + some devices, specifically the Surface Book 3, will not be supported.
- +
- config SURFACE_GPE
- tristate "Surface GPE/Lid Support Driver"
- depends on DMI
- diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c
- index 1301fab0ea14..85451eb94d98 100644
- --- a/drivers/platform/surface/surface_dtx.c
- +++ b/drivers/platform/surface/surface_dtx.c
- @@ -27,6 +27,7 @@
- #include <linux/workqueue.h>
-
- #include <linux/surface_aggregator/controller.h>
- +#include <linux/surface_aggregator/device.h>
- #include <linux/surface_aggregator/dtx.h>
-
-
- @@ -1194,7 +1195,94 @@ static struct platform_driver surface_dtx_platform_driver = {
- .probe_type = PROBE_PREFER_ASYNCHRONOUS,
- },
- };
- -module_platform_driver(surface_dtx_platform_driver);
- +
- +
- +/* -- SSAM device driver. --------------------------------------------------- */
- +
- +#ifdef CONFIG_SURFACE_AGGREGATOR_BUS
- +
- +static int surface_dtx_ssam_probe(struct ssam_device *sdev)
- +{
- + struct sdtx_device *ddev;
- +
- + ddev = sdtx_device_create(&sdev->dev, sdev->ctrl);
- + if (IS_ERR(ddev))
- + return PTR_ERR(ddev);
- +
- + ssam_device_set_drvdata(sdev, ddev);
- + return 0;
- +}
- +
- +static void surface_dtx_ssam_remove(struct ssam_device *sdev)
- +{
- + sdtx_device_destroy(ssam_device_get_drvdata(sdev));
- +}
- +
- +static const struct ssam_device_id surface_dtx_ssam_match[] = {
- + { SSAM_SDEV(BAS, 0x01, 0x00, 0x00) },
- + { },
- +};
- +MODULE_DEVICE_TABLE(ssam, surface_dtx_ssam_match);
- +
- +static struct ssam_device_driver surface_dtx_ssam_driver = {
- + .probe = surface_dtx_ssam_probe,
- + .remove = surface_dtx_ssam_remove,
- + .match_table = surface_dtx_ssam_match,
- + .driver = {
- + .name = "surface_dtx",
- + .pm = &surface_dtx_pm_ops,
- + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
- + },
- +};
- +
- +static int ssam_dtx_driver_register(void)
- +{
- + return ssam_device_driver_register(&surface_dtx_ssam_driver);
- +}
- +
- +static void ssam_dtx_driver_unregister(void)
- +{
- + ssam_device_driver_unregister(&surface_dtx_ssam_driver);
- +}
- +
- +#else /* CONFIG_SURFACE_AGGREGATOR_BUS */
- +
- +static int ssam_dtx_driver_register(void)
- +{
- + return 0;
- +}
- +
- +static void ssam_dtx_driver_unregister(void)
- +{
- +}
- +
- +#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */
- +
- +
- +/* -- Module setup. --------------------------------------------------------- */
- +
- +static int __init surface_dtx_init(void)
- +{
- + int status;
- +
- + status = ssam_dtx_driver_register();
- + if (status)
- + return status;
- +
- + status = platform_driver_register(&surface_dtx_platform_driver);
- + if (status)
- + ssam_dtx_driver_unregister();
- +
- + return status;
- +}
- +module_init(surface_dtx_init);
- +
- +static void __exit surface_dtx_exit(void)
- +{
- + platform_driver_unregister(&surface_dtx_platform_driver);
- + ssam_dtx_driver_unregister();
- +}
- +module_exit(surface_dtx_exit);
-
- MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
- MODULE_DESCRIPTION("Detachment-system driver for Surface System Aggregator Module");
- --
- 2.32.0
- From 6c4d22698f9e940ae13d6f58d46f28f2ae90fde9 Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Mon, 8 Mar 2021 19:48:19 +0100
- Subject: [PATCH] docs: driver-api: Add Surface DTX driver documentation
- Add documentation for the user-space interface of the Surface DTX
- (detachment system) driver, used on Microsoft Surface Book series
- devices.
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Link: https://lore.kernel.org/r/20210308184819.437438-4-luzmaximilian@gmail.com
- Signed-off-by: Hans de Goede <hdegoede@redhat.com>
- Patchset: surface-sam
- ---
- .../surface_aggregator/clients/dtx.rst | 718 ++++++++++++++++++
- .../surface_aggregator/clients/index.rst | 1 +
- MAINTAINERS | 1 +
- 3 files changed, 720 insertions(+)
- create mode 100644 Documentation/driver-api/surface_aggregator/clients/dtx.rst
- diff --git a/Documentation/driver-api/surface_aggregator/clients/dtx.rst b/Documentation/driver-api/surface_aggregator/clients/dtx.rst
- new file mode 100644
- index 000000000000..e7e7c20007f0
- --- /dev/null
- +++ b/Documentation/driver-api/surface_aggregator/clients/dtx.rst
- @@ -0,0 +1,718 @@
- +.. SPDX-License-Identifier: GPL-2.0+
- +
- +.. |__u16| replace:: :c:type:`__u16 <__u16>`
- +.. |sdtx_event| replace:: :c:type:`struct sdtx_event <sdtx_event>`
- +.. |sdtx_event_code| replace:: :c:type:`enum sdtx_event_code <sdtx_event_code>`
- +.. |sdtx_base_info| replace:: :c:type:`struct sdtx_base_info <sdtx_base_info>`
- +.. |sdtx_device_mode| replace:: :c:type:`struct sdtx_device_mode <sdtx_device_mode>`
- +
- +======================================================
- +User-Space DTX (Clipboard Detachment System) Interface
- +======================================================
- +
- +The ``surface_dtx`` driver is responsible for proper clipboard detachment
- +and re-attachment handling. To this end, it provides the ``/dev/surface/dtx``
- +device file, through which it can interface with a user-space daemon. This
- +daemon is then ultimately responsible for determining and taking necessary
- +actions, such as unmounting devices attached to the base,
- +unloading/reloading the graphics-driver, user-notifications, etc.
- +
- +There are two basic communication principles used in this driver: Commands
- +(in other parts of the documentation also referred to as requests) and
- +events. Commands are sent to the EC and may have a different implications in
- +different contexts. Events are sent by the EC upon some internal state
- +change. Commands are always driver-initiated, whereas events are always
- +initiated by the EC.
- +
- +.. contents::
- +
- +Nomenclature
- +============
- +
- +* **Clipboard:**
- + The detachable upper part of the Surface Book, housing the screen and CPU.
- +
- +* **Base:**
- + The lower part of the Surface Book from which the clipboard can be
- + detached, optionally (model dependent) housing the discrete GPU (dGPU).
- +
- +* **Latch:**
- + The mechanism keeping the clipboard attached to the base in normal
- + operation and allowing it to be detached when requested.
- +
- +* **Silently ignored commands:**
- + The command is accepted by the EC as a valid command and acknowledged
- + (following the standard communication protocol), but the EC does not act
- + upon it, i.e. ignores it.e upper part of the
- +
- +
- +Detachment Process
- +==================
- +
- +Warning: This part of the documentation is based on reverse engineering and
- +testing and thus may contain errors or be incomplete.
- +
- +Latch States
- +------------
- +
- +The latch mechanism has two major states: *open* and *closed*. In the
- +*closed* state (default), the clipboard is secured to the base, whereas in
- +the *open* state, the clipboard can be removed by a user.
- +
- +The latch can additionally be locked and, correspondingly, unlocked, which
- +can influence the detachment procedure. Specifically, this locking mechanism
- +is intended to prevent the dGPU, positioned in the base of the device, from
- +being hot-unplugged while in use. More details can be found in the
- +documentation for the detachment procedure below. By default, the latch is
- +unlocked.
- +
- +Detachment Procedure
- +--------------------
- +
- +Note that the detachment process is governed fully by the EC. The
- +``surface_dtx`` driver only relays events from the EC to user-space and
- +commands from user-space to the EC, i.e. it does not influence this process.
- +
- +The detachment process is started with the user pressing the *detach* button
- +on the base of the device or executing the ``SDTX_IOCTL_LATCH_REQUEST`` IOCTL.
- +Following that:
- +
- +1. The EC turns on the indicator led on the detach-button, sends a
- + *detach-request* event (``SDTX_EVENT_REQUEST``), and awaits further
- + instructions/commands. In case the latch is unlocked, the led will flash
- + green. If the latch has been locked, the led will be solid red
- +
- +2. The event is, via the ``surface_dtx`` driver, relayed to user-space, where
- + an appropriate user-space daemon can handle it and send instructions back
- + to the EC via IOCTLs provided by this driver.
- +
- +3. The EC waits for instructions from user-space and acts according to them.
- + If the EC does not receive any instructions in a given period, it will
- + time out and continue as follows:
- +
- + - If the latch is unlocked, the EC will open the latch and the clipboard
- + can be detached from the base. This is the exact behavior as without
- + this driver or any user-space daemon. See the ``SDTX_IOCTL_LATCH_CONFIRM``
- + description below for more details on the follow-up behavior of the EC.
- +
- + - If the latch is locked, the EC will *not* open the latch, meaning the
- + clipboard cannot be detached from the base. Furthermore, the EC sends
- + an cancel event (``SDTX_EVENT_CANCEL``) detailing this with the cancel
- + reason ``SDTX_DETACH_TIMEDOUT`` (see :ref:`events` for details).
- +
- +Valid responses by a user-space daemon to a detachment request event are:
- +
- +- Execute ``SDTX_IOCTL_LATCH_REQUEST``. This will immediately abort the
- + detachment process. Furthermore, the EC will send a detach-request event,
- + similar to the user pressing the detach-button to cancel said process (see
- + below).
- +
- +- Execute ``SDTX_IOCTL_LATCH_CONFIRM``. This will cause the EC to open the
- + latch, after which the user can separate clipboard and base.
- +
- + As this changes the latch state, a *latch-status* event
- + (``SDTX_EVENT_LATCH_STATUS``) will be sent once the latch has been opened
- + successfully. If the EC fails to open the latch, e.g. due to hardware
- + error or low battery, a latch-cancel event (``SDTX_EVENT_CANCEL``) will be
- + sent with the cancel reason indicating the specific failure.
- +
- + If the latch is currently locked, the latch will automatically be
- + unlocked before it is opened.
- +
- +- Execute ``SDTX_IOCTL_LATCH_HEARTBEAT``. This will reset the internal timeout.
- + No other actions will be performed, i.e. the detachment process will neither
- + be completed nor canceled, and the EC will still be waiting for further
- + responses.
- +
- +- Execute ``SDTX_IOCTL_LATCH_CANCEL``. This will abort the detachment process,
- + similar to ``SDTX_IOCTL_LATCH_REQUEST``, described above, or the button
- + press, described below. A *generic request* event (``SDTX_EVENT_REQUEST``)
- + is send in response to this. In contrast to those, however, this command
- + does not trigger a new detachment process if none is currently in
- + progress.
- +
- +- Do nothing. The detachment process eventually times out as described in
- + point 3.
- +
- +See :ref:`ioctls` for more details on these responses.
- +
- +It is important to note that, if the user presses the detach button at any
- +point when a detachment operation is in progress (i.e. after the EC has sent
- +the initial *detach-request* event (``SDTX_EVENT_REQUEST``) and before it
- +received the corresponding response concluding the process), the detachment
- +process is canceled on the EC-level and an identical event is being sent.
- +Thus a *detach-request* event, by itself, does not signal the start of the
- +detachment process.
- +
- +The detachment process may further be canceled by the EC due to hardware
- +failures or a low clipboard battery. This is done via a cancel event
- +(``SDTX_EVENT_CANCEL``) with the corresponding cancel reason.
- +
- +
- +User-Space Interface Documentation
- +==================================
- +
- +Error Codes and Status Values
- +-----------------------------
- +
- +Error and status codes are divided into different categories, which can be
- +used to determine if the status code is an error, and, if it is, the
- +severity and type of that error. The current categories are:
- +
- +.. flat-table:: Overview of Status/Error Categories.
- + :widths: 2 1 3
- + :header-rows: 1
- +
- + * - Name
- + - Value
- + - Short Description
- +
- + * - ``STATUS``
- + - ``0x0000``
- + - Non-error status codes.
- +
- + * - ``RUNTIME_ERROR``
- + - ``0x1000``
- + - Non-critical runtime errors.
- +
- + * - ``HARDWARE_ERROR``
- + - ``0x2000``
- + - Critical hardware failures.
- +
- + * - ``UNKNOWN``
- + - ``0xF000``
- + - Unknown error codes.
- +
- +Other categories are reserved for future use. The ``SDTX_CATEGORY()`` macro
- +can be used to determine the category of any status value. The
- +``SDTX_SUCCESS()`` macro can be used to check if the status value is a
- +success value (``SDTX_CATEGORY_STATUS``) or if it indicates a failure.
- +
- +Unknown status or error codes sent by the EC are assigned to the ``UNKNOWN``
- +category by the driver and may be implemented via their own code in the
- +future.
- +
- +Currently used error codes are:
- +
- +.. flat-table:: Overview of Error Codes.
- + :widths: 2 1 1 3
- + :header-rows: 1
- +
- + * - Name
- + - Category
- + - Value
- + - Short Description
- +
- + * - ``SDTX_DETACH_NOT_FEASIBLE``
- + - ``RUNTIME``
- + - ``0x1001``
- + - Detachment not feasible due to low clipboard battery.
- +
- + * - ``SDTX_DETACH_TIMEDOUT``
- + - ``RUNTIME``
- + - ``0x1002``
- + - Detachment process timed out while the latch was locked.
- +
- + * - ``SDTX_ERR_FAILED_TO_OPEN``
- + - ``HARDWARE``
- + - ``0x2001``
- + - Failed to open latch.
- +
- + * - ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``
- + - ``HARDWARE``
- + - ``0x2002``
- + - Failed to keep latch open.
- +
- + * - ``SDTX_ERR_FAILED_TO_CLOSE``
- + - ``HARDWARE``
- + - ``0x2003``
- + - Failed to close latch.
- +
- +Other error codes are reserved for future use. Non-error status codes may
- +overlap and are generally only unique within their use-case:
- +
- +.. flat-table:: Latch Status Codes.
- + :widths: 2 1 1 3
- + :header-rows: 1
- +
- + * - Name
- + - Category
- + - Value
- + - Short Description
- +
- + * - ``SDTX_LATCH_CLOSED``
- + - ``STATUS``
- + - ``0x0000``
- + - Latch is closed/has been closed.
- +
- + * - ``SDTX_LATCH_OPENED``
- + - ``STATUS``
- + - ``0x0001``
- + - Latch is open/has been opened.
- +
- +.. flat-table:: Base State Codes.
- + :widths: 2 1 1 3
- + :header-rows: 1
- +
- + * - Name
- + - Category
- + - Value
- + - Short Description
- +
- + * - ``SDTX_BASE_DETACHED``
- + - ``STATUS``
- + - ``0x0000``
- + - Base has been detached/is not present.
- +
- + * - ``SDTX_BASE_ATTACHED``
- + - ``STATUS``
- + - ``0x0001``
- + - Base has been attached/is present.
- +
- +Again, other codes are reserved for future use.
- +
- +.. _events:
- +
- +Events
- +------
- +
- +Events can be received by reading from the device file. They are disabled by
- +default and have to be enabled by executing ``SDTX_IOCTL_EVENTS_ENABLE``
- +first. All events follow the layout prescribed by |sdtx_event|. Specific
- +event types can be identified by their event code, described in
- +|sdtx_event_code|. Note that other event codes are reserved for future use,
- +thus an event parser must be able to handle any unknown/unsupported event
- +types gracefully, by relying on the payload length given in the event header.
- +
- +Currently provided event types are:
- +
- +.. flat-table:: Overview of DTX events.
- + :widths: 2 1 1 3
- + :header-rows: 1
- +
- + * - Name
- + - Code
- + - Payload
- + - Short Description
- +
- + * - ``SDTX_EVENT_REQUEST``
- + - ``1``
- + - ``0`` bytes
- + - Detachment process initiated/aborted.
- +
- + * - ``SDTX_EVENT_CANCEL``
- + - ``2``
- + - ``2`` bytes
- + - EC canceled detachment process.
- +
- + * - ``SDTX_EVENT_BASE_CONNECTION``
- + - ``3``
- + - ``4`` bytes
- + - Base connection state changed.
- +
- + * - ``SDTX_EVENT_LATCH_STATUS``
- + - ``4``
- + - ``2`` bytes
- + - Latch status changed.
- +
- + * - ``SDTX_EVENT_DEVICE_MODE``
- + - ``5``
- + - ``2`` bytes
- + - Device mode changed.
- +
- +Individual events in more detail:
- +
- +``SDTX_EVENT_REQUEST``
- +^^^^^^^^^^^^^^^^^^^^^^
- +
- +Sent when a detachment process is started or, if in progress, aborted by the
- +user, either via a detach button press or a detach request
- +(``SDTX_IOCTL_LATCH_REQUEST``) being sent from user-space.
- +
- +Does not have any payload.
- +
- +``SDTX_EVENT_CANCEL``
- +^^^^^^^^^^^^^^^^^^^^^
- +
- +Sent when a detachment process is canceled by the EC due to unfulfilled
- +preconditions (e.g. clipboard battery too low to detach) or hardware
- +failure. The reason for cancellation is given in the event payload detailed
- +below and can be one of
- +
- +* ``SDTX_DETACH_TIMEDOUT``: Detachment timed out while the latch was locked.
- + The latch has neither been opened nor unlocked.
- +
- +* ``SDTX_DETACH_NOT_FEASIBLE``: Detachment not feasible due to low clipboard
- + battery.
- +
- +* ``SDTX_ERR_FAILED_TO_OPEN``: Could not open the latch (hardware failure).
- +
- +* ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``: Could not keep the latch open (hardware
- + failure).
- +
- +* ``SDTX_ERR_FAILED_TO_CLOSE``: Could not close the latch (hardware failure).
- +
- +Other error codes in this context are reserved for future use.
- +
- +These codes can be classified via the ``SDTX_CATEGORY()`` macro to discern
- +between critical hardware errors (``SDTX_CATEGORY_HARDWARE_ERROR``) or
- +runtime errors (``SDTX_CATEGORY_RUNTIME_ERROR``), the latter of which may
- +happen during normal operation if certain preconditions for detachment are
- +not given.
- +
- +.. flat-table:: Detachment Cancel Event Payload
- + :widths: 1 1 4
- + :header-rows: 1
- +
- + * - Field
- + - Type
- + - Description
- +
- + * - ``reason``
- + - |__u16|
- + - Reason for cancellation.
- +
- +``SDTX_EVENT_BASE_CONNECTION``
- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- +
- +Sent when the base connection state has changed, i.e. when the base has been
- +attached, detached, or detachment has become infeasible due to low clipboard
- +battery. The new state and, if a base is connected, ID of the base is
- +provided as payload of type |sdtx_base_info| with its layout presented
- +below:
- +
- +.. flat-table:: Base-Connection-Change Event Payload
- + :widths: 1 1 4
- + :header-rows: 1
- +
- + * - Field
- + - Type
- + - Description
- +
- + * - ``state``
- + - |__u16|
- + - Base connection state.
- +
- + * - ``base_id``
- + - |__u16|
- + - Type of base connected (zero if none).
- +
- +Possible values for ``state`` are:
- +
- +* ``SDTX_BASE_DETACHED``,
- +* ``SDTX_BASE_ATTACHED``, and
- +* ``SDTX_DETACH_NOT_FEASIBLE``.
- +
- +Other values are reserved for future use.
- +
- +``SDTX_EVENT_LATCH_STATUS``
- +^^^^^^^^^^^^^^^^^^^^^^^^^^^
- +
- +Sent when the latch status has changed, i.e. when the latch has been opened,
- +closed, or an error occurred. The current status is provided as payload:
- +
- +.. flat-table:: Latch-Status-Change Event Payload
- + :widths: 1 1 4
- + :header-rows: 1
- +
- + * - Field
- + - Type
- + - Description
- +
- + * - ``status``
- + - |__u16|
- + - Latch status.
- +
- +Possible values for ``status`` are:
- +
- +* ``SDTX_LATCH_CLOSED``,
- +* ``SDTX_LATCH_OPENED``,
- +* ``SDTX_ERR_FAILED_TO_OPEN``,
- +* ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``, and
- +* ``SDTX_ERR_FAILED_TO_CLOSE``.
- +
- +Other values are reserved for future use.
- +
- +``SDTX_EVENT_DEVICE_MODE``
- +^^^^^^^^^^^^^^^^^^^^^^^^^^
- +
- +Sent when the device mode has changed. The new device mode is provided as
- +payload:
- +
- +.. flat-table:: Device-Mode-Change Event Payload
- + :widths: 1 1 4
- + :header-rows: 1
- +
- + * - Field
- + - Type
- + - Description
- +
- + * - ``mode``
- + - |__u16|
- + - Device operation mode.
- +
- +Possible values for ``mode`` are:
- +
- +* ``SDTX_DEVICE_MODE_TABLET``,
- +* ``SDTX_DEVICE_MODE_LAPTOP``, and
- +* ``SDTX_DEVICE_MODE_STUDIO``.
- +
- +Other values are reserved for future use.
- +
- +.. _ioctls:
- +
- +IOCTLs
- +------
- +
- +The following IOCTLs are provided:
- +
- +.. flat-table:: Overview of DTX IOCTLs
- + :widths: 1 1 1 1 4
- + :header-rows: 1
- +
- + * - Type
- + - Number
- + - Direction
- + - Name
- + - Description
- +
- + * - ``0xA5``
- + - ``0x21``
- + - ``-``
- + - ``EVENTS_ENABLE``
- + - Enable events for the current file descriptor.
- +
- + * - ``0xA5``
- + - ``0x22``
- + - ``-``
- + - ``EVENTS_DISABLE``
- + - Disable events for the current file descriptor.
- +
- + * - ``0xA5``
- + - ``0x23``
- + - ``-``
- + - ``LATCH_LOCK``
- + - Lock the latch.
- +
- + * - ``0xA5``
- + - ``0x24``
- + - ``-``
- + - ``LATCH_UNLOCK``
- + - Unlock the latch.
- +
- + * - ``0xA5``
- + - ``0x25``
- + - ``-``
- + - ``LATCH_REQUEST``
- + - Request clipboard detachment.
- +
- + * - ``0xA5``
- + - ``0x26``
- + - ``-``
- + - ``LATCH_CONFIRM``
- + - Confirm clipboard detachment request.
- +
- + * - ``0xA5``
- + - ``0x27``
- + - ``-``
- + - ``LATCH_HEARTBEAT``
- + - Send heartbeat signal to EC.
- +
- + * - ``0xA5``
- + - ``0x28``
- + - ``-``
- + - ``LATCH_CANCEL``
- + - Cancel detachment process.
- +
- + * - ``0xA5``
- + - ``0x29``
- + - ``R``
- + - ``GET_BASE_INFO``
- + - Get current base/connection information.
- +
- + * - ``0xA5``
- + - ``0x2A``
- + - ``R``
- + - ``GET_DEVICE_MODE``
- + - Get current device operation mode.
- +
- + * - ``0xA5``
- + - ``0x2B``
- + - ``R``
- + - ``GET_LATCH_STATUS``
- + - Get current device latch status.
- +
- +``SDTX_IOCTL_EVENTS_ENABLE``
- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- +
- +Defined as ``_IO(0xA5, 0x22)``.
- +
- +Enable events for the current file descriptor. Events can be obtained by
- +reading from the device, if enabled. Events are disabled by default.
- +
- +``SDTX_IOCTL_EVENTS_DISABLE``
- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- +
- +Defined as ``_IO(0xA5, 0x22)``.
- +
- +Disable events for the current file descriptor. Events can be obtained by
- +reading from the device, if enabled. Events are disabled by default.
- +
- +``SDTX_IOCTL_LATCH_LOCK``
- +^^^^^^^^^^^^^^^^^^^^^^^^^
- +
- +Defined as ``_IO(0xA5, 0x23)``.
- +
- +Locks the latch, causing the detachment procedure to abort without opening
- +the latch on timeout. The latch is unlocked by default. This command will be
- +silently ignored if the latch is already locked.
- +
- +``SDTX_IOCTL_LATCH_UNLOCK``
- +^^^^^^^^^^^^^^^^^^^^^^^^^^^
- +
- +Defined as ``_IO(0xA5, 0x24)``.
- +
- +Unlocks the latch, causing the detachment procedure to open the latch on
- +timeout. The latch is unlocked by default. This command will not open the
- +latch when sent during an ongoing detachment process. It will be silently
- +ignored if the latch is already unlocked.
- +
- +``SDTX_IOCTL_LATCH_REQUEST``
- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- +
- +Defined as ``_IO(0xA5, 0x25)``.
- +
- +Generic latch request. Behavior depends on the context: If no
- +detachment-process is active, detachment is requested. Otherwise the
- +currently active detachment-process will be aborted.
- +
- +If a detachment process is canceled by this operation, a generic detachment
- +request event (``SDTX_EVENT_REQUEST``) will be sent.
- +
- +This essentially behaves the same as a detachment button press.
- +
- +``SDTX_IOCTL_LATCH_CONFIRM``
- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- +
- +Defined as ``_IO(0xA5, 0x26)``.
- +
- +Acknowledges and confirms a latch request. If sent during an ongoing
- +detachment process, this command causes the latch to be opened immediately.
- +The latch will also be opened if it has been locked. In this case, the latch
- +lock is reset to the unlocked state.
- +
- +This command will be silently ignored if there is currently no detachment
- +procedure in progress.
- +
- +``SDTX_IOCTL_LATCH_HEARTBEAT``
- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- +
- +Defined as ``_IO(0xA5, 0x27)``.
- +
- +Sends a heartbeat, essentially resetting the detachment timeout. This
- +command can be used to keep the detachment process alive while work required
- +for the detachment to succeed is still in progress.
- +
- +This command will be silently ignored if there is currently no detachment
- +procedure in progress.
- +
- +``SDTX_IOCTL_LATCH_CANCEL``
- +^^^^^^^^^^^^^^^^^^^^^^^^^^^
- +
- +Defined as ``_IO(0xA5, 0x28)``.
- +
- +Cancels detachment in progress (if any). If a detachment process is canceled
- +by this operation, a generic detachment request event
- +(``SDTX_EVENT_REQUEST``) will be sent.
- +
- +This command will be silently ignored if there is currently no detachment
- +procedure in progress.
- +
- +``SDTX_IOCTL_GET_BASE_INFO``
- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- +
- +Defined as ``_IOR(0xA5, 0x29, struct sdtx_base_info)``.
- +
- +Get the current base connection state (i.e. attached/detached) and the type
- +of the base connected to the clipboard. This is command essentially provides
- +a way to query the information provided by the base connection change event
- +(``SDTX_EVENT_BASE_CONNECTION``).
- +
- +Possible values for ``struct sdtx_base_info.state`` are:
- +
- +* ``SDTX_BASE_DETACHED``,
- +* ``SDTX_BASE_ATTACHED``, and
- +* ``SDTX_DETACH_NOT_FEASIBLE``.
- +
- +Other values are reserved for future use.
- +
- +``SDTX_IOCTL_GET_DEVICE_MODE``
- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- +
- +Defined as ``_IOR(0xA5, 0x2A, __u16)``.
- +
- +Returns the device operation mode, indicating if and how the base is
- +attached to the clipboard. This is command essentially provides a way to
- +query the information provided by the device mode change event
- +(``SDTX_EVENT_DEVICE_MODE``).
- +
- +Returned values are:
- +
- +* ``SDTX_DEVICE_MODE_LAPTOP``
- +* ``SDTX_DEVICE_MODE_TABLET``
- +* ``SDTX_DEVICE_MODE_STUDIO``
- +
- +See |sdtx_device_mode| for details. Other values are reserved for future
- +use.
- +
- +
- +``SDTX_IOCTL_GET_LATCH_STATUS``
- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- +
- +Defined as ``_IOR(0xA5, 0x2B, __u16)``.
- +
- +Get the current latch status or (presumably) the last error encountered when
- +trying to open/close the latch. This is command essentially provides a way
- +to query the information provided by the latch status change event
- +(``SDTX_EVENT_LATCH_STATUS``).
- +
- +Returned values are:
- +
- +* ``SDTX_LATCH_CLOSED``,
- +* ``SDTX_LATCH_OPENED``,
- +* ``SDTX_ERR_FAILED_TO_OPEN``,
- +* ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``, and
- +* ``SDTX_ERR_FAILED_TO_CLOSE``.
- +
- +Other values are reserved for future use.
- +
- +A Note on Base IDs
- +------------------
- +
- +Base types/IDs provided via ``SDTX_EVENT_BASE_CONNECTION`` or
- +``SDTX_IOCTL_GET_BASE_INFO`` are directly forwarded from the EC in the lower
- +byte of the combined |__u16| value, with the driver storing the EC type from
- +which this ID comes in the high byte (without this, base IDs over different
- +types of ECs may be overlapping).
- +
- +The ``SDTX_DEVICE_TYPE()`` macro can be used to determine the EC device
- +type. This can be one of
- +
- +* ``SDTX_DEVICE_TYPE_HID``, for Surface Aggregator Module over HID, and
- +
- +* ``SDTX_DEVICE_TYPE_SSH``, for Surface Aggregator Module over Surface Serial
- + Hub.
- +
- +Note that currently only the ``SSH`` type EC is supported, however ``HID``
- +type is reserved for future use.
- +
- +Structures and Enums
- +--------------------
- +
- +.. kernel-doc:: include/uapi/linux/surface_aggregator/dtx.h
- +
- +API Users
- +=========
- +
- +A user-space daemon utilizing this API can be found at
- +https://github.com/linux-surface/surface-dtx-daemon.
- diff --git a/Documentation/driver-api/surface_aggregator/clients/index.rst b/Documentation/driver-api/surface_aggregator/clients/index.rst
- index 3ccabce23271..98ea9946b8a2 100644
- --- a/Documentation/driver-api/surface_aggregator/clients/index.rst
- +++ b/Documentation/driver-api/surface_aggregator/clients/index.rst
- @@ -11,6 +11,7 @@ This is the documentation for client drivers themselves. Refer to
- :maxdepth: 1
-
- cdev
- + dtx
- san
-
- .. only:: subproject and html
- diff --git a/MAINTAINERS b/MAINTAINERS
- index 3917e7363520..da1487d672a8 100644
- --- a/MAINTAINERS
- +++ b/MAINTAINERS
- @@ -11872,6 +11872,7 @@ MICROSOFT SURFACE DTX DRIVER
- M: Maximilian Luz <luzmaximilian@gmail.com>
- L: platform-driver-x86@vger.kernel.org
- S: Maintained
- +F: Documentation/driver-api/surface_aggregator/clients/dtx.rst
- F: drivers/platform/surface/surface_dtx.c
- F: include/uapi/linux/surface_aggregator/dtx.h
-
- --
- 2.32.0
- From 2ba03413577d1c0491ae621c72e9d11898fe1cb0 Mon Sep 17 00:00:00 2001
- From: Wei Yongjun <weiyongjun1@huawei.com>
- Date: Tue, 9 Mar 2021 13:15:00 +0000
- Subject: [PATCH] platform/surface: aggregator_registry: Make symbol
- 'ssam_base_hub_group' static
- The sparse tool complains as follows:
- drivers/platform/surface/surface_aggregator_registry.c:355:30: warning:
- symbol 'ssam_base_hub_group' was not declared. Should it be static?
- This symbol is not used outside of surface_aggregator_registry.c, so this
- commit marks it static.
- Fixes: 797e78564634 ("platform/surface: aggregator_registry: Add base device hub")
- Reported-by: Hulk Robot <hulkci@huawei.com>
- Signed-off-by: Wei Yongjun <weiyongjun1@huawei.com>
- Reviewed-by: Maximilian Luz <luzmaximilian@gmail.com>
- Link: https://lore.kernel.org/r/20210309131500.1885772-1-weiyongjun1@huawei.com
- Signed-off-by: Hans de Goede <hdegoede@redhat.com>
- Patchset: surface-sam
- ---
- drivers/platform/surface/surface_aggregator_registry.c | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
- diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
- index cdb4a95af3e8..86cff5fce3cd 100644
- --- a/drivers/platform/surface/surface_aggregator_registry.c
- +++ b/drivers/platform/surface/surface_aggregator_registry.c
- @@ -352,7 +352,7 @@ static struct attribute *ssam_base_hub_attrs[] = {
- NULL,
- };
-
- -const struct attribute_group ssam_base_hub_group = {
- +static const struct attribute_group ssam_base_hub_group = {
- .attrs = ssam_base_hub_attrs,
- };
-
- --
- 2.32.0
- From 7751eb2a68de5841822b775f1fb8dd49163feb24 Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Tue, 9 Mar 2021 17:25:50 +0100
- Subject: [PATCH] platform/surface: aggregator_registry: Add support for
- Surface Pro 7+
- The Surface Pro 7+ is essentially a refresh of the Surface Pro 7 with
- updated hardware and a new WSID identifier.
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Link: https://lore.kernel.org/r/20210309162550.302161-1-luzmaximilian@gmail.com
- Signed-off-by: Hans de Goede <hdegoede@redhat.com>
- Patchset: surface-sam
- ---
- drivers/platform/surface/surface_aggregator_registry.c | 5 ++++-
- 1 file changed, 4 insertions(+), 1 deletion(-)
- diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
- index 86cff5fce3cd..eccb9d1007cd 100644
- --- a/drivers/platform/surface/surface_aggregator_registry.c
- +++ b/drivers/platform/surface/surface_aggregator_registry.c
- @@ -191,7 +191,7 @@ static const struct software_node *ssam_node_group_sp6[] = {
- NULL,
- };
-
- -/* Devices for Surface Pro 7. */
- +/* Devices for Surface Pro 7 and Surface Pro 7+. */
- static const struct software_node *ssam_node_group_sp7[] = {
- &ssam_node_root,
- &ssam_node_bat_ac,
- @@ -521,6 +521,9 @@ static const struct acpi_device_id ssam_platform_hub_match[] = {
- /* Surface Pro 7 */
- { "MSHW0116", (unsigned long)ssam_node_group_sp7 },
-
- + /* Surface Pro 7+ */
- + { "MSHW0119", (unsigned long)ssam_node_group_sp7 },
- +
- /* Surface Book 2 */
- { "MSHW0107", (unsigned long)ssam_node_group_sb2 },
-
- --
- 2.32.0
- From 31da7032ce116c1d3678049476fb2a6bd0570d26 Mon Sep 17 00:00:00 2001
- From: kernel test robot <lkp@intel.com>
- Date: Fri, 19 Mar 2021 13:19:19 +0800
- Subject: [PATCH] platform/surface: fix semicolon.cocci warnings
- drivers/platform/surface/surface_dtx.c:651:2-3: Unneeded semicolon
- Remove unneeded semicolon.
- Generated by: scripts/coccinelle/misc/semicolon.cocci
- Fixes: 1d609992832e ("platform/surface: Add DTX driver")
- CC: Maximilian Luz <luzmaximilian@gmail.com>
- Reported-by: kernel test robot <lkp@intel.com>
- Signed-off-by: kernel test robot <lkp@intel.com>
- Reviewed-by: Maximilian Luz <luzmaximilian@gmail.com>
- Link: https://lore.kernel.org/r/20210319051919.GA39801@ae4f36e4f012
- Signed-off-by: Hans de Goede <hdegoede@redhat.com>
- Patchset: surface-sam
- ---
- drivers/platform/surface/surface_dtx.c | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
- diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c
- index 85451eb94d98..1fedacf74050 100644
- --- a/drivers/platform/surface/surface_dtx.c
- +++ b/drivers/platform/surface/surface_dtx.c
- @@ -649,7 +649,7 @@ static u32 sdtx_notifier(struct ssam_event_notifier *nf, const struct ssam_event
-
- default:
- return 0;
- - };
- + }
-
- if (in->length != len) {
- dev_err(ddev->dev,
- --
- 2.32.0
- From 2f1c22a481481c8623e98d8cc8b78d287755f3e3 Mon Sep 17 00:00:00 2001
- From: Dan Carpenter <dan.carpenter@oracle.com>
- Date: Fri, 26 Mar 2021 15:28:48 +0300
- Subject: [PATCH] platform/surface: clean up a variable in surface_dtx_read()
- The "&client->ddev->lock" and "&ddev->lock" are the same thing. Let's
- use "&ddev->lock" consistently.
- Signed-off-by: Dan Carpenter <dan.carpenter@oracle.com>
- Reviewed-by: Maximilian Luz <luzmaximilian@gmail.com>
- Link: https://lore.kernel.org/r/YF3TgCcpcCYl3a//@mwanda
- Signed-off-by: Hans de Goede <hdegoede@redhat.com>
- Patchset: surface-sam
- ---
- drivers/platform/surface/surface_dtx.c | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
- diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c
- index 1fedacf74050..63ce587e79e3 100644
- --- a/drivers/platform/surface/surface_dtx.c
- +++ b/drivers/platform/surface/surface_dtx.c
- @@ -487,7 +487,7 @@ static ssize_t surface_dtx_read(struct file *file, char __user *buf, size_t coun
- if (status < 0)
- return status;
-
- - if (down_read_killable(&client->ddev->lock))
- + if (down_read_killable(&ddev->lock))
- return -ERESTARTSYS;
-
- /* Need to check that we're not shut down again. */
- --
- 2.32.0
- From c0a095e902338a17e4b090ac78837aedd32c5abf Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Tue, 6 Apr 2021 01:12:22 +0200
- Subject: [PATCH] platform/surface: aggregator_registry: Give devices time to
- set up when connecting
- Sometimes, the "base connected" event that we rely on to (re-)attach the
- device connected to the base is sent a bit too early. When this happens,
- some devices may not be completely ready yet.
- Specifically, the battery has been observed to report zero-values for
- things like full charge capacity, which, however, is only loaded once
- when the driver for that device probes. This can thus result in battery
- readings being unavailable.
- As we cannot easily and reliably discern between devices that are not
- ready yet and devices that are not connected (i.e. will never be ready),
- delay adding these devices. This should give them enough time to set up.
- The delay is set to 2.5 seconds, which should give us a good safety
- margin based on testing and still be fairly responsive for users.
- To achieve that delay switch to updating via a delayed work struct,
- which means that we can also get rid of some locking.
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Link: https://lore.kernel.org/r/20210405231222.358113-1-luzmaximilian@gmail.com
- Signed-off-by: Hans de Goede <hdegoede@redhat.com>
- Patchset: surface-sam
- ---
- .../surface/surface_aggregator_registry.c | 98 ++++++++-----------
- 1 file changed, 40 insertions(+), 58 deletions(-)
- diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
- index eccb9d1007cd..685d37a7add1 100644
- --- a/drivers/platform/surface/surface_aggregator_registry.c
- +++ b/drivers/platform/surface/surface_aggregator_registry.c
- @@ -13,10 +13,10 @@
- #include <linux/kernel.h>
- #include <linux/limits.h>
- #include <linux/module.h>
- -#include <linux/mutex.h>
- #include <linux/platform_device.h>
- #include <linux/property.h>
- #include <linux/types.h>
- +#include <linux/workqueue.h>
-
- #include <linux/surface_aggregator/controller.h>
- #include <linux/surface_aggregator/device.h>
- @@ -287,6 +287,13 @@ static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *c
-
- /* -- SSAM base-hub driver. ------------------------------------------------- */
-
- +/*
- + * Some devices (especially battery) may need a bit of time to be fully usable
- + * after being (re-)connected. This delay has been determined via
- + * experimentation.
- + */
- +#define SSAM_BASE_UPDATE_CONNECT_DELAY msecs_to_jiffies(2500)
- +
- enum ssam_base_hub_state {
- SSAM_BASE_HUB_UNINITIALIZED,
- SSAM_BASE_HUB_CONNECTED,
- @@ -296,8 +303,8 @@ enum ssam_base_hub_state {
- struct ssam_base_hub {
- struct ssam_device *sdev;
-
- - struct mutex lock; /* Guards state update checks and transitions. */
- enum ssam_base_hub_state state;
- + struct delayed_work update_work;
-
- struct ssam_event_notifier notif;
- };
- @@ -335,11 +342,7 @@ static ssize_t ssam_base_hub_state_show(struct device *dev, struct device_attrib
- char *buf)
- {
- struct ssam_base_hub *hub = dev_get_drvdata(dev);
- - bool connected;
- -
- - mutex_lock(&hub->lock);
- - connected = hub->state == SSAM_BASE_HUB_CONNECTED;
- - mutex_unlock(&hub->lock);
- + bool connected = hub->state == SSAM_BASE_HUB_CONNECTED;
-
- return sysfs_emit(buf, "%d\n", connected);
- }
- @@ -356,16 +359,20 @@ static const struct attribute_group ssam_base_hub_group = {
- .attrs = ssam_base_hub_attrs,
- };
-
- -static int __ssam_base_hub_update(struct ssam_base_hub *hub, enum ssam_base_hub_state new)
- +static void ssam_base_hub_update_workfn(struct work_struct *work)
- {
- + struct ssam_base_hub *hub = container_of(work, struct ssam_base_hub, update_work.work);
- struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev);
- + enum ssam_base_hub_state state;
- int status = 0;
-
- - lockdep_assert_held(&hub->lock);
- + status = ssam_base_hub_query_state(hub, &state);
- + if (status)
- + return;
-
- - if (hub->state == new)
- - return 0;
- - hub->state = new;
- + if (hub->state == state)
- + return;
- + hub->state = state;
-
- if (hub->state == SSAM_BASE_HUB_CONNECTED)
- status = ssam_hub_add_devices(&hub->sdev->dev, hub->sdev->ctrl, node);
- @@ -374,51 +381,28 @@ static int __ssam_base_hub_update(struct ssam_base_hub *hub, enum ssam_base_hub_
-
- if (status)
- dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status);
- -
- - return status;
- -}
- -
- -static int ssam_base_hub_update(struct ssam_base_hub *hub)
- -{
- - enum ssam_base_hub_state state;
- - int status;
- -
- - mutex_lock(&hub->lock);
- -
- - status = ssam_base_hub_query_state(hub, &state);
- - if (!status)
- - status = __ssam_base_hub_update(hub, state);
- -
- - mutex_unlock(&hub->lock);
- - return status;
- }
-
- static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event)
- {
- - struct ssam_base_hub *hub;
- - struct ssam_device *sdev;
- - enum ssam_base_hub_state new;
- -
- - hub = container_of(nf, struct ssam_base_hub, notif);
- - sdev = hub->sdev;
- + struct ssam_base_hub *hub = container_of(nf, struct ssam_base_hub, notif);
- + unsigned long delay;
-
- if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION)
- return 0;
-
- if (event->length < 1) {
- - dev_err(&sdev->dev, "unexpected payload size: %u\n",
- - event->length);
- + dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length);
- return 0;
- }
-
- - if (event->data[0])
- - new = SSAM_BASE_HUB_CONNECTED;
- - else
- - new = SSAM_BASE_HUB_DISCONNECTED;
- + /*
- + * Delay update when the base is being connected to give devices/EC
- + * some time to set up.
- + */
- + delay = event->data[0] ? SSAM_BASE_UPDATE_CONNECT_DELAY : 0;
-
- - mutex_lock(&hub->lock);
- - __ssam_base_hub_update(hub, new);
- - mutex_unlock(&hub->lock);
- + schedule_delayed_work(&hub->update_work, delay);
-
- /*
- * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and
- @@ -430,7 +414,10 @@ static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam
-
- static int __maybe_unused ssam_base_hub_resume(struct device *dev)
- {
- - return ssam_base_hub_update(dev_get_drvdata(dev));
- + struct ssam_base_hub *hub = dev_get_drvdata(dev);
- +
- + schedule_delayed_work(&hub->update_work, 0);
- + return 0;
- }
- static SIMPLE_DEV_PM_OPS(ssam_base_hub_pm_ops, NULL, ssam_base_hub_resume);
-
- @@ -443,8 +430,6 @@ static int ssam_base_hub_probe(struct ssam_device *sdev)
- if (!hub)
- return -ENOMEM;
-
- - mutex_init(&hub->lock);
- -
- hub->sdev = sdev;
- hub->state = SSAM_BASE_HUB_UNINITIALIZED;
-
- @@ -456,27 +441,25 @@ static int ssam_base_hub_probe(struct ssam_device *sdev)
- hub->notif.event.mask = SSAM_EVENT_MASK_NONE;
- hub->notif.event.flags = SSAM_EVENT_SEQUENCED;
-
- + INIT_DELAYED_WORK(&hub->update_work, ssam_base_hub_update_workfn);
- +
- ssam_device_set_drvdata(sdev, hub);
-
- status = ssam_notifier_register(sdev->ctrl, &hub->notif);
- if (status)
- - goto err_register;
- -
- - status = ssam_base_hub_update(hub);
- - if (status)
- - goto err_update;
- + return status;
-
- status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group);
- if (status)
- - goto err_update;
- + goto err;
-
- + schedule_delayed_work(&hub->update_work, 0);
- return 0;
-
- -err_update:
- +err:
- ssam_notifier_unregister(sdev->ctrl, &hub->notif);
- + cancel_delayed_work_sync(&hub->update_work);
- ssam_hub_remove_devices(&sdev->dev);
- -err_register:
- - mutex_destroy(&hub->lock);
- return status;
- }
-
- @@ -487,9 +470,8 @@ static void ssam_base_hub_remove(struct ssam_device *sdev)
- sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group);
-
- ssam_notifier_unregister(sdev->ctrl, &hub->notif);
- + cancel_delayed_work_sync(&hub->update_work);
- ssam_hub_remove_devices(&sdev->dev);
- -
- - mutex_destroy(&hub->lock);
- }
-
- static const struct ssam_device_id ssam_base_hub_match[] = {
- --
- 2.32.0
- From 09e3d34a400dfbcf06674de5bfe68e16bb9e53d4 Mon Sep 17 00:00:00 2001
- From: Barry Song <song.bao.hua@hisilicon.com>
- Date: Wed, 3 Mar 2021 11:49:15 +1300
- Subject: [PATCH] genirq: Add IRQF_NO_AUTOEN for request_irq/nmi()
- Many drivers don't want interrupts enabled automatically via request_irq().
- So they are handling this issue by either way of the below two:
- (1)
- irq_set_status_flags(irq, IRQ_NOAUTOEN);
- request_irq(dev, irq...);
- (2)
- request_irq(dev, irq...);
- disable_irq(irq);
- The code in the second way is silly and unsafe. In the small time gap
- between request_irq() and disable_irq(), interrupts can still come.
- The code in the first way is safe though it's subobtimal.
- Add a new IRQF_NO_AUTOEN flag which can be handed in by drivers to
- request_irq() and request_nmi(). It prevents the automatic enabling of the
- requested interrupt/nmi in the same safe way as #1 above. With that the
- various usage sites of #1 and #2 above can be simplified and corrected.
- Signed-off-by: Barry Song <song.bao.hua@hisilicon.com>
- Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
- Signed-off-by: Ingo Molnar <mingo@kernel.org>
- Cc: dmitry.torokhov@gmail.com
- Link: https://lore.kernel.org/r/20210302224916.13980-2-song.bao.hua@hisilicon.com
- Patchset: surface-sam
- ---
- include/linux/interrupt.h | 4 ++++
- kernel/irq/manage.c | 11 +++++++++--
- 2 files changed, 13 insertions(+), 2 deletions(-)
- diff --git a/include/linux/interrupt.h b/include/linux/interrupt.h
- index 967e25767153..76f1161a441a 100644
- --- a/include/linux/interrupt.h
- +++ b/include/linux/interrupt.h
- @@ -61,6 +61,9 @@
- * interrupt handler after suspending interrupts. For system
- * wakeup devices users need to implement wakeup detection in
- * their interrupt handlers.
- + * IRQF_NO_AUTOEN - Don't enable IRQ or NMI automatically when users request it.
- + * Users will enable it explicitly by enable_irq() or enable_nmi()
- + * later.
- */
- #define IRQF_SHARED 0x00000080
- #define IRQF_PROBE_SHARED 0x00000100
- @@ -74,6 +77,7 @@
- #define IRQF_NO_THREAD 0x00010000
- #define IRQF_EARLY_RESUME 0x00020000
- #define IRQF_COND_SUSPEND 0x00040000
- +#define IRQF_NO_AUTOEN 0x00080000
-
- #define IRQF_TIMER (__IRQF_TIMER | IRQF_NO_SUSPEND | IRQF_NO_THREAD)
-
- diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c
- index 21ea370fccda..49288e941365 100644
- --- a/kernel/irq/manage.c
- +++ b/kernel/irq/manage.c
- @@ -1697,7 +1697,8 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
- irqd_set(&desc->irq_data, IRQD_NO_BALANCING);
- }
-
- - if (irq_settings_can_autoenable(desc)) {
- + if (!(new->flags & IRQF_NO_AUTOEN) &&
- + irq_settings_can_autoenable(desc)) {
- irq_startup(desc, IRQ_RESEND, IRQ_START_COND);
- } else {
- /*
- @@ -2090,10 +2091,15 @@ int request_threaded_irq(unsigned int irq, irq_handler_t handler,
- * which interrupt is which (messes up the interrupt freeing
- * logic etc).
- *
- + * Also shared interrupts do not go well with disabling auto enable.
- + * The sharing interrupt might request it while it's still disabled
- + * and then wait for interrupts forever.
- + *
- * Also IRQF_COND_SUSPEND only makes sense for shared interrupts and
- * it cannot be set along with IRQF_NO_SUSPEND.
- */
- if (((irqflags & IRQF_SHARED) && !dev_id) ||
- + ((irqflags & IRQF_SHARED) && (irqflags & IRQF_NO_AUTOEN)) ||
- (!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) ||
- ((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND)))
- return -EINVAL;
- @@ -2249,7 +2255,8 @@ int request_nmi(unsigned int irq, irq_handler_t handler,
-
- desc = irq_to_desc(irq);
-
- - if (!desc || irq_settings_can_autoenable(desc) ||
- + if (!desc || (irq_settings_can_autoenable(desc) &&
- + !(irqflags & IRQF_NO_AUTOEN)) ||
- !irq_settings_can_request(desc) ||
- WARN_ON(irq_settings_is_per_cpu_devid(desc)) ||
- !irq_supports_nmi(desc))
- --
- 2.32.0
- From 71e1f5ac8a599f20e9389bdcc05251d1e14a2b9a Mon Sep 17 00:00:00 2001
- From: Tian Tao <tiantao6@hisilicon.com>
- Date: Wed, 7 Apr 2021 15:00:52 +0800
- Subject: [PATCH] platform/surface: aggregator: move to use request_irq by
- IRQF_NO_AUTOEN flag
- disable_irq() after request_irq() still has a time gap in which
- interrupts can come. request_irq() with IRQF_NO_AUTOEN flag will
- disable IRQ auto-enable because of requesting.
- this patch is made base on "add IRQF_NO_AUTOEN for request_irq" which
- is being merged: https://lore.kernel.org/patchwork/patch/1388765/
- Signed-off-by: Tian Tao <tiantao6@hisilicon.com>
- Reviewed-by: Maximilian Luz <luzmaximilian@gmail.com>
- Link: https://lore.kernel.org/r/1617778852-26492-1-git-send-email-tiantao6@hisilicon.com
- Signed-off-by: Hans de Goede <hdegoede@redhat.com>
- Patchset: surface-sam
- ---
- drivers/platform/surface/aggregator/controller.c | 4 ++--
- 1 file changed, 2 insertions(+), 2 deletions(-)
- diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c
- index d006a36b2924..eace5e9374fe 100644
- --- a/drivers/platform/surface/aggregator/controller.c
- +++ b/drivers/platform/surface/aggregator/controller.c
- @@ -2483,7 +2483,8 @@ int ssam_irq_setup(struct ssam_controller *ctrl)
- * interrupt, and let the SAM resume callback during the controller
- * resume process clear it.
- */
- - const int irqf = IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_RISING;
- + const int irqf = IRQF_SHARED | IRQF_ONESHOT |
- + IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN;
-
- gpiod = gpiod_get(dev, "ssam_wakeup-int", GPIOD_ASIS);
- if (IS_ERR(gpiod))
- @@ -2501,7 +2502,6 @@ int ssam_irq_setup(struct ssam_controller *ctrl)
- return status;
-
- ctrl->irq.num = irq;
- - disable_irq(ctrl->irq.num);
- return 0;
- }
-
- --
- 2.32.0
- From d9deaec079e9f7b1e732ef6edec03989d99a2691 Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Wed, 5 May 2021 14:53:45 +0200
- Subject: [PATCH] platform/surface: aggregator: Do not mark interrupt as shared
- Having both IRQF_NO_AUTOEN and IRQF_SHARED set causes
- request_threaded_irq() to return with -EINVAL (see comment in flag
- validation in that function). As the interrupt is currently not shared
- between multiple devices, drop the IRQF_SHARED flag.
- Fixes: 507cf5a2f1e2 ("platform/surface: aggregator: move to use request_irq by IRQF_NO_AUTOEN flag")
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Patchset: surface-sam
- ---
- drivers/platform/surface/aggregator/controller.c | 3 +--
- 1 file changed, 1 insertion(+), 2 deletions(-)
- diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c
- index eace5e9374fe..a06964aa96e7 100644
- --- a/drivers/platform/surface/aggregator/controller.c
- +++ b/drivers/platform/surface/aggregator/controller.c
- @@ -2483,8 +2483,7 @@ int ssam_irq_setup(struct ssam_controller *ctrl)
- * interrupt, and let the SAM resume callback during the controller
- * resume process clear it.
- */
- - const int irqf = IRQF_SHARED | IRQF_ONESHOT |
- - IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN;
- + const int irqf = IRQF_ONESHOT | IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN;
-
- gpiod = gpiod_get(dev, "ssam_wakeup-int", GPIOD_ASIS);
- if (IS_ERR(gpiod))
- --
- 2.32.0
- From 6083e10f0f23437f53a013052f42293f333d0018 Mon Sep 17 00:00:00 2001
- From: Arnd Bergmann <arnd@arndb.de>
- Date: Fri, 14 May 2021 22:04:36 +0200
- Subject: [PATCH] platform/surface: aggregator: avoid clang
- -Wconstant-conversion warning
- Clang complains about the assignment of SSAM_ANY_IID to
- ssam_device_uid->instance:
- drivers/platform/surface/surface_aggregator_registry.c:478:25: error: implicit conversion from 'int' to '__u8' (aka 'unsigned char') changes value from 65535 to 255 [-Werror,-Wconstant-conversion]
- { SSAM_VDEV(HUB, 0x02, SSAM_ANY_IID, 0x00) },
- ~ ^~~~~~~~~~~~
- include/linux/surface_aggregator/device.h:71:23: note: expanded from macro 'SSAM_ANY_IID'
- #define SSAM_ANY_IID 0xffff
- ^~~~~~
- include/linux/surface_aggregator/device.h:126:63: note: expanded from macro 'SSAM_VDEV'
- SSAM_DEVICE(SSAM_DOMAIN_VIRTUAL, SSAM_VIRTUAL_TC_##cat, tid, iid, fun)
- ^~~
- include/linux/surface_aggregator/device.h:102:41: note: expanded from macro 'SSAM_DEVICE'
- .instance = ((iid) != SSAM_ANY_IID) ? (iid) : 0, \
- ^~~
- The assignment doesn't actually happen, but clang checks the type limits
- before checking whether this assignment is reached. Replace the ?:
- operator with a __builtin_choose_expr() invocation that avoids the
- warning for the untaken part.
- Fixes: eb0e90a82098 ("platform/surface: aggregator: Add dedicated bus and device type")
- Cc: platform-driver-x86@vger.kernel.org
- Signed-off-by: Arnd Bergmann <arnd@arndb.de>
- Reviewed-by: Nathan Chancellor <nathan@kernel.org>
- Reviewed-by: Maximilian Luz <luzmaximilian@gmail.com>
- Link: https://lore.kernel.org/r/20210514200453.1542978-1-arnd@kernel.org
- Signed-off-by: Hans de Goede <hdegoede@redhat.com>
- Patchset: surface-sam
- ---
- include/linux/surface_aggregator/device.h | 6 +++---
- 1 file changed, 3 insertions(+), 3 deletions(-)
- diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h
- index 4441ad667c3f..6ff9c58b3e17 100644
- --- a/include/linux/surface_aggregator/device.h
- +++ b/include/linux/surface_aggregator/device.h
- @@ -98,9 +98,9 @@ struct ssam_device_uid {
- | (((fun) != SSAM_ANY_FUN) ? SSAM_MATCH_FUNCTION : 0), \
- .domain = d, \
- .category = cat, \
- - .target = ((tid) != SSAM_ANY_TID) ? (tid) : 0, \
- - .instance = ((iid) != SSAM_ANY_IID) ? (iid) : 0, \
- - .function = ((fun) != SSAM_ANY_FUN) ? (fun) : 0 \
- + .target = __builtin_choose_expr((tid) != SSAM_ANY_TID, (tid), 0), \
- + .instance = __builtin_choose_expr((iid) != SSAM_ANY_IID, (iid), 0), \
- + .function = __builtin_choose_expr((fun) != SSAM_ANY_FUN, (fun), 0)
-
- /**
- * SSAM_VDEV() - Initialize a &struct ssam_device_id as virtual device with
- --
- 2.32.0
- From 9c33d72e05577956908445f2093720ab105b781d Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Thu, 13 May 2021 15:44:37 +0200
- Subject: [PATCH] platform/surface: dtx: Fix poll function
- The poll function should not return -ERESTARTSYS.
- Furthermore, locking in this function is completely unnecessary. The
- ddev->lock protects access to the main device and controller (ddev->dev
- and ddev->ctrl), ensuring that both are and remain valid while being
- accessed by clients. Both are, however, never accessed in the poll
- function. The shutdown test (via atomic bit flags) be safely done
- without locking, so drop locking here entirely.
- Reported-by: kernel test robot <lkp@intel.com>
- Fixes: 1d609992832e ("platform/surface: Add DTX driver)
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Link: https://lore.kernel.org/r/20210513134437.2431022-1-luzmaximilian@gmail.com
- Signed-off-by: Hans de Goede <hdegoede@redhat.com>
- Patchset: surface-sam
- ---
- drivers/platform/surface/surface_dtx.c | 8 +-------
- 1 file changed, 1 insertion(+), 7 deletions(-)
- diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c
- index 63ce587e79e3..5d9b758a99bb 100644
- --- a/drivers/platform/surface/surface_dtx.c
- +++ b/drivers/platform/surface/surface_dtx.c
- @@ -527,20 +527,14 @@ static __poll_t surface_dtx_poll(struct file *file, struct poll_table_struct *pt
- struct sdtx_client *client = file->private_data;
- __poll_t events = 0;
-
- - if (down_read_killable(&client->ddev->lock))
- - return -ERESTARTSYS;
- -
- - if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags)) {
- - up_read(&client->ddev->lock);
- + if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags))
- return EPOLLHUP | EPOLLERR;
- - }
-
- poll_wait(file, &client->ddev->waitq, pt);
-
- if (!kfifo_is_empty(&client->buffer))
- events |= EPOLLIN | EPOLLRDNORM;
-
- - up_read(&client->ddev->lock);
- return events;
- }
-
- --
- 2.32.0
- From 4311553db3d140a6a78e82bdc6514c731203d17e Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Sun, 23 May 2021 14:35:37 +0200
- Subject: [PATCH] platform/surface: aggregator_registry: Update comments for
- 15" AMD Surface Laptop 4
- The 15" AMD version of the Surface Laptop 4 shares its WSID HID with the
- 15" AMD version of the Surface Laptop 3. Update the comments
- accordingly.
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Patchset: surface-sam
- ---
- drivers/platform/surface/surface_aggregator_registry.c | 4 ++--
- 1 file changed, 2 insertions(+), 2 deletions(-)
- diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
- index 685d37a7add1..bdc09305aab7 100644
- --- a/drivers/platform/surface/surface_aggregator_registry.c
- +++ b/drivers/platform/surface/surface_aggregator_registry.c
- @@ -156,7 +156,7 @@ static const struct software_node *ssam_node_group_sl2[] = {
- NULL,
- };
-
- -/* Devices for Surface Laptop 3. */
- +/* Devices for Surface Laptop 3 and 4. */
- static const struct software_node *ssam_node_group_sl3[] = {
- &ssam_node_root,
- &ssam_node_bat_ac,
- @@ -521,7 +521,7 @@ static const struct acpi_device_id ssam_platform_hub_match[] = {
- /* Surface Laptop 3 (13", Intel) */
- { "MSHW0114", (unsigned long)ssam_node_group_sl3 },
-
- - /* Surface Laptop 3 (15", AMD) */
- + /* Surface Laptop 3 (15", AMD) and 4 (15", AMD) */
- { "MSHW0110", (unsigned long)ssam_node_group_sl3 },
-
- /* Surface Laptop Go 1 */
- --
- 2.32.0
- From a46d9237d3bf025f577319510993da8f72244c33 Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Sun, 23 May 2021 14:36:36 +0200
- Subject: [PATCH] platform/surface: aggregator_registry: Add support for 13"
- Intel Surface Laptop 4
- Add support for the 13" Intel version of the Surface Laptop 4.
- Use the existing node group for the Surface Laptop 3 since the 15" AMD
- version already shares its WSID HID with its predecessor and there don't
- seem to be any significant differences with regards to SAM.
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Patchset: surface-sam
- ---
- drivers/platform/surface/surface_aggregator_registry.c | 3 +++
- 1 file changed, 3 insertions(+)
- diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
- index bdc09305aab7..ef83461fa536 100644
- --- a/drivers/platform/surface/surface_aggregator_registry.c
- +++ b/drivers/platform/surface/surface_aggregator_registry.c
- @@ -524,6 +524,9 @@ static const struct acpi_device_id ssam_platform_hub_match[] = {
- /* Surface Laptop 3 (15", AMD) and 4 (15", AMD) */
- { "MSHW0110", (unsigned long)ssam_node_group_sl3 },
-
- + /* Surface Laptop 4 (13", Intel) */
- + { "MSHW0250", (unsigned long)ssam_node_group_sl3 },
- +
- /* Surface Laptop Go 1 */
- { "MSHW0118", (unsigned long)ssam_node_group_slg1 },
-
- --
- 2.32.0
- From 45ebc764a23cc78d163036f28e6a59a4150b8971 Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Sun, 23 May 2021 14:09:42 +0200
- Subject: [PATCH] platform/surface: aggregator_registry: Consolidate node
- groups for 5th- and 6th-gen devices
- 5th- and 6th-generation Surface devices have all SAM clients defined in
- ACPI, except for the platform profile/performance mode which his handled
- via the WSID (Windows Surface Integration Device). Thus, the node groups
- for those devices are the same and we can just use a single one instead
- of re-defining the same one over and over again.
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Patchset: surface-sam
- ---
- .../surface/surface_aggregator_registry.c | 47 +++++--------------
- 1 file changed, 12 insertions(+), 35 deletions(-)
- diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
- index ef83461fa536..4428c4330229 100644
- --- a/drivers/platform/surface/surface_aggregator_registry.c
- +++ b/drivers/platform/surface/surface_aggregator_registry.c
- @@ -119,8 +119,13 @@ static const struct software_node ssam_node_hid_base_iid6 = {
- .parent = &ssam_node_hub_base,
- };
-
- -/* Devices for Surface Book 2. */
- -static const struct software_node *ssam_node_group_sb2[] = {
- +/*
- + * Devices for 5th- and 6th-generations models:
- + * - Surface Book 2,
- + * - Surface Laptop 1 and 2,
- + * - Surface Pro 5 and 6.
- + */
- +static const struct software_node *ssam_node_group_gen5[] = {
- &ssam_node_root,
- &ssam_node_tmp_pprof,
- NULL,
- @@ -142,20 +147,6 @@ static const struct software_node *ssam_node_group_sb3[] = {
- NULL,
- };
-
- -/* Devices for Surface Laptop 1. */
- -static const struct software_node *ssam_node_group_sl1[] = {
- - &ssam_node_root,
- - &ssam_node_tmp_pprof,
- - NULL,
- -};
- -
- -/* Devices for Surface Laptop 2. */
- -static const struct software_node *ssam_node_group_sl2[] = {
- - &ssam_node_root,
- - &ssam_node_tmp_pprof,
- - NULL,
- -};
- -
- /* Devices for Surface Laptop 3 and 4. */
- static const struct software_node *ssam_node_group_sl3[] = {
- &ssam_node_root,
- @@ -177,20 +168,6 @@ static const struct software_node *ssam_node_group_slg1[] = {
- NULL,
- };
-
- -/* Devices for Surface Pro 5. */
- -static const struct software_node *ssam_node_group_sp5[] = {
- - &ssam_node_root,
- - &ssam_node_tmp_pprof,
- - NULL,
- -};
- -
- -/* Devices for Surface Pro 6. */
- -static const struct software_node *ssam_node_group_sp6[] = {
- - &ssam_node_root,
- - &ssam_node_tmp_pprof,
- - NULL,
- -};
- -
- /* Devices for Surface Pro 7 and Surface Pro 7+. */
- static const struct software_node *ssam_node_group_sp7[] = {
- &ssam_node_root,
- @@ -495,10 +472,10 @@ static struct ssam_device_driver ssam_base_hub_driver = {
-
- static const struct acpi_device_id ssam_platform_hub_match[] = {
- /* Surface Pro 4, 5, and 6 (OMBR < 0x10) */
- - { "MSHW0081", (unsigned long)ssam_node_group_sp5 },
- + { "MSHW0081", (unsigned long)ssam_node_group_gen5 },
-
- /* Surface Pro 6 (OMBR >= 0x10) */
- - { "MSHW0111", (unsigned long)ssam_node_group_sp6 },
- + { "MSHW0111", (unsigned long)ssam_node_group_gen5 },
-
- /* Surface Pro 7 */
- { "MSHW0116", (unsigned long)ssam_node_group_sp7 },
- @@ -507,16 +484,16 @@ static const struct acpi_device_id ssam_platform_hub_match[] = {
- { "MSHW0119", (unsigned long)ssam_node_group_sp7 },
-
- /* Surface Book 2 */
- - { "MSHW0107", (unsigned long)ssam_node_group_sb2 },
- + { "MSHW0107", (unsigned long)ssam_node_group_gen5 },
-
- /* Surface Book 3 */
- { "MSHW0117", (unsigned long)ssam_node_group_sb3 },
-
- /* Surface Laptop 1 */
- - { "MSHW0086", (unsigned long)ssam_node_group_sl1 },
- + { "MSHW0086", (unsigned long)ssam_node_group_gen5 },
-
- /* Surface Laptop 2 */
- - { "MSHW0112", (unsigned long)ssam_node_group_sl2 },
- + { "MSHW0112", (unsigned long)ssam_node_group_gen5 },
-
- /* Surface Laptop 3 (13", Intel) */
- { "MSHW0114", (unsigned long)ssam_node_group_sl3 },
- --
- 2.32.0
- From 2b18fb4c666e9c129abe78fc276c417e5e90c744 Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Wed, 10 Mar 2021 23:53:28 +0100
- Subject: [PATCH] HID: Add support for Surface Aggregator Module HID transport
- MIME-Version: 1.0
- Content-Type: text/plain; charset=UTF-8
- Content-Transfer-Encoding: 8bit
- Add a HID transport driver to support integrated HID devices on newer
- Microsoft Surface models (specifically 7th-generation, i.e. Surface
- Laptop 3, Surface Book 3, and later).
- On those models, the internal keyboard and touchpad (as well as some
- other HID devices with currently unknown function) are connected via the
- generic HID subsystem (TC=0x15) of the Surface System Aggregator Module
- (SSAM). This subsystem provides a generic HID transport layer, support
- for which is implemented by this driver.
- Co-developed-by: Blaž Hrastnik <blaz@mxxn.io>
- Signed-off-by: Blaž Hrastnik <blaz@mxxn.io>
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Signed-off-by: Jiri Kosina <jkosina@suse.cz>
- Patchset: surface-sam
- ---
- MAINTAINERS | 7 +
- drivers/hid/Kconfig | 2 +
- drivers/hid/Makefile | 2 +
- drivers/hid/surface-hid/Kconfig | 28 +++
- drivers/hid/surface-hid/Makefile | 6 +
- drivers/hid/surface-hid/surface_hid.c | 253 +++++++++++++++++++
- drivers/hid/surface-hid/surface_hid_core.c | 272 +++++++++++++++++++++
- drivers/hid/surface-hid/surface_hid_core.h | 77 ++++++
- 8 files changed, 647 insertions(+)
- create mode 100644 drivers/hid/surface-hid/Kconfig
- create mode 100644 drivers/hid/surface-hid/Makefile
- create mode 100644 drivers/hid/surface-hid/surface_hid.c
- create mode 100644 drivers/hid/surface-hid/surface_hid_core.c
- create mode 100644 drivers/hid/surface-hid/surface_hid_core.h
- diff --git a/MAINTAINERS b/MAINTAINERS
- index da1487d672a8..f54b22333ec6 100644
- --- a/MAINTAINERS
- +++ b/MAINTAINERS
- @@ -11891,6 +11891,13 @@ S: Maintained
- T: git git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git
- F: drivers/platform/surface/
-
- +MICROSOFT SURFACE HID TRANSPORT DRIVER
- +M: Maximilian Luz <luzmaximilian@gmail.com>
- +L: linux-input@vger.kernel.org
- +L: platform-driver-x86@vger.kernel.org
- +S: Maintained
- +F: drivers/hid/surface-hid/
- +
- MICROSOFT SURFACE HOT-PLUG DRIVER
- M: Maximilian Luz <luzmaximilian@gmail.com>
- L: platform-driver-x86@vger.kernel.org
- diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
- index c6a643f4fc5f..a0913a67f614 100644
- --- a/drivers/hid/Kconfig
- +++ b/drivers/hid/Kconfig
- @@ -1206,4 +1206,6 @@ source "drivers/hid/intel-ish-hid/Kconfig"
-
- source "drivers/hid/amd-sfh-hid/Kconfig"
-
- +source "drivers/hid/surface-hid/Kconfig"
- +
- endmenu
- diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
- index c4f6d5c613dc..1044ed238856 100644
- --- a/drivers/hid/Makefile
- +++ b/drivers/hid/Makefile
- @@ -145,3 +145,5 @@ obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-hid/
- obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ish-hid/
-
- obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/
- +
- +obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/
- diff --git a/drivers/hid/surface-hid/Kconfig b/drivers/hid/surface-hid/Kconfig
- new file mode 100644
- index 000000000000..642c7f0e64fe
- --- /dev/null
- +++ b/drivers/hid/surface-hid/Kconfig
- @@ -0,0 +1,28 @@
- +# SPDX-License-Identifier: GPL-2.0+
- +menu "Surface System Aggregator Module HID support"
- + depends on SURFACE_AGGREGATOR
- + depends on INPUT
- +
- +config SURFACE_HID
- + tristate "HID transport driver for Surface System Aggregator Module"
- + depends on SURFACE_AGGREGATOR_REGISTRY
- + select SURFACE_HID_CORE
- + help
- + Driver to support integrated HID devices on newer Microsoft Surface
- + models.
- +
- + This driver provides support for the HID transport protocol provided
- + by the Surface Aggregator Module (i.e. the embedded controller) on
- + 7th-generation Microsoft Surface devices, i.e. Surface Book 3 and
- + Surface Laptop 3. On those models, it is mainly used to connect the
- + integrated touchpad and keyboard.
- +
- + Say M or Y here, if you want support for integrated HID devices, i.e.
- + integrated touchpad and keyboard, on 7th generation Microsoft Surface
- + models.
- +
- +endmenu
- +
- +config SURFACE_HID_CORE
- + tristate
- + select HID
- diff --git a/drivers/hid/surface-hid/Makefile b/drivers/hid/surface-hid/Makefile
- new file mode 100644
- index 000000000000..62fc04632d3d
- --- /dev/null
- +++ b/drivers/hid/surface-hid/Makefile
- @@ -0,0 +1,6 @@
- +# SPDX-License-Identifier: GPL-2.0+
- +#
- +# Makefile - Surface System Aggregator Module (SSAM) HID transport driver.
- +#
- +obj-$(CONFIG_SURFACE_HID_CORE) += surface_hid_core.o
- +obj-$(CONFIG_SURFACE_HID) += surface_hid.o
- diff --git a/drivers/hid/surface-hid/surface_hid.c b/drivers/hid/surface-hid/surface_hid.c
- new file mode 100644
- index 000000000000..3477b31611ae
- --- /dev/null
- +++ b/drivers/hid/surface-hid/surface_hid.c
- @@ -0,0 +1,253 @@
- +// SPDX-License-Identifier: GPL-2.0+
- +/*
- + * Surface System Aggregator Module (SSAM) HID transport driver for the
- + * generic HID interface (HID/TC=0x15 subsystem). Provides support for
- + * integrated HID devices on Surface Laptop 3, Book 3, and later.
- + *
- + * Copyright (C) 2019-2021 Blaž Hrastnik <blaz@mxxn.io>,
- + * Maximilian Luz <luzmaximilian@gmail.com>
- + */
- +
- +#include <asm/unaligned.h>
- +#include <linux/hid.h>
- +#include <linux/kernel.h>
- +#include <linux/module.h>
- +#include <linux/types.h>
- +
- +#include <linux/surface_aggregator/controller.h>
- +#include <linux/surface_aggregator/device.h>
- +
- +#include "surface_hid_core.h"
- +
- +
- +/* -- SAM interface. -------------------------------------------------------- */
- +
- +struct surface_hid_buffer_slice {
- + __u8 entry;
- + __le32 offset;
- + __le32 length;
- + __u8 end;
- + __u8 data[];
- +} __packed;
- +
- +static_assert(sizeof(struct surface_hid_buffer_slice) == 10);
- +
- +enum surface_hid_cid {
- + SURFACE_HID_CID_OUTPUT_REPORT = 0x01,
- + SURFACE_HID_CID_GET_FEATURE_REPORT = 0x02,
- + SURFACE_HID_CID_SET_FEATURE_REPORT = 0x03,
- + SURFACE_HID_CID_GET_DESCRIPTOR = 0x04,
- +};
- +
- +static int ssam_hid_get_descriptor(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len)
- +{
- + u8 buffer[sizeof(struct surface_hid_buffer_slice) + 0x76];
- + struct surface_hid_buffer_slice *slice;
- + struct ssam_request rqst;
- + struct ssam_response rsp;
- + u32 buffer_len, offset, length;
- + int status;
- +
- + /*
- + * Note: The 0x76 above has been chosen because that's what's used by
- + * the Windows driver. Together with the header, this leads to a 128
- + * byte payload in total.
- + */
- +
- + buffer_len = ARRAY_SIZE(buffer) - sizeof(struct surface_hid_buffer_slice);
- +
- + rqst.target_category = shid->uid.category;
- + rqst.target_id = shid->uid.target;
- + rqst.command_id = SURFACE_HID_CID_GET_DESCRIPTOR;
- + rqst.instance_id = shid->uid.instance;
- + rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
- + rqst.length = sizeof(struct surface_hid_buffer_slice);
- + rqst.payload = buffer;
- +
- + rsp.capacity = ARRAY_SIZE(buffer);
- + rsp.pointer = buffer;
- +
- + slice = (struct surface_hid_buffer_slice *)buffer;
- + slice->entry = entry;
- + slice->end = 0;
- +
- + offset = 0;
- + length = buffer_len;
- +
- + while (!slice->end && offset < len) {
- + put_unaligned_le32(offset, &slice->offset);
- + put_unaligned_le32(length, &slice->length);
- +
- + rsp.length = 0;
- +
- + status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp,
- + sizeof(*slice));
- + if (status)
- + return status;
- +
- + offset = get_unaligned_le32(&slice->offset);
- + length = get_unaligned_le32(&slice->length);
- +
- + /* Don't mess stuff up in case we receive garbage. */
- + if (length > buffer_len || offset > len)
- + return -EPROTO;
- +
- + if (offset + length > len)
- + length = len - offset;
- +
- + memcpy(buf + offset, &slice->data[0], length);
- +
- + offset += length;
- + length = buffer_len;
- + }
- +
- + if (offset != len) {
- + dev_err(shid->dev, "unexpected descriptor length: got %u, expected %zu\n",
- + offset, len);
- + return -EPROTO;
- + }
- +
- + return 0;
- +}
- +
- +static int ssam_hid_set_raw_report(struct surface_hid_device *shid, u8 rprt_id, bool feature,
- + u8 *buf, size_t len)
- +{
- + struct ssam_request rqst;
- + u8 cid;
- +
- + if (feature)
- + cid = SURFACE_HID_CID_SET_FEATURE_REPORT;
- + else
- + cid = SURFACE_HID_CID_OUTPUT_REPORT;
- +
- + rqst.target_category = shid->uid.category;
- + rqst.target_id = shid->uid.target;
- + rqst.instance_id = shid->uid.instance;
- + rqst.command_id = cid;
- + rqst.flags = 0;
- + rqst.length = len;
- + rqst.payload = buf;
- +
- + buf[0] = rprt_id;
- +
- + return ssam_retry(ssam_request_sync, shid->ctrl, &rqst, NULL);
- +}
- +
- +static int ssam_hid_get_raw_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
- +{
- + struct ssam_request rqst;
- + struct ssam_response rsp;
- +
- + rqst.target_category = shid->uid.category;
- + rqst.target_id = shid->uid.target;
- + rqst.instance_id = shid->uid.instance;
- + rqst.command_id = SURFACE_HID_CID_GET_FEATURE_REPORT;
- + rqst.flags = 0;
- + rqst.length = sizeof(rprt_id);
- + rqst.payload = &rprt_id;
- +
- + rsp.capacity = len;
- + rsp.length = 0;
- + rsp.pointer = buf;
- +
- + return ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(rprt_id));
- +}
- +
- +static u32 ssam_hid_event_fn(struct ssam_event_notifier *nf, const struct ssam_event *event)
- +{
- + struct surface_hid_device *shid = container_of(nf, struct surface_hid_device, notif);
- +
- + if (event->command_id != 0x00)
- + return 0;
- +
- + hid_input_report(shid->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 0);
- + return SSAM_NOTIF_HANDLED;
- +}
- +
- +
- +/* -- Transport driver. ----------------------------------------------------- */
- +
- +static int shid_output_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
- +{
- + int status;
- +
- + status = ssam_hid_set_raw_report(shid, rprt_id, false, buf, len);
- + return status >= 0 ? len : status;
- +}
- +
- +static int shid_get_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
- +{
- + int status;
- +
- + status = ssam_hid_get_raw_report(shid, rprt_id, buf, len);
- + return status >= 0 ? len : status;
- +}
- +
- +static int shid_set_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
- +{
- + int status;
- +
- + status = ssam_hid_set_raw_report(shid, rprt_id, true, buf, len);
- + return status >= 0 ? len : status;
- +}
- +
- +
- +/* -- Driver setup. --------------------------------------------------------- */
- +
- +static int surface_hid_probe(struct ssam_device *sdev)
- +{
- + struct surface_hid_device *shid;
- +
- + shid = devm_kzalloc(&sdev->dev, sizeof(*shid), GFP_KERNEL);
- + if (!shid)
- + return -ENOMEM;
- +
- + shid->dev = &sdev->dev;
- + shid->ctrl = sdev->ctrl;
- + shid->uid = sdev->uid;
- +
- + shid->notif.base.priority = 1;
- + shid->notif.base.fn = ssam_hid_event_fn;
- + shid->notif.event.reg = SSAM_EVENT_REGISTRY_REG;
- + shid->notif.event.id.target_category = sdev->uid.category;
- + shid->notif.event.id.instance = sdev->uid.instance;
- + shid->notif.event.mask = SSAM_EVENT_MASK_STRICT;
- + shid->notif.event.flags = 0;
- +
- + shid->ops.get_descriptor = ssam_hid_get_descriptor;
- + shid->ops.output_report = shid_output_report;
- + shid->ops.get_feature_report = shid_get_feature_report;
- + shid->ops.set_feature_report = shid_set_feature_report;
- +
- + ssam_device_set_drvdata(sdev, shid);
- + return surface_hid_device_add(shid);
- +}
- +
- +static void surface_hid_remove(struct ssam_device *sdev)
- +{
- + surface_hid_device_destroy(ssam_device_get_drvdata(sdev));
- +}
- +
- +static const struct ssam_device_id surface_hid_match[] = {
- + { SSAM_SDEV(HID, 0x02, SSAM_ANY_IID, 0x00) },
- + { },
- +};
- +MODULE_DEVICE_TABLE(ssam, surface_hid_match);
- +
- +static struct ssam_device_driver surface_hid_driver = {
- + .probe = surface_hid_probe,
- + .remove = surface_hid_remove,
- + .match_table = surface_hid_match,
- + .driver = {
- + .name = "surface_hid",
- + .pm = &surface_hid_pm_ops,
- + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
- + },
- +};
- +module_ssam_device_driver(surface_hid_driver);
- +
- +MODULE_AUTHOR("Blaž Hrastnik <blaz@mxxn.io>");
- +MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
- +MODULE_DESCRIPTION("HID transport driver for Surface System Aggregator Module");
- +MODULE_LICENSE("GPL");
- diff --git a/drivers/hid/surface-hid/surface_hid_core.c b/drivers/hid/surface-hid/surface_hid_core.c
- new file mode 100644
- index 000000000000..7b27ec392232
- --- /dev/null
- +++ b/drivers/hid/surface-hid/surface_hid_core.c
- @@ -0,0 +1,272 @@
- +// SPDX-License-Identifier: GPL-2.0+
- +/*
- + * Common/core components for the Surface System Aggregator Module (SSAM) HID
- + * transport driver. Provides support for integrated HID devices on Microsoft
- + * Surface models.
- + *
- + * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
- + */
- +
- +#include <asm/unaligned.h>
- +#include <linux/hid.h>
- +#include <linux/kernel.h>
- +#include <linux/module.h>
- +#include <linux/types.h>
- +#include <linux/usb/ch9.h>
- +
- +#include <linux/surface_aggregator/controller.h>
- +
- +#include "surface_hid_core.h"
- +
- +
- +/* -- Device descriptor access. --------------------------------------------- */
- +
- +static int surface_hid_load_hid_descriptor(struct surface_hid_device *shid)
- +{
- + int status;
- +
- + status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_HID,
- + (u8 *)&shid->hid_desc, sizeof(shid->hid_desc));
- + if (status)
- + return status;
- +
- + if (shid->hid_desc.desc_len != sizeof(shid->hid_desc)) {
- + dev_err(shid->dev, "unexpected HID descriptor length: got %u, expected %zu\n",
- + shid->hid_desc.desc_len, sizeof(shid->hid_desc));
- + return -EPROTO;
- + }
- +
- + if (shid->hid_desc.desc_type != HID_DT_HID) {
- + dev_err(shid->dev, "unexpected HID descriptor type: got %#04x, expected %#04x\n",
- + shid->hid_desc.desc_type, HID_DT_HID);
- + return -EPROTO;
- + }
- +
- + if (shid->hid_desc.num_descriptors != 1) {
- + dev_err(shid->dev, "unexpected number of descriptors: got %u, expected 1\n",
- + shid->hid_desc.num_descriptors);
- + return -EPROTO;
- + }
- +
- + if (shid->hid_desc.report_desc_type != HID_DT_REPORT) {
- + dev_err(shid->dev, "unexpected report descriptor type: got %#04x, expected %#04x\n",
- + shid->hid_desc.report_desc_type, HID_DT_REPORT);
- + return -EPROTO;
- + }
- +
- + return 0;
- +}
- +
- +static int surface_hid_load_device_attributes(struct surface_hid_device *shid)
- +{
- + int status;
- +
- + status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_ATTRS,
- + (u8 *)&shid->attrs, sizeof(shid->attrs));
- + if (status)
- + return status;
- +
- + if (get_unaligned_le32(&shid->attrs.length) != sizeof(shid->attrs)) {
- + dev_err(shid->dev, "unexpected attribute length: got %u, expected %zu\n",
- + get_unaligned_le32(&shid->attrs.length), sizeof(shid->attrs));
- + return -EPROTO;
- + }
- +
- + return 0;
- +}
- +
- +
- +/* -- Transport driver (common). -------------------------------------------- */
- +
- +static int surface_hid_start(struct hid_device *hid)
- +{
- + struct surface_hid_device *shid = hid->driver_data;
- +
- + return ssam_notifier_register(shid->ctrl, &shid->notif);
- +}
- +
- +static void surface_hid_stop(struct hid_device *hid)
- +{
- + struct surface_hid_device *shid = hid->driver_data;
- +
- + /* Note: This call will log errors for us, so ignore them here. */
- + ssam_notifier_unregister(shid->ctrl, &shid->notif);
- +}
- +
- +static int surface_hid_open(struct hid_device *hid)
- +{
- + return 0;
- +}
- +
- +static void surface_hid_close(struct hid_device *hid)
- +{
- +}
- +
- +static int surface_hid_parse(struct hid_device *hid)
- +{
- + struct surface_hid_device *shid = hid->driver_data;
- + size_t len = get_unaligned_le16(&shid->hid_desc.report_desc_len);
- + u8 *buf;
- + int status;
- +
- + buf = kzalloc(len, GFP_KERNEL);
- + if (!buf)
- + return -ENOMEM;
- +
- + status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_REPORT, buf, len);
- + if (!status)
- + status = hid_parse_report(hid, buf, len);
- +
- + kfree(buf);
- + return status;
- +}
- +
- +static int surface_hid_raw_request(struct hid_device *hid, unsigned char reportnum, u8 *buf,
- + size_t len, unsigned char rtype, int reqtype)
- +{
- + struct surface_hid_device *shid = hid->driver_data;
- +
- + if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT)
- + return shid->ops.output_report(shid, reportnum, buf, len);
- +
- + else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT)
- + return shid->ops.get_feature_report(shid, reportnum, buf, len);
- +
- + else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT)
- + return shid->ops.set_feature_report(shid, reportnum, buf, len);
- +
- + return -EIO;
- +}
- +
- +static struct hid_ll_driver surface_hid_ll_driver = {
- + .start = surface_hid_start,
- + .stop = surface_hid_stop,
- + .open = surface_hid_open,
- + .close = surface_hid_close,
- + .parse = surface_hid_parse,
- + .raw_request = surface_hid_raw_request,
- +};
- +
- +
- +/* -- Common device setup. -------------------------------------------------- */
- +
- +int surface_hid_device_add(struct surface_hid_device *shid)
- +{
- + int status;
- +
- + status = surface_hid_load_hid_descriptor(shid);
- + if (status)
- + return status;
- +
- + status = surface_hid_load_device_attributes(shid);
- + if (status)
- + return status;
- +
- + shid->hid = hid_allocate_device();
- + if (IS_ERR(shid->hid))
- + return PTR_ERR(shid->hid);
- +
- + shid->hid->dev.parent = shid->dev;
- + shid->hid->bus = BUS_HOST;
- + shid->hid->vendor = cpu_to_le16(shid->attrs.vendor);
- + shid->hid->product = cpu_to_le16(shid->attrs.product);
- + shid->hid->version = cpu_to_le16(shid->hid_desc.hid_version);
- + shid->hid->country = shid->hid_desc.country_code;
- +
- + snprintf(shid->hid->name, sizeof(shid->hid->name), "Microsoft Surface %04X:%04X",
- + shid->hid->vendor, shid->hid->product);
- +
- + strscpy(shid->hid->phys, dev_name(shid->dev), sizeof(shid->hid->phys));
- +
- + shid->hid->driver_data = shid;
- + shid->hid->ll_driver = &surface_hid_ll_driver;
- +
- + status = hid_add_device(shid->hid);
- + if (status)
- + hid_destroy_device(shid->hid);
- +
- + return status;
- +}
- +EXPORT_SYMBOL_GPL(surface_hid_device_add);
- +
- +void surface_hid_device_destroy(struct surface_hid_device *shid)
- +{
- + hid_destroy_device(shid->hid);
- +}
- +EXPORT_SYMBOL_GPL(surface_hid_device_destroy);
- +
- +
- +/* -- PM ops. --------------------------------------------------------------- */
- +
- +#ifdef CONFIG_PM_SLEEP
- +
- +static int surface_hid_suspend(struct device *dev)
- +{
- + struct surface_hid_device *d = dev_get_drvdata(dev);
- +
- + if (d->hid->driver && d->hid->driver->suspend)
- + return d->hid->driver->suspend(d->hid, PMSG_SUSPEND);
- +
- + return 0;
- +}
- +
- +static int surface_hid_resume(struct device *dev)
- +{
- + struct surface_hid_device *d = dev_get_drvdata(dev);
- +
- + if (d->hid->driver && d->hid->driver->resume)
- + return d->hid->driver->resume(d->hid);
- +
- + return 0;
- +}
- +
- +static int surface_hid_freeze(struct device *dev)
- +{
- + struct surface_hid_device *d = dev_get_drvdata(dev);
- +
- + if (d->hid->driver && d->hid->driver->suspend)
- + return d->hid->driver->suspend(d->hid, PMSG_FREEZE);
- +
- + return 0;
- +}
- +
- +static int surface_hid_poweroff(struct device *dev)
- +{
- + struct surface_hid_device *d = dev_get_drvdata(dev);
- +
- + if (d->hid->driver && d->hid->driver->suspend)
- + return d->hid->driver->suspend(d->hid, PMSG_HIBERNATE);
- +
- + return 0;
- +}
- +
- +static int surface_hid_restore(struct device *dev)
- +{
- + struct surface_hid_device *d = dev_get_drvdata(dev);
- +
- + if (d->hid->driver && d->hid->driver->reset_resume)
- + return d->hid->driver->reset_resume(d->hid);
- +
- + return 0;
- +}
- +
- +const struct dev_pm_ops surface_hid_pm_ops = {
- + .freeze = surface_hid_freeze,
- + .thaw = surface_hid_resume,
- + .suspend = surface_hid_suspend,
- + .resume = surface_hid_resume,
- + .poweroff = surface_hid_poweroff,
- + .restore = surface_hid_restore,
- +};
- +EXPORT_SYMBOL_GPL(surface_hid_pm_ops);
- +
- +#else /* CONFIG_PM_SLEEP */
- +
- +const struct dev_pm_ops surface_hid_pm_ops = { };
- +EXPORT_SYMBOL_GPL(surface_hid_pm_ops);
- +
- +#endif /* CONFIG_PM_SLEEP */
- +
- +MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
- +MODULE_DESCRIPTION("HID transport driver core for Surface System Aggregator Module");
- +MODULE_LICENSE("GPL");
- diff --git a/drivers/hid/surface-hid/surface_hid_core.h b/drivers/hid/surface-hid/surface_hid_core.h
- new file mode 100644
- index 000000000000..4b1a7b57e035
- --- /dev/null
- +++ b/drivers/hid/surface-hid/surface_hid_core.h
- @@ -0,0 +1,77 @@
- +/* SPDX-License-Identifier: GPL-2.0+ */
- +/*
- + * Common/core components for the Surface System Aggregator Module (SSAM) HID
- + * transport driver. Provides support for integrated HID devices on Microsoft
- + * Surface models.
- + *
- + * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
- + */
- +
- +#ifndef SURFACE_HID_CORE_H
- +#define SURFACE_HID_CORE_H
- +
- +#include <linux/hid.h>
- +#include <linux/pm.h>
- +#include <linux/types.h>
- +
- +#include <linux/surface_aggregator/controller.h>
- +#include <linux/surface_aggregator/device.h>
- +
- +enum surface_hid_descriptor_entry {
- + SURFACE_HID_DESC_HID = 0,
- + SURFACE_HID_DESC_REPORT = 1,
- + SURFACE_HID_DESC_ATTRS = 2,
- +};
- +
- +struct surface_hid_descriptor {
- + __u8 desc_len; /* = 9 */
- + __u8 desc_type; /* = HID_DT_HID */
- + __le16 hid_version;
- + __u8 country_code;
- + __u8 num_descriptors; /* = 1 */
- +
- + __u8 report_desc_type; /* = HID_DT_REPORT */
- + __le16 report_desc_len;
- +} __packed;
- +
- +static_assert(sizeof(struct surface_hid_descriptor) == 9);
- +
- +struct surface_hid_attributes {
- + __le32 length;
- + __le16 vendor;
- + __le16 product;
- + __le16 version;
- + __u8 _unknown[22];
- +} __packed;
- +
- +static_assert(sizeof(struct surface_hid_attributes) == 32);
- +
- +struct surface_hid_device;
- +
- +struct surface_hid_device_ops {
- + int (*get_descriptor)(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len);
- + int (*output_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len);
- + int (*get_feature_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len);
- + int (*set_feature_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len);
- +};
- +
- +struct surface_hid_device {
- + struct device *dev;
- + struct ssam_controller *ctrl;
- + struct ssam_device_uid uid;
- +
- + struct surface_hid_descriptor hid_desc;
- + struct surface_hid_attributes attrs;
- +
- + struct ssam_event_notifier notif;
- + struct hid_device *hid;
- +
- + struct surface_hid_device_ops ops;
- +};
- +
- +int surface_hid_device_add(struct surface_hid_device *shid);
- +void surface_hid_device_destroy(struct surface_hid_device *shid);
- +
- +extern const struct dev_pm_ops surface_hid_pm_ops;
- +
- +#endif /* SURFACE_HID_CORE_H */
- --
- 2.32.0
- From ca407872def8e83d86ddaa834c2ec02fe725a1b2 Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Wed, 10 Mar 2021 23:53:29 +0100
- Subject: [PATCH] HID: surface-hid: Add support for legacy keyboard interface
- Add support for the legacy keyboard (KBD/TC=0x08) HID transport layer of
- the Surface System Aggregator Module (SSAM) to the Surface HID driver.
- On Surface Laptops 1 and 2, this interface is used to connect the
- integrated keyboard.
- Note that this subsystem interface essentially provides a limited HID
- transport layer. In contrast to the generic HID interface (TC=0x15) used
- on newer Surface models, this interface only allows (as far as we know)
- for a single device to be connected and is otherwise severely limited in
- terms of support for feature- and output-reports. Specifically, only
- caps-lock-LED output-reports and a single read-only feature-report are
- supported.
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Signed-off-by: Jiri Kosina <jkosina@suse.cz>
- Patchset: surface-sam
- ---
- drivers/hid/surface-hid/Kconfig | 14 ++
- drivers/hid/surface-hid/Makefile | 1 +
- drivers/hid/surface-hid/surface_kbd.c | 300 ++++++++++++++++++++++++++
- 3 files changed, 315 insertions(+)
- create mode 100644 drivers/hid/surface-hid/surface_kbd.c
- diff --git a/drivers/hid/surface-hid/Kconfig b/drivers/hid/surface-hid/Kconfig
- index 642c7f0e64fe..7ce9b5d641eb 100644
- --- a/drivers/hid/surface-hid/Kconfig
- +++ b/drivers/hid/surface-hid/Kconfig
- @@ -21,6 +21,20 @@ config SURFACE_HID
- integrated touchpad and keyboard, on 7th generation Microsoft Surface
- models.
-
- +config SURFACE_KBD
- + tristate "HID keyboard transport driver for Surface System Aggregator Module"
- + select SURFACE_HID_CORE
- + help
- + Driver to support HID keyboards on Surface Laptop 1 and 2 devices.
- +
- + This driver provides support for the HID transport protocol provided
- + by the Surface Aggregator Module (i.e. the embedded controller) on
- + Microsoft Surface Laptops 1 and 2. It is used to connect the
- + integrated keyboard on those devices.
- +
- + Say M or Y here, if you want support for the integrated keyboard on
- + Microsoft Surface Laptops 1 and 2.
- +
- endmenu
-
- config SURFACE_HID_CORE
- diff --git a/drivers/hid/surface-hid/Makefile b/drivers/hid/surface-hid/Makefile
- index 62fc04632d3d..4ae11cf09b25 100644
- --- a/drivers/hid/surface-hid/Makefile
- +++ b/drivers/hid/surface-hid/Makefile
- @@ -4,3 +4,4 @@
- #
- obj-$(CONFIG_SURFACE_HID_CORE) += surface_hid_core.o
- obj-$(CONFIG_SURFACE_HID) += surface_hid.o
- +obj-$(CONFIG_SURFACE_KBD) += surface_kbd.o
- diff --git a/drivers/hid/surface-hid/surface_kbd.c b/drivers/hid/surface-hid/surface_kbd.c
- new file mode 100644
- index 000000000000..0635341bc517
- --- /dev/null
- +++ b/drivers/hid/surface-hid/surface_kbd.c
- @@ -0,0 +1,300 @@
- +// SPDX-License-Identifier: GPL-2.0+
- +/*
- + * Surface System Aggregator Module (SSAM) HID transport driver for the legacy
- + * keyboard interface (KBD/TC=0x08 subsystem). Provides support for the
- + * integrated HID keyboard on Surface Laptops 1 and 2.
- + *
- + * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
- + */
- +
- +#include <asm/unaligned.h>
- +#include <linux/hid.h>
- +#include <linux/kernel.h>
- +#include <linux/module.h>
- +#include <linux/platform_device.h>
- +#include <linux/types.h>
- +
- +#include <linux/surface_aggregator/controller.h>
- +
- +#include "surface_hid_core.h"
- +
- +
- +/* -- SAM interface (KBD). -------------------------------------------------- */
- +
- +#define KBD_FEATURE_REPORT_SIZE 7 /* 6 + report ID */
- +
- +enum surface_kbd_cid {
- + SURFACE_KBD_CID_GET_DESCRIPTOR = 0x00,
- + SURFACE_KBD_CID_SET_CAPSLOCK_LED = 0x01,
- + SURFACE_KBD_CID_EVT_INPUT_GENERIC = 0x03,
- + SURFACE_KBD_CID_EVT_INPUT_HOTKEYS = 0x04,
- + SURFACE_KBD_CID_GET_FEATURE_REPORT = 0x0b,
- +};
- +
- +static int ssam_kbd_get_descriptor(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len)
- +{
- + struct ssam_request rqst;
- + struct ssam_response rsp;
- + int status;
- +
- + rqst.target_category = shid->uid.category;
- + rqst.target_id = shid->uid.target;
- + rqst.command_id = SURFACE_KBD_CID_GET_DESCRIPTOR;
- + rqst.instance_id = shid->uid.instance;
- + rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
- + rqst.length = sizeof(entry);
- + rqst.payload = &entry;
- +
- + rsp.capacity = len;
- + rsp.length = 0;
- + rsp.pointer = buf;
- +
- + status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(entry));
- + if (status)
- + return status;
- +
- + if (rsp.length != len) {
- + dev_err(shid->dev, "invalid descriptor length: got %zu, expected, %zu\n",
- + rsp.length, len);
- + return -EPROTO;
- + }
- +
- + return 0;
- +}
- +
- +static int ssam_kbd_set_caps_led(struct surface_hid_device *shid, bool value)
- +{
- + struct ssam_request rqst;
- + u8 value_u8 = value;
- +
- + rqst.target_category = shid->uid.category;
- + rqst.target_id = shid->uid.target;
- + rqst.command_id = SURFACE_KBD_CID_SET_CAPSLOCK_LED;
- + rqst.instance_id = shid->uid.instance;
- + rqst.flags = 0;
- + rqst.length = sizeof(value_u8);
- + rqst.payload = &value_u8;
- +
- + return ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, NULL, sizeof(value_u8));
- +}
- +
- +static int ssam_kbd_get_feature_report(struct surface_hid_device *shid, u8 *buf, size_t len)
- +{
- + struct ssam_request rqst;
- + struct ssam_response rsp;
- + u8 payload = 0;
- + int status;
- +
- + rqst.target_category = shid->uid.category;
- + rqst.target_id = shid->uid.target;
- + rqst.command_id = SURFACE_KBD_CID_GET_FEATURE_REPORT;
- + rqst.instance_id = shid->uid.instance;
- + rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
- + rqst.length = sizeof(payload);
- + rqst.payload = &payload;
- +
- + rsp.capacity = len;
- + rsp.length = 0;
- + rsp.pointer = buf;
- +
- + status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(payload));
- + if (status)
- + return status;
- +
- + if (rsp.length != len) {
- + dev_err(shid->dev, "invalid feature report length: got %zu, expected, %zu\n",
- + rsp.length, len);
- + return -EPROTO;
- + }
- +
- + return 0;
- +}
- +
- +static bool ssam_kbd_is_input_event(const struct ssam_event *event)
- +{
- + if (event->command_id == SURFACE_KBD_CID_EVT_INPUT_GENERIC)
- + return true;
- +
- + if (event->command_id == SURFACE_KBD_CID_EVT_INPUT_HOTKEYS)
- + return true;
- +
- + return false;
- +}
- +
- +static u32 ssam_kbd_event_fn(struct ssam_event_notifier *nf, const struct ssam_event *event)
- +{
- + struct surface_hid_device *shid = container_of(nf, struct surface_hid_device, notif);
- +
- + /*
- + * Check against device UID manually, as registry and device target
- + * category doesn't line up.
- + */
- +
- + if (shid->uid.category != event->target_category)
- + return 0;
- +
- + if (shid->uid.target != event->target_id)
- + return 0;
- +
- + if (shid->uid.instance != event->instance_id)
- + return 0;
- +
- + if (!ssam_kbd_is_input_event(event))
- + return 0;
- +
- + hid_input_report(shid->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 0);
- + return SSAM_NOTIF_HANDLED;
- +}
- +
- +
- +/* -- Transport driver (KBD). ----------------------------------------------- */
- +
- +static int skbd_get_caps_led_value(struct hid_device *hid, u8 rprt_id, u8 *buf, size_t len)
- +{
- + struct hid_field *field;
- + unsigned int offset, size;
- + int i;
- +
- + /* Get LED field. */
- + field = hidinput_get_led_field(hid);
- + if (!field)
- + return -ENOENT;
- +
- + /* Check if we got the correct report. */
- + if (len != hid_report_len(field->report))
- + return -ENOENT;
- +
- + if (rprt_id != field->report->id)
- + return -ENOENT;
- +
- + /* Get caps lock LED index. */
- + for (i = 0; i < field->report_count; i++)
- + if ((field->usage[i].hid & 0xffff) == 0x02)
- + break;
- +
- + if (i == field->report_count)
- + return -ENOENT;
- +
- + /* Extract value. */
- + size = field->report_size;
- + offset = field->report_offset + i * size;
- + return !!hid_field_extract(hid, buf + 1, size, offset);
- +}
- +
- +static int skbd_output_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
- +{
- + int caps_led;
- + int status;
- +
- + caps_led = skbd_get_caps_led_value(shid->hid, rprt_id, buf, len);
- + if (caps_led < 0)
- + return -EIO; /* Only caps LED output reports are supported. */
- +
- + status = ssam_kbd_set_caps_led(shid, caps_led);
- + if (status < 0)
- + return status;
- +
- + return len;
- +}
- +
- +static int skbd_get_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
- +{
- + u8 report[KBD_FEATURE_REPORT_SIZE];
- + int status;
- +
- + /*
- + * The keyboard only has a single hard-coded read-only feature report
- + * of size KBD_FEATURE_REPORT_SIZE. Try to load it and compare its
- + * report ID against the requested one.
- + */
- +
- + if (len < ARRAY_SIZE(report))
- + return -ENOSPC;
- +
- + status = ssam_kbd_get_feature_report(shid, report, ARRAY_SIZE(report));
- + if (status < 0)
- + return status;
- +
- + if (rprt_id != report[0])
- + return -ENOENT;
- +
- + memcpy(buf, report, ARRAY_SIZE(report));
- + return len;
- +}
- +
- +static int skbd_set_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
- +{
- + /* Not supported. See skbd_get_feature_report() for details. */
- + return -EIO;
- +}
- +
- +
- +/* -- Driver setup. --------------------------------------------------------- */
- +
- +static int surface_kbd_probe(struct platform_device *pdev)
- +{
- + struct ssam_controller *ctrl;
- + struct surface_hid_device *shid;
- +
- + /* Add device link to EC. */
- + ctrl = ssam_client_bind(&pdev->dev);
- + if (IS_ERR(ctrl))
- + return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl);
- +
- + shid = devm_kzalloc(&pdev->dev, sizeof(*shid), GFP_KERNEL);
- + if (!shid)
- + return -ENOMEM;
- +
- + shid->dev = &pdev->dev;
- + shid->ctrl = ctrl;
- +
- + shid->uid.domain = SSAM_DOMAIN_SERIALHUB;
- + shid->uid.category = SSAM_SSH_TC_KBD;
- + shid->uid.target = 2;
- + shid->uid.instance = 0;
- + shid->uid.function = 0;
- +
- + shid->notif.base.priority = 1;
- + shid->notif.base.fn = ssam_kbd_event_fn;
- + shid->notif.event.reg = SSAM_EVENT_REGISTRY_SAM;
- + shid->notif.event.id.target_category = shid->uid.category;
- + shid->notif.event.id.instance = shid->uid.instance;
- + shid->notif.event.mask = SSAM_EVENT_MASK_NONE;
- + shid->notif.event.flags = 0;
- +
- + shid->ops.get_descriptor = ssam_kbd_get_descriptor;
- + shid->ops.output_report = skbd_output_report;
- + shid->ops.get_feature_report = skbd_get_feature_report;
- + shid->ops.set_feature_report = skbd_set_feature_report;
- +
- + platform_set_drvdata(pdev, shid);
- + return surface_hid_device_add(shid);
- +}
- +
- +static int surface_kbd_remove(struct platform_device *pdev)
- +{
- + surface_hid_device_destroy(platform_get_drvdata(pdev));
- + return 0;
- +}
- +
- +static const struct acpi_device_id surface_kbd_match[] = {
- + { "MSHW0096" },
- + { },
- +};
- +MODULE_DEVICE_TABLE(acpi, surface_kbd_match);
- +
- +static struct platform_driver surface_kbd_driver = {
- + .probe = surface_kbd_probe,
- + .remove = surface_kbd_remove,
- + .driver = {
- + .name = "surface_keyboard",
- + .acpi_match_table = surface_kbd_match,
- + .pm = &surface_hid_pm_ops,
- + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
- + },
- +};
- +module_platform_driver(surface_kbd_driver);
- +
- +MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
- +MODULE_DESCRIPTION("HID legacy transport driver for Surface System Aggregator Module");
- +MODULE_LICENSE("GPL");
- --
- 2.32.0
- From bf74b38c3b0fca5cf1255a61760acab783272854 Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Fri, 23 Apr 2021 00:51:22 +0200
- Subject: [PATCH] HID: surface-hid: Fix integer endian conversion
- We want to convert from 16 bit (unsigned) little endian values contained
- in a packed struct to CPU native endian values here, not the other way
- around. So replace cpu_to_le16() with get_unaligned_le16(), using the
- latter instead of le16_to_cpu() to acknowledge that we are reading from
- a packed struct.
- Reported-by: kernel test robot <lkp@intel.com>
- Fixes: b05ff1002a5c ("HID: Add support for Surface Aggregator Module HID transport")
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Patchset: surface-sam
- ---
- drivers/hid/surface-hid/surface_hid_core.c | 6 +++---
- 1 file changed, 3 insertions(+), 3 deletions(-)
- diff --git a/drivers/hid/surface-hid/surface_hid_core.c b/drivers/hid/surface-hid/surface_hid_core.c
- index 7b27ec392232..5571e74abe91 100644
- --- a/drivers/hid/surface-hid/surface_hid_core.c
- +++ b/drivers/hid/surface-hid/surface_hid_core.c
- @@ -168,9 +168,9 @@ int surface_hid_device_add(struct surface_hid_device *shid)
-
- shid->hid->dev.parent = shid->dev;
- shid->hid->bus = BUS_HOST;
- - shid->hid->vendor = cpu_to_le16(shid->attrs.vendor);
- - shid->hid->product = cpu_to_le16(shid->attrs.product);
- - shid->hid->version = cpu_to_le16(shid->hid_desc.hid_version);
- + shid->hid->vendor = get_unaligned_le16(&shid->attrs.vendor);
- + shid->hid->product = get_unaligned_le16(&shid->attrs.product);
- + shid->hid->version = get_unaligned_le16(&shid->hid_desc.hid_version);
- shid->hid->country = shid->hid_desc.country_code;
-
- snprintf(shid->hid->name, sizeof(shid->hid->name), "Microsoft Surface %04X:%04X",
- --
- 2.32.0
- From df72fbd50fbc91af28598541702ef8f829b709c6 Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Mon, 7 Jun 2021 21:56:22 +0200
- Subject: [PATCH] HID: surface-hid: Fix get-report request
- Getting a report (e.g. feature report) from a device requires us to send
- a request indicating which report we want to retreive and then waiting
- for the corresponding response containing that report. We already
- provide the response structure to the request call, but the request
- isn't marked as a request that expects a response. Thus the request
- returns before we receive the response and the response buffer indicates
- a zero length response due to that.
- This essentially means that the get-report calls are broken and will
- always indicate that a report of length zero has been read.
- Fix this by appropriately marking the request.
- Fixes: b05ff1002a5c ("HID: Add support for Surface Aggregator Module HID transport")
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Patchset: surface-sam
- ---
- drivers/hid/surface-hid/surface_hid.c | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
- diff --git a/drivers/hid/surface-hid/surface_hid.c b/drivers/hid/surface-hid/surface_hid.c
- index 3477b31611ae..a3a70e4f3f6c 100644
- --- a/drivers/hid/surface-hid/surface_hid.c
- +++ b/drivers/hid/surface-hid/surface_hid.c
- @@ -143,7 +143,7 @@ static int ssam_hid_get_raw_report(struct surface_hid_device *shid, u8 rprt_id,
- rqst.target_id = shid->uid.target;
- rqst.instance_id = shid->uid.instance;
- rqst.command_id = SURFACE_HID_CID_GET_FEATURE_REPORT;
- - rqst.flags = 0;
- + rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
- rqst.length = sizeof(rprt_id);
- rqst.payload = &rprt_id;
-
- --
- 2.32.0
- From 4a2ca25f24557a8c8f8eb65d6a7cb49f62b2fb23 Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Tue, 6 Apr 2021 01:41:25 +0200
- Subject: [PATCH] power: supply: Add battery driver for Surface Aggregator
- Module
- On newer Microsoft Surface models (specifically 7th-generation, i.e.
- Surface Pro 7, Surface Book 3, Surface Laptop 3, and Surface Laptop Go),
- battery and AC status/information is no longer handled via standard ACPI
- devices, but instead directly via the Surface System Aggregator Module
- (SSAM), i.e. the embedded controller on those devices.
- While on previous generation models, battery status is also handled via
- SSAM, an ACPI shim was present to translate the standard ACPI battery
- interface to SSAM requests. The SSAM interface itself, which is modeled
- closely after the ACPI interface, has not changed.
- This commit introduces a new SSAM client device driver to support
- battery status/information via the aforementioned interface on said
- Surface models. It is in parts based on the standard ACPI battery
- driver.
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
- Patchset: surface-sam
- ---
- .../ABI/testing/sysfs-class-power-surface | 15 +
- MAINTAINERS | 7 +
- drivers/power/supply/Kconfig | 16 +
- drivers/power/supply/Makefile | 1 +
- drivers/power/supply/surface_battery.c | 865 ++++++++++++++++++
- 5 files changed, 904 insertions(+)
- create mode 100644 Documentation/ABI/testing/sysfs-class-power-surface
- create mode 100644 drivers/power/supply/surface_battery.c
- diff --git a/Documentation/ABI/testing/sysfs-class-power-surface b/Documentation/ABI/testing/sysfs-class-power-surface
- new file mode 100644
- index 000000000000..79cde4dcf2f5
- --- /dev/null
- +++ b/Documentation/ABI/testing/sysfs-class-power-surface
- @@ -0,0 +1,15 @@
- +What: /sys/class/power_supply/<supply_name>/alarm
- +Date: April 2021
- +KernelVersion: 5.13
- +Contact: Maximilian Luz <luzmaximilian@gmail.com>
- +Description:
- + Battery trip point. When the remaining battery capacity crosses this
- + value in either direction, the system will be notified and if
- + necessary woken.
- +
- + Set to zero to clear/disable.
- +
- + Access: Read, Write
- +
- + Valid values: In micro-Wh or micro-Ah, depending on the power unit
- + of the battery
- diff --git a/MAINTAINERS b/MAINTAINERS
- index f54b22333ec6..7ee93b732270 100644
- --- a/MAINTAINERS
- +++ b/MAINTAINERS
- @@ -11868,6 +11868,13 @@ F: drivers/scsi/smartpqi/smartpqi*.[ch]
- F: include/linux/cciss*.h
- F: include/uapi/linux/cciss*.h
-
- +MICROSOFT SURFACE BATTERY AND AC DRIVERS
- +M: Maximilian Luz <luzmaximilian@gmail.com>
- +L: linux-pm@vger.kernel.org
- +L: platform-driver-x86@vger.kernel.org
- +S: Maintained
- +F: drivers/power/supply/surface_battery.c
- +
- MICROSOFT SURFACE DTX DRIVER
- M: Maximilian Luz <luzmaximilian@gmail.com>
- L: platform-driver-x86@vger.kernel.org
- diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
- index 006b95eca673..cebeff10d543 100644
- --- a/drivers/power/supply/Kconfig
- +++ b/drivers/power/supply/Kconfig
- @@ -801,4 +801,20 @@ config BATTERY_ACER_A500
- help
- Say Y to include support for Acer Iconia Tab A500 battery fuel gauge.
-
- +config BATTERY_SURFACE
- + tristate "Battery driver for 7th-generation Microsoft Surface devices"
- + depends on SURFACE_AGGREGATOR_REGISTRY
- + help
- + Driver for battery devices connected via/managed by the Surface System
- + Aggregator Module (SSAM).
- +
- + This driver provides battery-information and -status support for
- + Surface devices where said data is not exposed via the standard ACPI
- + devices. On those models (7th-generation), battery-information is
- + instead handled directly via SSAM client devices and this driver.
- +
- + Say M or Y here to include battery status support for 7th-generation
- + Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3,
- + Surface Book 3, and Surface Laptop Go.
- +
- endif # POWER_SUPPLY
- diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
- index 5e5fdbbef531..134041538d2c 100644
- --- a/drivers/power/supply/Makefile
- +++ b/drivers/power/supply/Makefile
- @@ -101,3 +101,4 @@ obj-$(CONFIG_CHARGER_BD99954) += bd99954-charger.o
- obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o
- obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o
- obj-$(CONFIG_BATTERY_ACER_A500) += acer_a500_battery.o
- +obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o
- diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c
- new file mode 100644
- index 000000000000..4116dd839ecd
- --- /dev/null
- +++ b/drivers/power/supply/surface_battery.c
- @@ -0,0 +1,865 @@
- +// SPDX-License-Identifier: GPL-2.0+
- +/*
- + * Battery driver for 7th-generation Microsoft Surface devices via Surface
- + * System Aggregator Module (SSAM).
- + *
- + * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
- + */
- +
- +#include <asm/unaligned.h>
- +#include <linux/jiffies.h>
- +#include <linux/kernel.h>
- +#include <linux/module.h>
- +#include <linux/mutex.h>
- +#include <linux/power_supply.h>
- +#include <linux/sysfs.h>
- +#include <linux/types.h>
- +#include <linux/workqueue.h>
- +
- +#include <linux/surface_aggregator/device.h>
- +
- +
- +/* -- SAM interface. -------------------------------------------------------- */
- +
- +enum sam_event_cid_bat {
- + SAM_EVENT_CID_BAT_BIX = 0x15,
- + SAM_EVENT_CID_BAT_BST = 0x16,
- + SAM_EVENT_CID_BAT_ADP = 0x17,
- + SAM_EVENT_CID_BAT_PROT = 0x18,
- + SAM_EVENT_CID_BAT_DPTF = 0x53,
- +};
- +
- +enum sam_battery_sta {
- + SAM_BATTERY_STA_OK = 0x0f,
- + SAM_BATTERY_STA_PRESENT = 0x10,
- +};
- +
- +enum sam_battery_state {
- + SAM_BATTERY_STATE_DISCHARGING = BIT(0),
- + SAM_BATTERY_STATE_CHARGING = BIT(1),
- + SAM_BATTERY_STATE_CRITICAL = BIT(2),
- +};
- +
- +enum sam_battery_power_unit {
- + SAM_BATTERY_POWER_UNIT_mW = 0,
- + SAM_BATTERY_POWER_UNIT_mA = 1,
- +};
- +
- +/* Equivalent to data returned in ACPI _BIX method, revision 0. */
- +struct spwr_bix {
- + u8 revision;
- + __le32 power_unit;
- + __le32 design_cap;
- + __le32 last_full_charge_cap;
- + __le32 technology;
- + __le32 design_voltage;
- + __le32 design_cap_warn;
- + __le32 design_cap_low;
- + __le32 cycle_count;
- + __le32 measurement_accuracy;
- + __le32 max_sampling_time;
- + __le32 min_sampling_time;
- + __le32 max_avg_interval;
- + __le32 min_avg_interval;
- + __le32 bat_cap_granularity_1;
- + __le32 bat_cap_granularity_2;
- + __u8 model[21];
- + __u8 serial[11];
- + __u8 type[5];
- + __u8 oem_info[21];
- +} __packed;
- +
- +static_assert(sizeof(struct spwr_bix) == 119);
- +
- +/* Equivalent to data returned in ACPI _BST method. */
- +struct spwr_bst {
- + __le32 state;
- + __le32 present_rate;
- + __le32 remaining_cap;
- + __le32 present_voltage;
- +} __packed;
- +
- +static_assert(sizeof(struct spwr_bst) == 16);
- +
- +#define SPWR_BIX_REVISION 0
- +#define SPWR_BATTERY_VALUE_UNKNOWN 0xffffffff
- +
- +/* Get battery status (_STA) */
- +SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, {
- + .target_category = SSAM_SSH_TC_BAT,
- + .command_id = 0x01,
- +});
- +
- +/* Get battery static information (_BIX). */
- +SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_bix, struct spwr_bix, {
- + .target_category = SSAM_SSH_TC_BAT,
- + .command_id = 0x02,
- +});
- +
- +/* Get battery dynamic information (_BST). */
- +SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_bst, struct spwr_bst, {
- + .target_category = SSAM_SSH_TC_BAT,
- + .command_id = 0x03,
- +});
- +
- +/* Set battery trip point (_BTP). */
- +SSAM_DEFINE_SYNC_REQUEST_CL_W(ssam_bat_set_btp, __le32, {
- + .target_category = SSAM_SSH_TC_BAT,
- + .command_id = 0x04,
- +});
- +
- +
- +/* -- Device structures. ---------------------------------------------------- */
- +
- +struct spwr_psy_properties {
- + const char *name;
- + struct ssam_event_registry registry;
- +};
- +
- +struct spwr_battery_device {
- + struct ssam_device *sdev;
- +
- + char name[32];
- + struct power_supply *psy;
- + struct power_supply_desc psy_desc;
- +
- + struct delayed_work update_work;
- +
- + struct ssam_event_notifier notif;
- +
- + struct mutex lock; /* Guards access to state data below. */
- + unsigned long timestamp;
- +
- + __le32 sta;
- + struct spwr_bix bix;
- + struct spwr_bst bst;
- + u32 alarm;
- +};
- +
- +
- +/* -- Module parameters. ---------------------------------------------------- */
- +
- +static unsigned int cache_time = 1000;
- +module_param(cache_time, uint, 0644);
- +MODULE_PARM_DESC(cache_time, "battery state caching time in milliseconds [default: 1000]");
- +
- +
- +/* -- State management. ----------------------------------------------------- */
- +
- +/*
- + * Delay for battery update quirk. See spwr_external_power_changed() below
- + * for more details.
- + */
- +#define SPWR_AC_BAT_UPDATE_DELAY msecs_to_jiffies(5000)
- +
- +static bool spwr_battery_present(struct spwr_battery_device *bat)
- +{
- + lockdep_assert_held(&bat->lock);
- +
- + return le32_to_cpu(bat->sta) & SAM_BATTERY_STA_PRESENT;
- +}
- +
- +static int spwr_battery_load_sta(struct spwr_battery_device *bat)
- +{
- + lockdep_assert_held(&bat->lock);
- +
- + return ssam_retry(ssam_bat_get_sta, bat->sdev, &bat->sta);
- +}
- +
- +static int spwr_battery_load_bix(struct spwr_battery_device *bat)
- +{
- + int status;
- +
- + lockdep_assert_held(&bat->lock);
- +
- + if (!spwr_battery_present(bat))
- + return 0;
- +
- + status = ssam_retry(ssam_bat_get_bix, bat->sdev, &bat->bix);
- +
- + /* Enforce NULL terminated strings in case anything goes wrong... */
- + bat->bix.model[ARRAY_SIZE(bat->bix.model) - 1] = 0;
- + bat->bix.serial[ARRAY_SIZE(bat->bix.serial) - 1] = 0;
- + bat->bix.type[ARRAY_SIZE(bat->bix.type) - 1] = 0;
- + bat->bix.oem_info[ARRAY_SIZE(bat->bix.oem_info) - 1] = 0;
- +
- + return status;
- +}
- +
- +static int spwr_battery_load_bst(struct spwr_battery_device *bat)
- +{
- + lockdep_assert_held(&bat->lock);
- +
- + if (!spwr_battery_present(bat))
- + return 0;
- +
- + return ssam_retry(ssam_bat_get_bst, bat->sdev, &bat->bst);
- +}
- +
- +static int spwr_battery_set_alarm_unlocked(struct spwr_battery_device *bat, u32 value)
- +{
- + __le32 value_le = cpu_to_le32(value);
- +
- + lockdep_assert_held(&bat->lock);
- +
- + bat->alarm = value;
- + return ssam_retry(ssam_bat_set_btp, bat->sdev, &value_le);
- +}
- +
- +static int spwr_battery_update_bst_unlocked(struct spwr_battery_device *bat, bool cached)
- +{
- + unsigned long cache_deadline = bat->timestamp + msecs_to_jiffies(cache_time);
- + int status;
- +
- + lockdep_assert_held(&bat->lock);
- +
- + if (cached && bat->timestamp && time_is_after_jiffies(cache_deadline))
- + return 0;
- +
- + status = spwr_battery_load_sta(bat);
- + if (status)
- + return status;
- +
- + status = spwr_battery_load_bst(bat);
- + if (status)
- + return status;
- +
- + bat->timestamp = jiffies;
- + return 0;
- +}
- +
- +static int spwr_battery_update_bst(struct spwr_battery_device *bat, bool cached)
- +{
- + int status;
- +
- + mutex_lock(&bat->lock);
- + status = spwr_battery_update_bst_unlocked(bat, cached);
- + mutex_unlock(&bat->lock);
- +
- + return status;
- +}
- +
- +static int spwr_battery_update_bix_unlocked(struct spwr_battery_device *bat)
- +{
- + int status;
- +
- + lockdep_assert_held(&bat->lock);
- +
- + status = spwr_battery_load_sta(bat);
- + if (status)
- + return status;
- +
- + status = spwr_battery_load_bix(bat);
- + if (status)
- + return status;
- +
- + status = spwr_battery_load_bst(bat);
- + if (status)
- + return status;
- +
- + if (bat->bix.revision != SPWR_BIX_REVISION)
- + dev_warn(&bat->sdev->dev, "unsupported battery revision: %u\n", bat->bix.revision);
- +
- + bat->timestamp = jiffies;
- + return 0;
- +}
- +
- +static u32 sprw_battery_get_full_cap_safe(struct spwr_battery_device *bat)
- +{
- + u32 full_cap = get_unaligned_le32(&bat->bix.last_full_charge_cap);
- +
- + lockdep_assert_held(&bat->lock);
- +
- + if (full_cap == 0 || full_cap == SPWR_BATTERY_VALUE_UNKNOWN)
- + full_cap = get_unaligned_le32(&bat->bix.design_cap);
- +
- + return full_cap;
- +}
- +
- +static bool spwr_battery_is_full(struct spwr_battery_device *bat)
- +{
- + u32 state = get_unaligned_le32(&bat->bst.state);
- + u32 full_cap = sprw_battery_get_full_cap_safe(bat);
- + u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap);
- +
- + lockdep_assert_held(&bat->lock);
- +
- + return full_cap != SPWR_BATTERY_VALUE_UNKNOWN && full_cap != 0 &&
- + remaining_cap != SPWR_BATTERY_VALUE_UNKNOWN &&
- + remaining_cap >= full_cap &&
- + state == 0;
- +}
- +
- +static int spwr_battery_recheck_full(struct spwr_battery_device *bat)
- +{
- + bool present;
- + u32 unit;
- + int status;
- +
- + mutex_lock(&bat->lock);
- + unit = get_unaligned_le32(&bat->bix.power_unit);
- + present = spwr_battery_present(bat);
- +
- + status = spwr_battery_update_bix_unlocked(bat);
- + if (status)
- + goto out;
- +
- + /* If battery has been attached, (re-)initialize alarm. */
- + if (!present && spwr_battery_present(bat)) {
- + u32 cap_warn = get_unaligned_le32(&bat->bix.design_cap_warn);
- +
- + status = spwr_battery_set_alarm_unlocked(bat, cap_warn);
- + if (status)
- + goto out;
- + }
- +
- + /*
- + * Warn if the unit has changed. This is something we genuinely don't
- + * expect to happen, so make this a big warning. If it does, we'll
- + * need to add support for it.
- + */
- + WARN_ON(unit != get_unaligned_le32(&bat->bix.power_unit));
- +
- +out:
- + mutex_unlock(&bat->lock);
- +
- + if (!status)
- + power_supply_changed(bat->psy);
- +
- + return status;
- +}
- +
- +static int spwr_battery_recheck_status(struct spwr_battery_device *bat)
- +{
- + int status;
- +
- + status = spwr_battery_update_bst(bat, false);
- + if (!status)
- + power_supply_changed(bat->psy);
- +
- + return status;
- +}
- +
- +static u32 spwr_notify_bat(struct ssam_event_notifier *nf, const struct ssam_event *event)
- +{
- + struct spwr_battery_device *bat = container_of(nf, struct spwr_battery_device, notif);
- + int status;
- +
- + dev_dbg(&bat->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n",
- + event->command_id, event->instance_id, event->target_id);
- +
- + switch (event->command_id) {
- + case SAM_EVENT_CID_BAT_BIX:
- + status = spwr_battery_recheck_full(bat);
- + break;
- +
- + case SAM_EVENT_CID_BAT_BST:
- + status = spwr_battery_recheck_status(bat);
- + break;
- +
- + case SAM_EVENT_CID_BAT_PROT:
- + /*
- + * TODO: Implement support for battery protection status change
- + * event.
- + */
- + status = 0;
- + break;
- +
- + case SAM_EVENT_CID_BAT_DPTF:
- + /*
- + * TODO: Implement support for DPTF event.
- + */
- + status = 0;
- + break;
- +
- + default:
- + return 0;
- + }
- +
- + return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED;
- +}
- +
- +static void spwr_battery_update_bst_workfn(struct work_struct *work)
- +{
- + struct delayed_work *dwork = to_delayed_work(work);
- + struct spwr_battery_device *bat;
- + int status;
- +
- + bat = container_of(dwork, struct spwr_battery_device, update_work);
- +
- + status = spwr_battery_update_bst(bat, false);
- + if (status) {
- + dev_err(&bat->sdev->dev, "failed to update battery state: %d\n", status);
- + return;
- + }
- +
- + power_supply_changed(bat->psy);
- +}
- +
- +static void spwr_external_power_changed(struct power_supply *psy)
- +{
- + struct spwr_battery_device *bat = power_supply_get_drvdata(psy);
- +
- + /*
- + * Handle battery update quirk: When the battery is fully charged (or
- + * charged up to the limit imposed by the UEFI battery limit) and the
- + * adapter is plugged in or removed, the EC does not send a separate
- + * event for the state (charging/discharging) change. Furthermore it
- + * may take some time until the state is updated on the battery.
- + * Schedule an update to solve this.
- + */
- +
- + schedule_delayed_work(&bat->update_work, SPWR_AC_BAT_UPDATE_DELAY);
- +}
- +
- +
- +/* -- Properties. ----------------------------------------------------------- */
- +
- +static const enum power_supply_property spwr_battery_props_chg[] = {
- + POWER_SUPPLY_PROP_STATUS,
- + POWER_SUPPLY_PROP_PRESENT,
- + POWER_SUPPLY_PROP_TECHNOLOGY,
- + POWER_SUPPLY_PROP_CYCLE_COUNT,
- + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
- + POWER_SUPPLY_PROP_VOLTAGE_NOW,
- + POWER_SUPPLY_PROP_CURRENT_NOW,
- + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
- + POWER_SUPPLY_PROP_CHARGE_FULL,
- + POWER_SUPPLY_PROP_CHARGE_NOW,
- + POWER_SUPPLY_PROP_CAPACITY,
- + POWER_SUPPLY_PROP_CAPACITY_LEVEL,
- + POWER_SUPPLY_PROP_MODEL_NAME,
- + POWER_SUPPLY_PROP_MANUFACTURER,
- + POWER_SUPPLY_PROP_SERIAL_NUMBER,
- +};
- +
- +static const enum power_supply_property spwr_battery_props_eng[] = {
- + POWER_SUPPLY_PROP_STATUS,
- + POWER_SUPPLY_PROP_PRESENT,
- + POWER_SUPPLY_PROP_TECHNOLOGY,
- + POWER_SUPPLY_PROP_CYCLE_COUNT,
- + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
- + POWER_SUPPLY_PROP_VOLTAGE_NOW,
- + POWER_SUPPLY_PROP_POWER_NOW,
- + POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN,
- + POWER_SUPPLY_PROP_ENERGY_FULL,
- + POWER_SUPPLY_PROP_ENERGY_NOW,
- + POWER_SUPPLY_PROP_CAPACITY,
- + POWER_SUPPLY_PROP_CAPACITY_LEVEL,
- + POWER_SUPPLY_PROP_MODEL_NAME,
- + POWER_SUPPLY_PROP_MANUFACTURER,
- + POWER_SUPPLY_PROP_SERIAL_NUMBER,
- +};
- +
- +static int spwr_battery_prop_status(struct spwr_battery_device *bat)
- +{
- + u32 state = get_unaligned_le32(&bat->bst.state);
- + u32 present_rate = get_unaligned_le32(&bat->bst.present_rate);
- +
- + lockdep_assert_held(&bat->lock);
- +
- + if (state & SAM_BATTERY_STATE_DISCHARGING)
- + return POWER_SUPPLY_STATUS_DISCHARGING;
- +
- + if (state & SAM_BATTERY_STATE_CHARGING)
- + return POWER_SUPPLY_STATUS_CHARGING;
- +
- + if (spwr_battery_is_full(bat))
- + return POWER_SUPPLY_STATUS_FULL;
- +
- + if (present_rate == 0)
- + return POWER_SUPPLY_STATUS_NOT_CHARGING;
- +
- + return POWER_SUPPLY_STATUS_UNKNOWN;
- +}
- +
- +static int spwr_battery_prop_technology(struct spwr_battery_device *bat)
- +{
- + lockdep_assert_held(&bat->lock);
- +
- + if (!strcasecmp("NiCd", bat->bix.type))
- + return POWER_SUPPLY_TECHNOLOGY_NiCd;
- +
- + if (!strcasecmp("NiMH", bat->bix.type))
- + return POWER_SUPPLY_TECHNOLOGY_NiMH;
- +
- + if (!strcasecmp("LION", bat->bix.type))
- + return POWER_SUPPLY_TECHNOLOGY_LION;
- +
- + if (!strncasecmp("LI-ION", bat->bix.type, 6))
- + return POWER_SUPPLY_TECHNOLOGY_LION;
- +
- + if (!strcasecmp("LiP", bat->bix.type))
- + return POWER_SUPPLY_TECHNOLOGY_LIPO;
- +
- + return POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
- +}
- +
- +static int spwr_battery_prop_capacity(struct spwr_battery_device *bat)
- +{
- + u32 full_cap = sprw_battery_get_full_cap_safe(bat);
- + u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap);
- +
- + lockdep_assert_held(&bat->lock);
- +
- + if (full_cap == 0 || full_cap == SPWR_BATTERY_VALUE_UNKNOWN)
- + return -ENODATA;
- +
- + if (remaining_cap == SPWR_BATTERY_VALUE_UNKNOWN)
- + return -ENODATA;
- +
- + return remaining_cap * 100 / full_cap;
- +}
- +
- +static int spwr_battery_prop_capacity_level(struct spwr_battery_device *bat)
- +{
- + u32 state = get_unaligned_le32(&bat->bst.state);
- + u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap);
- +
- + lockdep_assert_held(&bat->lock);
- +
- + if (state & SAM_BATTERY_STATE_CRITICAL)
- + return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
- +
- + if (spwr_battery_is_full(bat))
- + return POWER_SUPPLY_CAPACITY_LEVEL_FULL;
- +
- + if (remaining_cap <= bat->alarm)
- + return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
- +
- + return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
- +}
- +
- +static int spwr_battery_get_property(struct power_supply *psy, enum power_supply_property psp,
- + union power_supply_propval *val)
- +{
- + struct spwr_battery_device *bat = power_supply_get_drvdata(psy);
- + u32 value;
- + int status;
- +
- + mutex_lock(&bat->lock);
- +
- + status = spwr_battery_update_bst_unlocked(bat, true);
- + if (status)
- + goto out;
- +
- + /* Abort if battery is not present. */
- + if (!spwr_battery_present(bat) && psp != POWER_SUPPLY_PROP_PRESENT) {
- + status = -ENODEV;
- + goto out;
- + }
- +
- + switch (psp) {
- + case POWER_SUPPLY_PROP_STATUS:
- + val->intval = spwr_battery_prop_status(bat);
- + break;
- +
- + case POWER_SUPPLY_PROP_PRESENT:
- + val->intval = spwr_battery_present(bat);
- + break;
- +
- + case POWER_SUPPLY_PROP_TECHNOLOGY:
- + val->intval = spwr_battery_prop_technology(bat);
- + break;
- +
- + case POWER_SUPPLY_PROP_CYCLE_COUNT:
- + value = get_unaligned_le32(&bat->bix.cycle_count);
- + if (value != SPWR_BATTERY_VALUE_UNKNOWN)
- + val->intval = value;
- + else
- + status = -ENODATA;
- + break;
- +
- + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
- + value = get_unaligned_le32(&bat->bix.design_voltage);
- + if (value != SPWR_BATTERY_VALUE_UNKNOWN)
- + val->intval = value * 1000;
- + else
- + status = -ENODATA;
- + break;
- +
- + case POWER_SUPPLY_PROP_VOLTAGE_NOW:
- + value = get_unaligned_le32(&bat->bst.present_voltage);
- + if (value != SPWR_BATTERY_VALUE_UNKNOWN)
- + val->intval = value * 1000;
- + else
- + status = -ENODATA;
- + break;
- +
- + case POWER_SUPPLY_PROP_CURRENT_NOW:
- + case POWER_SUPPLY_PROP_POWER_NOW:
- + value = get_unaligned_le32(&bat->bst.present_rate);
- + if (value != SPWR_BATTERY_VALUE_UNKNOWN)
- + val->intval = value * 1000;
- + else
- + status = -ENODATA;
- + break;
- +
- + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
- + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
- + value = get_unaligned_le32(&bat->bix.design_cap);
- + if (value != SPWR_BATTERY_VALUE_UNKNOWN)
- + val->intval = value * 1000;
- + else
- + status = -ENODATA;
- + break;
- +
- + case POWER_SUPPLY_PROP_CHARGE_FULL:
- + case POWER_SUPPLY_PROP_ENERGY_FULL:
- + value = get_unaligned_le32(&bat->bix.last_full_charge_cap);
- + if (value != SPWR_BATTERY_VALUE_UNKNOWN)
- + val->intval = value * 1000;
- + else
- + status = -ENODATA;
- + break;
- +
- + case POWER_SUPPLY_PROP_CHARGE_NOW:
- + case POWER_SUPPLY_PROP_ENERGY_NOW:
- + value = get_unaligned_le32(&bat->bst.remaining_cap);
- + if (value != SPWR_BATTERY_VALUE_UNKNOWN)
- + val->intval = value * 1000;
- + else
- + status = -ENODATA;
- + break;
- +
- + case POWER_SUPPLY_PROP_CAPACITY:
- + val->intval = spwr_battery_prop_capacity(bat);
- + break;
- +
- + case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
- + val->intval = spwr_battery_prop_capacity_level(bat);
- + break;
- +
- + case POWER_SUPPLY_PROP_MODEL_NAME:
- + val->strval = bat->bix.model;
- + break;
- +
- + case POWER_SUPPLY_PROP_MANUFACTURER:
- + val->strval = bat->bix.oem_info;
- + break;
- +
- + case POWER_SUPPLY_PROP_SERIAL_NUMBER:
- + val->strval = bat->bix.serial;
- + break;
- +
- + default:
- + status = -EINVAL;
- + break;
- + }
- +
- +out:
- + mutex_unlock(&bat->lock);
- + return status;
- +}
- +
- +
- +/* -- Alarm attribute. ------------------------------------------------------ */
- +
- +static ssize_t alarm_show(struct device *dev, struct device_attribute *attr, char *buf)
- +{
- + struct power_supply *psy = dev_get_drvdata(dev);
- + struct spwr_battery_device *bat = power_supply_get_drvdata(psy);
- + int status;
- +
- + mutex_lock(&bat->lock);
- + status = sysfs_emit(buf, "%d\n", bat->alarm * 1000);
- + mutex_unlock(&bat->lock);
- +
- + return status;
- +}
- +
- +static ssize_t alarm_store(struct device *dev, struct device_attribute *attr, const char *buf,
- + size_t count)
- +{
- + struct power_supply *psy = dev_get_drvdata(dev);
- + struct spwr_battery_device *bat = power_supply_get_drvdata(psy);
- + unsigned long value;
- + int status;
- +
- + status = kstrtoul(buf, 0, &value);
- + if (status)
- + return status;
- +
- + mutex_lock(&bat->lock);
- +
- + if (!spwr_battery_present(bat)) {
- + mutex_unlock(&bat->lock);
- + return -ENODEV;
- + }
- +
- + status = spwr_battery_set_alarm_unlocked(bat, value / 1000);
- + if (status) {
- + mutex_unlock(&bat->lock);
- + return status;
- + }
- +
- + mutex_unlock(&bat->lock);
- + return count;
- +}
- +
- +DEVICE_ATTR_RW(alarm);
- +
- +static struct attribute *spwr_battery_attrs[] = {
- + &dev_attr_alarm.attr,
- + NULL,
- +};
- +ATTRIBUTE_GROUPS(spwr_battery);
- +
- +
- +/* -- Device setup. --------------------------------------------------------- */
- +
- +static void spwr_battery_init(struct spwr_battery_device *bat, struct ssam_device *sdev,
- + struct ssam_event_registry registry, const char *name)
- +{
- + mutex_init(&bat->lock);
- + strncpy(bat->name, name, ARRAY_SIZE(bat->name) - 1);
- +
- + bat->sdev = sdev;
- +
- + bat->notif.base.priority = 1;
- + bat->notif.base.fn = spwr_notify_bat;
- + bat->notif.event.reg = registry;
- + bat->notif.event.id.target_category = sdev->uid.category;
- + bat->notif.event.id.instance = 0;
- + bat->notif.event.mask = SSAM_EVENT_MASK_STRICT;
- + bat->notif.event.flags = SSAM_EVENT_SEQUENCED;
- +
- + bat->psy_desc.name = bat->name;
- + bat->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY;
- + bat->psy_desc.get_property = spwr_battery_get_property;
- +
- + INIT_DELAYED_WORK(&bat->update_work, spwr_battery_update_bst_workfn);
- +}
- +
- +static int spwr_battery_register(struct spwr_battery_device *bat)
- +{
- + struct power_supply_config psy_cfg = {};
- + __le32 sta;
- + int status;
- +
- + /* Make sure the device is there and functioning properly. */
- + status = ssam_retry(ssam_bat_get_sta, bat->sdev, &sta);
- + if (status)
- + return status;
- +
- + if ((le32_to_cpu(sta) & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK)
- + return -ENODEV;
- +
- + /* Satisfy lockdep although we are in an exclusive context here. */
- + mutex_lock(&bat->lock);
- +
- + status = spwr_battery_update_bix_unlocked(bat);
- + if (status) {
- + mutex_unlock(&bat->lock);
- + return status;
- + }
- +
- + if (spwr_battery_present(bat)) {
- + u32 cap_warn = get_unaligned_le32(&bat->bix.design_cap_warn);
- +
- + status = spwr_battery_set_alarm_unlocked(bat, cap_warn);
- + if (status) {
- + mutex_unlock(&bat->lock);
- + return status;
- + }
- + }
- +
- + mutex_unlock(&bat->lock);
- +
- + bat->psy_desc.external_power_changed = spwr_external_power_changed;
- +
- + switch (get_unaligned_le32(&bat->bix.power_unit)) {
- + case SAM_BATTERY_POWER_UNIT_mW:
- + bat->psy_desc.properties = spwr_battery_props_eng;
- + bat->psy_desc.num_properties = ARRAY_SIZE(spwr_battery_props_eng);
- + break;
- +
- + case SAM_BATTERY_POWER_UNIT_mA:
- + bat->psy_desc.properties = spwr_battery_props_chg;
- + bat->psy_desc.num_properties = ARRAY_SIZE(spwr_battery_props_chg);
- + break;
- +
- + default:
- + dev_err(&bat->sdev->dev, "unsupported battery power unit: %u\n",
- + get_unaligned_le32(&bat->bix.power_unit));
- + return -EINVAL;
- + }
- +
- + psy_cfg.drv_data = bat;
- + psy_cfg.attr_grp = spwr_battery_groups;
- +
- + bat->psy = devm_power_supply_register(&bat->sdev->dev, &bat->psy_desc, &psy_cfg);
- + if (IS_ERR(bat->psy))
- + return PTR_ERR(bat->psy);
- +
- + return ssam_notifier_register(bat->sdev->ctrl, &bat->notif);
- +}
- +
- +
- +/* -- Driver setup. --------------------------------------------------------- */
- +
- +static int __maybe_unused surface_battery_resume(struct device *dev)
- +{
- + return spwr_battery_recheck_full(dev_get_drvdata(dev));
- +}
- +SIMPLE_DEV_PM_OPS(surface_battery_pm_ops, NULL, surface_battery_resume);
- +
- +static int surface_battery_probe(struct ssam_device *sdev)
- +{
- + const struct spwr_psy_properties *p;
- + struct spwr_battery_device *bat;
- +
- + p = ssam_device_get_match_data(sdev);
- + if (!p)
- + return -ENODEV;
- +
- + bat = devm_kzalloc(&sdev->dev, sizeof(*bat), GFP_KERNEL);
- + if (!bat)
- + return -ENOMEM;
- +
- + spwr_battery_init(bat, sdev, p->registry, p->name);
- + ssam_device_set_drvdata(sdev, bat);
- +
- + return spwr_battery_register(bat);
- +}
- +
- +static void surface_battery_remove(struct ssam_device *sdev)
- +{
- + struct spwr_battery_device *bat = ssam_device_get_drvdata(sdev);
- +
- + ssam_notifier_unregister(sdev->ctrl, &bat->notif);
- + cancel_delayed_work_sync(&bat->update_work);
- +}
- +
- +static const struct spwr_psy_properties spwr_psy_props_bat1 = {
- + .name = "BAT1",
- + .registry = SSAM_EVENT_REGISTRY_SAM,
- +};
- +
- +static const struct spwr_psy_properties spwr_psy_props_bat2_sb3 = {
- + .name = "BAT2",
- + .registry = SSAM_EVENT_REGISTRY_KIP,
- +};
- +
- +static const struct ssam_device_id surface_battery_match[] = {
- + { SSAM_SDEV(BAT, 0x01, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat1 },
- + { SSAM_SDEV(BAT, 0x02, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat2_sb3 },
- + { },
- +};
- +MODULE_DEVICE_TABLE(ssam, surface_battery_match);
- +
- +static struct ssam_device_driver surface_battery_driver = {
- + .probe = surface_battery_probe,
- + .remove = surface_battery_remove,
- + .match_table = surface_battery_match,
- + .driver = {
- + .name = "surface_battery",
- + .pm = &surface_battery_pm_ops,
- + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
- + },
- +};
- +module_ssam_device_driver(surface_battery_driver);
- +
- +MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
- +MODULE_DESCRIPTION("Battery driver for Surface System Aggregator Module");
- +MODULE_LICENSE("GPL");
- --
- 2.32.0
- From b1345bcac21c00c2e679203a6ffcefc03d066fc9 Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Tue, 6 Apr 2021 01:41:26 +0200
- Subject: [PATCH] power: supply: Add AC driver for Surface Aggregator Module
- On newer Microsoft Surface models (specifically 7th-generation, i.e.
- Surface Pro 7, Surface Book 3, Surface Laptop 3, and Surface Laptop Go),
- battery and AC status/information is no longer handled via standard ACPI
- devices, but instead directly via the Surface System Aggregator Module
- (SSAM), i.e. the embedded controller on those devices.
- While on previous generation models, AC status is also handled via SSAM,
- an ACPI shim was present to translate the standard ACPI AC interface to
- SSAM requests. The SSAM interface itself, which is modeled closely after
- the ACPI interface, has not changed.
- This commit introduces a new SSAM client device driver to support AC
- status/information via the aforementioned interface on said Surface
- models.
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
- Patchset: surface-sam
- ---
- MAINTAINERS | 1 +
- drivers/power/supply/Kconfig | 16 ++
- drivers/power/supply/Makefile | 1 +
- drivers/power/supply/surface_charger.c | 282 +++++++++++++++++++++++++
- 4 files changed, 300 insertions(+)
- create mode 100644 drivers/power/supply/surface_charger.c
- diff --git a/MAINTAINERS b/MAINTAINERS
- index 7ee93b732270..710617e26f3e 100644
- --- a/MAINTAINERS
- +++ b/MAINTAINERS
- @@ -11874,6 +11874,7 @@ L: linux-pm@vger.kernel.org
- L: platform-driver-x86@vger.kernel.org
- S: Maintained
- F: drivers/power/supply/surface_battery.c
- +F: drivers/power/supply/surface_charger.c
-
- MICROSOFT SURFACE DTX DRIVER
- M: Maximilian Luz <luzmaximilian@gmail.com>
- diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
- index cebeff10d543..91f7cf425ac9 100644
- --- a/drivers/power/supply/Kconfig
- +++ b/drivers/power/supply/Kconfig
- @@ -817,4 +817,20 @@ config BATTERY_SURFACE
- Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3,
- Surface Book 3, and Surface Laptop Go.
-
- +config CHARGER_SURFACE
- + tristate "AC driver for 7th-generation Microsoft Surface devices"
- + depends on SURFACE_AGGREGATOR_REGISTRY
- + help
- + Driver for AC devices connected via/managed by the Surface System
- + Aggregator Module (SSAM).
- +
- + This driver provides AC-information and -status support for Surface
- + devices where said data is not exposed via the standard ACPI devices.
- + On those models (7th-generation), AC-information is instead handled
- + directly via a SSAM client device and this driver.
- +
- + Say M or Y here to include AC status support for 7th-generation
- + Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3,
- + Surface Book 3, and Surface Laptop Go.
- +
- endif # POWER_SUPPLY
- diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
- index 134041538d2c..a7309a3d1a47 100644
- --- a/drivers/power/supply/Makefile
- +++ b/drivers/power/supply/Makefile
- @@ -102,3 +102,4 @@ obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o
- obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o
- obj-$(CONFIG_BATTERY_ACER_A500) += acer_a500_battery.o
- obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o
- +obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o
- diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c
- new file mode 100644
- index 000000000000..c2dd7e604d14
- --- /dev/null
- +++ b/drivers/power/supply/surface_charger.c
- @@ -0,0 +1,282 @@
- +// SPDX-License-Identifier: GPL-2.0+
- +/*
- + * AC driver for 7th-generation Microsoft Surface devices via Surface System
- + * Aggregator Module (SSAM).
- + *
- + * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
- + */
- +
- +#include <asm/unaligned.h>
- +#include <linux/kernel.h>
- +#include <linux/module.h>
- +#include <linux/mutex.h>
- +#include <linux/power_supply.h>
- +#include <linux/types.h>
- +
- +#include <linux/surface_aggregator/device.h>
- +
- +
- +/* -- SAM interface. -------------------------------------------------------- */
- +
- +enum sam_event_cid_bat {
- + SAM_EVENT_CID_BAT_ADP = 0x17,
- +};
- +
- +enum sam_battery_sta {
- + SAM_BATTERY_STA_OK = 0x0f,
- + SAM_BATTERY_STA_PRESENT = 0x10,
- +};
- +
- +/* Get battery status (_STA). */
- +SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, {
- + .target_category = SSAM_SSH_TC_BAT,
- + .command_id = 0x01,
- +});
- +
- +/* Get platform power source for battery (_PSR / DPTF PSRC). */
- +SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_psrc, __le32, {
- + .target_category = SSAM_SSH_TC_BAT,
- + .command_id = 0x0d,
- +});
- +
- +
- +/* -- Device structures. ---------------------------------------------------- */
- +
- +struct spwr_psy_properties {
- + const char *name;
- + struct ssam_event_registry registry;
- +};
- +
- +struct spwr_ac_device {
- + struct ssam_device *sdev;
- +
- + char name[32];
- + struct power_supply *psy;
- + struct power_supply_desc psy_desc;
- +
- + struct ssam_event_notifier notif;
- +
- + struct mutex lock; /* Guards access to state below. */
- +
- + __le32 state;
- +};
- +
- +
- +/* -- State management. ----------------------------------------------------- */
- +
- +static int spwr_ac_update_unlocked(struct spwr_ac_device *ac)
- +{
- + u32 old = ac->state;
- + int status;
- +
- + lockdep_assert_held(&ac->lock);
- +
- + status = ssam_retry(ssam_bat_get_psrc, ac->sdev, &ac->state);
- + if (status < 0)
- + return status;
- +
- + return old != ac->state;
- +}
- +
- +static int spwr_ac_update(struct spwr_ac_device *ac)
- +{
- + int status;
- +
- + mutex_lock(&ac->lock);
- + status = spwr_ac_update_unlocked(ac);
- + mutex_unlock(&ac->lock);
- +
- + return status;
- +}
- +
- +static int spwr_ac_recheck(struct spwr_ac_device *ac)
- +{
- + int status;
- +
- + status = spwr_ac_update(ac);
- + if (status > 0)
- + power_supply_changed(ac->psy);
- +
- + return status >= 0 ? 0 : status;
- +}
- +
- +static u32 spwr_notify_ac(struct ssam_event_notifier *nf, const struct ssam_event *event)
- +{
- + struct spwr_ac_device *ac;
- + int status;
- +
- + ac = container_of(nf, struct spwr_ac_device, notif);
- +
- + dev_dbg(&ac->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n",
- + event->command_id, event->instance_id, event->target_id);
- +
- + /*
- + * Allow events of all targets/instances here. Global adapter status
- + * seems to be handled via target=1 and instance=1, but events are
- + * reported on all targets/instances in use.
- + *
- + * While it should be enough to just listen on 1/1, listen everywhere to
- + * make sure we don't miss anything.
- + */
- +
- + switch (event->command_id) {
- + case SAM_EVENT_CID_BAT_ADP:
- + status = spwr_ac_recheck(ac);
- + return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED;
- +
- + default:
- + return 0;
- + }
- +}
- +
- +
- +/* -- Properties. ----------------------------------------------------------- */
- +
- +static const enum power_supply_property spwr_ac_props[] = {
- + POWER_SUPPLY_PROP_ONLINE,
- +};
- +
- +static int spwr_ac_get_property(struct power_supply *psy, enum power_supply_property psp,
- + union power_supply_propval *val)
- +{
- + struct spwr_ac_device *ac = power_supply_get_drvdata(psy);
- + int status;
- +
- + mutex_lock(&ac->lock);
- +
- + status = spwr_ac_update_unlocked(ac);
- + if (status)
- + goto out;
- +
- + switch (psp) {
- + case POWER_SUPPLY_PROP_ONLINE:
- + val->intval = !!le32_to_cpu(ac->state);
- + break;
- +
- + default:
- + status = -EINVAL;
- + goto out;
- + }
- +
- +out:
- + mutex_unlock(&ac->lock);
- + return status;
- +}
- +
- +
- +/* -- Device setup. --------------------------------------------------------- */
- +
- +static char *battery_supplied_to[] = {
- + "BAT1",
- + "BAT2",
- +};
- +
- +static void spwr_ac_init(struct spwr_ac_device *ac, struct ssam_device *sdev,
- + struct ssam_event_registry registry, const char *name)
- +{
- + mutex_init(&ac->lock);
- + strncpy(ac->name, name, ARRAY_SIZE(ac->name) - 1);
- +
- + ac->sdev = sdev;
- +
- + ac->notif.base.priority = 1;
- + ac->notif.base.fn = spwr_notify_ac;
- + ac->notif.event.reg = registry;
- + ac->notif.event.id.target_category = sdev->uid.category;
- + ac->notif.event.id.instance = 0;
- + ac->notif.event.mask = SSAM_EVENT_MASK_NONE;
- + ac->notif.event.flags = SSAM_EVENT_SEQUENCED;
- +
- + ac->psy_desc.name = ac->name;
- + ac->psy_desc.type = POWER_SUPPLY_TYPE_MAINS;
- + ac->psy_desc.properties = spwr_ac_props;
- + ac->psy_desc.num_properties = ARRAY_SIZE(spwr_ac_props);
- + ac->psy_desc.get_property = spwr_ac_get_property;
- +}
- +
- +static int spwr_ac_register(struct spwr_ac_device *ac)
- +{
- + struct power_supply_config psy_cfg = {};
- + __le32 sta;
- + int status;
- +
- + /* Make sure the device is there and functioning properly. */
- + status = ssam_retry(ssam_bat_get_sta, ac->sdev, &sta);
- + if (status)
- + return status;
- +
- + if ((le32_to_cpu(sta) & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK)
- + return -ENODEV;
- +
- + psy_cfg.drv_data = ac;
- + psy_cfg.supplied_to = battery_supplied_to;
- + psy_cfg.num_supplicants = ARRAY_SIZE(battery_supplied_to);
- +
- + ac->psy = devm_power_supply_register(&ac->sdev->dev, &ac->psy_desc, &psy_cfg);
- + if (IS_ERR(ac->psy))
- + return PTR_ERR(ac->psy);
- +
- + return ssam_notifier_register(ac->sdev->ctrl, &ac->notif);
- +}
- +
- +
- +/* -- Driver setup. --------------------------------------------------------- */
- +
- +static int __maybe_unused surface_ac_resume(struct device *dev)
- +{
- + return spwr_ac_recheck(dev_get_drvdata(dev));
- +}
- +SIMPLE_DEV_PM_OPS(surface_ac_pm_ops, NULL, surface_ac_resume);
- +
- +static int surface_ac_probe(struct ssam_device *sdev)
- +{
- + const struct spwr_psy_properties *p;
- + struct spwr_ac_device *ac;
- +
- + p = ssam_device_get_match_data(sdev);
- + if (!p)
- + return -ENODEV;
- +
- + ac = devm_kzalloc(&sdev->dev, sizeof(*ac), GFP_KERNEL);
- + if (!ac)
- + return -ENOMEM;
- +
- + spwr_ac_init(ac, sdev, p->registry, p->name);
- + ssam_device_set_drvdata(sdev, ac);
- +
- + return spwr_ac_register(ac);
- +}
- +
- +static void surface_ac_remove(struct ssam_device *sdev)
- +{
- + struct spwr_ac_device *ac = ssam_device_get_drvdata(sdev);
- +
- + ssam_notifier_unregister(sdev->ctrl, &ac->notif);
- +}
- +
- +static const struct spwr_psy_properties spwr_psy_props_adp1 = {
- + .name = "ADP1",
- + .registry = SSAM_EVENT_REGISTRY_SAM,
- +};
- +
- +static const struct ssam_device_id surface_ac_match[] = {
- + { SSAM_SDEV(BAT, 0x01, 0x01, 0x01), (unsigned long)&spwr_psy_props_adp1 },
- + { },
- +};
- +MODULE_DEVICE_TABLE(ssam, surface_ac_match);
- +
- +static struct ssam_device_driver surface_ac_driver = {
- + .probe = surface_ac_probe,
- + .remove = surface_ac_remove,
- + .match_table = surface_ac_match,
- + .driver = {
- + .name = "surface_ac",
- + .pm = &surface_ac_pm_ops,
- + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
- + },
- +};
- +module_ssam_device_driver(surface_ac_driver);
- +
- +MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
- +MODULE_DESCRIPTION("AC driver for Surface System Aggregator Module");
- +MODULE_LICENSE("GPL");
- --
- 2.32.0
- From e32231d4ac303912945147b70d733d21ea1b1b18 Mon Sep 17 00:00:00 2001
- From: Qiheng Lin <linqiheng@huawei.com>
- Date: Sat, 10 Apr 2021 12:12:46 +0800
- Subject: [PATCH] power: supply: surface-battery: Make some symbols static
- The sparse tool complains as follows:
- drivers/power/supply/surface_battery.c:700:1: warning:
- symbol 'dev_attr_alarm' was not declared. Should it be static?
- drivers/power/supply/surface_battery.c:805:1: warning:
- symbol 'surface_battery_pm_ops' was not declared. Should it be static?
- This symbol is not used outside of surface_battery.c, so this
- commit marks it static.
- Reported-by: Hulk Robot <hulkci@huawei.com>
- Signed-off-by: Qiheng Lin <linqiheng@huawei.com>
- Acked-by: Maximilian Luz <luzmaximilian@gmail.com>
- Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
- Patchset: surface-sam
- ---
- drivers/power/supply/surface_battery.c | 4 ++--
- 1 file changed, 2 insertions(+), 2 deletions(-)
- diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c
- index 4116dd839ecd..7efa431a62b2 100644
- --- a/drivers/power/supply/surface_battery.c
- +++ b/drivers/power/supply/surface_battery.c
- @@ -697,7 +697,7 @@ static ssize_t alarm_store(struct device *dev, struct device_attribute *attr, co
- return count;
- }
-
- -DEVICE_ATTR_RW(alarm);
- +static DEVICE_ATTR_RW(alarm);
-
- static struct attribute *spwr_battery_attrs[] = {
- &dev_attr_alarm.attr,
- @@ -802,7 +802,7 @@ static int __maybe_unused surface_battery_resume(struct device *dev)
- {
- return spwr_battery_recheck_full(dev_get_drvdata(dev));
- }
- -SIMPLE_DEV_PM_OPS(surface_battery_pm_ops, NULL, surface_battery_resume);
- +static SIMPLE_DEV_PM_OPS(surface_battery_pm_ops, NULL, surface_battery_resume);
-
- static int surface_battery_probe(struct ssam_device *sdev)
- {
- --
- 2.32.0
- From ae4baf9def6a94953b826b6cacb00b4ff72457d4 Mon Sep 17 00:00:00 2001
- From: Qiheng Lin <linqiheng@huawei.com>
- Date: Sat, 10 Apr 2021 12:12:49 +0800
- Subject: [PATCH] power: supply: surface-charger: Make symbol
- 'surface_ac_pm_ops' static
- The sparse tool complains as follows:
- drivers/power/supply/surface_charger.c:229:1: warning:
- symbol 'surface_ac_pm_ops' was not declared. Should it be static?
- This symbol is not used outside of surface_charger.c, so this
- commit marks it static.
- Reported-by: Hulk Robot <hulkci@huawei.com>
- Signed-off-by: Qiheng Lin <linqiheng@huawei.com>
- Acked-by: Maximilian Luz <luzmaximilian@gmail.com>
- Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
- Patchset: surface-sam
- ---
- drivers/power/supply/surface_charger.c | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
- diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c
- index c2dd7e604d14..81a5b79822c9 100644
- --- a/drivers/power/supply/surface_charger.c
- +++ b/drivers/power/supply/surface_charger.c
- @@ -226,7 +226,7 @@ static int __maybe_unused surface_ac_resume(struct device *dev)
- {
- return spwr_ac_recheck(dev_get_drvdata(dev));
- }
- -SIMPLE_DEV_PM_OPS(surface_ac_pm_ops, NULL, surface_ac_resume);
- +static SIMPLE_DEV_PM_OPS(surface_ac_pm_ops, NULL, surface_ac_resume);
-
- static int surface_ac_probe(struct ssam_device *sdev)
- {
- --
- 2.32.0
- From cf2597e96e97d4eb9dfb19ec4e6e3fe8c4226102 Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Tue, 4 May 2021 20:00:46 +0200
- Subject: [PATCH] power: supply: surface_battery: Fix battery event handling
- The battery subsystem of the Surface Aggregator Module EC requires us to
- register the battery notifier with instance ID 0. However, battery
- events are actually sent with the instance ID corresponding to the
- device, which is nonzero. Thus, the strict-matching approach doesn't
- work here and will discard events that the driver is expected to handle.
- To fix this we have to fall back on notifier matching by target-category
- only and have to manually check the instance ID in the notifier
- callback.
- Fixes: 167f77f7d0b3 ("power: supply: Add battery driver for Surface Aggregator Module")
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Patchset: surface-sam
- ---
- drivers/power/supply/surface_battery.c | 14 ++++++++++++--
- 1 file changed, 12 insertions(+), 2 deletions(-)
- diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c
- index 7efa431a62b2..5ec2e6bb2465 100644
- --- a/drivers/power/supply/surface_battery.c
- +++ b/drivers/power/supply/surface_battery.c
- @@ -345,6 +345,16 @@ static u32 spwr_notify_bat(struct ssam_event_notifier *nf, const struct ssam_eve
- struct spwr_battery_device *bat = container_of(nf, struct spwr_battery_device, notif);
- int status;
-
- + /*
- + * We cannot use strict matching when registering the notifier as the
- + * EC expects us to register it against instance ID 0. Strict matching
- + * would thus drop events, as those may have non-zero instance IDs in
- + * this subsystem. So we need to check the instance ID of the event
- + * here manually.
- + */
- + if (event->instance_id != bat->sdev->uid.instance)
- + return 0;
- +
- dev_dbg(&bat->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n",
- event->command_id, event->instance_id, event->target_id);
-
- @@ -720,8 +730,8 @@ static void spwr_battery_init(struct spwr_battery_device *bat, struct ssam_devic
- bat->notif.base.fn = spwr_notify_bat;
- bat->notif.event.reg = registry;
- bat->notif.event.id.target_category = sdev->uid.category;
- - bat->notif.event.id.instance = 0;
- - bat->notif.event.mask = SSAM_EVENT_MASK_STRICT;
- + bat->notif.event.id.instance = 0; /* need to register with instance 0 */
- + bat->notif.event.mask = SSAM_EVENT_MASK_TARGET;
- bat->notif.event.flags = SSAM_EVENT_SEQUENCED;
-
- bat->psy_desc.name = bat->name;
- --
- 2.32.0
- From 12c6919dc79bbd4d6782dd089460276b0072e4ba Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Tue, 11 May 2021 11:24:21 +0200
- Subject: [PATCH] power: supply: surface-charger: Fix type of integer variable
- The ac->state field is __le32, not u32. So change the variable we're
- temporarily storing it in to __le32 as well.
- Reported-by: kernel test robot <lkp@intel.com>
- Fixes: e61ffb344591 ("power: supply: Add AC driver for Surface Aggregator Module")
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Patchset: surface-sam
- ---
- drivers/power/supply/surface_charger.c | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
- diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c
- index 81a5b79822c9..a060c36c7766 100644
- --- a/drivers/power/supply/surface_charger.c
- +++ b/drivers/power/supply/surface_charger.c
- @@ -66,7 +66,7 @@ struct spwr_ac_device {
-
- static int spwr_ac_update_unlocked(struct spwr_ac_device *ac)
- {
- - u32 old = ac->state;
- + __le32 old = ac->state;
- int status;
-
- lockdep_assert_held(&ac->lock);
- --
- 2.32.0
- From fcab68d61af80d7a72ac2c920968f13d6ea9af16 Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Wed, 2 Jun 2021 18:27:21 +0200
- Subject: [PATCH] platform/surface: aggregator: Allow registering notifiers
- without enabling events
- Currently, each SSAM event notifier is directly tied to one group of
- events. This makes sense as registering a notifier will automatically
- take care of enabling the corresponding event group and normally drivers
- only need notifications for a very limited number of events, associated
- with different callbacks for each group.
- However, there are rare cases, especially for debugging, when we want to
- get notifications for a whole event target category instead of just a
- single group of events in that category. Registering multiple notifiers,
- i.e. one per group, may be infeasible due to two issues: a) we might not
- know every event enable/disable specification as some events are
- auto-enabled by the EC and b) forwarding this to the same callback will
- lead to duplicate events as we might not know the full event
- specification to perform the appropriate filtering.
- This commit introduces observer-notifiers, which are notifiers that are
- not tied to a specific event group and do not attempt to manage any
- events. In other words, they can be registered without enabling any
- event group or incrementing the corresponding reference count and just
- act as silent observers, listening to all currently/previously enabled
- events based on their match-specification.
- Essentially, this allows us to register one single notifier for a full
- event target category, meaning that we can process all events of that
- target category in a single callback without duplication. Specifically,
- this will be used in the cdev debug interface to forward events to
- user-space via a device file from which the events can be read.
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Patchset: surface-sam
- ---
- .../platform/surface/aggregator/controller.c | 69 +++++++++++--------
- include/linux/surface_aggregator/controller.h | 17 +++++
- 2 files changed, 58 insertions(+), 28 deletions(-)
- diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c
- index a06964aa96e7..cd3a6b77f48d 100644
- --- a/drivers/platform/surface/aggregator/controller.c
- +++ b/drivers/platform/surface/aggregator/controller.c
- @@ -2127,9 +2127,15 @@ int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl)
- * @ctrl: The controller to register the notifier on.
- * @n: The event notifier to register.
- *
- - * Register an event notifier and increment the usage counter of the
- - * associated SAM event. If the event was previously not enabled, it will be
- - * enabled during this call.
- + * Register an event notifier. Increment the usage counter of the associated
- + * SAM event if the notifier is not marked as an observer. If the event is not
- + * marked as an observer and is currently not enabled, it will be enabled
- + * during this call. If the notifier is marked as an observer, no attempt will
- + * be made at enabling any event and no reference count will be modified.
- + *
- + * Notifiers marked as observers do not need to be associated with one specific
- + * event, i.e. as long as no event matching is performed, only the event target
- + * category needs to be set.
- *
- * Return: Returns zero on success, %-ENOSPC if there have already been
- * %INT_MAX notifiers for the event ID/type associated with the notifier block
- @@ -2138,11 +2144,10 @@ int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl)
- * for the specific associated event, returns the status of the event-enable
- * EC-command.
- */
- -int ssam_notifier_register(struct ssam_controller *ctrl,
- - struct ssam_event_notifier *n)
- +int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notifier *n)
- {
- u16 rqid = ssh_tc_to_rqid(n->event.id.target_category);
- - struct ssam_nf_refcount_entry *entry;
- + struct ssam_nf_refcount_entry *entry = NULL;
- struct ssam_nf_head *nf_head;
- struct ssam_nf *nf;
- int status;
- @@ -2155,29 +2160,32 @@ int ssam_notifier_register(struct ssam_controller *ctrl,
-
- mutex_lock(&nf->lock);
-
- - entry = ssam_nf_refcount_inc(nf, n->event.reg, n->event.id);
- - if (IS_ERR(entry)) {
- - mutex_unlock(&nf->lock);
- - return PTR_ERR(entry);
- - }
- + if (!(n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)) {
- + entry = ssam_nf_refcount_inc(nf, n->event.reg, n->event.id);
- + if (IS_ERR(entry)) {
- + mutex_unlock(&nf->lock);
- + return PTR_ERR(entry);
- + }
-
- - ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
- - n->event.reg.target_category, n->event.id.target_category,
- - n->event.id.instance, entry->refcount);
- + ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
- + n->event.reg.target_category, n->event.id.target_category,
- + n->event.id.instance, entry->refcount);
- + }
-
- status = ssam_nfblk_insert(nf_head, &n->base);
- if (status) {
- - entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id);
- - if (entry->refcount == 0)
- - kfree(entry);
- + if (entry) {
- + entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id);
- + if (entry->refcount == 0)
- + kfree(entry);
- + }
-
- mutex_unlock(&nf->lock);
- return status;
- }
-
- - if (entry->refcount == 1) {
- - status = ssam_ssh_event_enable(ctrl, n->event.reg, n->event.id,
- - n->event.flags);
- + if (entry && entry->refcount == 1) {
- + status = ssam_ssh_event_enable(ctrl, n->event.reg, n->event.id, n->event.flags);
- if (status) {
- ssam_nfblk_remove(&n->base);
- kfree(ssam_nf_refcount_dec(nf, n->event.reg, n->event.id));
- @@ -2188,7 +2196,7 @@ int ssam_notifier_register(struct ssam_controller *ctrl,
-
- entry->flags = n->event.flags;
-
- - } else if (entry->flags != n->event.flags) {
- + } else if (entry && entry->flags != n->event.flags) {
- ssam_warn(ctrl,
- "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
- n->event.flags, entry->flags, n->event.reg.target_category,
- @@ -2205,17 +2213,16 @@ EXPORT_SYMBOL_GPL(ssam_notifier_register);
- * @ctrl: The controller the notifier has been registered on.
- * @n: The event notifier to unregister.
- *
- - * Unregister an event notifier and decrement the usage counter of the
- - * associated SAM event. If the usage counter reaches zero, the event will be
- - * disabled.
- + * Unregister an event notifier. Decrement the usage counter of the associated
- + * SAM event if the notifier is not marked as an observer. If the usage counter
- + * reaches zero, the event will be disabled.
- *
- * Return: Returns zero on success, %-ENOENT if the given notifier block has
- * not been registered on the controller. If the given notifier block was the
- * last one associated with its specific event, returns the status of the
- * event-disable EC-command.
- */
- -int ssam_notifier_unregister(struct ssam_controller *ctrl,
- - struct ssam_event_notifier *n)
- +int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n)
- {
- u16 rqid = ssh_tc_to_rqid(n->event.id.target_category);
- struct ssam_nf_refcount_entry *entry;
- @@ -2236,6 +2243,13 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl,
- return -ENOENT;
- }
-
- + /*
- + * If this is an observer notifier, do not attempt to disable the
- + * event, just remove it.
- + */
- + if (n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)
- + goto remove;
- +
- entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id);
- if (WARN_ON(!entry)) {
- /*
- @@ -2260,8 +2274,7 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl,
- }
-
- if (entry->refcount == 0) {
- - status = ssam_ssh_event_disable(ctrl, n->event.reg, n->event.id,
- - n->event.flags);
- + status = ssam_ssh_event_disable(ctrl, n->event.reg, n->event.id, n->event.flags);
- kfree(entry);
- }
-
- diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h
- index 0806796eabcb..cf4bb48a850e 100644
- --- a/include/linux/surface_aggregator/controller.h
- +++ b/include/linux/surface_aggregator/controller.h
- @@ -795,6 +795,20 @@ enum ssam_event_mask {
- #define SSAM_EVENT_REGISTRY_REG \
- SSAM_EVENT_REGISTRY(SSAM_SSH_TC_REG, 0x02, 0x01, 0x02)
-
- +/**
- + * enum ssam_event_notifier_flags - Flags for event notifiers.
- + * @SSAM_EVENT_NOTIFIER_OBSERVER:
- + * The corresponding notifier acts as observer. Registering a notifier
- + * with this flag set will not attempt to enable any event. Equally,
- + * unregistering will not attempt to disable any event. Note that a
- + * notifier with this flag may not even correspond to a certain event at
- + * all, only to a specific event target category. Event matching will not
- + * be influenced by this flag.
- + */
- +enum ssam_event_notifier_flags {
- + SSAM_EVENT_NOTIFIER_OBSERVER = BIT(0),
- +};
- +
- /**
- * struct ssam_event_notifier - Notifier block for SSAM events.
- * @base: The base notifier block with callback function and priority.
- @@ -803,6 +817,7 @@ enum ssam_event_mask {
- * @event.id: ID specifying the event.
- * @event.mask: Flags determining how events are matched to the notifier.
- * @event.flags: Flags used for enabling the event.
- + * @flags: Notifier flags (see &enum ssam_event_notifier_flags).
- */
- struct ssam_event_notifier {
- struct ssam_notifier_block base;
- @@ -813,6 +828,8 @@ struct ssam_event_notifier {
- enum ssam_event_mask mask;
- u8 flags;
- } event;
- +
- + unsigned long flags;
- };
-
- int ssam_notifier_register(struct ssam_controller *ctrl,
- --
- 2.32.0
- From 674b886f8e962823be0e849b33ca9f842319a429 Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Wed, 2 Jun 2021 18:34:48 +0200
- Subject: [PATCH] platform/surface: aggregator: Allow enabling of events
- without notifiers
- We can already enable and disable SAM events via one of two ways: either
- via a (non-observer) notifier tied to a specific event group, or a
- generic event enable/disable request. In some instances, however,
- neither method may be desirable.
- The first method will tie the event enable request to a specific
- notifier, however, when we want to receive notifications for multiple
- event groups of the same target category and forward this to the same
- notifier callback, we may receive duplicate events, i.e. one event per
- registered notifier. The second method will bypass the internal
- reference counting mechanism, meaning that a disable request will
- disable the event regardless of any other client driver using it, which
- may break the functionality of that driver.
- To address this problem, add new functions that allow enabling and
- disabling of events via the event reference counting mechanism built
- into the controller, without needing to register a notifier.
- This can then be used in combination with observer notifiers to process
- multiple events of the same target category without duplication in the
- same callback function.
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Patchset: surface-sam
- ---
- .../platform/surface/aggregator/controller.c | 135 ++++++++++++++++++
- include/linux/surface_aggregator/controller.h | 8 ++
- 2 files changed, 143 insertions(+)
- diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c
- index cd3a6b77f48d..49edddea417e 100644
- --- a/drivers/platform/surface/aggregator/controller.c
- +++ b/drivers/platform/surface/aggregator/controller.c
- @@ -2287,6 +2287,141 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not
- }
- EXPORT_SYMBOL_GPL(ssam_notifier_unregister);
-
- +/**
- + * ssam_controller_event_enable() - Enable the specified event.
- + * @ctrl: The controller to enable the event for.
- + * @reg: The event registry to use for enabling the event.
- + * @id: The event ID specifying the event to be enabled.
- + * @flags: The SAM event flags used for enabling the event.
- + *
- + * Increment the event reference count of the specified event. If the event has
- + * not been enabled previously, it will be enabled by this call.
- + *
- + * Note: In general, ssam_notifier_register() with a non-observer notifier
- + * should be preferred for enabling/disabling events, as this will guarantee
- + * proper ordering and event forwarding in case of errors during event
- + * enabling/disabling.
- + *
- + * Return: Returns zero on success, %-ENOSPC if the reference count for the
- + * specified event has reached its maximum, %-ENOMEM if the corresponding event
- + * entry could not be allocated. If this is the first time that this event has
- + * been enabled (i.e. the reference count was incremented from zero to one by
- + * this call), returns the status of the event-enable EC-command.
- + */
- +int ssam_controller_event_enable(struct ssam_controller *ctrl,
- + struct ssam_event_registry reg,
- + struct ssam_event_id id, u8 flags)
- +{
- + u16 rqid = ssh_tc_to_rqid(id.target_category);
- + struct ssam_nf_refcount_entry *entry;
- + struct ssam_nf_head *nf_head;
- + struct ssam_nf *nf;
- + int status;
- +
- + if (!ssh_rqid_is_event(rqid))
- + return -EINVAL;
- +
- + nf = &ctrl->cplt.event.notif;
- + nf_head = &nf->head[ssh_rqid_to_event(rqid)];
- +
- + mutex_lock(&nf->lock);
- +
- + entry = ssam_nf_refcount_inc(nf, reg, id);
- + if (IS_ERR(entry)) {
- + mutex_unlock(&nf->lock);
- + return PTR_ERR(entry);
- + }
- +
- + ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
- + reg.target_category, id.target_category, id.instance,
- + entry->refcount);
- +
- + if (entry->refcount == 1) {
- + status = ssam_ssh_event_enable(ctrl, reg, id, flags);
- + if (status) {
- + kfree(ssam_nf_refcount_dec(nf, reg, id));
- + mutex_unlock(&nf->lock);
- + return status;
- + }
- +
- + entry->flags = flags;
- +
- + } else if (entry->flags != flags) {
- + ssam_warn(ctrl,
- + "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
- + flags, entry->flags, reg.target_category,
- + id.target_category, id.instance);
- + }
- +
- + mutex_unlock(&nf->lock);
- + return 0;
- +}
- +EXPORT_SYMBOL_GPL(ssam_controller_event_enable);
- +
- +/**
- + * ssam_controller_event_disable() - Disable the specified event.
- + * @ctrl: The controller to disable the event for.
- + * @reg: The event registry to use for disabling the event.
- + * @id: The event ID specifying the event to be disabled.
- + * @flags: The flags used when enabling the event.
- + *
- + * Decrement the reference count of the specified event. If the reference count
- + * reaches zero, the event will be disabled.
- + *
- + * Note: In general, ssam_notifier_register()/ssam_notifier_unregister() with a
- + * non-observer notifier should be preferred for enabling/disabling events, as
- + * this will guarantee proper ordering and event forwarding in case of errors
- + * during event enabling/disabling.
- + *
- + * Return: Returns zero on success, %-ENOENT if the given event has not been
- + * enabled on the controller. If the reference count of the event reaches zero
- + * during this call, returns the status of the event-disable EC-command.
- + */
- +int ssam_controller_event_disable(struct ssam_controller *ctrl,
- + struct ssam_event_registry reg,
- + struct ssam_event_id id, u8 flags)
- +{
- + u16 rqid = ssh_tc_to_rqid(id.target_category);
- + struct ssam_nf_refcount_entry *entry;
- + struct ssam_nf_head *nf_head;
- + struct ssam_nf *nf;
- + int status = 0;
- +
- + if (!ssh_rqid_is_event(rqid))
- + return -EINVAL;
- +
- + nf = &ctrl->cplt.event.notif;
- + nf_head = &nf->head[ssh_rqid_to_event(rqid)];
- +
- + mutex_lock(&nf->lock);
- +
- + entry = ssam_nf_refcount_dec(nf, reg, id);
- + if (WARN_ON(!entry)) {
- + mutex_unlock(&nf->lock);
- + return -ENOENT;
- + }
- +
- + ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
- + reg.target_category, id.target_category, id.instance,
- + entry->refcount);
- +
- + if (entry->flags != flags) {
- + ssam_warn(ctrl,
- + "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
- + flags, entry->flags, reg.target_category,
- + id.target_category, id.instance);
- + }
- +
- + if (entry->refcount == 0) {
- + status = ssam_ssh_event_disable(ctrl, reg, id, flags);
- + kfree(entry);
- + }
- +
- + mutex_unlock(&nf->lock);
- + return status;
- +}
- +EXPORT_SYMBOL_GPL(ssam_controller_event_disable);
- +
- /**
- * ssam_notifier_disable_registered() - Disable events for all registered
- * notifiers.
- diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h
- index cf4bb48a850e..7965bdc669c5 100644
- --- a/include/linux/surface_aggregator/controller.h
- +++ b/include/linux/surface_aggregator/controller.h
- @@ -838,4 +838,12 @@ int ssam_notifier_register(struct ssam_controller *ctrl,
- int ssam_notifier_unregister(struct ssam_controller *ctrl,
- struct ssam_event_notifier *n);
-
- +int ssam_controller_event_enable(struct ssam_controller *ctrl,
- + struct ssam_event_registry reg,
- + struct ssam_event_id id, u8 flags);
- +
- +int ssam_controller_event_disable(struct ssam_controller *ctrl,
- + struct ssam_event_registry reg,
- + struct ssam_event_id id, u8 flags);
- +
- #endif /* _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H */
- --
- 2.32.0
- From 4f63f260aaa1b2f61d407fd32e9ecc1b108e5472 Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Thu, 3 Jun 2021 00:10:38 +0200
- Subject: [PATCH] platform/surface: aggregator: Update copyright
- It's 2021, update the copyright accordingly.
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Patchset: surface-sam
- ---
- drivers/platform/surface/aggregator/Kconfig | 2 +-
- drivers/platform/surface/aggregator/Makefile | 2 +-
- drivers/platform/surface/aggregator/bus.c | 2 +-
- drivers/platform/surface/aggregator/bus.h | 2 +-
- drivers/platform/surface/aggregator/controller.c | 2 +-
- drivers/platform/surface/aggregator/controller.h | 2 +-
- drivers/platform/surface/aggregator/core.c | 2 +-
- drivers/platform/surface/aggregator/ssh_msgb.h | 2 +-
- drivers/platform/surface/aggregator/ssh_packet_layer.c | 2 +-
- drivers/platform/surface/aggregator/ssh_packet_layer.h | 2 +-
- drivers/platform/surface/aggregator/ssh_parser.c | 2 +-
- drivers/platform/surface/aggregator/ssh_parser.h | 2 +-
- drivers/platform/surface/aggregator/ssh_request_layer.c | 2 +-
- drivers/platform/surface/aggregator/ssh_request_layer.h | 2 +-
- drivers/platform/surface/aggregator/trace.h | 2 +-
- include/linux/surface_aggregator/controller.h | 2 +-
- include/linux/surface_aggregator/device.h | 2 +-
- include/linux/surface_aggregator/serial_hub.h | 2 +-
- 18 files changed, 18 insertions(+), 18 deletions(-)
- diff --git a/drivers/platform/surface/aggregator/Kconfig b/drivers/platform/surface/aggregator/Kconfig
- index 3aaeea9f0433..fd6dc452f3e8 100644
- --- a/drivers/platform/surface/aggregator/Kconfig
- +++ b/drivers/platform/surface/aggregator/Kconfig
- @@ -1,5 +1,5 @@
- # SPDX-License-Identifier: GPL-2.0+
- -# Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
- +# Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
-
- menuconfig SURFACE_AGGREGATOR
- tristate "Microsoft Surface System Aggregator Module Subsystem and Drivers"
- diff --git a/drivers/platform/surface/aggregator/Makefile b/drivers/platform/surface/aggregator/Makefile
- index c112e2c7112b..c8498c41e758 100644
- --- a/drivers/platform/surface/aggregator/Makefile
- +++ b/drivers/platform/surface/aggregator/Makefile
- @@ -1,5 +1,5 @@
- # SPDX-License-Identifier: GPL-2.0+
- -# Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
- +# Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
-
- # For include/trace/define_trace.h to include trace.h
- CFLAGS_core.o = -I$(src)
- diff --git a/drivers/platform/surface/aggregator/bus.c b/drivers/platform/surface/aggregator/bus.c
- index a9b660af0917..0169677c243e 100644
- --- a/drivers/platform/surface/aggregator/bus.c
- +++ b/drivers/platform/surface/aggregator/bus.c
- @@ -2,7 +2,7 @@
- /*
- * Surface System Aggregator Module bus and device integration.
- *
- - * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
- + * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
- */
-
- #include <linux/device.h>
- diff --git a/drivers/platform/surface/aggregator/bus.h b/drivers/platform/surface/aggregator/bus.h
- index 7712baaed6a5..ed032c2cbdb2 100644
- --- a/drivers/platform/surface/aggregator/bus.h
- +++ b/drivers/platform/surface/aggregator/bus.h
- @@ -2,7 +2,7 @@
- /*
- * Surface System Aggregator Module bus and device integration.
- *
- - * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
- + * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
- */
-
- #ifndef _SURFACE_AGGREGATOR_BUS_H
- diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c
- index 49edddea417e..e91ee7e72c14 100644
- --- a/drivers/platform/surface/aggregator/controller.c
- +++ b/drivers/platform/surface/aggregator/controller.c
- @@ -2,7 +2,7 @@
- /*
- * Main SSAM/SSH controller structure and functionality.
- *
- - * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
- + * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
- */
-
- #include <linux/acpi.h>
- diff --git a/drivers/platform/surface/aggregator/controller.h b/drivers/platform/surface/aggregator/controller.h
- index 8297d34e7489..a0963c3562ff 100644
- --- a/drivers/platform/surface/aggregator/controller.h
- +++ b/drivers/platform/surface/aggregator/controller.h
- @@ -2,7 +2,7 @@
- /*
- * Main SSAM/SSH controller structure and functionality.
- *
- - * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
- + * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
- */
-
- #ifndef _SURFACE_AGGREGATOR_CONTROLLER_H
- diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c
- index 8dc2c267bcd6..5d780e55f4a1 100644
- --- a/drivers/platform/surface/aggregator/core.c
- +++ b/drivers/platform/surface/aggregator/core.c
- @@ -7,7 +7,7 @@
- * Handles communication via requests as well as enabling, disabling, and
- * relaying of events.
- *
- - * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
- + * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
- */
-
- #include <linux/acpi.h>
- diff --git a/drivers/platform/surface/aggregator/ssh_msgb.h b/drivers/platform/surface/aggregator/ssh_msgb.h
- index 1221f642dda1..e562958ffdf0 100644
- --- a/drivers/platform/surface/aggregator/ssh_msgb.h
- +++ b/drivers/platform/surface/aggregator/ssh_msgb.h
- @@ -2,7 +2,7 @@
- /*
- * SSH message builder functions.
- *
- - * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
- + * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
- */
-
- #ifndef _SURFACE_AGGREGATOR_SSH_MSGB_H
- diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.c b/drivers/platform/surface/aggregator/ssh_packet_layer.c
- index 15d96eac6811..5e08049fc3ac 100644
- --- a/drivers/platform/surface/aggregator/ssh_packet_layer.c
- +++ b/drivers/platform/surface/aggregator/ssh_packet_layer.c
- @@ -2,7 +2,7 @@
- /*
- * SSH packet transport layer.
- *
- - * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
- + * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
- */
-
- #include <asm/unaligned.h>
- diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.h b/drivers/platform/surface/aggregator/ssh_packet_layer.h
- index e8757d03f279..2eb329f0b91a 100644
- --- a/drivers/platform/surface/aggregator/ssh_packet_layer.h
- +++ b/drivers/platform/surface/aggregator/ssh_packet_layer.h
- @@ -2,7 +2,7 @@
- /*
- * SSH packet transport layer.
- *
- - * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
- + * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
- */
-
- #ifndef _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H
- diff --git a/drivers/platform/surface/aggregator/ssh_parser.c b/drivers/platform/surface/aggregator/ssh_parser.c
- index e2dead8de94a..b77912f8f13b 100644
- --- a/drivers/platform/surface/aggregator/ssh_parser.c
- +++ b/drivers/platform/surface/aggregator/ssh_parser.c
- @@ -2,7 +2,7 @@
- /*
- * SSH message parser.
- *
- - * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
- + * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
- */
-
- #include <asm/unaligned.h>
- diff --git a/drivers/platform/surface/aggregator/ssh_parser.h b/drivers/platform/surface/aggregator/ssh_parser.h
- index 63c38d350988..3bd6e180fd16 100644
- --- a/drivers/platform/surface/aggregator/ssh_parser.h
- +++ b/drivers/platform/surface/aggregator/ssh_parser.h
- @@ -2,7 +2,7 @@
- /*
- * SSH message parser.
- *
- - * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
- + * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
- */
-
- #ifndef _SURFACE_AGGREGATOR_SSH_PARSER_H
- diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.c b/drivers/platform/surface/aggregator/ssh_request_layer.c
- index 52a83a8fcf82..bfe1aaf38065 100644
- --- a/drivers/platform/surface/aggregator/ssh_request_layer.c
- +++ b/drivers/platform/surface/aggregator/ssh_request_layer.c
- @@ -2,7 +2,7 @@
- /*
- * SSH request transport layer.
- *
- - * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
- + * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
- */
-
- #include <asm/unaligned.h>
- diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.h b/drivers/platform/surface/aggregator/ssh_request_layer.h
- index cb35815858d1..9c3cbae2d4bd 100644
- --- a/drivers/platform/surface/aggregator/ssh_request_layer.h
- +++ b/drivers/platform/surface/aggregator/ssh_request_layer.h
- @@ -2,7 +2,7 @@
- /*
- * SSH request transport layer.
- *
- - * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
- + * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
- */
-
- #ifndef _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H
- diff --git a/drivers/platform/surface/aggregator/trace.h b/drivers/platform/surface/aggregator/trace.h
- index eb332bb53ae4..de64cf169060 100644
- --- a/drivers/platform/surface/aggregator/trace.h
- +++ b/drivers/platform/surface/aggregator/trace.h
- @@ -2,7 +2,7 @@
- /*
- * Trace points for SSAM/SSH.
- *
- - * Copyright (C) 2020 Maximilian Luz <luzmaximilian@gmail.com>
- + * Copyright (C) 2020-2021 Maximilian Luz <luzmaximilian@gmail.com>
- */
-
- #undef TRACE_SYSTEM
- diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h
- index 7965bdc669c5..068e1982ad37 100644
- --- a/include/linux/surface_aggregator/controller.h
- +++ b/include/linux/surface_aggregator/controller.h
- @@ -6,7 +6,7 @@
- * managing access and communication to and from the SSAM EC, as well as main
- * communication structures and definitions.
- *
- - * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
- + * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
- */
-
- #ifndef _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H
- diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h
- index 6ff9c58b3e17..f636c5310321 100644
- --- a/include/linux/surface_aggregator/device.h
- +++ b/include/linux/surface_aggregator/device.h
- @@ -7,7 +7,7 @@
- * Provides support for non-platform/non-ACPI SSAM clients via dedicated
- * subsystem.
- *
- - * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
- + * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
- */
-
- #ifndef _LINUX_SURFACE_AGGREGATOR_DEVICE_H
- diff --git a/include/linux/surface_aggregator/serial_hub.h b/include/linux/surface_aggregator/serial_hub.h
- index 64276fbfa1d5..c3de43edcffa 100644
- --- a/include/linux/surface_aggregator/serial_hub.h
- +++ b/include/linux/surface_aggregator/serial_hub.h
- @@ -6,7 +6,7 @@
- * Surface System Aggregator Module (SSAM). Provides the interface for basic
- * packet- and request-based communication with the SSAM EC via SSH.
- *
- - * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
- + * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
- */
-
- #ifndef _LINUX_SURFACE_AGGREGATOR_SERIAL_HUB_H
- --
- 2.32.0
- From e8076a85fafbd1ede0edea60727eda1f2b83ad40 Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Wed, 2 Jun 2021 19:29:16 +0200
- Subject: [PATCH] platform/surface: aggregator_cdev: Add support for forwarding
- events to user-space
- Currently, debugging unknown events requires writing a custom driver.
- This is somewhat difficult, slow to adapt, and not entirely
- user-friendly for quickly trying to figure out things on devices of some
- third-party user. We can do better. We already have a user-space
- interface intended for debugging SAM EC requests, so let's add support
- for receiving events to that.
- This commit provides support for receiving events by reading from the
- controller file. It additionally introduces two new IOCTLs to control
- which event categories will be forwarded. Specifically, a user-space
- client can specify which target categories it wants to receive events
- from by registering the corresponding notifier(s) via the IOCTLs and
- after that, read the received events by reading from the controller
- device.
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Patchset: surface-sam
- ---
- .../userspace-api/ioctl/ioctl-number.rst | 2 +-
- .../surface/surface_aggregator_cdev.c | 454 +++++++++++++++++-
- include/uapi/linux/surface_aggregator/cdev.h | 41 +-
- 3 files changed, 471 insertions(+), 26 deletions(-)
- diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
- index 1c28b8ef6677..cfd1610e5e95 100644
- --- a/Documentation/userspace-api/ioctl/ioctl-number.rst
- +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
- @@ -325,7 +325,7 @@ Code Seq# Include File Comments
- 0xA3 90-9F linux/dtlk.h
- 0xA4 00-1F uapi/linux/tee.h Generic TEE subsystem
- 0xA4 00-1F uapi/asm/sgx.h <mailto:linux-sgx@vger.kernel.org>
- -0xA5 01 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator
- +0xA5 01-05 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator
- <mailto:luzmaximilian@gmail.com>
- 0xA5 20-2F linux/surface_aggregator/dtx.h Microsoft Surface DTX driver
- <mailto:luzmaximilian@gmail.com>
- diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c
- index 79e28fab7e40..7b27c8ca38a5 100644
- --- a/drivers/platform/surface/surface_aggregator_cdev.c
- +++ b/drivers/platform/surface/surface_aggregator_cdev.c
- @@ -3,29 +3,68 @@
- * Provides user-space access to the SSAM EC via the /dev/surface/aggregator
- * misc device. Intended for debugging and development.
- *
- - * Copyright (C) 2020 Maximilian Luz <luzmaximilian@gmail.com>
- + * Copyright (C) 2020-2021 Maximilian Luz <luzmaximilian@gmail.com>
- */
-
- #include <linux/fs.h>
- +#include <linux/ioctl.h>
- #include <linux/kernel.h>
- +#include <linux/kfifo.h>
- #include <linux/kref.h>
- #include <linux/miscdevice.h>
- #include <linux/module.h>
- #include <linux/platform_device.h>
- +#include <linux/poll.h>
- #include <linux/rwsem.h>
- #include <linux/slab.h>
- #include <linux/uaccess.h>
- +#include <linux/vmalloc.h>
-
- #include <linux/surface_aggregator/cdev.h>
- #include <linux/surface_aggregator/controller.h>
-
- #define SSAM_CDEV_DEVICE_NAME "surface_aggregator_cdev"
-
- +
- +/* -- Main structures. ------------------------------------------------------ */
- +
- +enum ssam_cdev_device_state {
- + SSAM_CDEV_DEVICE_SHUTDOWN_BIT = BIT(0),
- +};
- +
- struct ssam_cdev {
- struct kref kref;
- struct rw_semaphore lock;
- +
- + struct device *dev;
- struct ssam_controller *ctrl;
- struct miscdevice mdev;
- + unsigned long flags;
- +
- + struct rw_semaphore client_lock; /* Guards client list. */
- + struct list_head client_list;
- +};
- +
- +struct ssam_cdev_client;
- +
- +struct ssam_cdev_notifier {
- + struct ssam_cdev_client *client;
- + struct ssam_event_notifier nf;
- +};
- +
- +struct ssam_cdev_client {
- + struct ssam_cdev *cdev;
- + struct list_head node;
- +
- + struct mutex notifier_lock; /* Guards notifier access for registration */
- + struct ssam_cdev_notifier *notifier[SSH_NUM_EVENTS];
- +
- + struct mutex read_lock; /* Guards FIFO buffer read access */
- + struct mutex write_lock; /* Guards FIFO buffer write access */
- + DECLARE_KFIFO(buffer, u8, 4096);
- +
- + wait_queue_head_t waitq;
- + struct fasync_struct *fasync;
- };
-
- static void __ssam_cdev_release(struct kref *kref)
- @@ -47,24 +86,167 @@ static void ssam_cdev_put(struct ssam_cdev *cdev)
- kref_put(&cdev->kref, __ssam_cdev_release);
- }
-
- -static int ssam_cdev_device_open(struct inode *inode, struct file *filp)
- +
- +/* -- Notifier handling. ---------------------------------------------------- */
- +
- +static u32 ssam_cdev_notifier(struct ssam_event_notifier *nf, const struct ssam_event *in)
- {
- - struct miscdevice *mdev = filp->private_data;
- - struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev);
- + struct ssam_cdev_notifier *cdev_nf = container_of(nf, struct ssam_cdev_notifier, nf);
- + struct ssam_cdev_client *client = cdev_nf->client;
- + struct ssam_cdev_event event;
- + size_t n = struct_size(&event, data, in->length);
- +
- + /* Translate event. */
- + event.target_category = in->target_category;
- + event.target_id = in->target_id;
- + event.command_id = in->command_id;
- + event.instance_id = in->instance_id;
- + event.length = in->length;
- +
- + mutex_lock(&client->write_lock);
- +
- + /* Make sure we have enough space. */
- + if (kfifo_avail(&client->buffer) < n) {
- + dev_warn(client->cdev->dev,
- + "buffer full, dropping event (tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n",
- + in->target_category, in->target_id, in->command_id, in->instance_id);
- + mutex_unlock(&client->write_lock);
- + return 0;
- + }
-
- - filp->private_data = ssam_cdev_get(cdev);
- - return stream_open(inode, filp);
- + /* Copy event header and payload. */
- + kfifo_in(&client->buffer, (const u8 *)&event, struct_size(&event, data, 0));
- + kfifo_in(&client->buffer, &in->data[0], in->length);
- +
- + mutex_unlock(&client->write_lock);
- +
- + /* Notify waiting readers. */
- + kill_fasync(&client->fasync, SIGIO, POLL_IN);
- + wake_up_interruptible(&client->waitq);
- +
- + /*
- + * Don't mark events as handled, this is the job of a proper driver and
- + * not the debugging interface.
- + */
- + return 0;
- }
-
- -static int ssam_cdev_device_release(struct inode *inode, struct file *filp)
- +static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 category, int priority)
- {
- - ssam_cdev_put(filp->private_data);
- - return 0;
- + struct ssam_cdev_notifier *nf;
- + int index = ((int)category) - 1;
- + int status;
- +
- + /* Validate notifier target category. */
- + if (index < 0 || index >= SSH_NUM_EVENTS)
- + return -EINVAL;
- +
- + mutex_lock(&client->notifier_lock);
- +
- + /* Check if the notifier has already been registered. */
- + if (client->notifier[index]) {
- + mutex_unlock(&client->notifier_lock);
- + return -EEXIST;
- + }
- +
- + /* Allocate new notifier. */
- + nf = kzalloc(sizeof(*nf), GFP_KERNEL);
- + if (!nf) {
- + mutex_unlock(&client->notifier_lock);
- + return -ENOMEM;
- + }
- +
- + /*
- + * Create a dummy notifier with the minimal required fields for
- + * observer registration. Note that we can skip fully specifying event
- + * and registry here as we do not need any matching and use silent
- + * registration, which does not enable the corresponding event.
- + */
- + nf->client = client;
- + nf->nf.base.fn = ssam_cdev_notifier;
- + nf->nf.base.priority = priority;
- + nf->nf.event.id.target_category = category;
- + nf->nf.event.mask = 0; /* Do not do any matching. */
- + nf->nf.flags = SSAM_EVENT_NOTIFIER_OBSERVER;
- +
- + /* Register notifier. */
- + status = ssam_notifier_register(client->cdev->ctrl, &nf->nf);
- + if (status)
- + kfree(nf);
- + else
- + client->notifier[index] = nf;
- +
- + mutex_unlock(&client->notifier_lock);
- + return status;
- }
-
- -static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg)
- +static int ssam_cdev_notifier_unregister(struct ssam_cdev_client *client, u8 category)
- +{
- + int index = ((int)category) - 1;
- + int status;
- +
- + /* Validate notifier target category. */
- + if (index < 0 || index >= SSH_NUM_EVENTS)
- + return -EINVAL;
- +
- + mutex_lock(&client->notifier_lock);
- +
- + /* Check if the notifier is currently registered. */
- + if (!client->notifier[index]) {
- + mutex_unlock(&client->notifier_lock);
- + return -ENOENT;
- + }
- +
- + /* Unregister and free notifier. */
- + status = ssam_notifier_unregister(client->cdev->ctrl, &client->notifier[index]->nf);
- + kfree(client->notifier[index]);
- + client->notifier[index] = NULL;
- +
- + mutex_unlock(&client->notifier_lock);
- + return status;
- +}
- +
- +static void ssam_cdev_notifier_unregister_all(struct ssam_cdev_client *client)
- +{
- + int i;
- +
- + down_read(&client->cdev->lock);
- +
- + /*
- + * This function may be used during shutdown, thus we need to test for
- + * cdev->ctrl instead of the SSAM_CDEV_DEVICE_SHUTDOWN_BIT bit.
- + */
- + if (client->cdev->ctrl) {
- + for (i = 0; i < SSH_NUM_EVENTS; i++)
- + ssam_cdev_notifier_unregister(client, i + 1);
- +
- + } else {
- + int count = 0;
- +
- + /*
- + * Device has been shut down. Any notifier remaining is a bug,
- + * so warn about that as this would otherwise hardly be
- + * noticeable. Nevertheless, free them as well.
- + */
- + mutex_lock(&client->notifier_lock);
- + for (i = 0; i < SSH_NUM_EVENTS; i++) {
- + count += !!(client->notifier[i]);
- + kfree(client->notifier[i]);
- + client->notifier[i] = NULL;
- + }
- + mutex_unlock(&client->notifier_lock);
- +
- + WARN_ON(count > 0);
- + }
- +
- + up_read(&client->cdev->lock);
- +}
- +
- +
- +/* -- IOCTL functions. ------------------------------------------------------ */
- +
- +static long ssam_cdev_request(struct ssam_cdev_client *client, struct ssam_cdev_request __user *r)
- {
- - struct ssam_cdev_request __user *r;
- struct ssam_cdev_request rqst;
- struct ssam_request spec = {};
- struct ssam_response rsp = {};
- @@ -72,7 +254,6 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg)
- void __user *rspdata;
- int status = 0, ret = 0, tmp;
-
- - r = (struct ssam_cdev_request __user *)arg;
- ret = copy_struct_from_user(&rqst, sizeof(rqst), r, sizeof(*r));
- if (ret)
- goto out;
- @@ -152,7 +333,7 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg)
- }
-
- /* Perform request. */
- - status = ssam_request_sync(cdev->ctrl, &spec, &rsp);
- + status = ssam_request_sync(client->cdev->ctrl, &spec, &rsp);
- if (status)
- goto out;
-
- @@ -177,48 +358,244 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg)
- return ret;
- }
-
- -static long __ssam_cdev_device_ioctl(struct ssam_cdev *cdev, unsigned int cmd,
- +static long ssam_cdev_notif_register(struct ssam_cdev_client *client,
- + const struct ssam_cdev_notifier_desc __user *d)
- +{
- + struct ssam_cdev_notifier_desc desc;
- + long ret;
- +
- + ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));
- + if (ret)
- + return ret;
- +
- + return ssam_cdev_notifier_register(client, desc.target_category, desc.priority);
- +}
- +
- +static long ssam_cdev_notif_unregister(struct ssam_cdev_client *client,
- + const struct ssam_cdev_notifier_desc __user *d)
- +{
- + struct ssam_cdev_notifier_desc desc;
- + long ret;
- +
- + ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));
- + if (ret)
- + return ret;
- +
- + return ssam_cdev_notifier_unregister(client, desc.target_category);
- +}
- +
- +
- +/* -- File operations. ------------------------------------------------------ */
- +
- +static int ssam_cdev_device_open(struct inode *inode, struct file *filp)
- +{
- + struct miscdevice *mdev = filp->private_data;
- + struct ssam_cdev_client *client;
- + struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev);
- +
- + /* Initialize client */
- + client = vzalloc(sizeof(*client));
- + if (!client)
- + return -ENOMEM;
- +
- + client->cdev = ssam_cdev_get(cdev);
- +
- + INIT_LIST_HEAD(&client->node);
- +
- + mutex_init(&client->notifier_lock);
- +
- + mutex_init(&client->read_lock);
- + mutex_init(&client->write_lock);
- + INIT_KFIFO(client->buffer);
- + init_waitqueue_head(&client->waitq);
- +
- + filp->private_data = client;
- +
- + /* Attach client. */
- + down_write(&cdev->client_lock);
- +
- + if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) {
- + up_write(&cdev->client_lock);
- + ssam_cdev_put(client->cdev);
- + vfree(client);
- + return -ENODEV;
- + }
- + list_add_tail(&client->node, &cdev->client_list);
- +
- + up_write(&cdev->client_lock);
- +
- + stream_open(inode, filp);
- + return 0;
- +}
- +
- +static int ssam_cdev_device_release(struct inode *inode, struct file *filp)
- +{
- + struct ssam_cdev_client *client = filp->private_data;
- +
- + /* Force-unregister all remaining notifiers of this client. */
- + ssam_cdev_notifier_unregister_all(client);
- +
- + /* Detach client. */
- + down_write(&client->cdev->client_lock);
- + list_del(&client->node);
- + up_write(&client->cdev->client_lock);
- +
- + /* Free client. */
- + mutex_destroy(&client->write_lock);
- + mutex_destroy(&client->read_lock);
- +
- + mutex_destroy(&client->notifier_lock);
- +
- + ssam_cdev_put(client->cdev);
- + vfree(client);
- +
- + return 0;
- +}
- +
- +static long __ssam_cdev_device_ioctl(struct ssam_cdev_client *client, unsigned int cmd,
- unsigned long arg)
- {
- switch (cmd) {
- case SSAM_CDEV_REQUEST:
- - return ssam_cdev_request(cdev, arg);
- + return ssam_cdev_request(client, (struct ssam_cdev_request __user *)arg);
- +
- + case SSAM_CDEV_NOTIF_REGISTER:
- + return ssam_cdev_notif_register(client,
- + (struct ssam_cdev_notifier_desc __user *)arg);
- +
- + case SSAM_CDEV_NOTIF_UNREGISTER:
- + return ssam_cdev_notif_unregister(client,
- + (struct ssam_cdev_notifier_desc __user *)arg);
-
- default:
- return -ENOTTY;
- }
- }
-
- -static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd,
- - unsigned long arg)
- +static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
- {
- - struct ssam_cdev *cdev = file->private_data;
- + struct ssam_cdev_client *client = file->private_data;
- long status;
-
- /* Ensure that controller is valid for as long as we need it. */
- + if (down_read_killable(&client->cdev->lock))
- + return -ERESTARTSYS;
- +
- + if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &client->cdev->flags)) {
- + up_read(&client->cdev->lock);
- + return -ENODEV;
- + }
- +
- + status = __ssam_cdev_device_ioctl(client, cmd, arg);
- +
- + up_read(&client->cdev->lock);
- + return status;
- +}
- +
- +static ssize_t ssam_cdev_read(struct file *file, char __user *buf, size_t count, loff_t *offs)
- +{
- + struct ssam_cdev_client *client = file->private_data;
- + struct ssam_cdev *cdev = client->cdev;
- + unsigned int copied;
- + int status = 0;
- +
- if (down_read_killable(&cdev->lock))
- return -ERESTARTSYS;
-
- - if (!cdev->ctrl) {
- + /* Make sure we're not shut down. */
- + if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) {
- up_read(&cdev->lock);
- return -ENODEV;
- }
-
- - status = __ssam_cdev_device_ioctl(cdev, cmd, arg);
- + do {
- + /* Check availability, wait if necessary. */
- + if (kfifo_is_empty(&client->buffer)) {
- + up_read(&cdev->lock);
- +
- + if (file->f_flags & O_NONBLOCK)
- + return -EAGAIN;
- +
- + status = wait_event_interruptible(client->waitq,
- + !kfifo_is_empty(&client->buffer) ||
- + test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT,
- + &cdev->flags));
- + if (status < 0)
- + return status;
- +
- + if (down_read_killable(&cdev->lock))
- + return -ERESTARTSYS;
- +
- + /* Need to check that we're not shut down again. */
- + if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) {
- + up_read(&cdev->lock);
- + return -ENODEV;
- + }
- + }
- +
- + /* Try to read from FIFO. */
- + if (mutex_lock_interruptible(&client->read_lock)) {
- + up_read(&cdev->lock);
- + return -ERESTARTSYS;
- + }
- +
- + status = kfifo_to_user(&client->buffer, buf, count, &copied);
- + mutex_unlock(&client->read_lock);
- +
- + if (status < 0) {
- + up_read(&cdev->lock);
- + return status;
- + }
- +
- + /* We might not have gotten anything, check this here. */
- + if (copied == 0 && (file->f_flags & O_NONBLOCK)) {
- + up_read(&cdev->lock);
- + return -EAGAIN;
- + }
- + } while (copied == 0);
-
- up_read(&cdev->lock);
- - return status;
- + return copied;
- +}
- +
- +static __poll_t ssam_cdev_poll(struct file *file, struct poll_table_struct *pt)
- +{
- + struct ssam_cdev_client *client = file->private_data;
- + __poll_t events = 0;
- +
- + if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &client->cdev->flags))
- + return EPOLLHUP | EPOLLERR;
- +
- + poll_wait(file, &client->waitq, pt);
- +
- + if (!kfifo_is_empty(&client->buffer))
- + events |= EPOLLIN | EPOLLRDNORM;
- +
- + return events;
- +}
- +
- +static int ssam_cdev_fasync(int fd, struct file *file, int on)
- +{
- + struct ssam_cdev_client *client = file->private_data;
- +
- + return fasync_helper(fd, file, on, &client->fasync);
- }
-
- static const struct file_operations ssam_controller_fops = {
- .owner = THIS_MODULE,
- .open = ssam_cdev_device_open,
- .release = ssam_cdev_device_release,
- + .read = ssam_cdev_read,
- + .poll = ssam_cdev_poll,
- + .fasync = ssam_cdev_fasync,
- .unlocked_ioctl = ssam_cdev_device_ioctl,
- .compat_ioctl = ssam_cdev_device_ioctl,
- - .llseek = noop_llseek,
- + .llseek = no_llseek,
- };
-
- +
- +/* -- Device and driver setup ----------------------------------------------- */
- +
- static int ssam_dbg_device_probe(struct platform_device *pdev)
- {
- struct ssam_controller *ctrl;
- @@ -236,6 +613,7 @@ static int ssam_dbg_device_probe(struct platform_device *pdev)
- kref_init(&cdev->kref);
- init_rwsem(&cdev->lock);
- cdev->ctrl = ctrl;
- + cdev->dev = &pdev->dev;
-
- cdev->mdev.parent = &pdev->dev;
- cdev->mdev.minor = MISC_DYNAMIC_MINOR;
- @@ -243,6 +621,9 @@ static int ssam_dbg_device_probe(struct platform_device *pdev)
- cdev->mdev.nodename = "surface/aggregator";
- cdev->mdev.fops = &ssam_controller_fops;
-
- + init_rwsem(&cdev->client_lock);
- + INIT_LIST_HEAD(&cdev->client_list);
- +
- status = misc_register(&cdev->mdev);
- if (status) {
- kfree(cdev);
- @@ -256,8 +637,32 @@ static int ssam_dbg_device_probe(struct platform_device *pdev)
- static int ssam_dbg_device_remove(struct platform_device *pdev)
- {
- struct ssam_cdev *cdev = platform_get_drvdata(pdev);
- + struct ssam_cdev_client *client;
-
- - misc_deregister(&cdev->mdev);
- + /*
- + * Mark device as shut-down. Prevent new clients from being added and
- + * new operations from being executed.
- + */
- + set_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags);
- +
- + down_write(&cdev->client_lock);
- +
- + /* Remove all notifiers registered by us. */
- + list_for_each_entry(client, &cdev->client_list, node) {
- + ssam_cdev_notifier_unregister_all(client);
- + }
- +
- + /* Wake up async clients. */
- + list_for_each_entry(client, &cdev->client_list, node) {
- + kill_fasync(&client->fasync, SIGIO, POLL_HUP);
- + }
- +
- + /* Wake up blocking clients. */
- + list_for_each_entry(client, &cdev->client_list, node) {
- + wake_up_interruptible(&client->waitq);
- + }
- +
- + up_write(&cdev->client_lock);
-
- /*
- * The controller is only guaranteed to be valid for as long as the
- @@ -266,8 +671,11 @@ static int ssam_dbg_device_remove(struct platform_device *pdev)
- */
- down_write(&cdev->lock);
- cdev->ctrl = NULL;
- + cdev->dev = NULL;
- up_write(&cdev->lock);
-
- + misc_deregister(&cdev->mdev);
- +
- ssam_cdev_put(cdev);
- return 0;
- }
- diff --git a/include/uapi/linux/surface_aggregator/cdev.h b/include/uapi/linux/surface_aggregator/cdev.h
- index fbcce04abfe9..4f393fafc235 100644
- --- a/include/uapi/linux/surface_aggregator/cdev.h
- +++ b/include/uapi/linux/surface_aggregator/cdev.h
- @@ -6,7 +6,7 @@
- * device. This device provides direct user-space access to the SSAM EC.
- * Intended for debugging and development.
- *
- - * Copyright (C) 2020 Maximilian Luz <luzmaximilian@gmail.com>
- + * Copyright (C) 2020-2021 Maximilian Luz <luzmaximilian@gmail.com>
- */
-
- #ifndef _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H
- @@ -73,6 +73,43 @@ struct ssam_cdev_request {
- } response;
- } __attribute__((__packed__));
-
- -#define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request)
- +/**
- + * struct ssam_cdev_notifier_desc - Notifier descriptor.
- + * @priority: Priority value determining the order in which notifier
- + * callbacks will be called. A higher value means higher
- + * priority, i.e. the associated callback will be executed
- + * earlier than other (lower priority) callbacks.
- + * @target_category: The event target category for which this notifier should
- + * receive events.
- + *
- + * Specifies the notifier that should be registered or unregistered,
- + * specifically with which priority and for which target category of events.
- + */
- +struct ssam_cdev_notifier_desc {
- + __s32 priority;
- + __u8 target_category;
- +} __attribute__((__packed__));
- +
- +/**
- + * struct ssam_cdev_event - SSAM event sent by the EC.
- + * @target_category: Target category of the event source. See &enum ssam_ssh_tc.
- + * @target_id: Target ID of the event source.
- + * @command_id: Command ID of the event.
- + * @instance_id: Instance ID of the event source.
- + * @length: Length of the event payload in bytes.
- + * @data: Event payload data.
- + */
- +struct ssam_cdev_event {
- + __u8 target_category;
- + __u8 target_id;
- + __u8 command_id;
- + __u8 instance_id;
- + __u16 length;
- + __u8 data[];
- +} __attribute__((__packed__));
- +
- +#define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request)
- +#define SSAM_CDEV_NOTIF_REGISTER _IOW(0xA5, 2, struct ssam_cdev_notifier_desc)
- +#define SSAM_CDEV_NOTIF_UNREGISTER _IOW(0xA5, 3, struct ssam_cdev_notifier_desc)
-
- #endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H */
- --
- 2.32.0
- From 6c1af5eaf46d1462ebf82ec40fee70cdc5d37405 Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Wed, 2 Jun 2021 19:30:42 +0200
- Subject: [PATCH] platform/surface: aggregator_cdev: Allow enabling of events
- from user-space
- While events can already be enabled and disabled via the generic request
- IOCTL, this bypasses the internal reference counting mechanism of the
- controller. Due to that, disabling an event will turn it off regardless
- of any other client having requested said event, which may break
- functionality of that client.
- To solve this, add IOCTLs wrapping the ssam_controller_event_enable()
- and ssam_controller_event_disable() functions, which have been
- previously introduced for this specific purpose.
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Patchset: surface-sam
- ---
- .../surface/surface_aggregator_cdev.c | 58 +++++++++++++++++++
- include/uapi/linux/surface_aggregator/cdev.h | 32 ++++++++++
- 2 files changed, 90 insertions(+)
- diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c
- index 7b27c8ca38a5..55bf55c93624 100644
- --- a/drivers/platform/surface/surface_aggregator_cdev.c
- +++ b/drivers/platform/surface/surface_aggregator_cdev.c
- @@ -384,6 +384,58 @@ static long ssam_cdev_notif_unregister(struct ssam_cdev_client *client,
- return ssam_cdev_notifier_unregister(client, desc.target_category);
- }
-
- +static long ssam_cdev_event_enable(struct ssam_cdev_client *client,
- + const struct ssam_cdev_event_desc __user *d)
- +{
- + struct ssam_cdev_event_desc desc;
- + struct ssam_event_registry reg;
- + struct ssam_event_id id;
- + long ret;
- +
- + /* Read descriptor from user-space. */
- + ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));
- + if (ret)
- + return ret;
- +
- + /* Translate descriptor. */
- + reg.target_category = desc.reg.target_category;
- + reg.target_id = desc.reg.target_id;
- + reg.cid_enable = desc.reg.cid_enable;
- + reg.cid_disable = desc.reg.cid_disable;
- +
- + id.target_category = desc.id.target_category;
- + id.instance = desc.id.instance;
- +
- + /* Disable event. */
- + return ssam_controller_event_enable(client->cdev->ctrl, reg, id, desc.flags);
- +}
- +
- +static long ssam_cdev_event_disable(struct ssam_cdev_client *client,
- + const struct ssam_cdev_event_desc __user *d)
- +{
- + struct ssam_cdev_event_desc desc;
- + struct ssam_event_registry reg;
- + struct ssam_event_id id;
- + long ret;
- +
- + /* Read descriptor from user-space. */
- + ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));
- + if (ret)
- + return ret;
- +
- + /* Translate descriptor. */
- + reg.target_category = desc.reg.target_category;
- + reg.target_id = desc.reg.target_id;
- + reg.cid_enable = desc.reg.cid_enable;
- + reg.cid_disable = desc.reg.cid_disable;
- +
- + id.target_category = desc.id.target_category;
- + id.instance = desc.id.instance;
- +
- + /* Disable event. */
- + return ssam_controller_event_disable(client->cdev->ctrl, reg, id, desc.flags);
- +}
- +
-
- /* -- File operations. ------------------------------------------------------ */
-
- @@ -467,6 +519,12 @@ static long __ssam_cdev_device_ioctl(struct ssam_cdev_client *client, unsigned i
- return ssam_cdev_notif_unregister(client,
- (struct ssam_cdev_notifier_desc __user *)arg);
-
- + case SSAM_CDEV_EVENT_ENABLE:
- + return ssam_cdev_event_enable(client, (struct ssam_cdev_event_desc __user *)arg);
- +
- + case SSAM_CDEV_EVENT_DISABLE:
- + return ssam_cdev_event_disable(client, (struct ssam_cdev_event_desc __user *)arg);
- +
- default:
- return -ENOTTY;
- }
- diff --git a/include/uapi/linux/surface_aggregator/cdev.h b/include/uapi/linux/surface_aggregator/cdev.h
- index 4f393fafc235..08f46b60b151 100644
- --- a/include/uapi/linux/surface_aggregator/cdev.h
- +++ b/include/uapi/linux/surface_aggregator/cdev.h
- @@ -90,6 +90,36 @@ struct ssam_cdev_notifier_desc {
- __u8 target_category;
- } __attribute__((__packed__));
-
- +/**
- + * struct ssam_cdev_event_desc - Event descriptor.
- + * @reg: Registry via which the event will be enabled/disabled.
- + * @reg.target_category: Target category for the event registry requests.
- + * @reg.target_id: Target ID for the event registry requests.
- + * @reg.cid_enable: Command ID for the event-enable request.
- + * @reg.cid_disable: Command ID for the event-disable request.
- + * @id: ID specifying the event.
- + * @id.target_category: Target category of the event source.
- + * @id.instance: Instance ID of the event source.
- + * @flags: Flags used for enabling the event.
- + *
- + * Specifies which event should be enabled/disabled and how to do that.
- + */
- +struct ssam_cdev_event_desc {
- + struct {
- + __u8 target_category;
- + __u8 target_id;
- + __u8 cid_enable;
- + __u8 cid_disable;
- + } reg;
- +
- + struct {
- + __u8 target_category;
- + __u8 instance;
- + } id;
- +
- + __u8 flags;
- +} __attribute__((__packed__));
- +
- /**
- * struct ssam_cdev_event - SSAM event sent by the EC.
- * @target_category: Target category of the event source. See &enum ssam_ssh_tc.
- @@ -111,5 +141,7 @@ struct ssam_cdev_event {
- #define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request)
- #define SSAM_CDEV_NOTIF_REGISTER _IOW(0xA5, 2, struct ssam_cdev_notifier_desc)
- #define SSAM_CDEV_NOTIF_UNREGISTER _IOW(0xA5, 3, struct ssam_cdev_notifier_desc)
- +#define SSAM_CDEV_EVENT_ENABLE _IOW(0xA5, 4, struct ssam_cdev_event_desc)
- +#define SSAM_CDEV_EVENT_DISABLE _IOW(0xA5, 5, struct ssam_cdev_event_desc)
-
- #endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H */
- --
- 2.32.0
- From 38fd84ffd2930a1800f8bbb308536096394b31dc Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Wed, 2 Jun 2021 19:38:07 +0200
- Subject: [PATCH] platform/surface: aggregator_cdev: Add lockdep support
- Mark functions with locking requirements via the corresponding lockdep
- calls for debugging and documentary purposes.
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Patchset: surface-sam
- ---
- .../platform/surface/surface_aggregator_cdev.c | 16 ++++++++++++++++
- 1 file changed, 16 insertions(+)
- diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c
- index 55bf55c93624..2cad4147645c 100644
- --- a/drivers/platform/surface/surface_aggregator_cdev.c
- +++ b/drivers/platform/surface/surface_aggregator_cdev.c
- @@ -137,6 +137,8 @@ static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 categ
- int index = ((int)category) - 1;
- int status;
-
- + lockdep_assert_held_read(&client->cdev->lock);
- +
- /* Validate notifier target category. */
- if (index < 0 || index >= SSH_NUM_EVENTS)
- return -EINVAL;
- @@ -185,6 +187,8 @@ static int ssam_cdev_notifier_unregister(struct ssam_cdev_client *client, u8 cat
- int index = ((int)category) - 1;
- int status;
-
- + lockdep_assert_held_read(&client->cdev->lock);
- +
- /* Validate notifier target category. */
- if (index < 0 || index >= SSH_NUM_EVENTS)
- return -EINVAL;
- @@ -254,6 +258,8 @@ static long ssam_cdev_request(struct ssam_cdev_client *client, struct ssam_cdev_
- void __user *rspdata;
- int status = 0, ret = 0, tmp;
-
- + lockdep_assert_held_read(&client->cdev->lock);
- +
- ret = copy_struct_from_user(&rqst, sizeof(rqst), r, sizeof(*r));
- if (ret)
- goto out;
- @@ -364,6 +370,8 @@ static long ssam_cdev_notif_register(struct ssam_cdev_client *client,
- struct ssam_cdev_notifier_desc desc;
- long ret;
-
- + lockdep_assert_held_read(&client->cdev->lock);
- +
- ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));
- if (ret)
- return ret;
- @@ -377,6 +385,8 @@ static long ssam_cdev_notif_unregister(struct ssam_cdev_client *client,
- struct ssam_cdev_notifier_desc desc;
- long ret;
-
- + lockdep_assert_held_read(&client->cdev->lock);
- +
- ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));
- if (ret)
- return ret;
- @@ -392,6 +402,8 @@ static long ssam_cdev_event_enable(struct ssam_cdev_client *client,
- struct ssam_event_id id;
- long ret;
-
- + lockdep_assert_held_read(&client->cdev->lock);
- +
- /* Read descriptor from user-space. */
- ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));
- if (ret)
- @@ -418,6 +430,8 @@ static long ssam_cdev_event_disable(struct ssam_cdev_client *client,
- struct ssam_event_id id;
- long ret;
-
- + lockdep_assert_held_read(&client->cdev->lock);
- +
- /* Read descriptor from user-space. */
- ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));
- if (ret)
- @@ -507,6 +521,8 @@ static int ssam_cdev_device_release(struct inode *inode, struct file *filp)
- static long __ssam_cdev_device_ioctl(struct ssam_cdev_client *client, unsigned int cmd,
- unsigned long arg)
- {
- + lockdep_assert_held_read(&client->cdev->lock);
- +
- switch (cmd) {
- case SSAM_CDEV_REQUEST:
- return ssam_cdev_request(client, (struct ssam_cdev_request __user *)arg);
- --
- 2.32.0
- From f8a8ca2b134d58d8f253acb92e83ae0d20cbedca Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Fri, 4 Jun 2021 22:28:41 +0200
- Subject: [PATCH] platform/surface: aggregator: Fixups for user-space event
- forwarding series
- Patchset: surface-sam
- ---
- .../platform/surface/aggregator/controller.c | 248 +++++++++++-------
- .../surface/surface_aggregator_cdev.c | 32 ++-
- 2 files changed, 174 insertions(+), 106 deletions(-)
- diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c
- index e91ee7e72c14..6646f4d6e10d 100644
- --- a/drivers/platform/surface/aggregator/controller.c
- +++ b/drivers/platform/surface/aggregator/controller.c
- @@ -407,6 +407,31 @@ ssam_nf_refcount_dec(struct ssam_nf *nf, struct ssam_event_registry reg,
- return NULL;
- }
-
- +/**
- + * ssam_nf_refcount_dec_free() - Decrement reference-/activation-count of the
- + * given event and free its entry if the reference count reaches zero.
- + * @nf: The notifier system reference.
- + * @reg: The registry used to enable/disable the event.
- + * @id: The event ID.
- + *
- + * Decrements the reference-/activation-count of the specified event, freeing
- + * its entry if it reaches zero.
- + *
- + * Note: ``nf->lock`` must be held when calling this function.
- + */
- +static void ssam_nf_refcount_dec_free(struct ssam_nf *nf,
- + struct ssam_event_registry reg,
- + struct ssam_event_id id)
- +{
- + struct ssam_nf_refcount_entry *entry;
- +
- + lockdep_assert_held(&nf->lock);
- +
- + entry = ssam_nf_refcount_dec(nf, reg, id);
- + if (entry && entry->refcount == 0)
- + kfree(entry);
- +}
- +
- /**
- * ssam_nf_refcount_empty() - Test if the notification system has any
- * enabled/active events.
- @@ -2122,6 +2147,109 @@ int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl)
-
- /* -- Top-level event registry interface. ----------------------------------- */
-
- +/**
- + * ssam_nf_refcount_enable() - Enable event for reference count entry if it has
- + * not already been enabled.
- + * @ctrl: The controller to enable the event on.
- + * @entry: The reference count entry for the event to be enabled.
- + * @flags: The flags used for enabling the event on the EC.
- + *
- + * Enable the event associated with the given reference count entry if the
- + * reference count equals one, i.e. the event has not previously been enabled.
- + * If the event has already been enabled (i.e. reference count not equal to
- + * one), check that the flags used for enabling match and warn about this if
- + * they do not.
- + *
- + * This does not modify the reference count itself, which is done with
- + * ssam_nf_refcount_inc() / ssam_nf_refcount_dec().
- + *
- + * Note: ``nf->lock`` must be held when calling this function.
- + *
- + * Return: Returns zero on success. If the event is enabled by this call,
- + * returns the status of the event-enable EC command.
- + */
- +static int ssam_nf_refcount_enable(struct ssam_controller *ctrl,
- + struct ssam_nf_refcount_entry *entry, u8 flags)
- +{
- + const struct ssam_event_registry reg = entry->key.reg;
- + const struct ssam_event_id id = entry->key.id;
- + struct ssam_nf *nf = &ctrl->cplt.event.notif;
- + int status;
- +
- + lockdep_assert_held(&nf->lock);
- +
- + ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
- + reg.target_category, id.target_category, id.instance, entry->refcount);
- +
- + if (entry->refcount == 1) {
- + status = ssam_ssh_event_enable(ctrl, reg, id, flags);
- + if (status)
- + return status;
- +
- + entry->flags = flags;
- +
- + } else if (entry->flags != flags) {
- + ssam_warn(ctrl,
- + "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
- + flags, entry->flags, reg.target_category, id.target_category,
- + id.instance);
- + }
- +
- + return 0;
- +}
- +
- +/**
- + * ssam_nf_refcount_disable_free() - Disable event for reference count entry if it is
- + * no longer in use and free the corresponding entry.
- + * @ctrl: The controller to disable the event on.
- + * @entry: The reference count entry for the event to be disabled.
- + * @flags: The flags used for enabling the event on the EC.
- + *
- + * If the reference count equals zero, i.e. the event is no longer requested by
- + * any client, the event will be disabled and the corresponding reference count
- + * entry freed. The reference count entry must not be used any more after a
- + * call to this function.
- + *
- + * Also checks if the flags used for disabling the event match the flags used
- + * for enabling the event and warns if they do not (regardless of reference
- + * count).
- + *
- + * This does not modify the reference count itself, which is done with
- + * ssam_nf_refcount_inc() / ssam_nf_refcount_dec().
- + *
- + * Note: ``nf->lock`` must be held when calling this function.
- + *
- + * Return: Returns zero on success. If the event is disabled by this call,
- + * returns the status of the event-enable EC command.
- + */
- +static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl,
- + struct ssam_nf_refcount_entry *entry, u8 flags)
- +{
- + const struct ssam_event_registry reg = entry->key.reg;
- + const struct ssam_event_id id = entry->key.id;
- + struct ssam_nf *nf = &ctrl->cplt.event.notif;
- + int status;
- +
- + lockdep_assert_held(&nf->lock);
- +
- + ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
- + reg.target_category, id.target_category, id.instance, entry->refcount);
- +
- + if (entry->flags != flags) {
- + ssam_warn(ctrl,
- + "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
- + flags, entry->flags, reg.target_category, id.target_category,
- + id.instance);
- + }
- +
- + if (entry->refcount == 0) {
- + status = ssam_ssh_event_disable(ctrl, reg, id, flags);
- + kfree(entry);
- + }
- +
- + return status;
- +}
- +
- /**
- * ssam_notifier_register() - Register an event notifier.
- * @ctrl: The controller to register the notifier on.
- @@ -2166,41 +2294,26 @@ int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notif
- mutex_unlock(&nf->lock);
- return PTR_ERR(entry);
- }
- -
- - ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
- - n->event.reg.target_category, n->event.id.target_category,
- - n->event.id.instance, entry->refcount);
- }
-
- status = ssam_nfblk_insert(nf_head, &n->base);
- if (status) {
- - if (entry) {
- - entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id);
- - if (entry->refcount == 0)
- - kfree(entry);
- - }
- + if (entry)
- + ssam_nf_refcount_dec_free(nf, n->event.reg, n->event.id);
-
- mutex_unlock(&nf->lock);
- return status;
- }
-
- - if (entry && entry->refcount == 1) {
- - status = ssam_ssh_event_enable(ctrl, n->event.reg, n->event.id, n->event.flags);
- + if (entry) {
- + status = ssam_nf_refcount_enable(ctrl, entry, n->event.flags);
- if (status) {
- ssam_nfblk_remove(&n->base);
- - kfree(ssam_nf_refcount_dec(nf, n->event.reg, n->event.id));
- + ssam_nf_refcount_dec_free(nf, n->event.reg, n->event.id);
- mutex_unlock(&nf->lock);
- synchronize_srcu(&nf_head->srcu);
- return status;
- }
- -
- - entry->flags = n->event.flags;
- -
- - } else if (entry && entry->flags != n->event.flags) {
- - ssam_warn(ctrl,
- - "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
- - n->event.flags, entry->flags, n->event.reg.target_category,
- - n->event.id.target_category, n->event.id.instance);
- }
-
- mutex_unlock(&nf->lock);
- @@ -2247,35 +2360,20 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not
- * If this is an observer notifier, do not attempt to disable the
- * event, just remove it.
- */
- - if (n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)
- - goto remove;
- -
- - entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id);
- - if (WARN_ON(!entry)) {
- - /*
- - * If this does not return an entry, there's a logic error
- - * somewhere: The notifier block is registered, but the event
- - * refcount entry is not there. Remove the notifier block
- - * anyways.
- - */
- - status = -ENOENT;
- - goto remove;
- - }
- -
- - ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
- - n->event.reg.target_category, n->event.id.target_category,
- - n->event.id.instance, entry->refcount);
- + if (!(n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)) {
- + entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id);
- + if (WARN_ON(!entry)) {
- + /*
- + * If this does not return an entry, there's a logic
- + * error somewhere: The notifier block is registered,
- + * but the event refcount entry is not there. Remove
- + * the notifier block anyways.
- + */
- + status = -ENOENT;
- + goto remove;
- + }
-
- - if (entry->flags != n->event.flags) {
- - ssam_warn(ctrl,
- - "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
- - n->event.flags, entry->flags, n->event.reg.target_category,
- - n->event.id.target_category, n->event.id.instance);
- - }
- -
- - if (entry->refcount == 0) {
- - status = ssam_ssh_event_disable(ctrl, n->event.reg, n->event.id, n->event.flags);
- - kfree(entry);
- + status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags);
- }
-
- remove:
- @@ -2313,17 +2411,13 @@ int ssam_controller_event_enable(struct ssam_controller *ctrl,
- struct ssam_event_id id, u8 flags)
- {
- u16 rqid = ssh_tc_to_rqid(id.target_category);
- + struct ssam_nf *nf = &ctrl->cplt.event.notif;
- struct ssam_nf_refcount_entry *entry;
- - struct ssam_nf_head *nf_head;
- - struct ssam_nf *nf;
- int status;
-
- if (!ssh_rqid_is_event(rqid))
- return -EINVAL;
-
- - nf = &ctrl->cplt.event.notif;
- - nf_head = &nf->head[ssh_rqid_to_event(rqid)];
- -
- mutex_lock(&nf->lock);
-
- entry = ssam_nf_refcount_inc(nf, reg, id);
- @@ -2332,25 +2426,11 @@ int ssam_controller_event_enable(struct ssam_controller *ctrl,
- return PTR_ERR(entry);
- }
-
- - ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
- - reg.target_category, id.target_category, id.instance,
- - entry->refcount);
- -
- - if (entry->refcount == 1) {
- - status = ssam_ssh_event_enable(ctrl, reg, id, flags);
- - if (status) {
- - kfree(ssam_nf_refcount_dec(nf, reg, id));
- - mutex_unlock(&nf->lock);
- - return status;
- - }
- -
- - entry->flags = flags;
- -
- - } else if (entry->flags != flags) {
- - ssam_warn(ctrl,
- - "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
- - flags, entry->flags, reg.target_category,
- - id.target_category, id.instance);
- + status = ssam_nf_refcount_enable(ctrl, entry, flags);
- + if (status) {
- + ssam_nf_refcount_dec_free(nf, reg, id);
- + mutex_unlock(&nf->lock);
- + return status;
- }
-
- mutex_unlock(&nf->lock);
- @@ -2382,40 +2462,22 @@ int ssam_controller_event_disable(struct ssam_controller *ctrl,
- struct ssam_event_id id, u8 flags)
- {
- u16 rqid = ssh_tc_to_rqid(id.target_category);
- + struct ssam_nf *nf = &ctrl->cplt.event.notif;
- struct ssam_nf_refcount_entry *entry;
- - struct ssam_nf_head *nf_head;
- - struct ssam_nf *nf;
- int status = 0;
-
- if (!ssh_rqid_is_event(rqid))
- return -EINVAL;
-
- - nf = &ctrl->cplt.event.notif;
- - nf_head = &nf->head[ssh_rqid_to_event(rqid)];
- -
- mutex_lock(&nf->lock);
-
- entry = ssam_nf_refcount_dec(nf, reg, id);
- - if (WARN_ON(!entry)) {
- + if (!entry) {
- mutex_unlock(&nf->lock);
- return -ENOENT;
- }
-
- - ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
- - reg.target_category, id.target_category, id.instance,
- - entry->refcount);
- -
- - if (entry->flags != flags) {
- - ssam_warn(ctrl,
- - "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
- - flags, entry->flags, reg.target_category,
- - id.target_category, id.instance);
- - }
- -
- - if (entry->refcount == 0) {
- - status = ssam_ssh_event_disable(ctrl, reg, id, flags);
- - kfree(entry);
- - }
- + status = ssam_nf_refcount_disable_free(ctrl, entry, flags);
-
- mutex_unlock(&nf->lock);
- return status;
- diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c
- index 2cad4147645c..30fb50fde450 100644
- --- a/drivers/platform/surface/surface_aggregator_cdev.c
- +++ b/drivers/platform/surface/surface_aggregator_cdev.c
- @@ -22,6 +22,7 @@
-
- #include <linux/surface_aggregator/cdev.h>
- #include <linux/surface_aggregator/controller.h>
- +#include <linux/surface_aggregator/serial_hub.h>
-
- #define SSAM_CDEV_DEVICE_NAME "surface_aggregator_cdev"
-
- @@ -131,22 +132,23 @@ static u32 ssam_cdev_notifier(struct ssam_event_notifier *nf, const struct ssam_
- return 0;
- }
-
- -static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 category, int priority)
- +static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 tc, int priority)
- {
- + const u16 rqid = ssh_tc_to_rqid(tc);
- + const u16 event = ssh_rqid_to_event(rqid);
- struct ssam_cdev_notifier *nf;
- - int index = ((int)category) - 1;
- int status;
-
- lockdep_assert_held_read(&client->cdev->lock);
-
- /* Validate notifier target category. */
- - if (index < 0 || index >= SSH_NUM_EVENTS)
- + if (!ssh_rqid_is_event(rqid))
- return -EINVAL;
-
- mutex_lock(&client->notifier_lock);
-
- /* Check if the notifier has already been registered. */
- - if (client->notifier[index]) {
- + if (client->notifier[event]) {
- mutex_unlock(&client->notifier_lock);
- return -EEXIST;
- }
- @@ -167,7 +169,7 @@ static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 categ
- nf->client = client;
- nf->nf.base.fn = ssam_cdev_notifier;
- nf->nf.base.priority = priority;
- - nf->nf.event.id.target_category = category;
- + nf->nf.event.id.target_category = tc;
- nf->nf.event.mask = 0; /* Do not do any matching. */
- nf->nf.flags = SSAM_EVENT_NOTIFIER_OBSERVER;
-
- @@ -176,35 +178,36 @@ static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 categ
- if (status)
- kfree(nf);
- else
- - client->notifier[index] = nf;
- + client->notifier[event] = nf;
-
- mutex_unlock(&client->notifier_lock);
- return status;
- }
-
- -static int ssam_cdev_notifier_unregister(struct ssam_cdev_client *client, u8 category)
- +static int ssam_cdev_notifier_unregister(struct ssam_cdev_client *client, u8 tc)
- {
- - int index = ((int)category) - 1;
- + const u16 rqid = ssh_tc_to_rqid(tc);
- + const u16 event = ssh_rqid_to_event(rqid);
- int status;
-
- lockdep_assert_held_read(&client->cdev->lock);
-
- /* Validate notifier target category. */
- - if (index < 0 || index >= SSH_NUM_EVENTS)
- + if (!ssh_rqid_is_event(rqid))
- return -EINVAL;
-
- mutex_lock(&client->notifier_lock);
-
- /* Check if the notifier is currently registered. */
- - if (!client->notifier[index]) {
- + if (!client->notifier[event]) {
- mutex_unlock(&client->notifier_lock);
- return -ENOENT;
- }
-
- /* Unregister and free notifier. */
- - status = ssam_notifier_unregister(client->cdev->ctrl, &client->notifier[index]->nf);
- - kfree(client->notifier[index]);
- - client->notifier[index] = NULL;
- + status = ssam_notifier_unregister(client->cdev->ctrl, &client->notifier[event]->nf);
- + kfree(client->notifier[event]);
- + client->notifier[event] = NULL;
-
- mutex_unlock(&client->notifier_lock);
- return status;
- @@ -482,6 +485,9 @@ static int ssam_cdev_device_open(struct inode *inode, struct file *filp)
-
- if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) {
- up_write(&cdev->client_lock);
- + mutex_destroy(&client->write_lock);
- + mutex_destroy(&client->read_lock);
- + mutex_destroy(&client->notifier_lock);
- ssam_cdev_put(client->cdev);
- vfree(client);
- return -ENODEV;
- --
- 2.32.0
- From fe4a216fc5f8e021db1d78df71c4f99ad5110bfd Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Fri, 4 Jun 2021 22:56:38 +0200
- Subject: [PATCH] platform/surface: aggregator: Do not return uninitialized
- value
- The status variable in ssam_nf_refcount_disable_free() is only set when
- the reference count equals zero. Otherwise, it is returned
- uninitialized. Fix this by always initializing status to zero.
- Reported-by: kernel test robot <lkp@intel.com>
- Fixes: 640ee17199e4 ("platform/surface: aggregator: Allow enabling of events without notifiers")
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Patchset: surface-sam
- ---
- drivers/platform/surface/aggregator/controller.c | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
- diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c
- index 6646f4d6e10d..634399387d76 100644
- --- a/drivers/platform/surface/aggregator/controller.c
- +++ b/drivers/platform/surface/aggregator/controller.c
- @@ -2228,7 +2228,7 @@ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl,
- const struct ssam_event_registry reg = entry->key.reg;
- const struct ssam_event_id id = entry->key.id;
- struct ssam_nf *nf = &ctrl->cplt.event.notif;
- - int status;
- + int status = 0;
-
- lockdep_assert_held(&nf->lock);
-
- --
- 2.32.0
- From 79c87e7add447e1021595ff6a56c34ded0734926 Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Fri, 4 Jun 2021 23:00:47 +0200
- Subject: [PATCH] platform/surface: aggregator: Drop unnecessary variable
- initialization
- The status variable in ssam_controller_event_disable() is always set, no
- need to initialize it.
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Patchset: surface-sam
- ---
- drivers/platform/surface/aggregator/controller.c | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
- diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c
- index 634399387d76..b8c377b3f932 100644
- --- a/drivers/platform/surface/aggregator/controller.c
- +++ b/drivers/platform/surface/aggregator/controller.c
- @@ -2464,7 +2464,7 @@ int ssam_controller_event_disable(struct ssam_controller *ctrl,
- u16 rqid = ssh_tc_to_rqid(id.target_category);
- struct ssam_nf *nf = &ctrl->cplt.event.notif;
- struct ssam_nf_refcount_entry *entry;
- - int status = 0;
- + int status;
-
- if (!ssh_rqid_is_event(rqid))
- return -EINVAL;
- --
- 2.32.0
- From 220729198b2a72ada278f36112f6c064d82c7a0e Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Wed, 2 Jun 2021 20:07:47 +0200
- Subject: [PATCH] docs: driver-api: Update Surface Aggregator user-space
- interface documentation
- Update the controller-device user-space interface (cdev) documentation
- for the newly introduced IOCTLs and event interface.
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Patchset: surface-sam
- ---
- .../surface_aggregator/clients/cdev.rst | 127 +++++++++++++++++-
- 1 file changed, 122 insertions(+), 5 deletions(-)
- diff --git a/Documentation/driver-api/surface_aggregator/clients/cdev.rst b/Documentation/driver-api/surface_aggregator/clients/cdev.rst
- index 248c1372d879..0134a841a079 100644
- --- a/Documentation/driver-api/surface_aggregator/clients/cdev.rst
- +++ b/Documentation/driver-api/surface_aggregator/clients/cdev.rst
- @@ -1,9 +1,8 @@
- .. SPDX-License-Identifier: GPL-2.0+
-
- -.. |u8| replace:: :c:type:`u8 <u8>`
- -.. |u16| replace:: :c:type:`u16 <u16>`
- .. |ssam_cdev_request| replace:: :c:type:`struct ssam_cdev_request <ssam_cdev_request>`
- .. |ssam_cdev_request_flags| replace:: :c:type:`enum ssam_cdev_request_flags <ssam_cdev_request_flags>`
- +.. |ssam_cdev_event| replace:: :c:type:`struct ssam_cdev_event <ssam_cdev_event>`
-
- ==============================
- User-Space EC Interface (cdev)
- @@ -23,6 +22,40 @@ These IOCTLs and their respective input/output parameter structs are defined in
- A small python library and scripts for accessing this interface can be found
- at https://github.com/linux-surface/surface-aggregator-module/tree/master/scripts/ssam.
-
- +.. contents::
- +
- +
- +Receiving Events
- +================
- +
- +Events can be received by reading from the device-file. The are represented by
- +the |ssam_cdev_event| datatype.
- +
- +Before events are available to be read, however, the desired notifiers must be
- +registered via the ``SSAM_CDEV_NOTIF_REGISTER`` IOCTL. Notifiers are, in
- +essence, callbacks, called when the EC sends an event. They are, in this
- +interface, associated with a specific target category and device-file-instance.
- +They forward any event of this category to the buffer of the corresponding
- +instance, from which it can then be read.
- +
- +Notifiers themselves do not enable events on the EC. Thus, it may additionally
- +be necessary to enable events via the ``SSAM_CDEV_EVENT_ENABLE`` IOCTL. While
- +notifiers work per-client (i.e. per-device-file-instance), events are enabled
- +globally, for the EC and all of its clients (regardless of userspace or
- +non-userspace). The ``SSAM_CDEV_EVENT_ENABLE`` and ``SSAM_CDEV_EVENT_DISABLE``
- +IOCTLs take care of reference counting the events, such that an event is
- +enabled as long as there is a client that has requested it.
- +
- +Note that enabled events are not automatically disabled once the client
- +instance is closed. Therefore any client process (or group of processes) should
- +balance their event enable calls with the corresponding event disable calls. It
- +is, however, perfectly valid to enable and disable events on different client
- +instances. For example, it is valid to set up notifiers and read events on
- +client instance ``A``, enable those events on instance ``B`` (note that these
- +will also be received by A since events are enabled/disabled globally), and
- +after no more events are desired, disable the previously enabled events via
- +instance ``C``.
- +
-
- Controller IOCTLs
- =================
- @@ -45,9 +78,33 @@ The following IOCTLs are provided:
- - ``REQUEST``
- - Perform synchronous SAM request.
-
- + * - ``0xA5``
- + - ``2``
- + - ``W``
- + - ``NOTIF_REGISTER``
- + - Register event notifier.
-
- -``REQUEST``
- ------------
- + * - ``0xA5``
- + - ``3``
- + - ``W``
- + - ``NOTIF_UNREGISTER``
- + - Unregister event notifier.
- +
- + * - ``0xA5``
- + - ``4``
- + - ``W``
- + - ``EVENT_ENABLE``
- + - Enable event source.
- +
- + * - ``0xA5``
- + - ``5``
- + - ``W``
- + - ``EVENT_DISABLE``
- + - Disable event source.
- +
- +
- +``SSAM_CDEV_REQUEST``
- +---------------------
-
- Defined as ``_IOWR(0xA5, 1, struct ssam_cdev_request)``.
-
- @@ -82,6 +139,66 @@ submitted, and completed (i.e. handed back to user-space) successfully from
- inside the IOCTL, but the request ``status`` member may still be negative in
- case the actual execution of the request failed after it has been submitted.
-
- -A full definition of the argument struct is provided below:
- +A full definition of the argument struct is provided below.
- +
- +``SSAM_CDEV_NOTIF_REGISTER``
- +----------------------------
- +
- +Defined as ``_IOW(0xA5, 2, struct ssam_cdev_notifier_desc)``.
- +
- +Register a notifier for the event target category specified in the given
- +notifier description with the specified priority. Notifiers registration is
- +required to receive events, but does not enable events themselves. After a
- +notifier for a specific target category has been registered, all events of that
- +category will be forwarded to the userspace client and can then be read from
- +the device file instance. Note that events may have to be enabled, e.g. via the
- +``SSAM_CDEV_EVENT_ENABLE`` IOCTL, before the EC will send them.
- +
- +Only one notifier can be registered per target category and client instance. If
- +a notifier has already been registered, this IOCTL will fail with ``-EEXIST``.
- +
- +Notifiers will automatically be removed when the device file instance is
- +closed.
- +
- +``SSAM_CDEV_NOTIF_UNREGISTER``
- +------------------------------
- +
- +Defined as ``_IOW(0xA5, 3, struct ssam_cdev_notifier_desc)``.
- +
- +Unregisters the notifier associated with the specified target category. The
- +priority field will be ignored by this IOCTL. If no notifier has been
- +registered for this client instance and the given category, this IOCTL will
- +fail with ``-ENOENT``.
- +
- +``SSAM_CDEV_EVENT_ENABLE``
- +--------------------------
- +
- +Defined as ``_IOW(0xA5, 4, struct ssam_cdev_event_desc)``.
- +
- +Enable the event associated with the given event descriptor.
- +
- +Note that this call will not register a notifier itself, it will only enable
- +events on the controller. If you want to receive events by reading from the
- +device file, you will need to register the corresponding notifier(s) on that
- +instance.
- +
- +Events are not automatically disabled when the device file is closed. This must
- +be done manually, via a call to the ``SSAM_CDEV_EVENT_DISABLE`` IOCTL.
- +
- +``SSAM_CDEV_EVENT_DISABLE``
- +---------------------------
- +
- +Defined as ``_IOW(0xA5, 5, struct ssam_cdev_event_desc)``.
- +
- +Disable the event associated with the given event descriptor.
- +
- +Note that this will not unregister any notifiers. Events may still be received
- +and forwarded to user-space after this call. The only safe way of stopping
- +events from being received is unregistering all previously registered
- +notifiers.
- +
- +
- +Structures and Enums
- +====================
-
- .. kernel-doc:: include/uapi/linux/surface_aggregator/cdev.h
- --
- 2.32.0
- From a12ef238262023ecb08d5fd7ac5b589c5cb41524 Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Wed, 5 May 2021 18:22:04 +0200
- Subject: [PATCH] pinctrl/amd: Add device HID for new AMD GPIO controller
- Add device HID AMDI0031 to the AMD GPIO controller driver match table.
- This controller can be found on Microsoft Surface Laptop 4 devices and
- seems similar enough that we can just copy the existing AMDI0030 entry.
- Cc: <stable@vger.kernel.org> # 5.10+
- Tested-by: Sachi King <nakato@nakato.io>
- Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
- Patchset: surface-sam
- ---
- drivers/pinctrl/pinctrl-amd.c | 1 +
- 1 file changed, 1 insertion(+)
- diff --git a/drivers/pinctrl/pinctrl-amd.c b/drivers/pinctrl/pinctrl-amd.c
- index 2d4acf21117c..c5950a3b4e4c 100644
- --- a/drivers/pinctrl/pinctrl-amd.c
- +++ b/drivers/pinctrl/pinctrl-amd.c
- @@ -991,6 +991,7 @@ static int amd_gpio_remove(struct platform_device *pdev)
- static const struct acpi_device_id amd_gpio_acpi_match[] = {
- { "AMD0030", 0 },
- { "AMDI0030", 0},
- + { "AMDI0031", 0},
- { },
- };
- MODULE_DEVICE_TABLE(acpi, amd_gpio_acpi_match);
- --
- 2.32.0
- From 5500228a79c6136a40035f557bd62160b2c7b4eb Mon Sep 17 00:00:00 2001
- From: Sachi King <nakato@nakato.io>
- Date: Sat, 29 May 2021 17:47:38 +1000
- Subject: [PATCH] ACPI: Add quirk for Surface Laptop 4 AMD missing irq 7
- override
- This patch is the work of Thomas Gleixner <tglx@linutronix.de> and is
- copied from:
- https://lore.kernel.org/lkml/87lf8ddjqx.ffs@nanos.tec.linutronix.de/
- This patch adds a quirk to the ACPI setup to patch in the the irq 7 pin
- setup that is missing in the laptops ACPI table.
- This patch was used for validation of the issue, and is not a proper
- fix, but is probably a better temporary hack than continuing to probe
- the Legacy PIC and run with the PIC in an unknown state.
- Patchset: surface-sam
- ---
- arch/x86/kernel/acpi/boot.c | 17 +++++++++++++++++
- 1 file changed, 17 insertions(+)
- diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c
- index 14cd3186dc77..ab3ba60cb6da 100644
- --- a/arch/x86/kernel/acpi/boot.c
- +++ b/arch/x86/kernel/acpi/boot.c
- @@ -21,6 +21,7 @@
- #include <linux/efi-bgrt.h>
- #include <linux/serial_core.h>
- #include <linux/pgtable.h>
- +#include <linux/dmi.h>
-
- #include <asm/e820/api.h>
- #include <asm/irqdomain.h>
- @@ -1155,6 +1156,17 @@ static void __init mp_config_acpi_legacy_irqs(void)
- }
- }
-
- +static const struct dmi_system_id surface_quirk[] __initconst = {
- + {
- + .ident = "Microsoft Surface Laptop 4 (AMD)",
- + .matches = {
- + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
- + DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953")
- + },
- + },
- + {}
- +};
- +
- /*
- * Parse IOAPIC related entries in MADT
- * returns 0 on success, < 0 on error
- @@ -1212,6 +1224,11 @@ static int __init acpi_parse_madt_ioapic_entries(void)
- acpi_sci_ioapic_setup(acpi_gbl_FADT.sci_interrupt, 0, 0,
- acpi_gbl_FADT.sci_interrupt);
-
- + if (dmi_check_system(surface_quirk)) {
- + pr_warn("Surface hack: Override irq 7\n");
- + mp_override_legacy_irq(7, 3, 3, 7);
- + }
- +
- /* Fill in identity legacy mappings where no override */
- mp_config_acpi_legacy_irqs();
-
- --
- 2.32.0
- From 14310a79f4542660ae09e7ee337547e1aa7a37bf Mon Sep 17 00:00:00 2001
- From: Maximilian Luz <luzmaximilian@gmail.com>
- Date: Thu, 3 Jun 2021 14:04:26 +0200
- Subject: [PATCH] ACPI: Add AMD 13" Surface Laptop 4 model to irq 7 override
- quirk
- The 13" version of the Surface Laptop 4 has the same problem as the 15"
- version, but uses a different SKU. Add that SKU to the quirk as well.
- Patchset: surface-sam
- ---
- arch/x86/kernel/acpi/boot.c | 9 ++++++++-
- 1 file changed, 8 insertions(+), 1 deletion(-)
- diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c
- index ab3ba60cb6da..fa1dcdd119e5 100644
- --- a/arch/x86/kernel/acpi/boot.c
- +++ b/arch/x86/kernel/acpi/boot.c
- @@ -1158,12 +1158,19 @@ static void __init mp_config_acpi_legacy_irqs(void)
-
- static const struct dmi_system_id surface_quirk[] __initconst = {
- {
- - .ident = "Microsoft Surface Laptop 4 (AMD)",
- + .ident = "Microsoft Surface Laptop 4 (AMD 15\")",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
- DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953")
- },
- },
- + {
- + .ident = "Microsoft Surface Laptop 4 (AMD 13\")",
- + .matches = {
- + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
- + DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1958:1959")
- + },
- + },
- {}
- };
-
- --
- 2.32.0
- From 418f3b0405c0134e8ce5fb53d857435b4865fa2e Mon Sep 17 00:00:00 2001
- From: Sachi King <nakato@nakato.io>
- Date: Sat, 29 May 2021 22:27:25 +1000
- Subject: [PATCH] platform/x86: amd-pmc: Add device HID for AMD PMC
- The Surface Laptop 4 appears to have used AMD0005 for the PMC instead of
- the AMDI0005 which would match the ACPI ID Registry.
- AMD appears to have previously used "AMD" in a number of IDs in the past,
- and AMD is not allocated to any other entity as an ID, so adding this ID
- should not cause any harm.
- Signed-off-by: Sachi King <nakato@nakato.io>
- Patchset: surface-sam
- ---
- drivers/platform/x86/amd-pmc.c | 1 +
- 1 file changed, 1 insertion(+)
- diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c
- index b9da58ee9b1e..0b5578a8a449 100644
- --- a/drivers/platform/x86/amd-pmc.c
- +++ b/drivers/platform/x86/amd-pmc.c
- @@ -275,6 +275,7 @@ static int amd_pmc_remove(struct platform_device *pdev)
- static const struct acpi_device_id amd_pmc_acpi_ids[] = {
- {"AMDI0005", 0},
- {"AMD0004", 0},
- + {"AMD0005", 0},
- { }
- };
- MODULE_DEVICE_TABLE(acpi, amd_pmc_acpi_ids);
- --
- 2.32.0
|