Académique Documents
Professionnel Documents
Culture Documents
12Replies
ThisisthefirstarticleoftheseriesonLinuxdevicedrivers,whichaimstopresenttheusuallytechnical
topicinawaythatismoreinterestingtoawidercrosssectionofreaders.
Afteraweekofhardwork,wefinallygotourdriverworking,wasthefirstlineasPugsmethisgirl
friendShweta.
Why?Whatwasyourdriverupto?Washesick?Andwhathardworkdidyoudo?,cameaseriesof
questionfromShwetawithanaughtysmile.
PugswasconfusedaswhatwasShwetatalkingabout.Whichdriverareyoutalkingabout?,he
exclaimed.
Whyareyouaskingme?Youshouldtellme,whichofyourdrivers,youaretalkingabout?,replied
Shweta.
Pugsclicked,AhCmon!Notmycardrivers.Iamtalkingaboutmydevicedriverwrittenonmy
computer.
Iknowacardriver,abusdriver,apilot,ascrewdriver.Butwhatisthisdevicedriver?,queried
Shweta.
AndthatwasallneededtotriggerPugspassiontoexplaintheconceptofdevicedriversforanewbie.
Inparticular,theLinuxdevicedrivers,whichhehadbeenworkingonsincemanyyears.
Ofdriversandbuses
Adriverisonewhodrivesmanages,controls,directs,monitorstheentityunderhiscommand.Soa
busdriverdoesthatwithabus.Similarly,adevicedriverdoesthatwithadevice.Adevicecouldbeany
peripheralconnectedtoacomputer,forexamplemouse,keyboard,screen/monitor,harddisk,
camera,clock,younameit.
Apilotcouldbeapersonorautomaticsystems,possiblymonitoredbyaperson.Similarly,devicedriver
couldbeapieceofsoftwareoranotherperipheral/device,possiblydrivenbyasoftware.However,ifit
isananotherperipheral/device,itisreferredasdevicecontrollerinthecommonparlance.Andby
driver,weonlymeanthesoftwaredriver.Adevicecontrollerisadeviceitselfandhencemanyatimesit
alsoneedsadriver,commonlyreferredasabusdriver.
Generalexamplesofdevicecontrollersincludeharddiskcontrollers,displaycontrollers,audiocontroller
forthecorrespondingdevices.Moretechnicalexampleswouldbethecontrollersforthehardware
protocols,suchasanIDEcontroller,PCIcontroller,USBcontroller,SPIcontroller,I2Ccontroller,etc.
Pictorially,thiswholeconceptcanbedepictedasinfigure1.
Figure1:Device&driverinteraction
DevicecontrollersaretypicallyconnectedtotheCPUthroughtheirrespectivelynamedbuses(collection
ofphysicallines),forexamplepcibus,idebus,etc.Intodaysembeddedworld,wemoreoftencome
acrossmicrocontrollersthanCPUs,whicharenothingbutCPU+variousdevicecontrollersbuiltontoa
singlechip.Thiseffectiveembeddingofdevicecontrollersprimarilyreducescost&space,makingit
suitableforembeddedsystems.Insuchcases,thebusesareintegratedintothechipitself.Doesthis
changeanythingonthedriversormoregenericallysoftwarefront?
Notmuchexceptthatthebusdriverscorrespondingtotheembeddeddevicecontrollers,arenow
developedunderthearchitecturespecificumbrella.
Drivershavetwoparts
Busdriversprovideshardwarespecificinterfaceforthecorrespondinghardwareprotocols,andarethe
bottommosthorizontalsoftwarelayersofanoperatingsystem(OS).Overthesesittheactualdevice
drivers.Theseoperateontheunderlyingdevicesusingthehorizontallayerinterfaces,andhenceare
devicespecific.However,thewholeideaofwritingthesedriversistoprovideanabstractiontotheuser.
Andsoontheotherend,thesedoprovideinterfacetotheuser.ThisinterfacevariesfromOStoOS.In
short,adevicedriverhastwoparts:i)Devicespecific,andii)OSspecific.Refertofigure2.
Figure2:Linuxdevicedriverpartition
Thedevicespecificportionofadevicedriverremainssameacrossalloperatingsystems,andismore
ofunderstandinganddecodingofthedevicedatasheets,thanofsoftwareprogramming.Adatasheet
foradeviceisadocumentwithtechnicaldetailsofthedevice,includingitsoperation,performance,
programming,etc.Later,Ishallshowsomeexamplesofdecodingdatasheetsaswell.However,the
OSspecificportionistheonewhichistightlycoupledwiththeOSmechanismsofuserinterfaces.This
istheonewhichdifferentiatesaLinuxdevicedriverfromaWindowsdevicedriverfromaMACdevice
driver.
Verticals
InLinux,adevicedriverprovidesasystemcallinterfacetotheuser.And,thisistheboundaryline
betweenthesocalledkernelspaceanduserspaceofLinux,asshowninfigure2.Figure3elaborates
onfurtherclassification.
BasedontheOSspecificinterfaceofadriver,inLinuxadriverisbroadlyclassifiedinto3verticals:
PacketorientedorNetworkvertical
BlockorientedorStoragevertical
ByteorientedorCharactervertical
Figure3:Linuxkerneloverview
Theothertwoverticals,looselytheCPUverticalandmemoryverticalputtogetherwiththeotherthree
verticalsgivethecompleteoverviewoftheLinuxkernel,likeanytextbookdefinitionofanOS:AnOS
does5managementsnamely:CPU/process,memory,network,storage,device/io.Thoughthese2
couldbeclassifiedasdevicedrivers,whereCPU&memoryaretherespectivedevices,thesetwoare
treateddifferentlyformanyreasons.
ThesearethecorefunctionalitiesofanyOS,beitmicroormonolithickernel.Moreoftenthannot,
addingcodeintheseareasismainlyaLinuxportingeffort,typicallyforanewCPUorarchitecture.
Moreover,thecodeinthesetwoverticalscannotbeloadedorunloadedonthefly,unliketheother
threeverticals.AndhenceforthtotalkaboutLinuxdevicedrivers,wewouldmeantotalkonlyonthe
laterthreeverticalsinfigure3.
Letsgetalittledeeperintothesethreeverticals.Networkconsistsof2parts:i)Networkprotocolstack,
andii)Networkinterfacecard(NIC)orsimplynetworkdevicedrivers,whichcouldbeforethernet,wifi,
oranyothernetworkhorizontals.Storageagainconsistsof2parts:i)Filesystemdriversfordecoding
thevariousformatsonvariouspartitions,andii)Blockdevicedriversforvariousstorage(hardware)
protocols,thatisthehorizontalslikeIDE,SCSI,MTD,etc.
Withthisyoumaywonder,isthattheonlysetofdevicesforwhichyouneeddrivers,orLinuxhas
driversfor.Justholdon.Youdefinitelyneeddriversforthewholelotofdevicesinterfacingwitha
system,andLinuxdohavedriversforthem.However,theirbyteorientedaccessibilityputsallofthem
underthecharacterverticalyesImeanititisthemajoritybucket.Infact,becauseofthisvastness,
characterdrivershavegotfurthersubclassified.So,youhavettydrivers,inputdrivers,consoledrivers,
framebufferdrivers,sounddrivers,etc.AndthetypicalhorizontalsherewouldbeRS232,PS/2,VGA,
I2C,I2S,SPI,etc.
Multipleverticalinterfacingdrivers
OnafinalnoteonthecompletepictureofplacementofallthedriversintheLinuxdriverecosystem,the
horizontalslikeUSB,PCI,etcspanbelowmultipleverticals.Why?AswehaveaUSBwifidongle,aUSB
pendrive,aswellasaUSBtoserialconverterallUSBbutthreedifferentverticals.
InLinux,busdriversorthehorizontals,areoftensplitintotwoparts,oreventwodrivers:i)Device
controllerspecific,andii)Anabstractionlayeroverthatfortheverticalstointerface,commonlycalled
cores.Aclassicalexamplewouldbetheusbcontrollerdriversohci,ehci,etcandtheUSB
abstractionusbcore.
Summingup
So,toconcludeadevicedriverisapieceofsoftwarewhichdrivesadevice,thoughtherearesomany
classifications.Andincaseitdrivesonlyanotherpieceofsoftware,wecallitjustadriver.Examplesare
filesystemdrivers,usbcore,etc.Hence,alldevicedriversaredriversbutalldriversarenotdevice
drivers.
HeyPugs!Justholdon.Wearegettinglateforourclass.Andyouknowwhatkindoftrouble,wecan
getinto.Letscontinuefromhere,tomorrow.,exclaimedShweta.
WiththatPugswrappedupsaying,Okay.Thisismajorlywhatthedevicedrivertheoryis.Ifyouare
interested,laterIshallshowyouthecode&whatallhavewebeendoingforallthevariouskindsof
drivers.Andtheyhurriedtowardstheirclassroom.
SecondArticle>>
WritingyourFirstLinuxdriverintheClassroom
3Replies
Thissecondarticle,whichispartoftheseriesonLinuxdevicedrivers,dealswiththeconceptof
dynamicallyloadingdrivers,firstwritingaLinuxdriver,beforebuildingandthenloadingit.
<<FirstArticle
AsShwetaandPugsreachedtheirclassroom,theywerealreadylate.Theirprofessorwasalreadyin
there.TheylookedateachotherandShwetasheepishlyasked,Maywecomein,sir.Cmon!!!you
guysarelateagain,calledoutprofessorGopi.Andwhatisyourexcuse,today?.Sir,wewere
discussingyourtopiconly.IwasexplainingheraboutdevicedriversinLinux,wasahurriedreplyfrom
Pugs.Goodone!!So,thenexplainmeaboutdynamicloadinginLinux.Yougetitrightandyoutwoare
excused,professoremphasized.Pugswasmorethanhappy.Andheverywellknew,howtomakehis
professorhappycriticizeWindows.So,thisiswhathesaid.
Asweknow,atypicaldriverinstallationonWindowsneedsarebootforittogetactivated.Thatisreally
notacceptable,ifweneedtodoit,sayonaserver.ThatswhereLinuxwinstherace.InLinux,wecan
load(/install)orunload(/uninstall)adriveronthefly.Anditisactiveforuseinstantlyafterload.Also,it
isdisabledwithunload,instantly.Thisisreferredasdynamicloading&unloadingofdriversinLinux.
Asexpectedheimpressedtheprofessor.Okay!takeyourseats.Butmakesureyouarenotlate
again.Withthis,theprofessorcontinuedtotheclass,So,asyounowalreadyknow,whatisdynamic
loading&unloadingofdriversinto&outof(Linux)kernel.Ishallteachyouhowtodoit.Andthen,we
wouldgetintowritingourfirstLinuxdrivertoday.
Dynamicallyloadingdrivers
Thesedynamicallyloadabledriversaremorecommonlyreferredasmodulesandbuiltintoindividual
fileswith.ko(kernelobject)extension.EveryLinuxsystemhasastandardplaceundertherootfile
system(/)foralltheprebuiltmodules.Theyareorganizedsimilartothekernelsourcetreestructure
under/lib/modules/<kernel_version>/kernel,where<kernel_version>wouldbetheoutputofthe
commandunameronthesystem.ProfessordemonstratestotheclassasshowninFigure4.
Figure4:Linuxprebuiltmodules
Now,letustakeoneoftheprebuiltmodulesandunderstandthevariousoperationswithit.
Heresalistofthevarious(shell)commandsrelevanttothedynamicoperations:
lsmodListthecurrentlyloadedmodules
insmod<module_file>Insert/Loadthemodulespecifiedby<module_file>
modprobe<module>Insert/Loadthe<module>alongwithitsdependencies
rmmod<module>Remove/Unloadthe<module>
Theseresideunderthe/sbindirectoryandaretobeexecutedwithrootprivileges.LetustaketheFAT
filesystemrelateddriversforourexperimentation.Thevariousmodulefileswouldbefat.ko,vfat.ko,
etc.underdirectoriesfat(&vfatforolderkernels)under/lib/modules/`unamer`/kernel/fs.Incase,they
areincompressed.gzformat,theyneedtobeuncompressedusinggunzip,forusing
withinsmod.vfatmoduledependsonfatmodule.So,fat.koneedstobeloadedbeforevfat.ko.Todoall
thesesteps(decompression&dependencyloading)automatically,modprobecanbeusedinstead.
Observethatthereisno.koforthemodulenametomodprobe.rmmodisusedtounloadthemodules.
Figure5demonstratesthiscompleteexperimentation.
Figure5:Linuxmoduleoperations
OurfirstLinuxdriver
Withthatunderstood,nowletswriteourfirstdriver.Yes,justbeforethat,someconceptstobesetright.
Adriverneverrunsbyitself.Itissimilartoalibrarythatgetsloadedforitsfunctionstobeinvokedbythe
runningapplications.Andhence,thoughwritteninC,itlacksthemain()function.Moreover,itwould
getloaded/linkedwiththekernel.Hence,itneedstobecompiledinsimilarwaysasthekernel.Even
theheaderfilestobeusedcanbepickedonlyfromthekernelsources,notfromthe
standard/usr/include.
OneinterestingfactaboutthekernelisthatitisanobjectorientedimplementationinC.Anditisso
profoundthatwewouldobservethesameevenwithourfirstdriver.AnyLinuxdriverconsistsofa
constructorandadestructor.Theconstructorofamodulegetscalledwheneverinsmodsucceedsin
loadingthemoduleintothekernel.Andthedestructorofthemodulegetscalled
wheneverrmmodsucceedsinunloadingthemoduleoutofthekernel.Thesetwoarelikenormal
functionsinthedriver,exceptthattheyarespecifiedastheinit&exitfunctions,respectivelybythe
macrosmodule_init()&module_exit()includedthroughthekernelheadermodule.h
/*ofd.cOurFirstDrivercode*/
#include<linux/module.h>
#include<linux/version.h>
#include<linux/kernel.h>
staticint__initofd_init(void)/*Constructor*/
{
printk(KERN_INFO"Namaskar:ofdregistered");
return0;
}
staticvoid__exitofd_exit(void)/*Destructor*/
{
printk(KERN_INFO"Alvida:ofdunregistered");
}
module_init(ofd_init);
module_exit(ofd_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AnilKumarPugalia<email@sarikapugs.com>");
MODULE_DESCRIPTION("OurFirstDriver");
Aboveisthecompletecodeforourfirstdriver,sayofd.c.Notethatthereisnostdio.h(auserspace
header),insteadananalogouskernel.h(akernelspaceheader).printk()beingtheprintf()analogous.
Additionally,version.hisincludedforversioncompatibilityofthemodulewiththekernelintowhichitis
goingtogetloaded.Also,theMODULE_*macrospopulatethemodulerelatedinformation,whichacts
likethemodulessignature.
BuildingourfirstLinuxdriver
OncewehavetheCcode,itistimetocompileitandcreatethemodulefileofd.ko.Andforthatweneed
tobuilditinthesimilarway,asthekernel.So,weshallusethekernelbuildsystemtodothesame.
HerefollowsourfirstdriversMakefile,whichwouldinvokethekernelsbuildsystemfromthekernel
source.ThekernelsMakefilewouldinturninvokeourfirstdriversMakefiletobuildourfirstdriver.The
kernelsourceisassumedtobeinstalledat/usr/src/linux.Incaseofittobeatanyotherlocation,the
KERNEL_SOURCEvariablehastobeappropriatelyupdated.
#Makefilemakefileofourfirstdriver
#ifKERNELRELEASEisnotdefined,we'vebeencalleddirectlyfromthecommandline.
#Invokethekernelbuildsystem.
ifeq(${KERNELRELEASE},)
KERNEL_SOURCE:=/usr/src/linux
PWD:=$(shellpwd)
default:
${MAKE}C${KERNEL_SOURCE}SUBDIRS=${PWD}modules
clean:
${MAKE}C${KERNEL_SOURCE}SUBDIRS=${PWD}clean
#OtherwiseKERNELRELEASEisdefined;we'vebeeninvokedfromthe
#kernelbuildsystemandcanuseitslanguage.
else
objm:=ofd.o
endif
Note1:Makefilesareveryspacesensitive.Thelinesnotstartingatthefirstcolumnhaveatabandnot
spaces.
Note2:ForbuildingaLinuxdriver,youneedtohavethekernelsource(orattheleastthekernel
headers)installedonyoursystem.
WiththeCcode(ofd.c)andMakefileready,allweneedtodoisputthemina(new)directoryofitsown,
andtheninvokemakeinthatdirectorytobuildourfirstdriver(ofd.ko).
$make
makeC/usr/src/linuxSUBDIRS=...modules
make[1]:Enteringdirectory`/usr/src/linux'
CC[M].../ofd.o
Buildingmodules,stage2.
MODPOST1modules
CC.../ofd.mod.o
LD[M].../ofd.ko
make[1]:Leavingdirectory`/usr/src/linux'
Summingup
Oncewehavetheofd.kofile,dotheusualstepsasroot,orwithsudo.
#su
#insmodofd.ko
#lsmod|head10
lsmodshouldshowyoutheofddriverloaded.
Whilethestudentsweretryingtheirfirstmodule,thebellrang,markingtheendforthissessionofthe
class.AndprofessorGopiconcluded,sayingCurrently,youmaynotbeabletoobserveanything,other
thanlsmodlistingshowingourfirstdriverloaded.Wherestheprintkoutputgone?Findthatoutfor
yourselfinthelabsessionandupdatemewithyourfindings.Moreover,todaysfirstdriverwouldbethe
templatetoanydriveryouwriteinLinux.Writinganyspecializedadvanceddriverisjustamatterof
whatgetsfilledintoitsconstructor&destructor.So,hereonwards,ourlearningsshallbeinenhancing
thisdrivertoachieveourspecificdriverfunctionalities.
Notes
1. Inmostoftodaysdistros,onemaysafelyhaveKERNEL_SOURCEsetto/lib/modules/$(shell
unamer)/build,insteadof/usr/src/linuxi.e.KERNEL_SOURCE:=/lib/modules/$(shelluname
r)/buildintheMakefile.
ThirdArticle>>
KernelCExtrasinaLinuxDriver
2Replies
Thisthirdarticle,intheseriesonLinuxdevicedriversdealswiththekernelsmessagelogging,
andkernelspecificGCCextensions.
<<SecondArticle
EnthusedbyhowPugsimpressedprofessorGopi,inthelastclass,Shwetadecidedtodosomething
similar.Andtherewasalreadyanopportunityfindingoutwherehastheoutputofprintkgone.So,as
soonassheenteredthelab,shegotholdofthebestlocatedsystem,loggedintoit,andtookcharge.
Knowingherprofessorprettywell,sheknewthattherewouldbeahintforthefinding,fromtheclass
itself.So,sheflashedbackwhatalltheprofessortaught,andsuddenlyrememberedtheerroroutput
demonstrationfrominsmodvfat.kodmesg|tail.Sheimmediatelytriedthatandforsurefoundout
theprintkoutput,there.Buthowdiditcomehere?Ataponhershoulderbroughtheroutofthethought.
Shallwegoforacoffee?,proposedPugs.ButIneedto.Iknowwhatyouarethinkingabout.,
interruptedPugs.Letsgo,yaar.Illexplainyouallaboutdmesg.
Kernelsmessagelogging
Onthecoffeetable,Pugsbegan:
Asfarasparametersareconcerned,printf&printkaresame,exceptthatwhenprogrammingforthe
kernelwedontbotheraboutthefloatformatsof%f,%lf&theirlikes.Howeverunlikeprintf,printkisnot
destinedtodumpitsoutputonsomeconsole.Infact,itcannotdoso,asitissomethingwhichisinthe
background,andexecuteslikealibrary,onlywhentriggeredeitherfromthehardwarespaceortheuser
space.So,thenwheredoesprintkprint?Alltheprintkcalls,justputtheircontentsintothe(log)ring
bufferofthekernel.Then,thesyslogdaemonrunningintheuserspacepicksthemforfinalprocessing
&redirectiontovariousdevices,asconfiguredinitsconfigurationfile/etc/syslog.conf.
YoumusthaveobservedtheoutofplacemacroKERN_INFO,intheprintkcalls,inthepreviousarticle.
Thatactuallyisaconstantstring,whichgetsconcatenatedwiththeformatstringafterit,makingita
singlestring.Notethatthereisnocomma(,)betweenthemtheyarenotwoseparatearguments.
Thereareeightsuchmacrosdefinedin<linux/kernel.h>underthekernelsource,namely:
#defineKERN_EMERG
"<0>"/*systemisunusable
*/
#defineKERN_ALERT
"<1>"/*actionmustbetakenimmediately
*/
#defineKERN_CRIT
"<2>"/*criticalconditions
*/
#defineKERN_ERR
"<3>"/*errorconditions
*/
#defineKERN_WARNING
"<4>"/*warningconditions
*/
#defineKERN_NOTICE
"<5>"/*normalbutsignificantcondition
*/
#defineKERN_INFO
"<6>"/*informational
*/
#defineKERN_DEBUG
"<7>"/*debuglevelmessages
*/
Dependingontheseloglevels(i.e.thefirst3charactersintheformatstring),thesyslogdaemoninthe
userspaceredirectsthecorrespondingmessagestotheirconfiguredlocationsatypicalonebeingthe
logfile/var/log/messagesforalltheloglevels.Hence,alltheprintkoutputsarebydefaultinthatfile.
Though,theycanbeconfigureddifferentlytosayserialport(/dev/ttyS0)orsayallconsoles,likewhat
happenstypicallyforKERN_EMERG.Now,/var/log/messagesisbuffered&containmessagesnotonly
fromthekernelbutalsofromvariousdaemonsrunningintheuserspace.Moreover,
the/var/log/messagesmostoftenisnotreadablebyanormaluser,andhenceauserspaceutility
dmesgisprovidedtodirectlyparsethekernelringbufferanddumpitonthestandardoutput.Figure6
showsthesnippetsfromthetwo.
Figure6:Kernelsmessagelogging
KernelspecificGCCextensions
WithalltheseShwetagotfrustrated,asshewantedtofindallthesebyherown,andthendoa
impressioninthenextclassbutallflop.Pissedoff,shesaid,Soasyouhaveexplainedallabout
printinginkernel,whydontyoutellabouttheweirdCinthedriveraswellthespecial
keywords__init,__exit,etc.
Thesearenotanyspecialkeywords.KernelCisnotanyweirdCbutjustthestandardCwithsome
additionalextensionsfromtheCcompilergcc.Macros__initand__exitarejusttwoofthese
extensions.However,thesedonothaveanyrelevanceincaseweareusingthemfordynamically
loadabledriver,butonlywhenthesamecodegetsbuiltintothekernel.Allthefunctionsmarked
with__initgetplacedinsidetheinitsectionofthekernelimageandallfunctionsmarkedwith__exitare
placedinsidetheexitsectionofthekernelimage,automaticallybygcc,duringkernelcompilation.What
isthebenefit?Allfunctionswith__initaresupposedtobeexecutedonlyonceduringbootup,tillthe
nextbootup.So,oncetheyareexecutedduringbootup,kernelfreesupRAMbyremovingthemby
freeinguptheinitsection.Similarly,allfunctionsinexitsectionaresupposedtobecalledduringsystem
shutdown.Now,ifsystemisshuttingdownanyway,whydoyouneedtodoanycleanups.Hence,
theexitsectionisnotevenbuiltintothekernelanothercooloptimization.
Thisisabeautifulexampleofhowkernel&gccgoeshandinhandtoachievelotofoptimizationsand
manyothertrickswecouldseeothers,aswegoalong.AndthatiswhyLinuxkernelcanbecompiled
onlyusinggccbasedcompilersacloseknitbond.
Kernelfunctionsreturnguidelines
Whilereturningfromcoffee,PugsstartedallpraisesfortheOSS&itscommunity.Doyouknowwhy
differentindividualsareabletocometogetherandcontributeexcellentlywithoutanyconflicts
moreoverinaprojectashugeasLinux?Therearemanyreasons.Butdefinitely,oneofthestrong
reasonsis,following&abidingbytheinherentcodingguidelines.Takeforexampletheguidelinefor
returningvaluesfromafunctioninkernelprogramming.
Anykernelfunctionneedingerrorhandling,typicallyreturnsanintegerliketypeandthereturnvalue
againfollowsaguideline.Foranerror,wereturnanegativenumberaminussignappendedwitha
macroincludedthroughthekernelheader<linux/errno.h>,thatincludesthevariouserrornumber
headersunderthekernelsources,namely<asm/errno.h>,<asmgeneric/errno.h>,<asmgeneric/errno
base.h>.Forsuccess,zeroisthemostcommonreturnvalue,unlessthereissomeadditional
informationtobeprovided.Inthatcase,apositivevalueisreturned,thevalueindicatingtheinformation
likenumberofbytestransferred.
KernelC=PureC
Oncebackintothelab,Shwetarememberedtheirprofessormentioningthatno/usr/includeheaders
canbeusedforkernelprogramming.ButPugssaidthatkernelCisjuststandardCwith
somegccextensions.Whythisconflict?Actuallythisisnotaconflict.StandardCisjustpureCjustthe
language.Theheadersarenotpartofit.ThosearepartofthestandardlibrariesbuiltinCforC
programmers,basedontheconceptofreusingcode.Doesthatmean,allstandardlibrariesandhence
allANSIstandardfunctionsarenotpartofpureC?Yes.Then,hadntitbeenreallytoughcodingthe
kernel.Notforthisreason.Inreality,kerneldevelopershavedevelopedtheirownneededsetof
functions,andtheyareallpartofthekernelcode.printkisjustoneofthem.Similarly,manystring
functions,memoryfunctions,areallpartofthekernelsourceundervariousdirectories
likekernel,ipc,lib,andthecorrespondingheadersunderinclude/linuxdirectory.
Oya!Thatiswhyweneedtohavekernelsourceforbuildingadriver,affirmedShweta.Ifnotthe
completesource,atleasttheheadersareamust.Andthatiswhywehaveseparatepackagestoinstall
completekernelsourceorjustthekernelheaders,addedPugs.Inthelab,allthesourcesaresetup.
ButifIwanttotryoutdriversonmyLinuxsystematmyhostelroom,howdoIgoaboutit?asked
Shweta.OurlabhaveFedora,wherethekernelsourcestypicallygetinstalled
under/usr/src/kernels/<kernel_version>unlikethestandardplace/usr/src/linux.Labadministratorsmust
haveinstalleditusingcommandlineyuminstallkerneldevel.IuseMandrivaandinstalledthekernel
sourcesusingurpmikernelsource,repliedPugs.But,IhaveUbuntu.Okay!!Forthatjustuseapt
getinstallpossibly,aptgetinstalllinuxsource.
Summingup
Labtimingswerejustgettingover.Suddenly,ShwetaputouthercuriosityHeyPugs!Whatisthenext
topicwearegoingtolearninourLinuxdevicedriversclass?.Hmmm!!Mostprobablycharacter
drivers.Withthisinformation,Shwetahurriedlypackedupherbag&headedtowardsherroomto
setupthekernelsourcesandtryoutthenextdriveronherown.Incaseyouarestuckup,justgiveme
acall.Illbethere,calledupPugsfromthebehindwithasmile.
FourthArticle>>
Notes:
1. Thedefaultsyslogfile/var/log/messagesmayvaryfromdistrotodistro.Forexample,inthe
latestUbuntudistros,itis/var/log/syslog.
OtherReferences:
1. Anotherpossiblepointertothemissing/var/log/messagesinUbuntu
LinuxCharacterDrivers
7Replies
Thisfourtharticle,whichispartoftheseriesonLinuxdevicedrivers,dealswiththevariousconceptsof
characterdriversandtheirimplementation.
<<ThirdArticle
ShwetaatherhostelroominfrontofherPC,allsettoexplorethecharactersofLinuxcharacterdrivers,
beforeitisbeingtaughtintheclass.SherecalledthefollowinglinesfromprofessorGopisclass:
todaysfirstdriverwouldbethetemplatetoanydriveryouwriteinLinux.Writinganyspecialized
advanceddriverisjustamatterofwhatgetsfilledintoitsconstructor&destructor..Withthat,she
tookoutthefirstdrivercode,andpoppedoutvariousreferencebookstostartwritingacharacterdriver
onherown.ShealsodownloadedtheonlineLinuxDeviceDriversbookbyJonathanCorbet,
AlessandroRubini,GregKroahHartmanfromhttp://lwn.net/Kernel/LDD3/.Herefollowsthesummary
fromhervariouscollations.
Wsofcharacterdrivers
Wealreadyknowwhataredriversandwhyweneedthem.Then,whatissospecialaboutcharacter
drivers?IfwewritedriversforbyteorientedoperationsorintheClingothecharacteroriented
operations,werefertothemascharacterdrivers.Andasthemajorityofdevicesarebyteoriented,the
majorityofdevicedriversarecharacterdevicedrivers.Takeforexample,serialdrivers,audiodrivers,
videodrivers,cameradrivers,basicI/Odrivers,.Infact,alldevicedriverswhichareneitherstorage
nornetworkdevicedriversareoneformortheotherformofcharacterdrivers.Letslookintothe
commonalitiesofthesecharacterdriversandhowShwetawroteoneofthem.
Figure7:Characterdriveroverview
Thecompleteconnection
AsshowninFigure7,foranyapplication(userspace)tooperateonabyteorienteddevice(hardware
space),itshouldusethecorrespondingcharacterdevicedriver(kernelspace).Andthecharacterdriver
usageisdonethroughthecorrespondingcharacterdevicefile(s),linkedtoitthroughthevirtualfile
system(VFS).Whatitmeansisthatanapplicationdoestheusualfileoperationsonthecharacter
devicefilethoseoperationsaretranslatedtothecorrespondingfunctionsintothelinkedcharacter
devicedriverbytheVFSthosefunctionsthendoesthefinallowlevelaccesstotheactualdevicesto
achievethedesiredresults.Notethatthoughtheapplicationdoestheusualfileoperations,their
outcomemaynotbetheusualones.Rather,theywouldbeasdrivenbythecorrespondingfunctionsin
thedevicedriver.Forexample,areadfollowedbyawritemaynotfetchwhathasbeenwritteninto,
unlikeinthecaseofregularfiles.Notethatthisistheusualexpectedbehaviourfordevicefiles.Lets
takeanaudiodevicefileasanexample.Whatwewriteintoitistheaudiodatawewanttoplayback,say
throughaspeaker.However,thereadwouldgetustheaudiodatawearerecording,saythrougha
microphone.Andtherecordeddataneednotbetheplayedbackdata.
Inthiscompleteconnectionfromapplicationtothedevice,therearefourmajorentitiesinvolved:
1. Application
2. Characterdevicefile
3. Characterdevicedriver
4. Characterdevice
Andtheinterestingthingisthat,allofthesecanexistindependentlyonasystem,withouttheother
beingthere.So,mereexistenceoftheseonasystemdoesntmeantheyarelinkedtoformthe
completeconnection.Rather,theyneedtobeexplicitlyconnected.Applicationgetsconnectedtoa
devicefilebyinvokingopensystemcallonthedevicefile.Devicefile(s)arelinkedtothedevicedriver
byspecificregistrationsbythedriver.Andthedevicedriverislinkedtoadevicebyitsdevicespecific
lowleveloperations.Thus,formingthecompleteconnection.Withthis,notethatthecharacterdevice
fileisnottheactualdevicebutjustaplaceholderfortheactualdevice.
Major&minornumber
Connectionbetweentheapplicationandthedevicefileisbasedonthenameofthedevicefile.
However,theconnectionbetweenthedevicefileandthedevicedriverisbasedonthenumberofthe
devicefile,notthename.Thisallowsauserspaceapplicationtohaveanynameforthedevicefile,and
enablesthekernelspacetohavetrivialindexbasedlinkagebetweenthedevicefile&thedevicedriver.
Thisdevicefilenumberismorecommonlyreferredasthe<major,minor>pair,orthemajor&minor
numbersofthedevicefile.Earlier(tillkernel2.4),onemajornumberwasforonedriver,andtheminor
numberusedtorepresentthesubfunctionalitiesofthedriver.Withkernel2.6,thisdistinctionisno
longermandatorytherecouldbemultipledriversundersamemajornumberbutobviouslywith
differentminornumberranges.However,thisismorecommonwiththenonreservedmajornumbers
andstandardmajornumbersaretypicallypreservedforsingledrivers.Forexample,4forserial
interfaces,13formice,14foraudiodevices,.Thefollowingcommandwouldlistthevarious
characterdevicefilesonyoursystem:
$lsl/dev/|grep^c
<major,minor>relatedsupportinkernel2.6
Type:(definedinkernelheader<linux/types.h>)
dev_t//containsbothmajor&minornumbers
Macros:(definedinkernelheader<linux/kdev_t.h>)
MAJOR(dev_tdev)//extractsthemajornumberfromdev
MINOR(dev_tdev)//extractstheminornumberfromdev
MKDEV(intmajor,intminor)//createsthedevfrommajor&minor
Connectingthedevicefilewiththedevicedriverinvolvestwosteps:
1. Registeringforthe<major,minor>rangeofdevicefiles
2. Linkingthedevicefileoperationstothedevicedriverfunctions
FirststepisachievedusingeitherofthefollowingtwoAPIs:(definedinkernelheader<linux/fs.h>)
intregister_chrdev_region(dev_tfirst,unsignedintcnt,char*name);
intalloc_chrdev_region(
dev_t*first,unsignedintfirstminor,unsignedintcnt,char*name);
FirstAPIregistersthecntnumberofdevicefilenumbersstartingfromfirst,withthename.SecondAPI
dynamicallyfiguresoutafreemajornumberandregistersthecntnumberofdevicefilenumbers
startingfrom<thefreemajor,firstminor>,withthename.Ineithercase,the/proc/deviceskernelwindow
liststhenamewiththeregisteredmajornumber.Withthisinformation,Shwetaaddedthefollowinginto
thefirstdrivercode.
#include<linux/types.h>
#include<linux/kdev_t.h>
#include<linux/fs.h>
staticdev_tfirst;//Globalvariableforthefirstdevicenumber
Intheconstructor,sheadded:
intret;
if((ret=alloc_chrdev_region(&first,0,3,"Shweta"))<0)
{
returnret;
}
printk(KERN_INFO"<Major,Minor>:<%d,%d>\n",MAJOR(first),MINOR(first));
Inthedestructor,sheadded:
unregister_chrdev_region(first,3);
Puttingitalltogether,itbecomes:
#include<linux/module.h>
#include<linux/version.h>
#include<linux/kernel.h>
#include<linux/types.h>
#include<linux/kdev_t.h>
#include<linux/fs.h>
staticdev_tfirst;//Globalvariableforthefirstdevicenumber
staticint__initofcd_init(void)/*Constructor*/
{
intret;
printk(KERN_INFO"Namaskar:ofcdregistered");
if((ret=alloc_chrdev_region(&first,0,3,"Shweta"))<0)
printk(KERN_INFO"<Major,Minor>:<%d,%d>\n",MAJOR(first),MINOR(first));
return0;
returnret;
}
staticvoid__exitofcd_exit(void)/*Destructor*/
{
unregister_chrdev_region(first,3);
printk(KERN_INFO"Alvida:ofcdunregistered");
}
module_init(ofcd_init);
module_exit(ofcd_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AnilKumarPugalia<email@sarikapugs.com>");
MODULE_DESCRIPTION("OurFirstCharacterDriver");
Then,Shwetarepeatedtheusualsteps,shelearntforthefirstdriver
Buildthedriver(.kofile)bytypingmake
Loadthedriverusinginsmod
Listtheloadedmodulesusinglsmod
Unloadthedriverusingrmmod
Summingup
Additionally,beforeunloadingthedriver,shepeepedintothekernelwindow/proc/devicestolookfor
theregisteredmajornumberwiththenameShwetausingcat/proc/devices.Itwasrightthere.Butshe
couldntfindanydevicefilecreatedunder/devwiththesamemajornumber.So,shecreatedthemby
handusingmknod,andthentriedreading&writingthose.Figure8showsallthese.Pleasenotethat
themajornumber250mayvaryfromsystemtosystembasedontheavailability.Figure8alsoshows
theresults,Shwetagotfromreading&writingoneofthedevicefiles.Thatremindedherthatthe
secondstepforconnectingthedevicefilewiththedevicedriverLinkingthedevicefileoperationsto
thedevicedriverfunctionsisnotyetdone.Sherealizedthatsheneedstodigfurtherinformationto
completethisstepandalsotofigureoutthereasonforthemissingdevicefilesunder/dev.Weshall
continuefurtherinournextarticle,tofigureoutwhatmoreisShwetalearningandhowisshegoing
aheadwithherfirstcharacterdriver.
Figure8:Characterdevicefileexperiments
FifthArticle>>
Characterdevicefiles:Creation&Operations
7Replies
Thisfiftharticle,whichispartoftheseriesonLinuxdevicedrivers,iscontinuationofthevarious
conceptsofcharacterdriversandtheirimplementation,dealtwithinthepreviousarticle.
<<FourthArticle
Inourpreviousarticle,wenotedthatevenwiththeregistrationfor<major,minor>devicerange,the
devicefileswerenotcreatedunderthe/dev,ratherShwetahadtocreatethembyhandusingmknod.
However,onfurtherstudy,Shwetafiguredoutawayfortheautomaticcreationofthedevicefilesusing
theudevdaemon.Shealsolearntthesecondstepforconnectingthedevicefilewiththedevicedriver
Linkingthedevicefileoperationstothedevicedriverfunctions.Hereareherlearnings.
Automaticcreationofdevicefiles
Earlierinkernel2.4,automaticcreationofdevicefileswasdonebythekernelitself,bycallingthe
appropriateAPIsofdevfs.However,askernelevolved,kerneldevelopersrealizedthatdevicefilesare
moreofauserspacethingandhenceasapolicyonlytheusersshoulddealwithit,notthekernel.With
thisidea,nowkernelonlypopulatestheappropriatedeviceclass&deviceinfointothe/syswindowfor
thedeviceunderconsideration.Andthen,theuserspaceneedtointerpretitandtakeanappropriate
action.InmostLinuxdesktopsystems,theudevdaemonpicksupthatinformationandaccordingly
createsthedevicefiles.
udevcanbefurtherconfiguredusingitsconfigurationfilestotunethedevicefilenames,their
permissions,theirtypes,etc.So,asfarasdriverisconcerned,theappropriate/sysentriesneedtobe
populatedusingtheLinuxdevicemodelAPIsdeclaredin<linux/device.h>andtherestwouldbe
handledbyudev.Deviceclassiscreatedasfollows:
structclass*cl=class_create(THIS_MODULE,"<deviceclassname>");
andthenthedeviceinfo(<major,minor>)underthisclassispopulatedby:
device_create(cl,NULL,first,NULL,"<devicenameformat>",);
wherefirstisthedev_twiththecorresponding<major,minor>.
Thecorrespondingcomplementaryortheinversecalls,whichshouldbecalledinchronologically
reverseorder,areasfollows:
device_destroy(cl,first);
class_destroy(cl);
RefertoFigure9,forthe/sysentriescreatedusingchardrvasthe<deviceclassname>andmynull
asthe<devicenameformat>.Thatalsoshowsthedevicefile,createdbyudev,basedonthe<major>:
<minor>entryinthedevfile.
Figure9:Automaticdevicefilecreation
Incaseofmultipleminors,device_create()anddevice_destroy()APIsmaybeputinforloop,and
the<devicenameformat>stringcouldbeuseful.Forexample,thedevice_create()callinaforloop
indexedbyicouldbeasfollows:
device_create(cl,NULL,MKDEV(MAJOR(first),MINOR(first)+i),NULL,"mynull%d",i);
Fileoperations
Whateversystemcallsormorecommonlyfileoperationswetalkofoveraregularfile,areapplicableto
thedevicefilesaswell.Thatswhatwesayafileisafile,andinLinuxalmosteverythingisafilefrom
userspaceperspective.Thedifferenceliesinthekernelspace,wherevirtualfilesystem(VFS)decodes
thefiletypeandtransfersthefileoperationstotheappropriatechannel,likefilesystemmoduleincase
ofaregularfileordirectory,correspondingdevicedriverincaseofadevicefile.Ourdiscussionof
interestisthesecondcase.
Now,forVFStopassthedevicefileoperationsontothedriver,itshouldhavebeentoldaboutthat.And
yes,thatiswhatiscalledregisteringthefileoperationsbythedriverwiththeVFS.Thisinvolvestwo
steps.(Theparenthesisedtextbelowreferstothenulldrivercodefollowingit.)First,istofillinafile
operationsstructure(structfile_operationspugs_fops)withthedesiredfileoperations
(my_open,my_close,my_read,my_write,)andtoinitializethecharacterdevicestructure(structcdev
c_dev)withthat,usingcdev_init().ThesecondstepistohandthisstructuretotheVFSusingthe
callcdev_add().Bothcdev_init()andcdev_add()aredeclaredin<linux/cdev.h>.Obviously,theactual
fileoperations(my_open,my_close,my_read,my_write)alsohadtobecodedbyShweta.So,tostart
with,Shwetakeptthemassimpleaspossible,soastosay,aseasyasthenulldriver.
Thenulldriver
Followingthesesteps,Shwetaputallthepiecestogethertoattemptherfirstcharacterdevicedriver.
Letsseewhatwastheoutcome.Heresthecompletecode:
#include<linux/module.h>
#include<linux/version.h>
#include<linux/kernel.h>
#include<linux/types.h>
#include<linux/kdev_t.h>
#include<linux/fs.h>
#include<linux/device.h>
#include<linux/cdev.h>
staticdev_tfirst;//Globalvariableforthefirstdevicenumber
staticstructcdevc_dev;//Globalvariableforthecharacterdevicestructure
staticstructclass*cl;//Globalvariableforthedeviceclass
staticintmy_open(structinode*i,structfile*f)
{
printk(KERN_INFO"Driver:open()\n");
return0;
}
staticintmy_close(structinode*i,structfile*f)
{
printk(KERN_INFO"Driver:close()\n");
return0;
}
staticssize_tmy_read(structfile*f,char__user*buf,size_tlen,loff_t*off)
{
printk(KERN_INFO"Driver:read()\n");
return0;
}
staticssize_tmy_write(structfile*f,constchar__user*buf,size_tlen,
loff_t*off)
printk(KERN_INFO"Driver:write()\n");
returnlen;
}
staticstructfile_operationspugs_fops=
{
.owner=THIS_MODULE,
.open=my_open,
.release=my_close,
.read=my_read,
.write=my_write
};
staticint__initofcd_init(void)/*Constructor*/
{
intret;
structdevice*dev_ret;
printk(KERN_INFO"Namaskar:ofcdregistered");
if((ret=alloc_chrdev_region(&first,0,1,"Shweta"))<0)
if(IS_ERR(cl=class_create(THIS_MODULE,"chardrv")))
returnret;
unregister_chrdev_region(first,1);
returnPTR_ERR(cl);
if(IS_ERR(dev_ret=device_create(cl,NULL,first,NULL,"mynull")))
class_destroy(cl);
unregister_chrdev_region(first,1);
returnPTR_ERR(dev_ret);
cdev_init(&c_dev,&pugs_fops);
if((ret=cdev_add(&c_dev,first,1))<0)
device_destroy(cl,first);
class_destroy(cl);
unregister_chrdev_region(first,1);
returnret;
return0;
}
staticvoid__exitofcd_exit(void)/*Destructor*/
{
cdev_del(&c_dev);
device_destroy(cl,first);
class_destroy(cl);
unregister_chrdev_region(first,1);
printk(KERN_INFO"Alvida:ofcdunregistered");
}
module_init(ofcd_init);
module_exit(ofcd_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AnilKumarPugalia<email@sarikapugs.com>");
MODULE_DESCRIPTION("OurFirstCharacterDriver");
Then,Shwetarepeatedtheusualbuildwithnewteststepsasfollows:
Buildthedriver(.kofile)byrunningmake.
Loadthedriverusinginsmod.
Listtheloadedmodulesusinglsmod.
Listthemajornumberallocatedusingcat/proc/devices.
nulldriverspecificexperiments(RefertoFigure10fordetails).
Unloadthedriverusingrmmod.
Figure10:nulldriverexperiments
Summingup
Shwetawassurelyhappyasallonherownshegotacharacterdriverwritten,whichworkssameasthe
driverforthestandarddevicefile/dev/null.Tounderstandwhatitmeans,checkforyourselfthe<major,
minor>tuplefor/dev/null,andsimilarlyalsotryouttheechoandcatcommandswithit.
ButonethingstartedbotheringShweta.Shehadgotherowncalls
(my_open,my_close,my_read,my_write)inherdriver,buthowaretheyworkingsounusuallyunlike
anyregularfilesystemcalls.Whatssounusual?WhateverIwrite,Igetnothingwhenreadisntthat
unusual,atleastfromregularfileoperationsperspective.Anyguessesonhowwouldshecrackthis
nut?Watchoutforthenextarticle.
SixthArticle>>
Notes:
1. Forusingafixedmajornumber,youmayuseregister_chrdev_region()instead
ofalloc_chrdev_region().
2. Usekernelversion>=2.6.3xfortheclass_create()andthedevice_create()APIstocompile
properlyworkasexplained.As,beforethatversiontheyhavebeenrapidlyevolvingand
changing.
3. KernelAPIs(likeclass_create(),device_create())whichreturnspointers,shouldbechecked
usingIS_ERRmacroinsteadofcomparingwithNULL,asNULLiszero(i.e.successandnotan
error).TheseAPIsreturnnegativepointersonerrorerrorcodefromwhichcouldbeextracted
usingPTR_ERR.Seetheusageintheaboveexample.
OtherReferences:
1. Workingofudevdaemon
Decodingthecharacterdevicefileoperations
2Replies
Thissixtharticle,whichispartoftheseriesonLinuxdevicedrivers,iscontinuationofthevarious
conceptsofcharacterdriversandtheirimplementation,dealtwithintheprevioustwoarticles.
<<FifthArticle
So,whatwasyourguessonhowwouldShwetacrackthenut?Obviously,usingthenutcrackernamed
Pugs.Wasntitobvious?<Smile>Inourpreviousarticle,wesawhowShwetawaspuzzledwithreading
nodata,evenafterwritingintothe/dev/mynullcharacterdevicefile.Suddenly,abellrangnotinside
herhead,arealoneatthedoor.Andforsure,therewastheavatarofPugs.
Howcomeyourehere?,exclaimedShweta.Afterreadingyourtweet,whatelse?Coolthatyou
crackedyourfirstcharacterdriverallonyourown.Thatsamazing.So,whatareyouuptonow?,said
Pugs.Illtellyouontheconditionthatyoudonotbecomeaspoilsport,repliedShweta.Okayyaar,Ill
onlygiveyoupointers.Andthatalso,onlyifIaskfor.Okie.Iamtryingtodecodetheworkingof
characterdevicefileoperations.Ihaveanidea.Whydontyoudecodeandexplainmeyour
understanding?.Notabadidea.Withthat,Shwetatailedthedmesglogtoobservetheprintksoutput
fromherdriver.Alongside,sheopenedhernulldrivercodeonherconsole,specificallyobservingthe
devicefileoperationsmy_open,my_close,my_read,andmy_write.
staticintmy_open(structinode*i,structfile*f)
{
printk(KERN_INFO"Driver:open()\n");
return0;
}
staticintmy_close(structinode*i,structfile*f)
{
printk(KERN_INFO"Driver:close()\n");
return0;
}
staticssize_tmy_read(structfile*f,char__user*buf,size_tlen,loff_t*off)
{
printk(KERN_INFO"Driver:read()\n");
return0;
}
staticssize_tmy_write(
structfile*f,constchar__user*buf,size_tlen,loff_t*off)
printk(KERN_INFO"Driver:write()\n");
returnlen;
Basedontheearlierunderstandingofreturnvalueofthefunctionsin
kernel,my_open()andmy_close()aretrivial.Theirreturntypesbeingintandbothofthemreturning
zero,meaningsuccess.However,thereturntypesofbothmy_read()andmy_write()arenotint,
butssize_t.Onfurtherdiggingthroughkernelheaders,thatturnsouttobesignedword.So,returninga
negativenumberwouldbeausualerror.Butanonnegativereturnvaluewouldhaveanadditional
meaning.Forreaditwouldbenumberofbytesread,andforwriteitwouldbenumberofbyteswritten.
Readingthedevicefile
Forunderstandingthisindetail,thecompleteflowhastoberelookedat.Letstakereadfirst.So,when
theuserdoesareadontothedevicefile/dev/mynull,thatsystemcallcomestothevirtualfilesystem
(VFS)layerinthekernel.VFSdecodesthe<major,minor>tuple&figuresoutthatitneedtoredirectit
tothedriversfunctionmy_read(),registeredwithit.Sofromthatangle,my_read()isinvokedasa
requesttoread,fromusthedevicedriverwriters.Andhence,itsreturnvaluewouldindicatetothe
requestertheuser,astohowmanybytesishegettingfromthereadrequest.Inournulldriver
example,wereturnedzeromeaningnobytesavailableorinotherwordsendoffile.Andhence,when
thedevicefileisbeingread,theresultisalwaysnothing,independentofwhatiswrittenintoit.
Hmmm!!!So,ifIchangeitto1,woulditstartgivingmesomedata?,Pugsaskedinhisverifyingstyle.
Shwetapausedforawhilelookedattheparametersofthefunctionmy_read()andconfirmedwitha
butdatawouldbesentbutitwouldbesomejunkdata,asthemy_read()functionisnotreally
populatingthedataintothebuf(secondparameterofmy_read()),providedbytheuser.In
fact,my_read()shouldwritedataintobuf,accordingtolen(thirdparameterofmy_read()),thecountin
bytesrequestedbytheuser.
Tobemorespecific,writelessthanorequaltolenbytesofdataintobuf,andthesamenumberbe
usedasthereturnvalue.Itisnotatypoinread,wewriteintobufthatscorrect.Wereadthedata
from(possibly)anunderlyingdeviceandthenwritethatdataintotheuserbuffer,sothattheusergets
it,i.e.readsit.Thatsreallysmartofyou,expressedPugswithsarcasm.
Writingintothedevicefile
Similarly,thewriteisjustthereverseprocedure.Userprovideslen(thirdparameterofmy_write())bytes
ofdatatobewritten,intobuf(secondparameterofmy_write()).my_write()wouldreadthatdataand
possiblywriteintoanunderlyingdevice,andaccordinglyreturnthenumberofbytes,ithasbeenableto
writesuccessfully.Aha!!Thatswhyallmywritesinto/dev/mynullhavebeensuccessful,withoutbeing
actuallydoinganyreadorwrite,exclaimedShwetafilledwithhappinessofunderstandingthecomplete
flowofdevicefileoperations.
Preservingthelastcharacter
ThatwasenoughShwetanotgivinganychancetoPugstoadd,correctorevenspeak.So,Pugs
cameupwithachallenge.Okay.Seemslikeyouarethoroughlyclearwiththeread/writefunda.Then,
heresaquestionforyou.Canyoumodifythesemy_read()andmy_write()functionssuchthat
wheneverIread/dev/mynull,Igetthelastcharacterwritteninto/dev/mynull?
Confidentenough,Shwetatookthechallengeandmodifiedthemy_read()andmy_write()functionsas
follows,alongwithanadditionofastaticglobalcharacter:
staticcharc;
staticssize_tmy_read(structfile*f,char__user*buf,size_tlen,loff_t*off)
{
printk(KERN_INFO"Driver:read()\n");
buf[0]=c;
return1;
}
staticssize_tmy_write(
structfile*f,constchar__user*buf,size_tlen,loff_t*off)
printk(KERN_INFO"Driver:write()\n");
c=buf[len1];
returnlen;
Almostthere,butwhatiftheuserhasprovidedaninvalidbuffer,orwhatiftheuserbufferisswapped
out.Wouldntthisdirectaccessofuserspacebufjustcrashandoopsthekernel,pouncedPugs.
Shwetanotgivingupthechallenge,divesintohercollatedmaterialandfiguresoutthattherearetwo
APIsjusttoensurethattheuserspacebuffersaresafetoaccessandthenupdatethem,aswell.With
thecompleteunderstandingoftheAPIs,sherewrotetheabovecodesnippetalongwithincludingthe
correspondingheader<asm/uaccess.h>,asfollows,leavingnochanceforPugstocomment:
#include<asm/uaccess.h>
staticcharc;
staticssize_tmy_read(structfile*f,char__user*buf,size_tlen,loff_t*off)
{
printk(KERN_INFO"Driver:read()\n");
if(copy_to_user(buf,&c,1)!=0)
else
returnEFAULT;
return1;
}
staticssize_tmy_write(
structfile*f,constchar__user*buf,size_tlen,loff_t*off)
printk(KERN_INFO"Driver:write()\n");
if(copy_from_user(&c,buf+len1,1)!=0)
else
returnEFAULT;
returnlen;
Then,Shwetarepeatedtheusualbuildandteststepsasfollows:
Buildthemodifiednulldriver(.kofile)byrunningmake.
Loadthedriverusinginsmod.
Writeinto/dev/mynull,sayusingechonPugs>/dev/mynull
Readfrom/dev/mynullusingcat/dev/mynull(StopusingCtrl+C)
Unloadthedriverusingrmmod.
Summingup
Oncating/dev/mynull,theoutputwasanonstopinfinitesequenceofs,asmy_read()givesthelast
onecharacterforever.So,PugsintervenesandpressesCtrl+Ctostoptheinfiniteread,andtriesto
explain,Ifthisistobechangedtothelastcharacteronlyonce,my_read()needstoreturn1thefirst
timeandzerofromsecondtimeonwards.Thiscanbeachievedusingtheoff(fourthparameter
ofmy_read()).ShwetanodsherheadtosupportPugsego.
SeventhArticle>>
Addon
Andheresthemodifiedreadusingtheoff:
staticssize_tmy_read(structfile*f,char__user*buf,size_tlen,loff_t*off)
{
printk(KERN_INFO"Driver:read()\n");
if(*off==0)
if(copy_to_user(buf,&c,1)!=0)
else
(*off)++;
return1;
else
returnEFAULT;
return0;
GenericHardwareAccessinLinux
2Replies
Thisseventharticle,whichispartoftheseriesonLinuxdevicedrivers,talksaboutaccessinghardware
inLinux.
<<SixthArticle
Shwetawasalljubilantabouthercharacterdriverachievements,assheenteredtheLinuxdevice
driverslaboratoryonthesecondfloorofhercollege.Whynot?Manyofherclassmateshadalready
readherblog&commentedonherexpertise.Andtodaywasachanceforshowoffatananotherlevel.
Tillnow,itwasallsoftware.TodayslabwasonaccessinghardwareinLinux.Studentsareexpectedto
learnbyexperimentationtoaccessvariouskindsofhardwareinLinuxonvariousarchitecturesover
multiplelabsessionshere.
Asusual,thelabstaffareabitskepticaltoletthestudentsdirectlygetontothehardware,withoutany
background.Sotobuildtheirbackground,theyhavepreparedsomeslidepresentations,whichcanbe
accessedfromSysPlayswebsite.
Generichardwareinterfacing
Aseveryonesettledinthelaboratory,labexpertPritistartedwiththeintroductiontohardware
interfacinginLinux.Skippingthetheoreticaldetails,thefirstinterestingslidewasaboutthegeneric
architecturetransparenthardwareinterfacing.SeeFigure11.
Figure11:Hardwaremapping
Thebasicassumptionbeingthatthearchitectureis32bit.Forothers,thememorymapwouldchange
accordingly.For32bitaddressbus,theaddress/memorymaprangesfrom0(0x00000000)to2321
(0xFFFFFFFF).Andanarchitectureindependentlayoutofthismemorymapwouldbeasshowninthe
Figure11memory(RAM)anddeviceregions(registers&memoriesofdevices)mappedinan
interleavedfashion.Thearchitecturedependentthingwouldbewhattheseaddressesareactually
there.Forexample,inanx86architecture,theinitial3GB(0x00000000to0xBFFFFFFF)istypicallyfor
RAMandthelater1GB(0xC0000000to0xFFFFFFFF)fordevicemaps.However,iftheRAMisless,
say2GB,devicemapscouldstartfrom2GB(0x80000000).
Typeincat/proc/iomemtolistthememorymaponyoursystem.cat/proc/meminfowouldgiveyouan
approximateRAMsizeonyoursystem.RefertoFigure12forasnapshot.
Figure12:Physical&busaddressesonanx86system
Irrespectiveoftheactualvalues,theaddressesreferringtoRAMaretermedasphysicaladdresses.
Andtheaddressesreferringtodevicemapsaretermedasbusaddresses,asthesedevicesarealways
mappedthroughsomearchitecturespecificbus.Forexample,PCIbusinx86architecture,AMBAbus
inARMarchitectures,SuperHywaybusinSuperH(orSH)architectures,GXbusonPowerPC(orPPC),
etc.
Allthearchitecturedependentvaluesofthesephysicalandbusaddressesareeitherdynamically
configurableoraretobeobtainedfromthedatasheets(i.e.hardwaremanuals)ofthecorresponding
architectureprocessors/controllers.Buttheinterestingpartisthat,inLinuxnoneofthesearedirectly
accessiblebutaretobemappedtovirtualaddressesandthenaccessedthroughthat.Thus,making
theRAManddeviceaccessesgenericenough,exceptjustmappingthemtovirtualaddresses.Andthe
correspondingAPIsformapping&unmappingthedevicebusaddressestovirtualaddressesare:
#include<asm/io.h>
void*ioremap(unsignedlongdevice_bus_address,unsignedlongdevice_region_size);
voidiounmap(void*virt_addr);
Theseareprototypedin<asm/io.h>.Oncemappedtovirtualaddresses,itboilsdowntothedevice
datasheet,astowhichsetofdeviceregistersand/ordevicememorytoreadfromorwriteinto,by
addingtheiroffsetstothevirtualaddressreturnedbyioremap().Forthat,thefollowingaretheAPIs
(prototypedinthesameheaderfile<asm/io.h>):
#include<asm/io.h>
unsignedintioread8(void*virt_addr);
unsignedintioread16(void*virt_addr);
unsignedintioread32(void*virt_addr);
unsignedintiowrite8(u8value,void*virt_addr);
unsignedintiowrite16(u16value,void*virt_addr);
unsignedintiowrite32(u32value,void*virt_addr);
AccessingthevideoRAMofDOSdays
Afterthisfirstsetofinformation,studentsweredirectedfortheliveexperiments.Theyweresuggested
todoaninitialexperimentwiththevideoRAMofDOSdaystounderstandtheusageoftheabove
APIs.Shwetagotontothesystemdisplayedthe/proc/iomemwindowoneverysimilartoasshown
inFigure12.Fromthere,shegotthevideoRAMaddressrangingfrom0x000A0000to0x000BFFFF.
AndwiththatsheaddedtheaboveAPIswithappropriateparametersintotheconstructorand
destructorofheralreadywrittennulldrivertoconvertitintoavramdriver.Then,sheaddedtheuser
accesstothevideoRAMthroughread&writecallsofthevramdriver.Hereswhatshecodedinthe
newfilevideo_ram.c:
#include<linux/module.h>
#include<linux/version.h>
#include<linux/kernel.h>
#include<linux/types.h>
#include<linux/kdev_t.h>
#include<linux/fs.h>
#include<linux/device.h>
#include<linux/cdev.h>
#include<linux/uaccess.h>
#include<asm/io.h>
#defineVRAM_BASE0x000A0000
#defineVRAM_SIZE0x00020000
staticvoid__iomem*vram;
staticdev_tfirst;
staticstructcdevc_dev;
staticstructclass*cl;
staticintmy_open(structinode*i,structfile*f)
{
return0;
}
staticintmy_close(structinode*i,structfile*f)
{
return0;
}
staticssize_tmy_read(structfile*f,char__user*buf,size_tlen,loff_t*off)
{
inti;
u8byte;
if(*off>=VRAM_SIZE)
if(*off+len>VRAM_SIZE)
for(i=0;i<len;i++)
return0;
len=VRAM_SIZE*off;
byte=ioread8((u8*)vram+*off+i);
if(copy_to_user(buf+i,&byte,1))
*off+=len;
returnlen;
returnEFAULT;
}
staticssize_tmy_write(
structfile*f,constchar__user*buf,size_tlen,loff_t*off)
inti;
u8byte;
if(*off>=VRAM_SIZE)
if(*off+len>VRAM_SIZE)
for(i=0;i<len;i++)
if(copy_from_user(&byte,buf+i,1))
iowrite8(byte,(u8*)vram+*off+i);
*off+=len;
returnlen;
return0;
len=VRAM_SIZE*off;
returnEFAULT;
}
staticstructfile_operationsvram_fops=
{
.owner=THIS_MODULE,
.open=my_open,
.release=my_close,
.read=my_read,
.write=my_write
};
staticint__initvram_init(void)/*Constructor*/
{
intret;
structdevice*dev_ret;
if((vram=ioremap(VRAM_BASE,VRAM_SIZE))==NULL)
printk(KERN_ERR"MappingvideoRAMfailed\n");
returnENOMEM;
if((ret=alloc_chrdev_region(&first,0,1,"vram"))<0)
if(IS_ERR(cl=class_create(THIS_MODULE,"chardrv")))
unregister_chrdev_region(first,1);
returnPTR_ERR(cl);
if(IS_ERR(dev_ret=device_create(cl,NULL,first,NULL,"vram")))
class_destroy(cl);
unregister_chrdev_region(first,1);
returnPTR_ERR(dev_ret);
cdev_init(&c_dev,&vram_fops);
if((ret=cdev_add(&c_dev,first,1))<0)
device_destroy(cl,first);
class_destroy(cl);
unregister_chrdev_region(first,1);
returnret;
return0;
returnret;
}
staticvoid__exitvram_exit(void)/*Destructor*/
{
cdev_del(&c_dev);
device_destroy(cl,first);
class_destroy(cl);
unregister_chrdev_region(first,1);
iounmap(vram);
}
module_init(vram_init);
module_exit(vram_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AnilKumarPugalia<email@sarikapugs.com>");
MODULE_DESCRIPTION("VideoRAMDriver");
Summingup
Then,Shwetarepeatedthefollowingsteps:
Buildthevramdriver(video_ram.kofile)byrunningmakewiththesameMakefilechangedto
buildthisdriver.
Usualloadofthedriverusinginsmodvideo_ram.ko.
Usualwriteinto/dev/vram,sayusingechon0123456789>/dev/vram.
Readthe/dev/vramcontentsusingxxd/dev/vram|less.Theusualcat/dev/vramalsocanbe
usedbutthatwouldgiveallbinarycontent.xxdshowsthemupashexadecimalincentrewiththe
correspondingASCIIalongtherightside.
Usualunloadthedriverusingrmmodvideo_ram.
Note1:TodayssystemstypicallyuseseparatevideocardshavingtheirownvideoRAM.So,thevideo
RAMusedintheDOSdays,i.e.theonementionedinthisarticleisunusedandmanyatimesnoteven
present.Hence,playingaroundwithit,issafe,withoutanyeffectonthesystem,orthedisplay.
Note2:Moreover,ifthevideoRAMisabsent,theread/writemaynotbeactuallyreading/writing,but
justsending/receivingsignalsintheair.Insuchacase,writeswouldnotdoanychange,andreads
wouldkeeponreadingthesamevaluethusxxdshowingthesamevalues.
Itwasyethalfanhourleftforthepracticalclasstobeoverandalunchbreak.So,Shwetadecidedto
walkaroundandpossiblyhelpsomebodyintheirexperiments.
EighthArticle>>
Notes:
1. Whenapointeristaggedwith__iomem,itenablesthatpointerforcompilerchecks&/or
optimizations,relevantforI/Omappedmemory.
OtherReferences:
1. TranslatingaddressesinKernelSpaceondifferentarchitectures
2. AddressingConceptsinLinux
3. LinuxMemoryManagementOverview
Accessingx86specificI/OmappedhardwareinLinux
2Replies
Thiseightharticle,whichispartoftheseriesonLinuxdevicedrivers,continuesontalkingabout
accessinghardwareinLinux.
<<SeventhArticle
SeconddayintheLinuxdevicedriverslaboratorywasexpectedtobequitedifferentfromthetypical
softwareorientedclasses.Apartfromaccessing&programmingthearchitecturespecificI/Omapped
hardwareinx86,ithadlottoofferforfirsttimersinreadinghardwaredevicemanuals(commonly
referredasdatasheets)andtounderstandthemforwritingdevicedrivers.
Contrastthiswiththepreviouslaboratorysession,whichtaughtaboutthegenericarchitecture
transparenthardwareinterfacing.Itwasallaboutmappingandaccessingmemorymappeddevicesin
Linux,withoutanydevicespecificdetail.
x86specifichardwareinterfacing
Unlikemostotherarchitectures,x86hasanadditionalhardwareaccessingmechanismthroughadirect
I/Omapping.Itisadirect16bitaddressingschemeanddoesntneedamappingtovirtualaddressfor
itsaccessing.Theseaddressesarereferredtoasportaddresses,orinshortports.Asx86hasthisas
anadditionalaccessingmechanism,itcallsforadditionalsetofx86(assembly/machinecode)
instructions.Andyes,therearetheinputinstructionsinb,inw,inlforreadingan8bitbyte,a16bitword,
anda32bitlongwordrespectively,fromtheI/Omappeddevicesthroughtheports.Andthe
correspondingoutputinstructionsareoutb,outw,outl,respectively.AndtheequivalentC
functions/macrosareasfollows(availablethroughtheheader<asm/io.h>):
u8inb(unsignedlongport);
u16inw(unsignedlongport);
u32inl(unsignedlongport);
voidoutb(u8value,unsignedlongport);
voidoutw(u16value,unsignedlongport);
voidoutl(u32value,unsignedlongport);
Thebasicquestionmayarise,astowhichalldevicesareI/Omappedandwhataretheportaddresses
ofthesedevices.Theanswerisprettysimple.Asperx86specific,allthesedevices&theirmappings
arex86standardandhencepredefined.Figure13showsasnippetofthesemappingsthroughthe
kernelwindow/proc/ioports.ThelistingincludespredefinedDMA,timer,RTC,serial,parallel,PCIbus
interfacestonameafew.
Figure13:x86specificI/Oports
Simplesttheserialonx86platform
Forexample,thefirstserialportisalwaysI/Omappedfrom0x3F8to0x3FF.Butwhatdoesthis
mappingmean?Whatdowedowiththis?Howdoesithelpustousetheserialport?
Thatiswhereadatasheetofthedevicecontrollingthecorrespondingportneedstobelookedup.
Serialportiscontrolledbytheserialcontrollerdevice,commonlyknownasanUART(Universal
AsynchronousReceiver/Transmitter)orattimesaUSART(UniversalSynchronous/Asynchronous
Receiver/Transmitter).OnPCs,thetypicalUARTusedisPC16550D.Thedatasheet
(uart_pc16550d.pdf)forthesamehasalsobeenincludedintheselfextractingLDDKPackage.sh,used
fortheLinuxdevicedriverkit.Figure14showstherelevantportionofit.
Ingeneral,fromwhere&howdowegetthesedevicedatasheets?Typically,anonlinesearchwiththe
correspondingdevicenumbershouldyieldtheirdatasheetlinks.Andhowdoesonegetthedevice
number?Simple,byhavingalookatthedevice.Ifitisinsideadesktop,openitupandcheckitout.
Yes,thisistheleastyoumayhavetodotogetgoingwiththehardwareforwritingdevicedrivers.
Assumingallthishackinghasbeendone,itistimetopeepintothedatasheetofUARTPC16550D.
Foradevicedriverwriter,theusualsectionsofinterestinadatasheetaretheonesrelatedtoregisters
ofthedevice.Why?As,itistheseregisters,whichadevicedriverwriterneedtoreadfromand/orwrite
intofinallyusethedevice.Page14ofthedatasheet(alsoshowninFigure14)showsthecomplete
tableofallthetwelve8bitregisterspresentintheUARTPC16550D.Eachofthe8rowscorrespondsto
therespectivebitoftheregisters.Also,notethattheregisteraddressesstartfrom0andgoestill7.The
interestingthingtonoteaboutthisisthatadatasheetalwaysgivestheregisteroffsets,whichthen
needtobeaddedtothebaseaddressofthedevice,togettheactualregisteraddresses.Whodecides
thebaseaddressandwhereisitobtainedfrom?Baseaddressesaretypicallyboard/platformspecific,
unlesstheyaredynamicallyconfigurablelikeinthecaseofPCIdevices.Inthecasehere,i.e.serial
deviceonx86,itisdictatedbythex86architectureandthatiswhatpreciselywasthestartingserial
portaddressmentionedabove0x3F8.Andtheeightregisteroffsets0to7aretheonesexactly
mappingtotheeightportaddresses0x3F8to0x3FF.So,thesearetheactualaddressestobereador
writtenforreadingorwritingthecorrespondingserialregisters,toachievethedesiredserialoperations,
aspertheregisterdescriptions.
Figure14:RegistersofUARTPC16550D
Alltheserialregisteroffsetsandtheregisterbitmasksaredefinedintheheader<linux/serial_reg.h>.
So,ratherthanhardcodingthesevaluesfromthedatasheet,thecorrespondingmacroscouldbeused
instead.Allthefollowingcodeusesthesemacrosalongwiththefollowing:
#defineSERIAL_PORT_BASE0x3F8
Operatingonthedeviceregisters
TosummarizeallthesedecodingofUARTPC16550Ddatasheet,hereareafewexamplesofhowto
doreadandwriteoperationsoftheserialregistersandtheirbits.
ReadingandwritingtheLineControlRegister(LCR):
u8val;
val=inb(SERIAL_PORT_BASE+UART_LCR/*3*/);
outb(val,SERIAL_PORT_BASE+UART_LCR/*3*/);
SettingandclearingtheDivisorLatchAccessBit(DLAB)inLCR:
u8val;
val=inb(SERIAL_PORT_BASE+UART_LCR/*3*/);
/*SettingDLAB*/
val|=UART_LCR_DLAB/*0x80*/;
outb(val,SERIAL_PORT_BASE+UART_LCR/*3*/);
/*ClearingDLAB*/
val&=~UART_LCR_DLAB/*0x80*/;
outb(val,SERIAL_PORT_BASE+UART_LCR/*3*/);
ReadingandwritingtheDivisorLatch:
u8dlab;
u16val;
dlab=inb(SERIAL_PORT_BASE+UART_LCR);
dlab|=UART_LCR_DLAB;//SettingDLABtoaccessDivisorLatch
outb(dlab,SERIAL_PORT_BASE+UART_LCR);
val=inw(SERIAL_PORT_BASE+UART_DLL/*0*/);
outw(val,SERIAL_PORT_BASE+UART_DLL/*0*/);
BlinkinganLED
TogetarealexperienceofthelowlevelhardwareaccessandLinuxdevicedrivers,thebestwaywould
betoplaywiththeLinuxdevicedriverkit(LDDK).However,justforthefeeloflowlevelhardware
access,ablinkinglightemittingdiode(LED)maybetriedasfollows:
Connectalightemittingdiode(LED)witha330ohmresistorinseriesacrossthepin3(Tx)&pin
5(Gnd)oftheDB9connectorofyourPC.
Pullup&downthetransmit(Tx)linewitha500msdelay,byloadingtheblink_leddriver
usinginsmodblink_led.ko,andthenunloadingthedriverusingrmmodblink_led,before
reloading.
Belowistheblink_led.c,tobecompiledintotheblink_led.kodriver,byrunningmakeusingtheusual
driverMakefile:
#include<linux/module.h>
#include<linux/version.h>
#include<linux/types.h>
#include<linux/delay.h>
#include<asm/io.h>
#include<linux/serial_reg.h>
#defineSERIAL_PORT_BASE0x3F8
int__initinit_module()
{
inti;
u8data;
data=inb(SERIAL_PORT_BASE+UART_LCR);
for(i=0;i<5;i++)
/*PullingtheTxlinelow*/
data|=UART_LCR_SBC;
outb(data,SERIAL_PORT_BASE+UART_LCR);
msleep(500);
/*DefaultingtheTxlinehigh*/
data&=~UART_LCR_SBC;
outb(data,SERIAL_PORT_BASE+UART_LCR);
msleep(500);
return0;
}
void__exitcleanup_module()
{
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AnilKumarPugalia<email@sarikapugs.com>");
MODULE_DESCRIPTION("BlinkingLEDHack");
Summingup
AreyouwonderingaswherehasShwetagonetoday?Shehasbunkedalltheclasses.Watchoutfor
thenextarticletofindoutwhy.
NinthArticle>>
Notes
1. Theaboveexampleistodemonstratehowbareboneeasythelowlevelaccesscouldget.
However,tomakeitmoreperfect,oneshouldusethe
APIsrequest_region()andrelease_region(),respectivelybeforeandaftertheaccessesoftheI/O
portaddresses,respectivelytoacquireandreleasetherangeofI/Oportaddressestoaccess.
2. Also,youmighthaveobservedthatthereisnomodule_init()&module_exit()intheabovedriver.
Butnonetheless,insmod&rmmoddowork.Howisthat?Thatis
becauseinit_module()&cleanup_module()arethepredefinednamesfortheconstructor&the
destructor,respectively.Hence,youdonotneedmodule_init()&module_exit()totranslateyour
otherfunctionnamestothesepredefinedones.Caution:Sincekernel2.6onwards,ifyouare
buildingthedriverintothekernel,youshoulddefineyourownfunctionnames&
usemodule_init()&module_exit().
I/OControlinLinux
2Replies
Thisnintharticle,whichispartoftheseriesonLinuxdevicedrivers,talksaboutthetypicalioctl()
implementationandusageinLinux.
<<EighthArticle
Getmealaptopandtellmeabouttheexperimentsonthex86specifichardwareinterfacingconducted
inyesterdaysLinuxdevicedriverslaboratorysession,andalsoaboutwhatsplannedforthenext
session,criedShweta,exasperatedatbeingconfinedtobedandnotbeingabletoattendtheclasses.
Calmdown!!!Dontworryaboutthat.Wellhelpyoumakeupforyourclasses.Butfirsttelluswhat
happenedtoyou,sosuddenly,askedoneofherfriends,whohadcometovisitherinthehospital.Its
allthefaultofthosechaats,IhadinRohansbirthdayparty.Ihadsuchapainfulfoodpoisoningthatled
mehere,blamedShweta.Howareyoufeelingnow?,askedRohansheepishly.Illbeallfinejust
tellmeallaboutthefunwithhardware,youguyshad.Ihadbeenwaitingtoattendthatsessionandall
thishadtohappen,rightthen.
RohansatdownbesidesShwetaandsummarizedthesessiontoher,hopingtosootheher.That
excitedhermoreandshestartingforcingthemtotellherabouttheupcomingsessions,aswell.They
knewthatthosewouldbetodosomethingwithhardware,butwereunawareofthedetails.Meanwhile,
thedoctorcomesinandrequestseverybodytowaitoutside.Thatwasanopportunitytoplanand
prepare.Andtheydecidedtotalkaboutthemostcommonhardwarecontrollingoperation:theioctl().
Hereishowitwent.
Introducinganioctl()
Inputoutputcontrol(ioctl,inshort)isacommonoperationorsystemcallavailablewithmostofthe
drivercategories.Itisaonebillfitsallkindofsystemcall.Ifthereisnoothersystemcall,whichmeets
therequirement,thendefinitelyioctl()istheonetouse.Practicalexamplesincludevolumecontrolfor
anaudiodevice,displayconfigurationforavideodevice,readingdeviceregisters,basically
anythingtodowithanydeviceinput/output,orforthatmatteranydevicespecificoperations.Infact,it
isevenmoreversatileneednotbetiedtoanydevicespecificthingsbutanykindofoperation.An
exampleincludesdebuggingadriver,saybyqueryingofdriverdatastructures.
Questionishowcouldallthesevarietybeachievedbyasinglefunctionprototype.Thetrickisusing
itstwokeyparameters:thecommandandthecommandsargument.Thecommandisjustsome
number,representingsomeoperation,definedaspertherequirement.Theargumentisthe
correspondingparameterfortheoperation.Andthentheioctl()functionimplementationdoesaswitch
caseoverthecommandimplementingthecorrespondingfunctionalities.Thefollowinghadbeenits
prototypeinLinuxkernel,forquitesometime:
intioctl(structinode*i,structfile*f,unsignedintcmd,unsignedlongarg);
Though,recentlyfromkernel2.6.35,ithaschangedtothefollowing:
longioctl(structfile*f,unsignedintcmd,unsignedlongarg);
Ifthereisaneedformorearguments,allofthemareputinastructureandapointertothestructure
becomestheonecommandargument.Whetherintegerorpointer,theargumentistakenupasalong
integerinkernelspaceandaccordinglytypecastandprocessed.
ioctl()istypicallyimplementedaspartofthecorrespondingdriverandthenanappropriatefunction
pointerinitializedwithit,exactlyaswithothersystemcallsopen(),read(),Forexample,incharacter
drivers,itistheioctlorunlocked_ioctl(sincekernel2.6.35)functionpointerfieldinthestruct
file_operations,whichistobeinitialized.
Againlikeothersystemcalls,itcanbeequivalentlyinvokedfromtheuserspaceusingtheioctl()system
call,prototypedin<sys/ioctl.h>as:
intioctl(intfd,intcmd,...);
Here,cmdisthesameasimplementedinthedriversioctl()andthevariableargumentconstruct()is
ahacktobeabletopassanytypeofargument(thoughonlyone)tothedriversioctl().Other
parameterswillbeignored.
Notethatboththecommandandcommandargumenttypedefinitionsneedtobesharedacrossthe
driver(inkernelspace)andtheapplication(inuserspace).So,thesedefinitionsarecommonlyputinto
headerfilesforeachspace.
Queryingthedriverinternalvariables
Tobetterunderstandtheboringtheoryexplainedabove,heresthecodesetforthedebugginga
driverexamplementionedabove.Thisdriverhas3staticglobalvariablesstatus,dignity,ego,which
needtobequeriedandpossiblyoperatedfromanapplication.query_ioctl.hdefinesthecorresponding
commandsandcommandargumenttype.Listingfollows:
#ifndefQUERY_IOCTL_H
#defineQUERY_IOCTL_H
#include<linux/ioctl.h>
typedefstruct
{
intstatus,dignity,ego;
}query_arg_t;
#defineQUERY_GET_VARIABLES_IOR('q',1,query_arg_t*)
#defineQUERY_CLR_VARIABLES_IO('q',2)
#endif
Usingthese,thedriversioctl()implementationinquery_ioctl.cwouldbe:
staticintstatus=1,dignity=3,ego=5;
#if(LINUX_VERSION_CODE<KERNEL_VERSION(2,6,35))
staticintmy_ioctl(structinode*i,structfile*f,unsignedintcmd,
unsignedlongarg)
#else
staticlongmy_ioctl(structfile*f,unsignedintcmd,unsignedlongarg)
#endif
{
query_arg_tq;
switch(cmd)
caseQUERY_GET_VARIABLES:
q.status=status;
q.dignity=dignity;
q.ego=ego;
if(copy_to_user((query_arg_t*)arg,&q,
break;
caseQUERY_CLR_VARIABLES:
status=0;
dignity=0;
ego=0;
break;
default:
return0;
sizeof(query_arg_t)))
returnEACCES;
returnEINVAL;
Andfinallythecorrespondinginvocationfunctionsfromtheapplicationquery_app.cwouldbeasfollows:
#include<stdio.h>
#include<sys/ioctl.h>
#include"query_ioctl.h"
voidget_vars(intfd)
{
query_arg_tq;
if(ioctl(fd,QUERY_GET_VARIABLES,&q)==1)
else
printf("Status:%d\n",q.status);
printf("Dignity:%d\n",q.dignity);
printf("Ego
perror("query_appsioctlget");
:%d\n",q.ego);
}
voidclr_vars(intfd)
{
if(ioctl(fd,QUERY_CLR_VARIABLES)==1)
perror("query_appsioctlclr");
CompletecodeoftheabovementionedthreefilesisincludedinthefolderQueryIoctl,wherethe
requiredMakefileisalsopresent.Youmaydownloaditstarbzippedfileasquery_ioctl_code.tar.bz2,
untaritandthen,dothefollowingtotryout:
Buildthequery_ioctldriver(query_ioctl.kofile)andtheapplication(query_appfile)by
runningmakeusingtheprovidedMakefile.
Loadthedriverusinginsmodquery_ioctl.ko.
Withappropriateprivilegesandcommandlinearguments,runtheapplicationquery_app:
./query_app#Todisplaythedrivervariables
./query_appc#Toclearthedrivervariables
./query_appg#Todisplaythedrivervariables
./query_apps#Tosetthedrivervariables(Notmentionedabove)
Unloadthedriverusingrmmodquery_ioctl.
Definingtheioctl()commands
Visitingtimeisover,camecallingthesecurityguard.AndallofShwetasvisitorspackeduptoleave.
Stoppingthem,Shwetasaid,Hey!!Thanksalotforallthishelp.Icouldunderstandmostofthiscode,
includingtheneedforcopy_to_user(),aswehavelearntearlier.Butjustaquestion,whatare
these_IOR,_IO,etcusedindefiningthecommandsinquery_ioctl.h.Yousaidwecouldjustuse
numbersforthesame.Butyouareusingalltheseweirdthings.Actually,theyareusualnumbersonly.
Justthat,nowadditionally,someusefulcommandrelatedinformationisalsoencodedaspartofthese
numbersusingthesevariousmacros,asperthePortableOperatingSystemInterface(POSIX)standard
forioctl.Thestandardtalksaboutthe32bitcommandnumbersbeingformedoffourcomponents
embeddedintothe[31:0]bits:
1. Directionofcommandoperation[bits31:30]read,write,both,ornonefilledbythe
correspondingmacro(_IOR,_IOW,_IOWR,_IO)
2. Sizeofthecommandargument[bits29:16]computedusingsizeof()withthecommand
argumentstypethethirdargumenttothesemacros
3. 8bitmagicnumber[bits15:8]torenderthecommandsuniqueenoughtypicallyanASCII
character(thefirstargumenttothesemacros)
4. Originalcommandnumber[bits7:0]theactualcommandnumber(1,2,3,),definedasper
ourrequirementthesecondargumenttothesemacros
Checkouttheheader<asmgeneric/ioctl.h>forimplementationdetails,concludedRohanwhile
hurryingoutoftheroomwithasighofrelief.
TenthArticle>>
Notes:
1. TheintentionbehindthePOSIXstandardofencodingthecommandistobeabletoverifythe
parameters,direction,etcrelatedtothecommand,evenbeforeitispassedtothedriver,sayby
VFS.ItisjustthatLinuxhasnotyetimplementedtheverificationpart.
KernelSpaceDebuggersinLinux
2Replies
Thistentharticle,whichispartoftheseriesonLinuxdevicedrivers,talksaboutkernelspacedebugging
inLinux.
<<NinthArticle
Shwetawasbackfromhospitalandrelaxinginthelibrarybyreadingupvariousbooks.Sincethetime
shehasknowntheioctlwayofdebugging,shehasbeenimpatienttoknowmoreaboutdebuggingin
kernelspace.Thebasiccuriositycomingfromthefactthathowandwherewouldonerunthekernel
spacedebugger,ifthereisany.Contrastthiswithapplication/userspacedebugging,wherewehave
theOSrunningunderneath,andashelloraGUIoverittorunthedebugger,sayforexample,theGNU
debugger(gdb),thedatadisplaydebugger(ddd).Andviola,shecameacrossthisinterestingkernel
spacedebuggingmechanismusingkgdb,providedaspartofthekernelitself,sincekernel2.6.26
Debuggerchallengeinkernelspace
Asweneedsomeinterfacetobeup,torunadebuggertodebuganything,adebuggerfordebugging
thekernel,couldbevisualizedin2possibleways:
1. Putthedebuggerintothekernelitself.Andthenthedebuggerrunsfromwithin,accessible
throughtheusualmonitororconsole.Anexampleofitiskdb.Untilkernel2.6.35,itwasnot
official,meaningitssourcecodewasneededtobedownloadedas2setofpatches(one
architecturedependentandonearchitectureindependent)
fromftp://oss.sgi.com/projects/kdb/download/andthentobepatchedwiththekernelsource
thoughsincekernel2.6.35,majorityofitispartofthekernelsourcesofficialrelease.Ineither
case,thekdbsupportneedstobeenabledinthekernelsourceandthenthekernelistobe
compiled,installedandbootedwith.Thebootscreenitselfwouldgivethekdbdebugging
interface.
2. Putaminimaldebuggingserverintothekerneladebuggerclientwouldthenconnecttoitfroma
remotehostsystemsuserspaceoversomeinterface,sayserialorethernet.Anexampleforthat
iskgdbthekernelsgdbserver,tobeusedwithgdb(client)fromaremotesystemovereither
serialorethernet.Sincekernel2.6.26,itsserialinterfaceparthasbeenmergedwithkernel
sourcesofficialrelease.Though,ifinterestedinitsnetworkinterfacepart,itwouldstillneedtobe
patchedwithoneofthereleasesfromhttp://sourceforge.net/projects/kgdb/.Ineithercase,the
nextstepwouldbetoenablekgdbsupportinthekernelsourceandthenthekernelistobe
compiled,installedandbootedwiththoughthistimetobeconnectedwitharemotedebugger
client.
Pleasenotethatinboththeabovecases,thecompletekernelsourceforthekerneltobedebuggedis
needed,unlikeincaseofbuildingmodules,wherejustheadersaresufficient.Hereishowtoplay
aroundwithkgdboverserialinterface.
SettinguptheLinuxkernelwithkgdb
Prerequisite:Eitherkernelsourcepackagefortherunningkernelisinstalledonyoursystem,ora
correspondingkernelsourcereleasehasbeendownloadedfromhttp://kernel.org.
Firstofall,thekerneltobedebuggedneedtohavekgdbenabledandbuiltintoit.Toachievethat,the
kernelsourcehastobeconfiguredwithCONFIG_KGDB=y.Additionally,forkgdbover
serial,CONFIG_KGDB_SERIAL_CONSOLE=yneedstobeconfigured.AndCONFIG_DEBUG_INFOis
preferredforsymbolicdatatobebuiltintokernelformakingdebuggingwithgdbmore
meaningful.CONFIG_FRAME_POINTER=yenablesframepointersinthekernelallowinggdbto
constructmoreaccuratestackbacktraces.AlltheseoptionsareavailableunderKernelhackinginthe
menuobtainedbyissuingthefollowingcommandsinthekernelsourcedirectory(preferablyasrootor
usingsudo):
$makemrproper#Tocleanupproperly
$makeoldconfig#Configurethekernelsameasthecurrentrunningone
$makemenuconfig#Startthencursesbasedmenuforfurtherconfiguration
SeethehighlightedselectionsinFigure15,forhowandwherewouldtheseoptionsbe:
KGDB:kerneldebuggingwithremotegdbCONFIG_KGDB
KGDB:usekgdbovertheserialconsoleCONFIG_KGDB_SERIAL_CONSOLE
CompilethekernelwithdebuginfoCONFIG_DEBUG_INFO
CompilethekernelwithframepointersCONFIG_FRAME_POINTER
Figure15:Configuringkernelwithkgdb
Onceconfiguredandsaved,thekernelcanbebuiltbytypingmakeinthekernelsourcedirectory.And
thenamakeinstallisexpectedtoinstallit,alongwithaddinganentryfortheinstalledkernelin
thegrubconfigurationfile.Dependingonthedistribution,thegrubconfigurationfilemay
be/boot/grub/menu.lst,/etc/grub.cfg,orsomethingsimilar.Onceinstalled,thekgdbrelatedkernelboot
parameters,needtobeaddedtothisnewlyaddedentry.
Figure16:grubconfigurationforkernelwithkgdb
Figure16highlightsthekernelbootparametersaddedtothenewlyinstalledkernel,inthegrubs
configurationfile.
kgdbocisforgdbconnectingoverconsoleandthebasicformatis:kgdboc=<serial_device>,<baud_rate>
where:
<serial_device>istheserialdevicefileonthesystem,whichwouldrunthekerneltobedebugged,for
theserialporttobeusedfordebugging
<baud_rate>isthebaudrateoftheserialporttobeusedfordebugging
kgdbwaitenablesthebootingkerneltowaittillaremotegdb(i.e.gdbonanothersystem)connectstoit,
andthisparametershouldbepassedonlyafterkgdboc.
Withthis,thesystemisreadytorebootintothisnewlybuiltandinstalledkernel.Onreboot,atthegrubs
menu,selecttobootfromthisnewkernel,andthenitwillwaitforgdbtoconnectwithitfromananother
systemovertheserialport.
Alltheabovesnapshotshavebeenwithkernelsource2.6.33.14,downloadedfromhttp://kernel.org.
Andthesameshouldworkforattheleastany2.6.3xreleaseofkernelsource.Also,thesnapshotsare
capturedforkgdboverserialdevicefile/dev/ttyS0,i.e.thefirstserialport.
Settingupgdbonanothersystem
Prerequisite:Serialportsofthesystemtobedebuggedandtheanothersystemtorungdbfrom,
shouldbeconnectedusinganullmodem(i.e.acrossoverserial)cable.
Herearethegdbcommandstogetthegdbfromtheothersystemconnecttothewaitingkernel.All
thesecommandshavetobegivenonthegdbprompt,aftertypinggdbontheshell.
Connectingoverserialport/dev/ttyS0(ofthesystemrunninggdb)withbaudrate115200bps:
$gdb
...
(gdb)filevmlinux
(gdb)setremoteinterruptsequenceCtrlC
(gdb)setremotebaud115200
(gdb)targetremote/dev/ttyS0
(gdb)continue
Intheabovecommands,vmlinuxisthekernelimagebuiltwithkgdbenabledandneedstobecopied
intothedirectoryonthesystem,fromwheregdbisbeingexecuted.Also,theserialdevicefileandits
baudratehastobecorrectlygivenasperonessystemsetup.
Debuggingusinggdbwithkgdb
Afterthis,itisalllikedebugginganapplicationfromgdb.OnemaystopexecutionusingCtrl+C,add
breakpointsusingb[reak],stepexecutionusings[tep]orn[ext],theusualgdbway.Fordetailson
howtousegdb,thereareenoughtutorialsavailableonline.Infact,ifnotcomfortablewithtext
basedgdb,thedebuggingcouldbemadeGUIbased,usinganyofthestandardGUItoolsovergdb,for
example,ddd,Eclipse,etc.
Summingup
Bynow,Shwetawasallexcitedtotryoutthekernelspacedebuggingexperimentusingkgdb.Andas
sheneededtwosystemstotryitout,shedecidedtogototheLinuxdevicedriverslab.There,sheset
upthesystemtobedebugged,withthekgdbenabledkernel,andconnecteditwithananothersystem
usinganullmodemcable.Thenonthesecondsystem,sheexecutedgdbtoremotelyconnectwithand
stepthroughthekernelonthefirstsystem.
EleventhArticle>>
Notes
1. Parameterforusingkgdboverethernetiskgdboe.Ithasthefollowingformat:kgdboe=
[<this_udp_port>]@<this_ip>/[this_dev],
[<remote_udp_port>]@<remote_ip>/[<remote_mac_addr>]
where:
<this_udp_port>isoptionalanddefaultsto6443,
<this_ip>isIPaddressofthesystem,whichwouldrunthekerneltobedebugged,
<this_dev>isoptionalanddefaultstoeth0,
<remote_udp_port>isoptionalanddefaultsto6442,
<remote_ip>isIPaddressofthesystem,fromwhichgdbwouldbeconnectingfrom,
<remote_mac_addr>isoptionalanddefaultstobroadcast.
Herearethegdbcommandsforconnectingtokgdbovernetworkport6443toIPaddress
192.168.1.2:
$gdb
...
(gdb)filevmlinux
(gdb)setremotebreak0
(gdb)targetremoteudp:192.168.1.2:6443
(gdb)continue
USBDriversinLinux
2Replies
Thiseleventharticle,whichispartoftheseriesonLinuxdevicedrivers,getsyoustartedwithwriting
yourfirstUSBdriverinLinux.
<<TenthArticle
Pugspendrivewasthedevice,Shwetawasplayingwith,whenbothofthemsatdowntoexplorethe
worldofUSBdriversinLinux.Thefastestwaytogethangofone,theusualPugsway,wastopickupa
USBdeviceandwriteadriverforittoexperimentwith.So,theychosependriveakaUSBstick,
availableathand.ItwasJetFlashfromTranscendwithvendorID0x058fandproductID0x6387.
USBdevicedetectioninLinux
WhetheradriverofaUSBdeviceisthereornotonaLinuxsystem,avalidUSBdevicewouldalways
getdetectedatthehardwareandkernelspacesofaUSBenabledLinuxsystem.AvalidUSBdeviceis
adevicedesignedanddetectedasperUSBprotocolspecifications.Hardwarespacedetectionisdone
bytheUSBhostcontrollertypicallyanativebusdevice,e.g.aPCIdeviceonx86systems.The
correspondinghostcontrollerdriverwouldpickandtranslatethelowlevelphysicallayerinformationinto
higherlevelUSBprotocolspecificinformation.TheUSBprotocolformattedinformationabouttheUSB
deviceisthenpopulatedintothegenericUSBcorelayer(usbcoredriver)inthekernelspace,thus
enablingthedetectionofaUSBdeviceinthekernelspace,evenwithouthavingitsspecificdriver.
Afterthis,itisuptothevariousdrivers,interfaces,andapplications(whicharedependentonthe
variousLinuxdistributions),tohavetheuserspaceviewofthedetecteddevices.Figure17showsatop
tobottomviewofUSBsubsysteminLinux.AbasiclistingofalldetectedUSBdevicescanbeobtained
usingthelsusbcommand,asroot.Figure18showsthesame,withoutandwiththependrivebeing
insertedintothesystem.Avoptiontolsusbprovidesdetailedinformation.InmanyLinuxdistributions
likeMandriva,Fedora,usbfsdriverisloadedaspartofthedefaultconfiguration.Thisenablesthe
detectedUSBdevicedetailstobeviewedinamoretechnofriendlywaythroughthe/procwindow
usingcat/proc/bus/usb/devices.Figure19showsatypicalsnippetofthesame,clippedaroundthepen
drivespecificsection.Thecompletelistingbasicallycontainssuchsections,eachforoneofthevalid
USBdevicesdetectedontheLinuxsystem.
Figure17:USBsubsysteminLinux
Figure18:Outputoflsusb
Figure19:USBsprocwindowsnippet
DecodingaUSBdevicesection
Tofurtherdecodethesesections,avalidUSBdeviceneedstobeunderstoodfirst.AllvalidUSBdevices
containoneormoreconfigurations.AconfigurationofaUSBdeviceislikeaprofile,wherethedefault
oneisthecommonlyusedone.Assuch,Linuxsupportsonlyoneconfigurationperdevicethedefault
one.Foreveryconfiguration,thedevicemayhaveoneormoreinterfaces.Aninterfacecorrespondsto
thefunctionalityprovidedbythedevice.Therewouldbeasmanyinterfacesasthenumberof
independentfunctionalitiesprovidedbythedevice.So,sayanMFD(multifunctiondevice)USBprinter
candoprinting,scanning,andfaxing,thenitmostlikelywouldhaveatleastthreeinterfacesonefor
eachofthefunctionalities.So,unlikeotherdevicedrivers,aUSBdevicedriveristypically
associated/writtenperinterface,ratherthanthedeviceasawholemeaningoneUSBdevicemay
havemultipledevicedrivers.Thoughdefinitelyoneinterfacecanhaveamaximumofonedriveronly.
Moreover,itisokayandcommontohaveasingleUSBdevicedriverforalltheinterfacesofaUSB
device.TheDriver=entryintheprocwindowoutput(Figure19)showstheinterfaceistodriver
mappinga(none)indicatingnoassociateddriver.Foreveryinterface,therewouldbeoneormore
endpoints.Anendpointislikeapipefortransferringinformationeitherintoorfromtheinterfaceofthe
device,dependingonthefunctionality.Dependingonthetypeofinformation,theendpointshavefour
types:
Control
Interrupt
Bulk
Isochronous
AsperUSBprotocolspecification,allvalidUSBdeviceshaveanimplicitspecialcontrolendpointzero,
theonlybidirectionalendpoint.Figure20showsthecompletepictorialrepresentationofavalidUSB
device,basedontheaboveexplanation.
ComingbacktotheUSBdevicesections(Figure19),thefirstletteroneachlinerepresentsthevarious
partsoftheUSBdevicespecificationjustexplained.Forexample,Dfordevice,Cforconfiguration,Ifor
interface,Eforendpoint,etc.Detailsaboutthemandvariousothersareavailableunderthekernel
sourceDocumentation/usb/proc_usb_info.txt
Figure20:USBdeviceoverview
TheUSBpendrivedriverregistration
SeemsliketherearesomanythingstoknowabouttheUSBprotocoltobeabletowritethefirstUSB
driveritselfdeviceconfiguration,interfaces,transferpipes,theirfourtypes,andsomanyother
symbolslikeT,B,S,underaUSBdevicespecification,sighedShweta.Yes,butdontyouworry
allthesecanbetalkedindetail,later.Letsdothefirstthingfirstgetourpendrivesinterface
associatedwithourUSBdevicedriver(pen_register.ko),consoledPugs.LikeanyotherLinuxdevice
driver,herealsoweneedtheconstructorandthedestructorbasicallythesamedrivertemplatethat
hasbeenusedforallthedrivers.Thoughthecontentwouldvaryasthisisahardwareprotocollayer
driver,i.e.ahorizontaldriverunlikeacharacterdriver,whichwasoneoftheverticaldrivers,discussed
sofar.Andthedifferencewouldbethatinsteadofregisteringwith&unregisteringfromVFS,herewe
woulddothatwiththecorrespondingprotocollayertheUSBcoreinthiscaseinsteadofprovidinga
userspaceinterfacelikeadevicefile,itwouldgetconnectedwiththeactualdeviceinthehardware
space.TheUSBcoreAPIsforthesameareasfollows(prototypedin<linux/usb.h>):
intusb_register(structusb_driver*driver);
voidusb_deregister(structusb_driver*);
Aspartoftheusb_driverstructure,thefieldstobeprovidedarethedriversname,IDtableforauto
detectingtheparticulardeviceandthe2callbackfunctionstobeinvokedbyUSBcoreduringhot
pluggingandhotremovalofthedevice,respectively.Puttingitalltogether,pen_register.cwouldlook
like:
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/usb.h>
staticintpen_probe(structusb_interface*interface,conststructusb_device_id*id)
{
printk(KERN_INFO"Pendrive(%04X:%04X)plugged\n",id>idVendor,
return0;
}
staticvoidpen_disconnect(structusb_interface*interface)
{
printk(KERN_INFO"Pendriveremoved\n");
}
staticstructusb_device_idpen_table[]=
{
{USB_DEVICE(0x058F,0x6387)},
{}/*Terminatingentry*/
};
id>idProduct);
MODULE_DEVICE_TABLE(usb,pen_table);
staticstructusb_driverpen_driver=
{
.name="pen_driver",
.id_table=pen_table,
.probe=pen_probe,
.disconnect=pen_disconnect,
};
staticint__initpen_init(void)
{
returnusb_register(&pen_driver);
}
staticvoid__exitpen_exit(void)
{
usb_deregister(&pen_driver);
}
module_init(pen_init);
module_exit(pen_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AnilKumarPugalia<email@sarikapugs.com>");
MODULE_DESCRIPTION("USBPenRegistrationDriver");
Then,theusualstepsforanyLinuxdevicedrivermayberepeated:
Buildthedriver(.kofile)byrunningmake
Loadthedriverusinginsmod
Listtheloadedmodulesusinglsmod
Unloadthedriverusingrmmod
Butsurprisinglytheresultswouldntbeasexpected.Checkfordmesgandtheprocwindowtoseethe
variouslogsanddetails.NotbecauseUSBdriverisdifferentfromacharacterdriver.Buttheresa
catch.Figure19showsthatthependrivehasoneinterface(numbered0),whichisalreadyassociated
withtheusualusbstoragedriver.Now,inordertogetourdriverassociatedwiththatinterface,weneed
tounloadtheusbstoragedriver(i.e.rmmodusbstorage)afterplugginginthependrive,andthenload
ourdriver.Oncethissequenceisfollowed,theresultswouldbeasexpected.Figure21showsa
glimpseofthepossiblelogsandprocwindowsnippet.Repeathotplugginginandhotpluggingoutthe
pendrivetoobservetheprobeanddisconnectcallsinactionbutdontforgetunloadingtheusb
storagedriver,everytimeyoupluginthependriver.
Figure21:Pendriverinaction
Summingup
Finally!!!Somethingintoaction.,relievedShweta.Butseemsliketherearesomanythingsaround
(likethedeviceIDtable,probe,disconnect,),yettobeassimilatedandunderstoodtogetacomplete
USBdevicedriver,inplace,shecontinued.Yes,youareright.Letstakethemonebyone,with
breaks,repliedPugstakingastretchingbreak.
TwelfthArticle>>
Notes
1. Makesurethatyoureplacethevendorid&deviceidintheabovecodeexamplesbytheones
ofyourpendrive.
2. Onemaywonder,ashowdoestheusbstoragegetautoloaded.Theanswerliesinthemodule
autoloadruleswrittendowninthefile/lib/modules/<kernel_version>/modules.usbmap.Ifyouare
anexpert,youmaycommentoutthecorrespondingline,forittonotgetautoloaded.And
uncommentitback,onceyouaredonewithyourexperiments.
3. Inlatestdistros,youmaynotfindthedetaileddescriptionoftheUSBdevicesusingcat
/proc/bus/usb/devices,asthe/proc/bus/usb/itselfhasbeendeprecated.Youcanfindthesame
detailedinfousingcat/sys/kernel/debug/usb/devicesthoughyoumayneedrootpermissions
forthesame.Also,ifyoudonotseeanyfileunder/sys/kernel/debug(evenasroot),thenyou
mayhavetofirstmountthedebugfilesystem,asfollows:mounttdebugfsnone
/sys/kernel/debug
USBDriversinLinux(Continued)
2Replies
Thistwelftharticle,whichispartoftheseriesonLinuxdevicedrivers,getsyoufurtherwithwritingyour
firstUSBdriverinLinuxacontinuationfromthepreviousarticle.
<<EleventhArticle
Pugscontinued,LetsbuildupontheUSBdevicedrivercodedinourprevioussession,usingthesame
handyJetFlashpendrivefromTranscendwithvendorid0x058fandproductid0x6387.Forthat,we
woulddigfurtherintotheUSBprotocolandthenconvertthelearningintocode.
USBendpointsandtheirtypes
Dependingonthetypeandattributesofinformationtobetransferred,aUSBdevicemayhaveoneor
moreendpoints,eachbelongingtooneofthefollowingfourcategories:
ControlFortransferringcontrolinformation.Examplesincluderesettingthedevice,querying
informationaboutthedevice,etc.AllUSBdevicesalwayshavethedefaultcontrolendpointpoint
zero.
InterruptForsmallandfastdatatransfer,typicallyofupto8bytes.Examplesincludedata
transferforserialports,humaninterfacedevices(HIDs)likekeyboard,mouse,etc.
BulkForbigbutcomparativelyslowdatatransfer.Atypicalexampleisdatatransfersformass
storagedevices.
IsochronousForbigdatatransferwithbandwidthguarantee,thoughdataintegritymaynotbe
guaranteed.Typicalpracticalusageexamplesincludetransfersoftimesensitivedatalikeof
audio,video,etc.
Additionally,allbutcontrolendpointscouldbeinorout,indicatingitsdirectionofdatatransfer.in
indicatesdataflowfromUSBdevicetothehostmachineandoutindicatesdataflowfromthehost
machinetoUSBdevice.Technically,anendpointisidentifiedusinga8bitnumber,mostsignificantbit
(MSB)ofwhichindicatesthedirection0meaningout,and1meaningin.Controlendpointsarebi
directionalandtheMSBisignored.
Figure19:USBsprocwindowsnippet
Figure19showsatypicalsnippetofUSBdevicespecificationsfordevicesconnectedonasystem.To
bespecific,theE:linesinthefigureshowsexampleofaninterruptendpointofaUHCIHostController
andtwobulkendpointsofthependriveunderconsideration.Also,theendpointnumbers(inhex)are
respectively0x81,0x01,0x82.TheMSBofthefirstandthirdbeing1indicatinginendpoints,
representedby(I)inthefigure.Secondoneisan(O)fortheoutendpoint.MaxPSspecifiesthe
maximumpacketsize,i.e.thedatasizethatcanbetransferredinasinglego.Againasexpected,for
theinterruptendpointitis2(<=8),and64forthebulkendpoints.Ivlspecifiestheintervalin
millisecondstobegivenbetweentwoconsecutivedatapackettransfersforpropertransferandismore
significantfortheinterruptendpoints.
DecodingaUSBdevicesection
AswehavejustdiscussedtheE:line,itisrighttimetodecodetherelevantfieldsofothersaswell.In
short,theselinesinaUSBdevicesectiongivesthecompleteoverviewoftheUSBdeviceasperthe
USBspecifications,asdiscussedinourpreviousarticle.ReferbacktoFigure19.Thefirstletterofthe
firstlineofeverydevicesectionisaTindicatingthepositionofthedeviceintheUSBtree,uniquely
identifiedbythetriplet<usbbusnumber,usbtreelevel,usbport>.Drepresentsthedevicedescriptor
containingatleastthedeviceversion,deviceclass/category,andthenumberofconfigurationsavailable
forthisdevice.TherewouldbeasmanyClinesasthenumberofconfigurations,typicallyone.C
representstheconfigurationdescriptorcontainingitsindex,deviceattributesinthisconfiguration,
maximumpower(actuallycurrent)thedevicewoulddrawinthisconfiguration,andthenumberof
interfacesunderthisconfiguration.DependingonthattherewouldbeatleastthatmanyIlines.There
couldbemoreincaseofaninterfacehavingalternates,i.e.sameinterfacenumberbutwithdifferent
propertiesatypicalscenarioforwebcams.
Irepresentstheinterfacedescriptorwithitsindex,alternatenumber,functionalityclass/categoryof
thisinterface,driverassociatedwiththisinterface,andthenumberofendpointsunderthisinterface.
Theinterfaceclassmayormaynotbesameasthatofthedeviceclass.Anddependingonthenumber
ofendpoints,therewouldbeasmanyElines,detailsofwhichhavealreadybeendiscussedabove.
The*aftertheC&Irepresentsthecurrentlyactiveconfigurationandinterface,respectively.P
lineprovidesthevendorid,productid,andtheproductrevision.Slinesarestringdescriptorsshowing
upsomevendorspecificdescriptiveinformationaboutthedevice.
Peepinginto/proc/bus/usb/devicesisgoodtofigureoutwhetheradevicehasbeendetectedornot
andpossiblytogetthefirstcutoverviewofthedevice.Butmostprobablythisinformationwouldbe
requiredinwritingthedriverforthedeviceaswell.So,isthereawaytoaccessitusingCcode?,
askedShweta.Yesdefinitely,thatswhatIamgoingtotellyounext.Doyourememberthatassoonas
aUSBdeviceispluggedintothesystem,theUSBhostcontrollerdriverpopulatesitsinformationinto
thegenericUSBcorelayer?Tobeprecise,itputsthatintoasetofstructuresembeddedintoone
another,exactlyaspertheUSBspecifications,repliedPugs.Thefollowingaretheexactdata
structuresdefinedin<linux/usb.h>orderedhereinreverseforflowclarity:
structusb_device
{
...
structusb_device_descriptordescriptor;
structusb_host_config*config,*actconfig;
...
};
structusb_host_config
{
structusb_config_descriptordesc;
...
structusb_interface*interface[USB_MAXINTERFACES];
...
};
structusb_interface
{
structusb_host_interface*altsetting/*array*/,*cur_altsetting;
...
};
structusb_host_interface
{
structusb_interface_descriptordesc;
structusb_host_endpoint*endpoint/*array*/;
...
};
structusb_host_endpoint
{
structusb_endpoint_descriptordesc;
...
};
So,withaccesstothestructusb_devicehandleforaspecificdevice,alltheUSBspecificinformation
aboutthedevicecanbedecoded,asshownthroughthe/procwindow.Buthowtogetthedevice
handle?Infact,thedevicehandleisnotavailabledirectlyinadriver,rathertheperinterfacehandles
(pointerstostructusb_interface)areavailable,asUSBdriversarewrittenfordeviceinterfacesrather
thanthedeviceasawhole.Recallthattheprobe&disconnectcallbacks,whichareinvokedbyUSB
coreforeveryinterfaceoftheregistereddevice,havethecorrespondinginterfacehandleastheirfirst
parameter.Refertheprototypesbelow:
int(*probe)(structusb_interface*interface,conststructusb_device_id*id);
void(*disconnect)(structusb_interface*interface);
Sowiththeinterfacepointer,allinformationaboutthecorrespondinginterfacecanbeaccessed.Andto
getthecontainerdevicehandle,thefollowingmacrocomestorescue:
structusb_devicedevice=interface_to_usbdev(interface);
Addingthesenewlearningintothelastmonthsregistrationonlydriver,getsthefollowingcodelisting
(pen_info.c):
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/usb.h>
staticstructusb_device*device;
staticintpen_probe(structusb_interface*interface,conststructusb_device_id*id)
{
structusb_host_interface*iface_desc;
structusb_endpoint_descriptor*endpoint;
inti;
iface_desc=interface>cur_altsetting;
printk(KERN_INFO"Peni/f%dnowprobed:(%04X:%04X)\n",
iface_desc>desc.bInterfaceNumber,
id>idVendor,id>idProduct);
printk(KERN_INFO"ID>bNumEndpoints:%02X\n",
printk(KERN_INFO"ID>bInterfaceClass:%02X\n",
for(i=0;i<iface_desc>desc.bNumEndpoints;i++)
endpoint=&iface_desc>endpoint[i].desc;
printk(KERN_INFO"ED[%d]>bEndpointAddress:0x%02X\n",
printk(KERN_INFO"ED[%d]>bmAttributes:0x%02X\n",
printk(KERN_INFO"ED[%d]>wMaxPacketSize:0x%04X(%d)\n",
i,endpoint>wMaxPacketSize,
endpoint>wMaxPacketSize);
device=interface_to_usbdev(interface);
return0;
iface_desc>desc.bNumEndpoints);
iface_desc>desc.bInterfaceClass);
i,endpoint>bEndpointAddress);
i,endpoint>bmAttributes);
}
staticvoidpen_disconnect(structusb_interface*interface)
{
printk(KERN_INFO"Peni/f%dnowdisconnected\n",
interface>cur_altsetting>desc.bInterfaceNumber);
}
staticstructusb_device_idpen_table[]=
{
{USB_DEVICE(0x058F,0x6387)},
{}/*Terminatingentry*/
};
MODULE_DEVICE_TABLE(usb,pen_table);
staticstructusb_driverpen_driver=
.name="pen_info",
.probe=pen_probe,
.disconnect=pen_disconnect,
.id_table=pen_table,
};
staticint__initpen_init(void)
{
returnusb_register(&pen_driver);
}
staticvoid__exitpen_exit(void)
{
usb_deregister(&pen_driver);
}
module_init(pen_init);
module_exit(pen_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AnilKumarPugalia<email@sarikapugs.com>");
MODULE_DESCRIPTION("USBPenInfoDriver");
Then,theusualstepsforanyLinuxdevicedrivermayberepeated,alongwiththependrivesteps:
Buildthedriver(pen_info.kofile)byrunningmake.
Loadthedriverusinginsmodpen_info.ko.
Pluginthependrive(aftermakingsurethatusbstoragedriverisnotalreadyloaded).
Unplugoutthependrive.
Checktheoutputofdmesgforthelogs.
Unloadthedriverusingrmmodpen_info.
Figure22showsasnippetoftheabovestepsonPugssystem.Remembertoensure(intheoutput
ofcat/proc/bus/usb/devices)thattheusualusbstoragedriverisnottheoneassociatedwiththepen
driveinterface,ratheritshouldbethepen_infodriver.
Figure22:Outputofdmesg
Summingup
Beforetakinganotherbreak,Pugssharedtwoofthemanymechanismsforadrivertospecifyitsdevice
totheUSBcore,usingthestructusb_device_idtable.Firstoneisbyspecifyingthe<vendorid,product
id>pairusingtheUSB_DEVICE()macro(asdoneabove),andthesecondoneisbyspecifyingthe
deviceclass/categoryusingtheUSB_DEVICE_INFO()macro.Infact,manymoremacrosareavailable
in<linux/usb.h>forvariouscombinations.Moreover,multipleofthesemacroscouldbespecifiedin
theusb_device_idtable(terminatedbyanullentry),formatchingwithanyoneofthecriteria,enabling
towriteasingledriverforpossiblymanydevices.
Earlieryoumentionedwritingmultipledriversforasingledevice,aswell.Basically,howdowe
selectivelyregisterornotregisteraparticularinterfaceofaUSBdevice?,queriedShweta.Sure.
Thatsnextinlineofourdiscussion,alongwiththeultimatetaskinanydevicedriverthedatatransfer
mechanismsrepliedPugs.
ThirteenthArticle>>
Notes
1. Makesurethatyoureplacethevendorid&deviceidintheabovecodeexamplesbytheones
ofyourpendrive.
2. Onemaywonder,ashowdoestheusbstoragegetautoloaded.Theanswerliesinthemodule
autoloadruleswrittendowninthefile/lib/modules/<kernel_version>/modules.usbmap.Ifyouare
anexpert,youmaycommentoutthecorrespondingline,forittonotgetautoloaded.And
uncommentitback,onceyouaredonewithyourexperiments.
3. Inlatestdistros,youmaynotfindthedetaileddescriptionoftheUSBdevicesusingcat
/proc/bus/usb/devices,asthe/proc/bus/usb/itselfhasbeendeprecated.Youcanfindthesame
detailedinfousingcat/sys/kernel/debug/usb/devicesthoughyoumayneedrootpermissions
forthesame.Also,ifyoudonotseeanyfileunder/sys/kernel/debug(evenasroot),thenyou
mayhavetofirstmountthedebugfilesystem,asfollows:mounttdebugfsnone
/sys/kernel/debug
USBDriversinLinux:DataTransferto&fromUSBDevices
12Replies
Thisthirteentharticle,whichispartoftheseriesonLinuxdevicedrivers,detailsouttheultimatestepof
datatransfertoandfromaUSBdeviceusingyourfirstUSBdriverinLinuxacontinuationfromthe
previoustwoarticles.
<<TwelfthArticle
USBmiscellany
Pugscontinued,Toansweryourquestionabouthowadriverselectivelyregistersorskipsaparticular
interfaceofaUSBdevice,youneedtounderstandthesignificanceofthereturnvalueofprobe()
callback.NotethattheUSBcorewouldinvokeprobeforalltheinterfacesofadetecteddevice,except
theoneswhicharealreadyregistered.So,forthefirsttime,itwouldcallforall.Now,iftheprobereturns
0,itmeansthedriverhasregisteredforthatinterface.Returninganerrorcodeindicatesnotregistering
forit.Thatsall.Thatwassimple,commentedShweta.
Now,letstalkabouttheultimatedatatransfersto&fromaUSBdevice,continuedPugs.Butbefore
thattellmewhatisthisMODULE_DEVICE_TABLE?ThisisbotheringmesinceyouexplainedtheUSB
deviceidtablemacros,askedShwetapausingPugs.Thatsanothertrivialstuff.Itismainlyforthe
userspacedepmod,saidPugs.Moduleisanothernameforadriver,whichisdynamicallyloadable
andunloadable.ThemacroMODULE_DEVICE_TABLEgeneratestwovariablesinamodulesreadonly
section,whichisextractedbydepmodandstoredinglobalmapfiles
under/lib/modules/<kernel_version>.modules.usbmapandmodules.pcimaparetwosuchfilesforUSB
&PCIdevicedrivers,respectively.Thisenablesautoloadingofthesedrivers,aswesawusbstorage
drivergettingautoloaded.
USBdatatransfer
TimeforUSBdatatransfers.LetsbuildupontheUSBdevicedrivercodedinourprevioussessions,
usingthesamehandyJetFlashpendrivefromTranscendwithvendorid0x058fandproductid
0x6387.
USBbeingahardwareprotocol,itformstheusualhorizontallayerinthekernelspace.Andhenceforit
toprovideaninterfacetouserspace,ithastoconnectthroughoneoftheverticallayers.Ascharacter
(driver)verticalisalreadydiscussed,itisthecurrentpreferredchoicefortheconnectionwiththeUSB
horizontal,forunderstandingthecompletedatatransferflow.Also,wedonotneedtogetafree
unreservedcharactermajornumber,butcanusethecharactermajornumber180,reservedforUSB
basedcharacterdevicefiles.Moreover,toachievethiscompletecharacterdriverlogicwithUSB
horizontalinonego,thefollowingaretheAPIsdeclaredin<linux/usb.h>:
intusb_register_dev(structusb_interface*intf,
structusb_class_driver*class_driver);
voidusb_deregister_dev(structusb_interface*intf,
structusb_class_driver*class_driver);
Usually,wewouldexpectthesefunctionstobeinvokedintheconstructorandthedestructorofa
module,respectively.However,toachievethehotplugnplaybehaviourforthe(character)devicefiles
correspondingtoUSBdevices,theseareinsteadinvokedintheprobeandthedisconnectcallbacks,
respectively.Firstparameterintheabovefunctionsistheinterfacepointerreceivedasthefirst
parameterinbothprobeanddisconnect.Secondparameterstructusb_class_driverneedstobe
populatedwiththesuggesteddevicefilenameandthesetofdevicefileoperations,before
invokingusb_register_dev().Fortheactualusage,refertothe
functionspen_probe()andpen_disconnect()inthecodelistingofpen_driver.cbelow.
Moreover,asthefileoperations(write,read,)arenowprovided,thatiswhereexactlyweneedtodo
thedatatransferstoandfromtheUSBdevice.So,pen_write()andpen_read()belowshowsthe
possiblecallstousb_bulk_msg()(prototypedin<linux/usb.h>)todothetransfersoverthependrives
bulkendpoints0x01and0x82,respectively.RefertotheElinesofthemiddlesectioninFigure19for
theendpointnumberlistingsofourpendrive.Refertotheheaderfile<linux/usb.h>underkernel
sources,forthecompletelistofUSBcoreAPIprototypesfortheotherendpointspecificdatatransfer
functionslikeusb_control_msg(),usb_interrupt_msg(),etc.usb_rcvbulkpipe(),usb_sndbulkpipe(),and
manysuchothermacros,alsodefinedin<linux/usb.h>,computetheactualendpointbitmasktobe
passedtothevariousUSBcoreAPIs.
Figure19:USBsprocwindowsnippet
NotethatapendrivebelongstoaUSBmassstorageclass,whichexpectsasetofSCSIlikecommands
tobetransactedoverthebulkendpoints.So,arawread/writeasshowninthecodelistingbelowmay
notreallydoadatatransferasexpected,unlessthedataisappropriatelyformatted.Butstill,this
summarizestheoverallcodeflowofaUSBdriver.TogetafeelofrealworkingUSBdatatransferina
simpleandelegantway,onewouldneedsomekindofcustomUSBdevice,somethingliketheone
availableateSrijan.
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/usb.h>
#defineMIN(a,b)(((a)<=(b))?(a):(b))
#defineBULK_EP_OUT0x01
#defineBULK_EP_IN0x82
#defineMAX_PKT_SIZE512
staticstructusb_device*device;
staticstructusb_class_driverclass;
staticunsignedcharbulk_buf[MAX_PKT_SIZE];
staticintpen_open(structinode*i,structfile*f)
return0;
}
staticintpen_close(structinode*i,structfile*f)
{
return0;
}
staticssize_tpen_read(structfile*f,char__user*buf,size_tcnt,loff_t*off)
{
intretval;
intread_cnt;
/*Readthedatafromthebulkendpoint*/
retval=usb_bulk_msg(device,usb_rcvbulkpipe(device,BULK_EP_IN),
if(retval)
printk(KERN_ERR"Bulkmessagereturned%d\n",retval);
returnretval;
if(copy_to_user(buf,bulk_buf,MIN(cnt,read_cnt)))
returnMIN(cnt,read_cnt);
bulk_buf,MAX_PKT_SIZE,&read_cnt,5000);
returnEFAULT;
}
staticssize_tpen_write(structfile*f,constchar__user*buf,size_tcnt,
loff_t*off)
intretval;
intwrote_cnt=MIN(cnt,MAX_PKT_SIZE);
if(copy_from_user(bulk_buf,buf,MIN(cnt,MAX_PKT_SIZE)))
/*Writethedataintothebulkendpoint*/
retval=usb_bulk_msg(device,usb_sndbulkpipe(device,BULK_EP_OUT),
if(retval)
printk(KERN_ERR"Bulkmessagereturned%d\n",retval);
returnretval;
returnwrote_cnt;
returnEFAULT;
bulk_buf,MIN(cnt,MAX_PKT_SIZE),&wrote_cnt,5000);
staticstructfile_operationsfops=
{
.open=pen_open,
.release=pen_close,
.read=pen_read,
.write=pen_write,
};
staticintpen_probe(structusb_interface*interface,conststructusb_device_id*id)
{
intretval;
device=interface_to_usbdev(interface);
class.name="usb/pen%d";
class.fops=&fops;
if((retval=usb_register_dev(interface,&class))<0)
/*Somethingpreventedusfromregisteringthisdriver*/
printk(KERN_ERR"Notabletogetaminorforthisdevice.");
else
returnretval;
printk(KERN_INFO"Minorobtained:%d\n",interface>minor);
}
staticvoidpen_disconnect(structusb_interface*interface)
{
usb_deregister_dev(interface,&class);
}
/*Tableofdevicesthatworkwiththisdriver*/
staticstructusb_device_idpen_table[]=
{
{USB_DEVICE(0x058F,0x6387)},
{}/*Terminatingentry*/
};
MODULE_DEVICE_TABLE(usb,pen_table);
staticstructusb_driverpen_driver=
{
.name="pen_driver",
.probe=pen_probe,
.disconnect=pen_disconnect,
.id_table=pen_table,
};
staticint__initpen_init(void)
{
intresult;
/*RegisterthisdriverwiththeUSBsubsystem*/
if((result=usb_register(&pen_driver)))
returnresult;
printk(KERN_ERR"usb_registerfailed.Errornumber%d",result);
}
staticvoid__exitpen_exit(void)
{
/*DeregisterthisdriverwiththeUSBsubsystem*/
usb_deregister(&pen_driver);
}
module_init(pen_init);
module_exit(pen_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AnilKumarPugalia<email@sarikapugs.com>");
MODULE_DESCRIPTION("USBPenDeviceDriver");
Asareminder,theusualstepsforanyLinuxdevicedrivermayberepeatedwiththeabovecode,along
withthependrivesteps:
Buildthedriver(pen_driver.kofile)byrunningmake.
Loadthedriverusinginsmodpen_driver.ko.
Pluginthependrive(aftermakingsurethatusbstoragedriverisnotalreadyloaded).
Checkforthedynamiccreationof/dev/pen0.(0beingtheminornumberobtained
checkdmesglogsforthevalueonyoursystem)
Possiblytrysomewrite/readon/dev/pen0.(Thoughyoumaymostlygetconnectiontimeout
and/orbrokenpipeerrorsbecauseofnonconformantSCSIcommands)
Unplugoutthependriveandlookoutforgone/dev/pen0.
Unloadthedriverusingrmmodpen_driver.
Summingup
Meanwhile,PugshookeduphisfirstofitskindcreationtheLinuxdevicedriverkit(LDDK)intohis
systemtoshowalivedemonstrationoftheUSBdatatransfers.Aha!Finallyacoolcompleteworking
USBdriver,quippedexcitedShweta.Wanttohavemorefun.Wecoulddoablockdriveroverit,
addedPugs.O!Really,Shwetaaskedwithagleeonherface.Yes.Butbeforethatwewouldneedto
understandthepartitioningmechanisms,commentedPugs.
FourteenthArticle>>
Notes
1. Makesurethatyoureplacethevendorid&deviceidintheabovecodeexamplesbytheones
ofyourpendrive.Also,makesurethattheendpointnumbersusedintheabovecodeexamples
matchtheendpointnumbersofyourpendrive.Otherwise,youmaygetanerrorlikebulk
messagereturnederror22Invalidargument,whilereadingfrompendevice.
2. Also,makesurethatthedriverfromthepreviousarticleisunloaded,i.e.pen_infoisnotloaded.
Otherwise,itmaygiveanerrormessageinsmod:errorinsertingpen_driver.ko':1Deviceor
resourcebusy,whiledoinginsmodpen_driver.ko.
3. Onemaywonder,ashowdoestheusbstoragegetautoloaded.Theanswerliesinthemodule
autoloadruleswrittendowninthefile/lib/modules/<kernel_version>/modules.usbmap.Ifyouare
anexpert,youmaycommentoutthecorrespondingline,forittonotgetautoloaded.And
uncommentitback,onceyouaredonewithyourexperiments.
4. Inlatestdistros,youmaynotfindthedetaileddescriptionoftheUSBdevicesusingcat
/proc/bus/usb/devices,asthe/proc/bus/usb/itselfhasbeendeprecated.Youcanfindthesame
detailedinfousingcat/sys/kernel/debug/usb/devicesthoughyoumayneedrootpermissions
forthesame.Also,ifyoudonotseeanyfileunder/sys/kernel/debug(evenasroot),thenyou
mayhavetofirstmountthedebugfilesystem,asfollows:mounttdebugfsnone
/sys/kernel/debug.
UnderstandingthePartitions:Adiveinsidetheharddisk
2Replies
Thisfourteentharticle,whichispartoftheseriesonLinuxdevicedrivers,takesyouforawalkinsidea
harddisk.
<<ThirteenthArticle
Harddiskdesign
Doesntitsoundlikeamechanicalengineeringsubject:Designofharddisk?,questionedShweta.
Yes,itdoes.Butunderstandingitgetsusaninsightintoitsprogrammingaspect,reasonedPugswhile
waitingforthecommencementoftheseminaronstoragesystems.
Figure23:Partitionlistingbyfdisk
Theseminarstartedwithafewharddisksinthepresentershandandthenadivedownintohersystem
showingtheoutputoffdiskl,asshowninFigure23.Thefirstlineshowstheharddisksizeinhuman
friendlyformatandinbytes.Thesecondlinementionsthenumberoflogicalheads,logicalsectorsper
track,andtheactualnumberofcylindersonthediskthesetogetherarereferredasthegeometryof
thedisk.The255headsindicatingthenumberofplattersordisks,asonereadwriteheadisneeded
perdisk.LetsnumberthemsayD1,D2,,D255.Now,eachdiskwouldhavethesamenumberof
concentriccirculartracks,startingfromoutsidetoinside.Intheabovecasethereare60801suchtracks
perdisk.LetsnumberthemsayT1,T2,,T60801.Andaparticulartracknumberfromallthedisks
formsacylinderofthesamenumber.Forexample,tracksT2fromD1,D2,,D255willalltogether
formthecylinderC2.Now,eachtrackhasthesamenumberoflogicalsectors63inourcase,sayS1,
S2,,S63.Andeachsectoristypically512bytes.Giventhisdata,onecanactuallycomputethetotal
usableharddisksize,usingthefollowingformula:
Usableharddisksizeinbytes=(Numberofheadsordisks)*(Numberoftracksperdisk)*(Numberof
sectorspertrack)*(Numberofbytespersector,i.e.sectorsize).
Forthediskunderconsiderationitwouldbe:255*60801*63*512bytes=500105249280bytes.Note
thatthisnumbermaybeslightlylessthantheactualharddisk500107862016bytesinourcase.The
reasonforthatisthattheformuladoesntconsiderthebytesinlastpartialorincompletecylinder.And
theprimaryreasonforthatisthedifferencebetweentodaystechnologyoforganizingtheactual
physicaldiskgeometryandthetraditionalgeometryrepresentationusingheads,cylinders,sectors.
Notethatinthefdiskoutput,wereferredtotheheads,andsectorspertrackaslogicalnottheactual
one.Onemayask,iftodaysdisksdoesnthavesuchphysicalgeometryconcepts,thenwhytostill
maintainthatandrepresenttheminmoreoflogicalform.Themainreasonistobeabletocontinuewith
sameconceptsofpartitioningandbeabletomaintainthesamepartitiontableformatsespeciallyforthe
mostprevalentDOStypepartitiontables,whichheavilydependonthissimplisticgeometry.Notethe
computationofcylindersize(255heads*63sectors/track*512bytes/sector=8225280bytes)inthe
thirdlineandthenthedemarcationofpartitionsinunitsofcompletecylinders.
DOStypepartitiontables
ThisbringsustothenextimportanttopicofunderstandingtheDOStypepartitiontables.Butinthefirst
place,whatisapartitionandratherwhytoevenpartition?Aharddiskcanbedividedintoonemore
logicaldisks,eachofwhichiscalledapartition.Andthisisusefulfororganizingdifferenttypesofdata
separately.Forexample,differentoperatingsystemdata,userdata,temporarydata,etc.So,partitions
arebasicallylogicaldivisionsandhenceneedtobemaintainedthroughsomemetadata,whichisthe
partitiontable.ADOStypepartitiontablecontains4partitionentries,eachbeinga16byteentry.And
eachoftheseentriescanbedepictedbythefollowingCstructure:
typedefstruct
{
unsignedcharboot_type;//0x00Inactive;0x80Active(Bootable)
unsignedcharstart_head;
unsignedcharstart_sec:6;
unsignedcharstart_cyl_hi:2;
unsignedcharstart_cyl;
unsignedcharpart_type;
unsignedcharend_head;
unsignedcharend_sec:6;
unsignedcharend_cyl_hi:2;
unsignedcharend_cyl;
unsignedintabs_start_sec;
unsignedintsec_in_part;
}PartEntry;
Andthispartitiontablefollowedbythetwobytesignature0xAA55residesattheendofharddisksfirst
sector,commonlyknownasMasterBootRecordorMBR(inshort).Hence,thestartingoffsetofthis
partitiontablewithintheMBRis512(4*16+2)=446.Also,a4bytedisksignatureisplacedatthe
offset440.Theremainingtop440bytesoftheMBRaretypicallyusedtoplacethefirstpieceofboot
code,thatisloadedbytheBIOStobootupthesystemfromthedisk.Listingofpart_info.ccontains
thesevariousdefines,andthecodeforparsingandprintingaformattedoutputofthepartitiontableof
onesharddisk.
Fromthepartitiontableentrystructure,itcouldbenotedthatthestartandendcylinderfieldsareonly
10bitslong,thusallowingamaximumof1023cylindersonly.However,fortodayshugeharddisks,this
sizeisnowaysufficient.Andhenceinoverflowcases,thecorresponding<head,cylinder,sector>triplet
inthepartitiontableentryissettothemaximumvalue,andtheactualvalueiscomputedusingthelast
twofields:theabsolutestartsectornumber(abs_start_sec)andthenumberofsectorsinthispartition
(sec_in_part).Listingofpart_info.ccontainsthecodeforthisaswell.
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#defineMBR_DISK_SIGNATURE_OFFSET440
#definePARTITION_ENTRY_SIZE16//sizeof(PartEntry)
#definePARTITION_TABLE_SIZE(4*PARTITION_ENTRY_SIZE)
typedefstruct
{
unsignedcharboot_type;//0x00Inactive;0x80Active(Bootable)
unsignedcharstart_head;
unsignedcharstart_sec:6;
unsignedcharstart_cyl_hi:2;
unsignedcharstart_cyl;
unsignedcharpart_type;
unsignedcharend_head;
unsignedcharend_sec:6;
unsignedcharend_cyl_hi:2;
unsignedcharend_cyl;
unsignedintabs_start_sec;
unsignedintsec_in_part;
}PartEntry;
typedefstruct
{
unsignedcharboot_code[MBR_DISK_SIGNATURE_OFFSET];
unsignedintdisk_signature;
unsignedshortpad;
unsignedcharpt[PARTITION_TABLE_SIZE];
unsignedshortsignature;
}MBR;
voidprint_computed(unsignedintsector)
{
unsignedintheads,cyls,tracks,sectors;
sectors=sector%63+1/*Asindexedfrom1*/;
tracks=sector/63;
cyls=tracks/255+1/*Asindexedfrom1*/;
heads=tracks%255;
printf("(%3d/%5d/%1d)",heads,cyls,sectors);
}
intmain(intargc,char*argv[])
{
char*dev_file="/dev/sda";
intfd,i,rd_val;
MBRm;
PartEntry*p=(PartEntry*)(m.pt);
if(argc==2)
if((fd=open(dev_file,O_RDONLY))==1)
dev_file=argv[1];
fprintf(stderr,"Failedopening%s:",dev_file);
perror("");
return1;
if((rd_val=read(fd,&m,sizeof(m)))!=sizeof(m))
fprintf(stderr,"Failedreading%s:",dev_file);
perror("");
close(fd);
return2;
close(fd);
printf("\nDOStypePartitionTableof%s:\n",dev_file);
printf("BStart(H/C/S)End(H/C/S)TypeStartSecTotSec\n");
for(i=0;i<4;i++)
printf("%d:%d(%3d/%4d/%2d)(%3d/%4d/%2d)%02X%10d%9d\n",
i+1,!!(p[i].boot_type&0x80),
p[i].start_head,
1+((p[i].start_cyl_hi<<8)|p[i].start_cyl),
p[i].start_sec,
p[i].end_head,
1+((p[i].end_cyl_hi<<8)|p[i].end_cyl),
p[i].end_sec,
p[i].part_type,
p[i].abs_start_sec,p[i].sec_in_part);
printf("\nRecomputedPartitionTableof%s:\n",dev_file);
printf("BStart(H/C/S)End(H/C/S)TypeStartSecTotSec\n");
for(i=0;i<4;i++)
printf("%d:%d",i+1,!!(p[i].boot_type&0x80));
print_computed(p[i].abs_start_sec);
printf("");
print_computed(p[i].abs_start_sec+p[i].sec_in_part1);
printf("%02X%10d%9d\n",p[i].part_type,
printf("\n");
return0;
p[i].abs_start_sec,p[i].sec_in_part);
Astheabovecode(part_info.c)isanapplication,compileittoanexecutable(./part_info)asfollows:gcc
part_info.copart_info,andthenrun./part_info/dev/sdatocheckoutyourprimarypartitioning
informationon/dev/sda.Figure24showstheoutputof./part_infoonthepresenterssystem.Compare
itwiththefdiskoutputasshowninfigure23.
Figure24:Outputof./part_info
PartitiontypesandBootrecords
Nowasthispartitiontableishardcodedtohave4entries,thatdictatesthemaximumnumberof
partitions,wecanhave,namely4.Thesearecalledprimarypartitions,eachhavingatypeassociated
withthemintheircorrespondingpartitiontableentry.Thevarioustypesaretypicallycoinedbythe
variousoperatingsystemvendorsandhenceitisasortofmappingtothevariousoperatingsystems,
forexample,DOS,Minix,Linux,Solaris,BSD,FreeBSD,QNX,W95,NovellNetware,etctobeused
for/withtheparticularoperatingsystem.However,thisismoreofegoandformalitythanareal
requirement.
Apartfromthese,oneofthe4primarypartitionscanactuallybelabeledassomethingreferredasan
extendedpartition,andthathasaspecialsignificance.Asnamesuggests,itisusedtofurtherextend
theharddiskdivision,i.e.tohavemorepartitions.Thesemorepartitionsarereferredaslogical
partitionsandarecreatedwithintheextendedpartition.Metadataofthelogicalpartitionsismaintained
inalinkedlistformat,allowingunlimitednumberoflogicalpartitions,atleasttheoretically.Forthat,the
firstsectoroftheextendedpartition,commonlyreferredtoasBootRecordorBR(inshort),isusedina
similarwayasMBRtostore(thelinkedlistheadof)thepartitiontableforthelogicalpartitions.
Subsequentlinkedlistnodesarestoredinthefirstsectorofthesubsequentlogicalpartitions,referred
toasLogicalBootRecordorLBR(inshort).Eachlinkedlistnodeisacomplete4entrypartitiontable,
thoughonlythefirsttwoentriesareusedthefirstforthelinkedlistdata,namely,informationaboutthe
immediatelogicalpartition,andsecondoneasthelinkedlistsnextpointer,pointingtothelistof
remaininglogicalpartitions.
Tocompareandunderstandtheprimarypartitioningdetailsonyoursystemsharddisk,followthese
steps(asrootuserandhencewithcare):
./part_info/dev/sda#Displaysthepartitiontableon/dev/sda
fdiskl/dev/sda#Todisplayandcomparethepartitiontableentrieswiththeabove
Incaseyouhavemultipleharddisks(/dev/sdb,),orharddiskdevicefilewithothernames(/dev/hda,
),oranextendedpartition,youmaytry./part_info<device_file_name>onthemaswell.Tryingonan
extendedpartitionwouldgivetheinformationaboutthestartingpartitiontableofthelogicalpartitions.
Summingup
Rightnowwecarefullyandselectivelyplayed(readonly)withoursystemsharddisk.Whycarefully?
Asotherwise,wemayrenderoursystemnonbootable.Butnolearningiscompletewithoutatotal
exploration.Hence,inourpostlunchsession,wewouldcreateadummydiskonRAManddothe
destructiveexplorations.
FifteenthArticle>>
DiskonRAM:Playingdestructively
6Replies
Thisfifteentharticle,whichispartoftheseriesonLinuxdevicedrivers,experimentswithadummyhard
diskonRAMtodemonstratetheblockdrivers.
<<FourteenthArticle
Playfirst,ruleslater
Afteradeliciouslunch,theorymakesaudiencesleepy.So,letsstartwiththecodeitself.Code
demonstratedisavailableatdor_code.tar.bz2.Thistarballcontains3Csourcefiles,2Cheaders,
andaMakefile.Asusual,executingmakewillbuildthediskonramdriver(dor.ko)thistime
combiningthe3Cfiles.CheckouttheMakefiletoseehow.makecleanwoulddotheusualcleanof
thebuiltstuff.
Oncebuilt,thefollowingaretheexperimentingsteps(RefertoFigures25,26,27):
Loadthedriverdor.kousinginsmod.Thiswouldcreatetheblockdevicefilesrepresentingthe
diskon512KibiBytes(KiB)ofRAM,with3primaryand3logicalpartitions.
Checkouttheautomaticallycreatedblockdevicefiles(/dev/rb*)./dev/rbistheentirediskof512
KiBsize.rb1,rb2,rb3aretheprimarypartitionswithrb2beingtheextendedpartitionand
containingthe3logicalpartitionsrb5,rb6,rb7.
Readtheentiredisk(/dev/rb)usingthediskdumputilitydd.
Zerooutthefirstsectorofthedisksfirstpartition(/dev/rb1)againusingdd.
Writesometextintothedisksfirstpartition(/dev/rb1)usingcat.
Displaytheinitialcontentsofthefirstpartition(/dev/rb1)usingthexxdutility.SeeFigure26for
thexxdoutput.
Displaythepartitioninfoforthediskusingfdisk.SeeFigure27forthefdiskoutput.
(Quick)Formatthethirdprimarypartition(/dev/rb3)asvfatfilesystem(likeyourpendrive),
usingmkfs.vfat(Figure27).
Mountthenewlyformattedpartitionusingmount,sayat/mnt(Figure27).
Diskusageutilitydfwouldnowshowthispartitionmountedat/mnt(Figure27).Youmaygo
aheadandstoreyourfilesthere.But,pleaserememberthatthesepartitionsareallonadiskon
RAM,andsononpersistent.Hence,
Unloadingthedriverusingrmmoddorwouldvanisheverything.Thoughthepartitionneedsto
beunmountedusingumount/mntbeforedoingthat.
Pleasenotethatalltheaboveexperimentingstepsneedtobeexecutedwithrootprivileges.
Figure25:PlayingwithDiskonRAMdriver
Figure26:xxdshowingtheinitialdataonthefirstpartition(/dev/rb1)
Figure27:Formattingthethirdpartition(/dev/rb3)
Now,letslearntherules
WehavejustnowplayedaroundwiththediskonRAMbutwithoutactuallyknowingtherules,i.e.the
internaldetailsofthegame.So,letsdigintothenittygrittiestodecodetherules.Eachofthethree.c
filesrepresentaspecificpartofthedriver.ram_device.candram_device.habstracttheunderlyingRAM
operationslikevmalloc/vfree,memcpy,etc,providingdiskoperationAPIslikeinit/cleanup,read/write,
etc.partition.candpartition.hprovidethefunctionalitytoemulatethevariouspartitiontablesonthedisk
onRAM.Recalltheprelunchsession(i.e.thepreviousarticle)tounderstandthedetailsofpartitioning.
Thecodeinthisisresponsibleforthepartitioninformationlikenumber,type,size,etcthatisshownup
onthediskonRAMusingfdisk.ram_block.cisthecoreblockdriverimplementationexposingthedisk
onRAMastheblockdevicefiles(/dev/rb*)totheuserspace.Inotherwords,thefour
filesram_device.*andpartition.*formthehorizontallayerofthedevicedriverandram_block.cformsthe
vertical(block)layerofthedevicedriver.So,letsunderstandthatindetail.
Theblockdriverbasics
Conceptually,theblockdriversareverysimilartocharacterdrivers,especiallywithregardstothe
following:
Usageofdevicefiles
Majorandminornumbers
Devicefileoperations
Conceptofdeviceregistration
So,ifonealreadyknowscharacterdriverimplementations,itwouldbesimilartounderstandtheblock
drivers.Though,theyaredefinitelynotidentical.Thekeydifferencescouldbelistedoutasfollows:
Abstractionforblockorientedversusbyteorienteddevices
BlockdriversaredesignedtobeusedbyI/Oschedulers,foroptimalperformance.Comparethat
withcharacterdriverstobeusedbyVFS.
BlockdriversaredesignedtobeintegratedwiththeLinuxbuffercachemechanismforefficient
dataaccess.Characterdriversarepassthroughdrivers,accessingthehardwaredirectly.
Andthesetriggertheimplementationdifferences.Letsanalyzethekeycodesnippets
fromram_block.c,startingatthedriversconstructorrb_init().
Firststepistoregisterfora8bit(block)majornumber.Andregisteringforthatimplicitlymeans
registeringforallthe2568bitminornumbersassociatedwiththat.Thefunctionforthatis:
intregister_blkdev(unsignedintmajor,constchar*name);
majoristhemajornumbertoberegistered.nameisaregistrationlabeldisplayedunderthekernel
window/proc/devices.Interestingly,register_blkdev()triestoallocate®isterafreelyavailablemajor
number,when0ispassedforitsfirstparametermajorandonsuccess,theallocatedmajornumberis
returned.Thecorrespondingderegistrationfunctionis:
voidunregister_blkdev(unsignedintmajor,constchar*name);
Bothareprototypedin<linux/fs.h>
Secondstepistoprovidethedevicefileoperations,throughthestruct
block_device_operations(prototypedin<linux/blkdev.h>)fortheregisteredmajornumberdevicefiles.
However,theseoperationsaretoofewcomparedtothecharacterdevicefileoperations,andmostly
insignificant.Toelaborate,therearenooperationseventoreadandwrite.Thatssurprising.Butaswe
alreadyknowthattheblockdriversneedtointegratewiththeI/Oschedulers,thereadwrite
implementationisachievedthroughsomethingcalledrequestqueues.So,alongwithprovidingthe
devicefileoperations,thefollowingneedstoprovided:
Requestqueueforqueuingtheread/writerequests
Spinlockassociatedwiththerequestqueueforitsconcurrentaccessprotection
Requestfunctiontoprocesstherequestsqueuedintherequestqueue
Also,thereisnoseparateinterfaceforblockdevicefilecreations,sothefollowingarealsoprovided:
Devicefilenameprefix,commonlyreferredasdisk_name(rbinthedordriver)
Startingminornumberforthedevicefiles,commonlyreferredasthefirst_minor
Finally,twoblockdevicespecificthingsarealsoprovidedalongwiththeabove,namely:
Maximumnumberofpartitionssupportedforthisblockdevice,byspecifyingthetotalminors
Underlyingdevicesizeinunitsof512bytesectors,forthelogicalblockaccessabstraction
Alltheseareregisteredthroughthestructgendiskusingthefunction:
voidadd_disk(structgendisk*disk);
Thecorrespondingdeletefunctionis:
voiddel_gendisk(structgendisk*disk);
Priortoadd_disk(),thevariousfieldsofstructgendiskneedtobeinitialized,eitherdirectlyorusing
variousmacros/functionslikeset_capacity().major,first_minor,fops,queue,disk_namearetheminimal
fieldstobeinitializeddirectly.Andevenbeforetheinitializationofthesefields,thestructgendiskneeds
tobeallocatedusingthefunction:
structgendisk*alloc_disk(intminors);
whereminorsisthetotalnumberofpartitionssupportedforthisdisk.Andthecorrespondinginverse
functionwouldbe:
voidput_disk(structgendisk*disk);
Alltheseareprototypedin<linux/genhd.h>.
Requestqueueanditsrequestprocessingfunction
Therequestqueuealsoneedstobeinitializedandsetupintothestructgendisk,beforetheadd_disk().
Therequestqueueisinitializedbycalling:
structrequest_queue*blk_init_queue(request_fn_proc*,spinlock_t*);
providingtherequestprocessingfunctionandtheinitializedconcurrencyprotectionspinlock,asits
parameters.Thecorrespondingqueuecleanupfunctionis:
voidblk_cleanup_queue(structrequest_queue*);
Therequest(processing)functionshouldbedefinedwiththefollowingprototype:
voidrequest_fn(structrequest_queue*q);
Anditshouldbecodedtofetcharequestfromitsparameterq,sayusing
structrequest*blk_fetch_request(structrequest_queue*q);
andtheneitherprocessitorinitiatetheprocessing.Whateveritdoes,itshouldbenonblocking,asthis
requestfunctioniscalledfromanonprocesscontext,andalsoaftertakingthequeuesspinlock.So,
moreoveronlythefunctionsnotreleasingortakingthequeuesspinlockshouldbeusedwithinthe
requestfunction.
Atypicalrequestprocessingasdemonstratedbythefunctionrb_request()inram_block.cis:
while((req=blk_fetch_request(q))!=NULL)/*Fetchingarequest*/
{
/*Processingtherequest:theactualdatatransfer*/
ret=rb_transfer(req);/*Ourcustomfunction*/
/*Informingthattherequesthasbeenprocessedwithreturnofret*/
__blk_end_request_all(req,ret);
Requestanditsprocessing
rb_transfer()isourkeyfunction,whichparsesastructrequestandaccordinglydoestheactualdata
transfer.Thestructrequestmainlycontainsthedirectionofdatatransfer,startingsectorforthedata
transfer,totalnumberofsectorsforthedatatransfer,andthescattergatherbufferfordatatransfer.
Thevariousmacrostoextracttheseinformationfromthestructrequestareasfollows:
rq_data_dir(req);/*Operation:0readfromdevice;otherwisewritetodevice*/
blk_req_pos(req);/*Startingsectortoprocess*/
blk_req_sectors(req);/*Totalsectorstoprocess*/
rq_for_each_segment(bv,req,iter)/*Iteratortoextractindividualbuffers*/
rq_for_each_segment()isthespecialonewhichiteratesoverthestructrequest(req)usingiter,and
extractingtheindividualbufferinformationintothestructbio_vec(bv:basicinput/outputvector)oneach
iteration.And,thenoneachextraction,theappropriatedatatransferisdone,basedontheoperation
type,invokingoneofthefollowingAPIsfromram_device.c:
voidramdevice_write(sector_tsector_off,u8*buffer,unsignedintsectors);
voidramdevice_read(sector_tsector_off,u8*buffer,unsignedintsectors);
Checkoutthecompletecodeofrb_transfer()inram_block.c
Summingup
Withthat,wehaveactuallylearntthebeautifulblockdriversbytraversingthroughthedesignofahard
disk,andplayingaroundwithpartitioning,formatting,andvariousotherrawoperationsonaharddisk.
Thanksforyourpatientlistening.Now,thesessionisopenforquestions.Or,youmaypostyourqueries
ascomments,below.
SixteenthArticle>>
KernelWindow:Peepingthrough/proc
5Replies
Thissixteentharticle,whichispartoftheseriesonLinuxdevicedrivers,demonstratesthecreationand
usageoffilesunderthe/procvirtualfilesystem.
<<FifteenthArticle
Usefulkernelwindows
Aftermanymonths,ShwetaandPugsgottogetherforapeacefultechnicalromancing.Allthrough,we
havebeenusingallkindsofkernelwindowsespeciallythroughthe/procvirtualfilesystem(usingcat),
tohelpusoutindecodingthevariousnittygrittiesofLinuxdevicedrivers.Heresanonexhaustive
summarylisting:
/proc/modulesListingofallthedynamicallyloadedmodules
/proc/devicesListingofalltheregisteredcharacterandblockmajornumbers
/proc/iomemListingofonsystemphysicalRAM&busdeviceaddresses
/proc/ioportsListingofonsystemI/Oportaddresses(speciallyforx86systems)
/proc/interruptsListingofalltheregisteredinterruptrequestnumbers
/proc/softirqsListingofalltheregisteredsoftirqs
/proc/kallsymsListingofalltherunningkernelsymbols,includingfromloadedmodules
/proc/partitionsListingofcurrentlyconnectedblockdevices&theirpartitions
/proc/filesystemsListingofcurrentlyactivefilesystemdrivers
/proc/swapsListingofcurrentlyactiveswaps
/proc/cpuinfoInformationabouttheCPU(s)onthesystem
/proc/meminfoInformationaboutthememoryonthesystem,viz.RAM,swap,
Customkernelwindows
Yes,thesehavebeenreallyhelpfulinunderstandinganddebuggingtheLinuxdevicedrivers.Butisit
possibleforustoalsoprovidesomehelp?Yes,Imeancanwecreateonesuchkernelwindow
through/proc?,questionedShweta.
Whyone?Asmanyasyouwant.AndthatssimplejustusetherightsetofAPIsandthereyougo.
Foryoueverythingissimple.
Noyaar,thisisseriouslysimple,smiledPugs.Justwatchout,mecreatingoneforyou.
Andinjiffies,Pugscreatedtheproc_window.cfilebelow(includingthechanges,whichhastakenplace
sincekernelv3.10):
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/version.h>
#if(LINUX_VERSION_CODE<KERNEL_VERSION(3,10,0))
#else
#include<linux/fs.h>
#include<linux/seq_file.h>
#endif
#include<linux/proc_fs.h>
#include<linux/jiffies.h>
#include<linux/uaccess.h>
#if(LINUX_VERSION_CODE<KERNEL_VERSION(3,10,0))
#defineSTR_PRINTF(str,args...)sprintf(page+len,str,##args);
#else
#defineSTR_PRINTF(str,args...)seq_printf(m,str,##args);
#endif
staticstructproc_dir_entry*parent,*file,*link;
staticintstate=0;
#if(LINUX_VERSION_CODE<KERNEL_VERSION(3,10,0))
staticinttime_read(char*page,char**start,off_toff,intcount,int*eof,
void*data)
#else
staticinttime_read(structseq_file*m,void*v)
#endif
{
intlen=0,val;
unsignedlongact_jiffies;
len+=STR_PRINTF("state=%d\n",state);
act_jiffies=jiffiesINITIAL_JIFFIES;
val=jiffies_to_msecs(act_jiffies);
switch(state)
case0:
len+=STR_PRINTF("time=%ldjiffies\n",act_jiffies);
break;
case1:
len+=STR_PRINTF("time=%dmsecs\n",val);
break;
case2:
len+=STR_PRINTF("time=%ds%dms\n",
break;
case3:
val/=1000;
len+=STR_PRINTF("time=%02d:%02d:%02d\n",
break;
default:
len+=STR_PRINTF("<notimplemented>\n");
break;
returnlen;
val/1000,val%1000);
val/3600,(val/60)%60,val%60);
}
#if(LINUX_VERSION_CODE<KERNEL_VERSION(3,10,0))
staticinttime_write(structfile*file,constchar__user*buffer,
unsignedlongcount,void*data)
#else
staticssize_ttime_write(structfile*file,constchar__user*buffer,size_tcount,
loff_t*off)
#endif
{
charkbuf[2];
if(count>2)
if(copy_from_user(kbuf,buffer,count))
if((count==2)&&(kbuf[1]!='\n'))
if((kbuf[0]<'0')||('9'<kbuf[0]))
state=kbuf[0]'0';
returncount;
returncount;
returnEFAULT;
returncount;
returncount;
}
#if(LINUX_VERSION_CODE<KERNEL_VERSION(3,10,0))
#else
staticinttime_open(structinode*inode,structfile*file)
{
returnsingle_open(file,time_read,NULL);
}
staticstructfile_operationsfops=
{
.open=time_open,
.read=seq_read,
.write=time_write
};
#endif
staticint__initproc_win_init(void)
{
if((parent=proc_mkdir("anil",NULL))==NULL)
return1;
#if(LINUX_VERSION_CODE<KERNEL_VERSION(3,10,0))
if((file=create_proc_entry("rel_time",0666,parent))==NULL)
#else
if((file=proc_create("rel_time",0666,parent,&fops))==NULL)
#endif
remove_proc_entry("anil",NULL);
return1;
#if(LINUX_VERSION_CODE<KERNEL_VERSION(3,10,0))
file>read_proc=time_read;
file>write_proc=time_write;
#endif
if((link=proc_symlink("rel_time_l",parent,"rel_time"))==NULL)
remove_proc_entry("rel_time",parent);
remove_proc_entry("anil",NULL);
return1;
#if(LINUX_VERSION_CODE<KERNEL_VERSION(3,10,0))
link>uid=0;
link>gid=100;
#else
proc_set_user(link,KUIDT_INIT(0),KGIDT_INIT(100));
#endif
return0;
}
staticvoid__exitproc_win_exit(void)
{
remove_proc_entry("rel_time_l",parent);
remove_proc_entry("rel_time",parent);
remove_proc_entry("anil",NULL);
}
module_init(proc_win_init);
module_exit(proc_win_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AnilKumarPugalia<email@sarikapugs.com>");
MODULE_DESCRIPTION("Kernelwindow/procDemonstrationDriver");
AndthenPugsdidthefollowingsteps:
Builtthedriver(proc_window.kofile)usingtheusualdriversMakefile.
Loadedthedriverusinginsmod.
Showedvariousexperimentsusingthenewlycreatedprocwindows.(RefertoFigure28.)
Andfinally,unloadedthedriverusingrmmod.
Figure28:Peepingthrough/proc
Demystifyingthedetails
Startingfromtheconstructorproc_win_init(),threeprocentriesarecreated:
Directoryanilunder/proc(i.e.NULLparent)withdefaultpermissions0755,usingproc_mkdir()
Regularfilerel_timeundertheabovedirectoryanilwithpermissions0666,
usingcreate_proc_entry()orproc_create()(sincekernelv3.10)
Softlinkrel_time_ltotheabovefilerel_timeunderthesamedirectoryanil,usingproc_symlink()
Thecorrespondingremovalofthesethreeisachievedusingremove_proc_entry()inthe
destructorproc_win_exit(),inchronologicalreverseorder.
Foreveryentrycreatedunder/proc,acorrespondingstructproc_dir_entryiscreated.Afterwhichmany
ofitsfieldscouldbefurtherupdatedasneeded:
modePermissionsofthefile
uidUserIDofthefile
gidGroupIDofthefile
Sincekernelv3.10,specificAPIproc_set_user()hasbeenprovidedtoupdatetheuid&gid,insteadof
directlymodifyingthefields.
Additionally,foraregularfile,thefollowingtwofunctionpointersforreadandwriteoverthefilecouldbe
provided,respectively:
int(*read_proc)(char*page,char**start,off_toff,intcount,int*eof,void*data)
int(*write_proc)(structfile*file,constchar__user*buffer,unsignedlongcount,void*data)
Again,sincekernelv3.10,theread&writefieldsofstructfile_operationsaretobeusedrespectively,
insteadoftheabovetwo.
write_proc()isverysimilartothecharacterdevicefileoperationwrite.Andtheabovecode
implementation,letstheusertowriteadigitfrom0to9,andaccordinglysetstheinternalstate.
read_proc()intheabovecodeimplementationprovidesthecurrentstateandthetimesincethesystem
hasbeenbootedupindifferentunitsbasedonthecurrentstate.Theseare,jiffiesinstate0
millisecondsinstate1seconds&millisecondsinstate2hours:minutes:secondsinstate3and<not
implemented>inotherstates.Andtocheckthecomputationaccuracy,Figure29highlightsthesystem
uptimeintheoutputoftop.read_proc()spageparameterisapagesizedbuffer,typicallytobefilledup
withcountbytesfromoffsetoff.Butmoreoftenthannot(becauseofsmallcontent),justpageisfilled
up,ignoringallotherparameters.Sincekernelv3.10,thereadlogichastobeimplementedthrough
somethingcalledasequencefile,whichisimplementedbyspecifyingthepre
definedseq_read()functionforthefileoperationread,andthenbyprovidingtheabovelogicinthe
sequencefilesreadfunctionthroughthefileoperationopenusingsingle_open().
Figure29:Comparisonwithtopsoutput
Allthe/procrelatedstructuredefinitionsandfunctiondeclarationsareavailable
through<linux/proc_fs.h>.Thesequencefilerelatedstuff(sincekernelv3.10)areavailable
through<linux/seq_file.h>.Andthejiffiesrelatedfunctiondeclarationsandmacrodefinitionsare
in<linux/jiffies.h>.Onaspecialnote,theactualjiffiesarebeingcalculatedby
subtractingINITIAL_JIFFIES,asonbootup,jiffiesisinitializedtoINITIAL_JIFFIESinsteadofzero.
Summingup
HeyPugs!!!Whydidyoucreatethefoldernamedasanil?WhoisthisAnil?Youcouldhaveusedmy
nameormaybeyours.suggestedShweta.Ha!!Thatsasurprise.MyrealnameisAnilonly.Itisjust
thateveryoneincollegeknowsmeonlyasPugs,smiledPugs.Watchoutforfurthertechnical
romancingofPugsakaAnil.
SeventeenthArticle>>
Notes
1. Oneofthepowerfulusageofcreatingourownprocwindowisforcustomizeddebuggingby
querying.Asimplebutcoolmodificationoftheabovedriver,couldbeinsteadofgettingthejiffies,
itcouldread/writehardwareregisters.
ModuleInteractions
2Replies
Thisseventeentharticle,whichispartoftheseriesonLinuxdevicedrivers,demonstratesvarious
interactionswithaLinuxmodule.
<<SixteenthArticle
AsShwetaandPugsaregearingupfortheirfinalsemesterprojectinLinuxdrivers,theyareclosingon
somefinaltidbitsoftechnicalromancing.ThismainlyincludesthevariouscommunicationswithaLinux
module(dynamicallyloadableandunloadabledriver),namelyaccessingitsvariables,callingits
functions,andpassingparameterstoit.
Globalvariablesandfunctions
Onemightthinkaswhatabigdealinaccessingthevariablesandfunctionsofamodule,outsideit.Just
makethemglobal,declarethemexterninaheader,includetheheader,andaccess.Inthegeneral
applicationdevelopmentparadigm,itisthissimplebutinkerneldevelopmentenvironment,itisnotso.
Though,recommendationstomakeeverythingstaticbydefault,hasalwaysbeenthere,therewereand
arecaseswherenonstaticglobalsmaybeneeded.Asimpleexamplecouldbeadriverspanningover
multiplefilesandfunction(s)fromonefileneededtobecalledintheother.Nowtoavoidanykernel
collisionevenwithsuchcases,everymoduleisembodiedinitsownnamespace.Andweknowthattwo
moduleswiththesamenamecannotbeloadedatthesametime.Thusbydefaultzerocollisionis
achieved.However,thisalsoimpliesthatbydefaultnothingfromamodulecanbemadereallyglobal
throughoutthekernel,evenifwewantto.Andexactlyforsuchscenarios,the<linux/module.h>header
definesthefollowingmacros:
EXPORT_SYMBOL(sym)
EXPORT_SYMBOL_GPL(sym)
EXPORT_SYMBOL_GPL_FUTURE(sym)
Eachoftheseexportsthesymbolpassedastheirparameter,withadditionallyputtingtheminthe
default,_gpland_gpl_futuresections,respectively.Andhenceonlyoneofthemhastobeusedfora
particularsymbolthoughthesymbolcouldbeeitheravariablenameorafunctionname.Heresthe
completecode(our_glob_syms.c)todemonstratethesame:
#include<linux/module.h>
#include<linux/device.h>
staticstructclass*cool_cl;
staticstructclass*get_cool_cl(void)
{
returncool_cl;
}
EXPORT_SYMBOL(cool_cl);
EXPORT_SYMBOL_GPL(get_cool_cl);
staticint__initglob_sym_init(void)
{
if(IS_ERR(cool_cl=class_create(THIS_MODULE,"cool")))
/*Creates/sys/class/cool/*/
return0;
returnPTR_ERR(cool_cl);
}
staticvoid__exitglob_sym_exit(void)
{
/*Removes/sys/class/cool/*/
class_destroy(cool_cl);
}
module_init(glob_sym_init);
module_exit(glob_sym_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AnilKumarPugalia<email@sarikapugs.com>");
MODULE_DESCRIPTION("GlobalSymbolsexportingDriver");
Eachexportedsymbolalsohaveacorrespondingstructureplacedintoeachofthekernelsymboltable
(__ksymtab),kernelstringtable(__kstrtab),andkernelCRCtable(__kcrctab)sections,markingittobe
globallyaccessible.Figure30showsafilteredsnippetofthe/proc/kallsymskernelwindow,beforeand
afterloadingthemoduleour_glob_syms.ko,whichhasbeencompiledusingthedriversusualmakefile.
Figure30:Ourglobalsymbolsmodule
Thefollowingcodeshowsthesupportingheaderfile(our_glob_syms.h),tobeincludedbymodules
usingtheexportedsymbolscool_clandget_cool_cl:
#ifndefOUR_GLOB_SYMS_H
#defineOUR_GLOB_SYMS_H
#ifdef__KERNEL__
#include<linux/device.h>
externstructclass*cool_cl;
externstructclass*get_cool_cl(void);
#endif
#endif
Figure30alsoshowsthefileModule.symvers,generatedbycompilationofthemoduleour_glob_syms.
Thiscontainsthevariousdetailsofalltheexportedsymbolsinitsdirectory.Apartfromincludingthe
aboveheaderfile,themodulesusingtheexportedsymbols,possiblyshouldhavethis
fileModule.symversintheirbuilddirectory.
Notethe<linux/device.h>headerintheaboveexamples,isbeingincludedforthevariousclassrelated
declarations&definitions,whichhasbeenalreadycoveredunderthecharacterdriversdiscussions.
ModuleParameters
Beingawareofpassingcommandlineargumentstoanapplication,itisanaturalquesttoaskif
somethingsimilarcanbedonewithamodule.Andtheanswerisyes.Parameterscanbepassedtoa
modulealongwithloadingit,sayusinginsmod.Interestinglyenoughandincontrastwiththecommand
lineargumentstoanapplication,thesecanbemodifiedevenlateraswell,throughsysfsinteractions.
Themoduleparametersaresetupusingthefollowingmacro(definedin<linux/moduleparam.h>,
includedthrough<linux/module.h>):
module_param(name,type,perm)
where,nameistheparametername,typeisthetypeoftheparameter,andpermisthepermissionsof
thesysfsfilecorrespondingtothisparameter.Supportedtypevalues
are:byte,short,ushort,int,uint,long,ulong,charp(characterpointer),boolorinvbool(inverted
boolean).Thefollowingmodulecode(module_param.c)demonstratesamoduleparameter:
#include<linux/module.h>
#include<linux/kernel.h>
staticintcfg_value=3;
module_param(cfg_value,int,0764);
staticint__initmod_par_init(void)
{
printk(KERN_INFO"Loadedwith%d\n",cfg_value);
return0;
}
staticvoid__exitmod_par_exit(void)
{
printk(KERN_INFO"Unloadedcfgvalue:%d\n",cfg_value);
}
module_init(mod_par_init);
module_exit(mod_par_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AnilKumarPugalia<email@sarikapugs.com>");
MODULE_DESCRIPTION("ModuleParameterdemonstrationDriver");
Notethatbeforetheparametersetup,avariableofthesamenameandcompatibletypeneedstobe
defined.
Subsequently,thefollowingstepsandexperimentsareshowninFigures31and32:
Buildingthedriver(module_param.kofile)usingthedriversusualmakefile
Loadingthedriverusinginsmod(withandwithoutparameters)
Variousexperimentsthroughthecorresponding/sysentries
Andfinally,unloadingthedriverusingrmmod
Figure31:Experimentswithmoduleparameter
Figure32:Experimentswithmoduleparameter(asroot)
Observethefollowing:
Initialvalue(3)ofcfg_valuebecomesitsdefaultvaluewheninsmodisdonewithoutany
parameters
Permission0764givesrwxtotheuser,rwtothegroup,andrfortheothersonthe
filecfg_valueunderparametersofmodule_paramunder/sys/module/
Checkforyourself:
Outputofdmesg|tailoneveryinsmodandrmmodfortheoutputofprintks
Trywritingintothe/sys/module/module_param/parameters/cfg_valuefileasanormaluser
Summingup
Withthis,theduohaveafairlygoodunderstandingofLinuxdriversandtheyareallsettostartworking
ontheirfinalsemesterproject.Anyguessesonwhattheirtopicisallabout?Hint:Theyhavepickedup
oneofthemostdauntingLinuxdrivertopic.Letussee,howtheyfairatit.
FileSystems:TheSemesterProject
2Replies
Thiseighteentharticle,whichispartoftheseriesonLinuxdevicedrivers,kickstartswithintroducingthe
conceptofafilesystem,bysimulatingoneinuserspace.
<<SeventeenthArticle
Ifyouhavenotyetguessedthesemesterprojecttopic,thenyoushouldhaveacarefullookatthetable
ofcontentsofthevariousbooksonLinuxdevicedrivers.Youllfindthatnobooklistsachapteronfile
systems.Notbecausetheyarenotusedornotuseful.Infact,therearecloseto100differentfile
systemsexisting,asoftoday,andpossiblymostoftheminactiveuse.Then,whyaretheynottalkedin
general?Andaresomanyfilesystemsrequired?Whichoneofthemisthebest?Letsunderstand
theseonebyone.
Inreality,thereisnobestfilesystem,andinfacttheremaynotbeonetocome,aswell.Thisis
becauseaparticularfilesystemsuitsbestonlyforaparticularsetofrequirements.Thus,different
requirementsaskfordifferentbestfilesystems,leadingtosomanyactivefilesystemsandmanymore
tocome.So,basicallytheyarenotgenericenoughtobetalkedgenerically,rathermoreofaresearch
topic.Andthereasonforthembeingthetoughestofalldriversissimple:Leastavailabilityofgeneric
writtenmaterialthebestdocumentationbeingthecodeofvariousfilesystems.Hmmm!Sounds
perfectforasemesterproject,right?
Inordertogettodosucharesearchproject,alotofsmallerstepshastobetaken.Thefirstonebeing
understandingtheconceptofafilesystemitself.Thatcouldbedonebestbysimulatingoneinuser
space.Shwetatooktheownershipofgettingthisfirststepdone,asfollows:
Usearegularfileforapartition,thatisfortheactualdatastorageofthefilesystem(Hardware
spaceequivalent)
Designabasicfilesystemstructure(Kernelspaceequivalent)andimplementthe
format/mkfscommandforit,overtheregularfile
Provideaninterface/shelltotypecommandsandoperateonthefilesystem,similartotheusual
bashshell.Ingeneral,thisstepisachievedbytheshellalongwiththecorrespondingfilesystem
driverinkernelspace.Butherethattranslationisembodied/simulatedintotheuserspace
interface,itself.(Nextarticleshalldiscussthisindetail)
Thedefaultregularfilechosenis.sfsf(simulatingfilesystemfile)inthecurrentdirectory.Starting.
(dot)isforittobehidden.Thebasicfilesystemdesignmainlycontainstwostructures,thesuperblock
containingtheinfoaboutthefilesystem,andthefileentrystructurecontainingtheinfoabouteachfilein
thefilesystem.HereareShwetasdefinesandstructuresdefinedinsfs_ds.h
#ifndefSFS_DS_H
#defineSFS_DS_H
#defineSIMULA_FS_TYPE0x13090D15/*MagicNumberforourfilesystem*/
#defineSIMULA_FS_BLOCK_SIZE512/*inbytes*/
#defineSIMULA_FS_ENTRY_SIZE64/*inbytes*/
#defineSIMULA_FS_DATA_BLOCK_CNT((SIMULA_FS_ENTRY_SIZE(16+3*4))/4)
#defineSIMULA_DEFAULT_FILE".sfsf"
typedefunsignedintuint4_t;
typedefstructsfs_super_block
{
uint4_ttype;/*Magicnumbertoidentifythefilesystem*/
uint4_tblock_size;/*Unitofallocation*/
uint4_tpartition_size;/*inblocks*/
uint4_tentry_size;/*inbytes*/
uint4_tentry_table_size;/*inblocks*/
uint4_tentry_table_block_start;/*inblocks*/
uint4_tentry_count;/*Totalentriesinthefilesystem*/
uint4_tdata_block_start;/*inblocks*/
uint4_treserved[SIMULA_FS_BLOCK_SIZE/48];
}sfs_super_block_t;/*MakingitofSIMULA_FS_BLOCK_SIZE*/
typedefstructsfs_file_entry
{
charname[16];
uint4_tsize;/*inbytes*/
uint4_ttimestamp;/*SecondssinceEpoch*/
uint4_tperms;/*Permissionsforuser*/
uint4_tblocks[SIMULA_FS_DATA_BLOCK_CNT];
}sfs_file_entry_t;
#endif
NotethatShwetahasputsomeredundantfieldsinthesfs_super_block_tratherthancomputingthem
everytime.Andpracticallythatisnotspaceinefficient,asanywaylotofemptyreservedspaceis
availableinthesuperblock,whichisexpectedtobethecompletefirstblockofapartition.For
example,entry_countisaredundantfieldasitissameastheentrytablessize(inbytes)dividedbythe
entryssize,whichbotharepartofthesuperblockstructure.Moreover,thedata_block_startnumber
couldalsobecomputedbyhowmanyblockshavebeenusedbeforeit,butagainnotpreferred.
Also,notethehardcodedassumptionsmadeabouttheblocksizeof512bytes,andsomerandom
magicnumberforthesimulafilesystem.Thismagicnumberisusedtoverifythatwearedealingwith
therightfilesystem,whenoperatingonit.
Comingtothefileentry,itcontainsthefollowingforeveryfile:
Nameofupto15characters(1bytefor)
Sizeinbytes
Timestamp(creationormodification?NotyetdecidedbyShweta)
Permissions(justoneset,fortheuser)
Arrayofblocknumbersforupto9datablocks.Why9?Tomakethecompleteentryof
SIMULA_FS_ENTRY_SIZE(64).
Withthefilesystemdesignready,themakefilesystem(mkfs)ormorecommonlyknownformat
applicationisimplementednext,asformat_sfs.c:
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include"sfs_ds.h"
#defineSFS_ENTRY_RATIO0.10/*10%ofallblocks*/
#defineSFS_ENTRY_TABLE_BLOCK_START1
sfs_super_block_tsb=
{
.type=SIMULA_FS_TYPE,
.block_size=SIMULA_FS_BLOCK_SIZE,
.entry_size=SIMULA_FS_ENTRY_SIZE,
.entry_table_block_start=SFS_ENTRY_TABLE_BLOCK_START
};
sfs_file_entry_tfe;/*All0's*/
voidwrite_super_block(intsfs_handle,sfs_super_block_t*sb)
{
write(sfs_handle,sb,sizeof(sfs_super_block_t));
voidclear_file_entries(intsfs_handle,sfs_super_block_t*sb)
{
inti;
for(i=0;i<sb>entry_count;i++)
write(sfs_handle,&fe,sizeof(fe));
}
voidmark_data_blocks(intsfs_handle,sfs_super_block_t*sb)
{
charc=0;
lseek(sfs_handle,sb>partition_size*sb>block_size1,SEEK_SET);
write(sfs_handle,&c,1);/*Tomakethefilesizetopartitionsize*/
}
intmain(intargc,char*argv[])
{
intsfs_handle;
if(argc!=2)
fprintf(stderr,"Usage:%s<partitionsizein512byteblocks>\n",
return1;
sb.partition_size=atoi(argv[1]);
sb.entry_table_size=sb.partition_size*SFS_ENTRY_RATIO;
sb.entry_count=sb.entry_table_size*sb.block_size/sb.entry_size;
sb.data_block_start=SFS_ENTRY_TABLE_BLOCK_START+sb.entry_table_size;
sfs_handle=creat(SIMULA_DEFAULT_FILE,
if(sfs_handle==1)
perror("Nopermissionstoformat");
return2;
write_super_block(sfs_handle,&sb);
clear_file_entries(sfs_handle,&sb);
mark_data_blocks(sfs_handle,&sb);
close(sfs_handle);
return0;
argv[0]);
S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
Notetheheuristicof10%oftotalspacetobeusedforthefileentries,definedbySFS_ENTRY_RATIO.
Apartfromthat,thisformatapplicationtakesthepartitionsizeasinputandaccordinglycreatesthe
defaultfile.sfsf,with:
Itsfirstblockasthesuperblock,usingwrite_super_block()
Thenext10%oftotalblocksasthefileentryszeroedouttable,usingclear_file_entries()
Andtheremainingasthedatablocks,usingmark_data_blocks().Thisbasicallywritesazeroat
theend,toactuallyextendtheunderlyingfile.sfsftothepartitionsize
Asanyotherformat/mkfsapplication,format_sfs.ciscompiledusinggcc,andexecutedontheshellas
follows:
$gccformat_sfs.coformat_sfs
$./format_sfs1024#Partitionsizeinblocksof512bytes
$lsal#Listthe.sfsfcreatedwithasizeof512KiBytes
Figure33showsthe.sfsfpictoriallyforthepartitionsizeof1024blocksof512byteseach.
Figure33:Simulafilesystemon512KiBofpartition(.sfsf)
Summingup
Withtheabovedesignofsimulafilesystem(sfs_ds.h),alongwiththeimplementationforitsformat
command(format_sfs.c),Shwetahasthuscreatedtheemptyfilesystemoverthesimulated
partition.sfsf.Now,aslistedearlier,thefinalstepinsimulatingtheuserspacefilesystemistocreate
theinterface/shelltotypecommandsandoperateontheemptyfilesystemjustcreatedon.sfsf.Lets
watchoutforShwetacodingthataswell,completingthefirstsmallsteptowardsunderstandingtheir
project.
NineteenthArticle>>
FileSystems:TheSemesterProjectPartII
2Replies
Thisnineteentharticle,whichispartoftheseriesonLinuxdevicedrivers,continueswithintroducingthe
conceptofafilesystem,bysimulatingoneinuserspace.
<<EighteenthArticle
Inthepreviousarticle,Shwetareadiedthepartitiononthe.sfsffilebyformatingitwith
theformat_sfsapplication.Tocompletetheunderstandingofafilesystem,thenextstepinits
simulationistobrowseandplayaroundwiththefilesystemcreated.HereisShwetasfirstcutbrowser
applicationtoachievethesame.Letshaveacloserlook.sfs_ds.histhesameheaderfile,already
createdbyShweta.
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
#include<time.h>
#include"sfs_ds.h"
sfs_super_block_tsb;
voidsfs_list(intsfs_handle)
{
inti;
sfs_file_entry_tfe;
lseek(sfs_handle,sb.entry_table_block_start*sb.block_size,SEEK_SET);
for(i=0;i<sb.entry_count;i++)
read(sfs_handle,&fe,sizeof(sfs_file_entry_t));
if(!fe.name[0])continue;
printf("%15s%10dbytes%c%c%c%s",
fe.name,fe.size,
fe.perms&04?'r':'',
fe.perms&02?'w':'',
fe.perms&01?'x':'',
ctime((time_t*)&fe.timestamp)
);
}
voidsfs_create(intsfs_handle,char*fn)
{
inti;
sfs_file_entry_tfe;
lseek(sfs_handle,sb.entry_table_block_start*sb.block_size,SEEK_SET);
for(i=0;i<sb.entry_count;i++)
read(sfs_handle,&fe,sizeof(sfs_file_entry_t));
if(!fe.name[0])break;
if(strcmp(fe.name,fn)==0)
printf("File%salreadyexists\n",fn);
return;
if(i==sb.entry_count)
printf("Noentriesleft\n",fn);
return;
lseek(sfs_handle,(off_t)(sb.entry_size),SEEK_CUR);
strncpy(fe.name,fn,15);
fe.name[15]=0;
fe.size=0;
fe.timestamp=time(NULL);
fe.perms=07;
for(i=0;i<SIMULA_FS_DATA_BLOCK_CNT;i++)
write(sfs_handle,&fe,sizeof(sfs_file_entry_t));
fe.blocks[i]=0;
}
voidsfs_remove(intsfs_handle,char*fn)
{
inti;
sfs_file_entry_tfe;
lseek(sfs_handle,sb.entry_table_block_start*sb.block_size,SEEK_SET);
for(i=0;i<sb.entry_count;i++)
read(sfs_handle,&fe,sizeof(sfs_file_entry_t));
if(!fe.name[0])continue;
if(strcmp(fe.name,fn)==0)break;
if(i==sb.entry_count)
printf("File%sdoesn'texist\n",fn);
return;
lseek(sfs_handle,(off_t)(sb.entry_size),SEEK_CUR);
memset(&fe,0,sizeof(sfs_file_entry_t));
write(sfs_handle,&fe,sizeof(sfs_file_entry_t));
}
voidbrowse_sfs(intsfs_handle)
{
intdone;
charcmd[256],*fn;
intret;
done=0;
printf("WelcometoSFSBrowsingShellv1.0\n\n");
printf("Blocksize
printf("Partitionsize:%dblocks\n",sb.partition_size);
printf("Fileentrysize:%dbytes\n",sb.entry_size);
printf("Entrytblsize:%dblocks\n",sb.entry_table_size);
printf("Entrycount
printf("\n");
while(!done)
printf("$>");
ret=scanf("%[^\n]",cmd);
if(ret<0)
done=1;
printf("\n");
continue;
else
getchar();
if(ret==0)continue;
if(strcmp(cmd,"?")==0)
printf("Supportedcommands:\n");
printf("\t?\tquit\tlist\tcreate\tremove\n");
continue;
:%dbytes\n",sb.block_size);
:%d\n",sb.entry_count);
elseif(strcmp(cmd,"quit")==0)
done=1;
continue;
elseif(strcmp(cmd,"list")==0)
sfs_list(sfs_handle);
continue;
elseif(strncmp(cmd,"create",6)==0)
if(cmd[6]=='')
fn=cmd+7;
while(*fn=='')fn++;
if(*fn!='')
sfs_create(sfs_handle,fn);
continue;
elseif(strncmp(cmd,"remove",6)==0)
if(cmd[6]=='')
fn=cmd+7;
while(*fn=='')fn++;
if(*fn!='')
sfs_remove(sfs_handle,fn);
continue;
printf("Unknown/Incorrectcommand:%s\n",cmd);
printf("Supportedcommands:\n");
printf("\t?\tquit\tlist\tcreate<file>\tremove<file>\n");
}
intmain(intargc,char*argv[])
{
char*sfs_file=SIMULA_DEFAULT_FILE;
intsfs_handle;
if(argc>2)
fprintf(stderr,"Incorrectinvocation.Possibilitiesare:\n");
fprintf(stderr,
"\t%s/*Picksup%sasthedefaultpartition_file*/\n",
argv[0],SIMULA_DEFAULT_FILE);
fprintf(stderr,"\t%s[partition_file]\n",argv[0]);
return1;
if(argc==2)
sfs_handle=open(sfs_file,O_RDWR);
if(sfs_handle==1)
fprintf(stderr,"UnabletobrowseSFSover%s\n",sfs_file);
return2;
read(sfs_handle,&sb,sizeof(sfs_super_block_t));
if(sb.type!=SIMULA_FS_TYPE)
fprintf(stderr,"InvalidSFSdetected.Givingup.\n");
close(sfs_handle);
return3;
browse_sfs(sfs_handle);
close(sfs_handle);
return0;
sfs_file=argv[1];
Theabove(shelllike)programprimarilyreadsthesuperblockfromthepartitionfile(.sfsfbydefault,or
thefileprovidedfromcommandline),andthengetsintobrowsingthefilesystembasedonthat
information,usingthebrowse_sfs()function.Notethecheckperformedforthevalidfilesystemonthe
partitionfileusingthemagicnumberSIMULA_FS_TYPE.
browse_sfs()printsthefilesysteminformationandprovidesfourbasicfilesystemfunctionalitiesusing
thefollowingcommands:
quittoquitthefilesystembrowser
listtolistthecurrentfilesinthefilesystem(usingsfs_list())
create<filename>tocreateanewfileinthefilesystem(usingsfs_create(filename))
remove<filename>toremoveanexistingfilefromthefilesystem(usingsfs_remove(filename))
Figure34showsthebrowserinaction,usingtheabovecommands.
Figure34:Simulafilesystembrowseroutput
sfs_list()traversesthroughallthefileentriesinthepartitionandprintsallthenonnullfilenameentries
withfilename,size,permissions,anditscreationtimestamp.sfs_create()looksupforanavailable(null
filename)entryandthenupdatesitwiththegivenfilename,sizeof0bytes,permissionsofrwx,and
thecurrenttimestamp.Andsfs_remove()looksupforanexistingfileentry,havingthefilenametobe
removed,andthennullifiesit.Theotherpartsintheabovecodearemoreofbasicerrorhandlingcases
likeinvalidcommandinbrowse_sfs(),existingfilenameinsfs_create(),nonexistingfilename
insfs_remove(),etc.
Notethattheaboveapplicationisthefirstcutoneofafullfledgedapplication.Andsothefilescreated
rightnowarejustwiththebasicfixedparameters.And,thereisnowaytochangetheirpermissions,
content,etc,yet.However,itmustbeclearbynowthataddingthosefeaturesisjustamatterofadding
commandsandtheircorrespondingfunctions,whichwouldbeprovidedforafewmoreinteresting
featuresbyShwetaasshefurtherworksoutthedetailsofthebrowsingapplication.
Summingup
Oncedonewiththeotherfeaturesaswell,thenextsetofstepswouldtaketheprojectduofurther
closertotheirfinalgoals.Watchoutforwhotakeschargeofthenextstepofmovingthefilesystem
logictothekernelspace.
TwentiethArticle>>
FileSystems:TheSemesterProjectPartIII
2Replies
Thistwentietharticle,whichispartoftheseriesonLinuxdevicedrivers,completesthebasicsimulation
ofafilesysteminuserspace.
<<NineteenthArticle
Tillnow,Shwetahadimplemented4basicfunctionalitiesofthesimulatedfilesystem(sfs)browser,
namelyquit(browser),list(files),create(anemptyfile),remove(afile).Heresheadds3littleadvanced
functionalitiestogetafeelofacompletebasicfilesystem:
Changingpermissionsofafile
Readingfromafile
Writingintoafile
Heresasneakpeekintoherthinkingprocessashowshecameupwithherimplementations.
Forthevariouscommandimplementations,twoverycommonrequirementskeeppoppingquiteoften:
Gettingtheindexoftheentryforaparticularfilename
Updatingtheentryforagivenfilename
Hencethesetworequirements,capturedasthefunctionssfs_lookup()andsfs_update():
intsfs_lookup(intsfs_handle,char*fn,sfs_file_entry_t*fe)
{
inti;
lseek(sfs_handle,sb.entry_table_block_start*sb.block_size,SEEK_SET);
for(i=0;i<sb.entry_count;i++)
read(sfs_handle,fe,sizeof(sfs_file_entry_t));
if(!fe>name[0])continue;
if(strcmp(fe>name,fn)==0)returni;
return1;
voidsfs_update(intsfs_handle,char*fn,int*size,intupdate_ts,int*perms)
{
inti;
sfs_file_entry_tfe;
if((i=sfs_lookup(sfs_handle,fn,&fe))==1)
printf("File%sdoesn'texist\n",fn);
return;
if(size)fe.size=*size;
if(update_ts)fe.timestamp=time(NULL);
if(perms&&(*perms<=07))fe.perms=*perms;
lseek(sfs_handle,sb.entry_table_block_start*sb.block_size+
write(sfs_handle,&fe,sizeof(sfs_file_entry_t));
i*sb.entry_size,SEEK_SET);
sfs_lookup()traversesthroughalltheentries(skippingtheinvalidi.e.emptyfilenameentries),tillitfinds
thefilenamematchandthenreturnstheindexandtheentryofthematchedentryinthefunctionsthird
parameter.Itreturns1incaseofnomatchisfound.
sfs_update()usessfs_lookup()togettheentryanditsindexforthegivenfilename.Then,itupdatesit
backintothefilesystemwith:a)size,ifpassed(i.e.nonNULL),b)currenttimestampifupdate_tsisset,
c)permissions,ifpassed(i.e.nonNULL).
Changingfilepermissions
IntheSimulafilesystem,filepermissionsarebasicallyacombinationrwxfortheuseronly,storedas
aninteger,withLinuxlikenotationof4forr,2forw,1forx.Forchangingthepermissionsofagiven
filename,sfs_update()canbeusedbestbypassingNULLpointerforsize,zeroforupdate_ts,andthe
pointertopermissionstochangeforperms.Sosfs_chperm()wouldbeasfollows:
voidsfs_chperm(intsfs_handle,char*fn,intperm)
{
sfs_update(sfs_handle,fn,NULL,0,&perm);
Readingafile
Readingafileisbasicallysequentiallyreading&displayingthecontentsofthedatablocksindicatedby
theirpositionfromtheblocksarrayoffilesentryanddisplayingthatonstdoutsfiledescriptor1.A
coupleofthingstobetakencareof:
Fileisassumedtobewithoutholes,i.e.blockpositionof0intheblocksarrayindicatesnomore
datablocksforthefile
Readingshouldnotgobeyondthefilesize.Specialcaretobetakenwhilereadingthelastblock
withdata,asitmaybepartiallyvalid
Heresthecompletereadfunction,keepingtrackofvaliddatausingbyteslefttoread:
uint1_tblock[SIMULA_FS_BLOCK_SIZE];//Sharedasglobalwithsfs_write
voidsfs_read(intsfs_handle,char*fn)
{
inti,block_i,already_read,rem_to_read,to_read;
sfs_file_entry_tfe;
if((i=sfs_lookup(sfs_handle,fn,&fe))==1)
printf("File%sdoesn'texist\n",fn);
return;
already_read=0;
rem_to_read=fe.size;
for(block_i=0;block_i<SIMULA_FS_DATA_BLOCK_CNT;block_i++)
if(!fe.blocks[block_i])break;
to_read=(rem_to_read>=sb.block_size)?
lseek(sfs_handle,fe.blocks[block_i]*sb.block_size,SEEK_SET);
read(sfs_handle,block,to_read);
write(1,block,to_read);
already_read+=to_read;
rem_to_read=to_read;
if(!rem_to_read)break;
sb.block_size:rem_to_read;
Writingafile
Interestingly,writeisnotatrivialfunction.Gettingdatafromtheuserthroughbrowserisokay.But
basedonthat,freeavailableblockshastobeobtained,filledandthentheirpositionbenoted
sequentiallyintheblocksarrayofthefilesentry.Typically,wedothiswheneverwehavereceiveda
blockfulldata,exceptthelastblock.Thatstrickyhowdoweknowthelastblock?So,wereadtillend
ofinput,markedbyControlDonitsownlinefromtheuserandthatisindicatedbyareturnof0from
read.Andinthatcase,wecheckifanynonfullblockofdataislefttobewritten,andifyesfollowthe
sameprocedureofobtainingafreeavailableblock,fillingitup(withthepartialdata),andupdatingits
positionintheblocksarray.
Afterallthis,wehavefinallywrittenthefiledata,alongwiththedatablockpositionsintheblocksarray
ofthefilesentry.Andnowitstimetoupdatefilesentrywiththetotalsizeofdatawritten,aswellas
timestamptocurrentlymodified.Oncedone,thisentryhastobeupdatedbackintotheentrytable,
whichisthelaststep.Andinthisflow,thefollowingshouldntbemissedoutduringgetting&fillingup
freeblocks:
Checkfornomoreblockpositionsavailableinblocksarrayofthefilesentry
Checkfornomorefreeblocksavailableinthefilesystem
Ineitherofthe2cases,thethoughtistodoagracefulstopwithdatabeingwrittenuptothemaximum
possible,anddiscardingtherest.
Onceagainalloftheseputtogetherareinthefunctionbelow:
uint1_tblock[SIMULA_FS_BLOCK_SIZE];//Sharedasglobalwithsfs_read
voidsfs_write(intsfs_handle,char*fn)
{
inti,cur_read_i,to_read,cur_read,total_size,block_i,free_i;
sfs_file_entry_tfe;
if((i=sfs_lookup(sfs_handle,fn,&fe))==1)
printf("File%sdoesn'texist\n",fn);
return;
cur_read_i=0;
to_read=sb.block_size;
total_size=0;
block_i=0;
while((cur_read=read(0,block+cur_read_i,to_read))>0)
if(cur_read==to_read)
/*Writethisblock*/
if(block_i==SIMULA_FS_DATA_BLOCK_CNT)
if((free_i=get_data_block(sfs_handle))==1)
lseek(sfs_handle,free_i*sb.block_size,SEEK_SET);
write(sfs_handle,block,sb.block_size);
fe.blocks[block_i]=free_i;
block_i++;
total_size+=sb.block_size;
/*Resetvariousvariables*/
cur_read_i=0;
to_read=sb.block_size;
else
cur_read_i+=cur_read;
to_read=cur_read;
if((cur_read<=0)&&(cur_read_i))
/*Writethispartialblock*/
if((block_i!=SIMULA_FS_DATA_BLOCK_CNT)&&
lseek(sfs_handle,fe.blocks[block_i]*sb.block_size,
write(sfs_handle,block,cur_read_i);
total_size+=cur_read_i;
fe.size=total_size;
fe.timestamp=time(NULL);
lseek(sfs_handle,sb.entry_table_block_start*sb.block_size+
write(sfs_handle,&fe,sizeof(sfs_file_entry_t));
Thelaststride
break;/*Filesizelimit*/
break;/*Filesystemfull*/
((fe.blocks[block_i]=get_data_block(sfs_handle))!=1))
SEEK_SET);
i*sb.entry_size,SEEK_SET);
Withtheabove3sfscommandfunctions,thefinalchangetothebrowse_sfs()functionin(previous
articles)browse_sfs.cwouldbetoaddthecasesforhandlingthese3newcommandsofchperm,write,
andread.
Oneofthedauntingquestions,ifithasnotyetbotheredyou,ishowdoyoufindthefreeavailable
blocks.Noticethatinsfs_write(),wejustcalledafunctionget_data_block()andeverythingwent
smooth.Butthinkthroughhowwouldthatbeimplemented.Doyouneedtotraverseallthefileentrys
everytimetofigureoutwhichallhasbeenusedandtheremainingarefree.Thatwouldbekilling.
Instead,aneasiertechniquewouldbetogettheusedonesbyparsingallthefileentrys,onlyinitially
once,andthenkeeptrackofthemwhenevermoreentriesareusedorfreedup.Butforthatacomplete
frameworkneedstobeputinplace,whichincludes:
uint1_ttypedef(insfs_ds.h)
used_blocksdynamicarraytokeeptrackofusedblocks(inbrowse_sfs.c)
Functioninit_browsing()toinitializethedynamicarrayused_blocks,i.e.allocateandmarkthe
initialusedblocks(inbrowse_sfs.c)
Correspondinglytheinversefunctionshut_browsing()tocleanupthesame(inbrowse_sfs.c)
Anddefinitelythefunctionsget_data_block()andput_data_block()torespectivelygetandput
backthefreedatablocksbasedonthedynamicarrayused_blocks(inbrowse_sfs.c)
Allthesethoughts,incorporatedintheearliersfs_ds.handbrowse_sfs.cfiles,alongwithaMakefileand
theearlierformatterapplicationformat_sfs.c,areavailablefromsfs_code.tar.bz2.Oncecompiled
intobrowse_sfsandexecutedas./browse_sfs,itshowsupassomethinglikeinFigure35.
Figure35:DemoofSimulafilesystembrowsersnewfeatures
Summingup
AsShweta,showedtheaboveworkingdemotoherprojectmate,heobservedsomemissouts,and
challengedhertofindthemoutonherown.Hehintedthemtoberelatedtothenewlyadded
functionalityandgettingfreeblockframeworksomeevenvisiblefromthedemo,i.e.Figure35.Can
youhelpShweta,findthemout?Ifyes,posttheminthecommentsbelow.
TheSemesterProjectPartIV:FormattingaPenDrive
2Replies
Thistwentyfirstarticle,whichispartoftheseriesonLinuxdevicedrivers,takesthenextsteptowards
writingafilesystemmodulebywritingaformattingapplicationforyourrealpendrive.
<<TwentiethArticle
ThanksfriendsforyourconfidenceinShwetaandnottryingtohelpheroutinfiguringouttheissues
withhercode.Sheindeedfiguredoutandfixedthefollowingissuesinhercode:
sfs_read()andsfs_write()needtocheckforthereadandwritefilepermissionsbefore
proceedingtoreadandwrite,respectively
sfs_write()shouldfreeanypreviouslyallocatedblocks,aswriteisalwaysoverwrite
Moreover,theearlierwrittensfs_remove()also,nowneedstofreeuptheallocatedblocks
SFSFormatforarealpartition
ThereafterPugstooktheleadandslightlymodifiedShwetasformat_sfs.candsfs_ds.hfilestoformata
realpendrivespartition.Thekeychangeisthatinsteadofcreatingthedefaultregularfile.sfsfto
format,nowitwouldbeoperatingonanexistingblockdevicefilecorrespondingtoanunderlying
partition,saysomethinglike/dev/sdb1.So,
Itwouldgetthepartitionssizefromthepartitionsblockdevicefileitself,ratherthantakingitasa
commandlineargument
Accordingly,itwouldnowexpectthepartitionsblockdevicefilenameinsteadofsize,asmain()s
firstargument.
Also,itwouldnotneedthemark_data_block()togrowthefileequaltothepartitionsize
TheioctlcommandBLKGETSIZE64getsthe64bitsizeoftheunderlyingblockdevicepartition,in
bytes.Then,itisdividedbytheblocksize(SIMULA_FS_BLOCK_SIZE)togetthepartitionssizein
blockunits.Hereisthecorrespondingmodifiedsnippetofthemain()function
informat_real_sfs.c(updatedformat_sfs.c),alongwiththe
requiredtypedefinreal_sfs_ds.h(updatedsfs_ds.h).(Note:Forreadability,Pugsrenamed
alluint*bybyte*):
typedefunsignedlonglongbyte8_t;
...
byte8_tsize;
sfs_handle=open(argv[1],O_RDWR);
if(sfs_handle==1)
{
fprintf(stderr,"Errorformatting%s:%s\n",argv[1],strerror(errno));
return2;
}
if(ioctl(sfs_handle,BLKGETSIZE64,&size)==1)
{
fprintf(stderr,"Errorgettingsizeof%s:%s\n",argv[1],strerror(errno));
return3;
}
sb.partition_size=size/SIMULA_FS_BLOCK_SIZE;
Aspertheabovecode,thefollowingadditionalheaderfilesneedtobeincluded:
#include<errno.h>/*Forerrno*/
#include<string.h>/*Forstrerror()*/
#include<sys/ioctl.h>/*Forioctl()*/
#include<linux/fs.h>/*ForBLKGETSIZE64*/
Withalltheabovechangescompiledintoformat_real_sfs,Pugspluggedinhispendrive,partitionof
whichgotautomounted.Then,hetookbackupofitscontentandunmountedthesamereadyfora
realSFSformatofthependrivepartition.
Caution:Takeabackupofyourpendrivescontentyouareformattingitforreal.Becareful
inchoosingtherightpartitionofyourpendrive.Otherwise,youmayforeverlosedatafrom
yourharddiskorevenmakeyoursystemunbootable.Youhavebeenwarned.
Figure36:Formattingthependrive
Figure36demonstratesalltheabovebutbackupstepsatrootprompt#.Instead,onemayusesudo,
aswell.NotethatPugsgothispendrivepartitionmountedat/media/10ACBF1C,andthe
correspondingdevicefileis/dev/sdb1(/dev/sdbbeingthecompletependrive).Youmayhaveboth
thesedifferently.Accordingly,followthestepsforyourself.Also,notethat,therealSFSformattingis
thenstartedusingthefollowingcommand:
#./format_real_sfs/dev/sdb1
Andthen,thereisa^C(CtrlC)immediatelyafterthat,basicallyterminatingtheformatting.Aha!Did
Pugsrealizesomethingimportantwasthereonthependrive?Notreally,ashispendriveisalready
empty.Actually,whathappenedisthatformattingwasgoingonforquitesometimesoPugshad
somedoubtabouthiscodechangesandsoheterminatedit.Reviewinghiscodedidntyieldmuch,so
hereissuedtheformatting,thistimewiththetimecommand,tofigureoutexactlyhowmuchtimeisthe
formattingtakingandthenmaybedebug/fixthat.Andfinally!Theformattingiscompletebutaftera
whopping430.88seconds(7+minutes),yesminutes.timebasicallyshowstherealtimetaken(includes
thetimewhenotherprocesseshasbeenrunningaftercontextswitch),timeexecutedinuserspace,
timeexecutedinkernelspace.Thatshugesomethingneedsoptimization.Anditdidnttakemuch
timeforaclosereviewtounderminetheissue.Thekeytimetakercodewouldbe
theclear_file_entries()function.Rightnowitsclearingthefileentriesonebyone,i.e.writing64byte
sizedfileentriesonebyonethatsprettynonoptimal.Abetterapproachwouldbetofillupablock
withsuchentries,andthenwritetheseblocksonebyone.Incaseofa512byteblock
(i.e.SIMULA_FS_BLOCK_SIZEdefinedas512),thatwouldmean8fileentriesina512byteblockand
thenwritingthese512byteblocksonebyone.So,Pugschangedtheclear_file_entries()functiontodo
thesame,andviola!formattingiscompleteinalittlelessthan26seconds.Herestheanswertoyour
curiositytherewrittenclear_file_entries()function:
voidclear_file_entries(intsfs_handle,sfs_super_block_t*sb)
{
inti;
byte1_tblock[SIMULA_FS_BLOCK_SIZE];
for(i=0;i<sb>block_size/sb>entry_size;i++)
for(i=0;i<sb>entry_table_size;i++)
memcpy(block+i*sb>entry_size,&fe,sizeof(fe));
write(sfs_handle,block,sizeof(block));
Now,youmayplugoutandpluginthependriveback.Andyoumaywonderthatneitheritisauto
mounted,noryouareabletomountit.Thatsexpected,asnowitisformattedwithafilesystemwhichis
notyetcodedas(kernel)moduleandthereisnooneinthekerneltodecodethesame.So,codingthat
kernelmodulewouldbetheultimatesteptogeteverythingworkinglikewithanyotherexistingfile
systems(vfat,ext3,).Ifyouareworriedthatyourpendriveisspoiled,youmayreformatitwiththe
FAT32(vfat)filesystemasfollows(asrootorwithsudo):
#mkfs.vfat/dev/sdb1#Becarefulwiththecorrectpartitiondevicefile
andthenplugout&pluginthependrivetogetautomounted.But,youknowPugsbeingacool
carefreeguy,insteadwentaheadtotryoutbrowsingtheSimulafilesystemcreatedonthependrive
partition.
Browsingthependrivepartition
Obviously,therewereslightmodificationstothebrowse_sfs.capplicationaswell,inlinewiththe
changestoformat_sfs.c.Majoronebeingcompulsorilytakingthepartitionsdevicefiletobrowseasthe
commandlineargument,insteadofbrowsingthedefaultregularfile.sfsf.
Alltheupdatedfiles(real_sfs_ds.h,format_real_sfs.c,browse_real_sfs.candMakefile)areavailable
fromrsfs_code.tar.bz2.
Figure37:SFSbrowseronpendrive
Figure37showsthebrowserinaction.However,thecoolestbrowsingwouldbethesamewayasis
donewithallotherfilesystems,usingtheshellcommandscd,ls,Yes,andforthatwewouldneed
therealSFSmoduleinplace.KeepfollowingwhatsPugsuptoforgettingthatinplace.
TwentysecondArticle>>
TheSemesterProjectPartV:FileSystemModuleTemplate
2Replies
Thistwentysecondarticle,whichispartoftheseriesonLinuxdevicedrivers,laysoutabarebonefile
systemmodule.
<<TwentyfirstArticle
Withtheformattingofthependrive,thefilesystemisallsetinthehardwarespace.Now,itistheturnto
decodethatusingacorrespondingfilesystemmoduleinthekernelspace,andaccordinglyprovidethe
userspacefilesysteminterface,forittobebrowsedlikeanyotherfilesystems.
The5setsofSystemCalls
Unlikecharacterorblockdrivers,thefilesystemdriversinvolvenotjustonestructureoffunction
pointers,butinstead5structuresoffunctionpointers,forthevariousinterfaces,providedbyafile
system.Theseare:
structfile_system_typecontainsfunctionstooperateonthesuperblock
structsuper_operationscontainsfunctionstooperateontheinodes
structinode_operationscontainsfunctionstooperateonthedirectoryentries
structfile_operationscontainsfunctionstooperateonthefiledata(throughpagecache)
structaddress_space_operationscontainspagecacheoperationsforthefiledata
Withthese,thereweremanynewtermsforPugs.Hereferredthefollowingglossarytounderstandthe
varioustermsusedaboveandlaterinthefilesystemmoduledevelopment:
PagecacheorBuffercache:PoolofRAMbuffers,eachofpagesize(typically4096bytes).
Thesebuffersareusedasthecacheforthefiledatareadfromtheunderlyinghardware,thus
increasingtheperformanceoffileoperations
Inode:Structurecontainingthemetadata/informationofafile,likepermissions,owner,etc.
Thoughfilenameisametadataofafile,forbetterspaceutilization,intypicalLinuxfilesystems,
itisnotkeptininode,insteadinsomethingcalleddirectoryentries.Collectionofinodes,iscalled
aninodetable
Directoryentry:Structurecontainingthenameandinodenumberofafileordirectory.Intypical
Linuxbasedfilesystems,acollectionofdirectoryentriesfortheimmediatefilesanddirectoriesof
saydirectoryD,isstoredinthedatablocksofthedirectoryD
Superblock:Structurecontainingtheinformationaboutthevariousdatastructuresofthefile
systems,liketheinodetables,Basicallythemetametadata,i.e.metadataforthemetadata
VirtualFileSystem(VFS):Conceptualfilesystemlayerinterfacingthekernelspacetouser
spaceinanabstractmanner,showingeverythingasafile,andtranslatingtheiroperationsfrom
usertotheappropriateentityinthekernelspace
Eachoneoftheabovefivestructurescontainsalistoffunctionpointers,whichneedstobepopulated
dependingonwhatallfeaturesarethereortobesupportedinthefilesystem(module).For
example,structfile_system_typemaycontainsystemcallsformountingandunmountingafilesystem,
basicallyoperatingonitssuperblockstructsuper_operationsmaycontaininoderead/writesystem
callsstructinode_operationsmaycontainfunctiontolookupdirectoryentriesstruct
file_operationsmaygenericallyoperateonthepagecachedfiledata,whichmayinturninvokepage
cacheoperations,definedinthestructaddress_space_operations.Forthesevariousoperations,most
ofthesefunctionswilltheninterfacewiththecorrespondingunderlyingblockdevicedrivertoultimately
operatewiththeformattedfilesysteminthehardwarespace.
TostartwithPugslaidoutthecompleteframeworkofhisrealSFSmodule,butwithminimal
functionality,goodenoughtocompile,load,andnotcrashthekernel.Hepopulatedonlythefirstof
thesefivestructuresthestructfile_system_typeandleftalltheothersempty.Herestheexactcode
ofthestructuredefinitions:
#include<linux/fs.h>/*Forsystemcalls,structures,...*/
staticstructfile_system_typesfs;
staticstructsuper_operationssfs_sops;
staticstructinode_operationssfs_iops;
staticstructfile_operationssfs_fops;
staticstructaddress_space_operationssfs_aops;
#include<linux/version.h>/*ForLINUX_VERSION_CODE&KERNEL_VERSION*/
staticstructfile_system_typesfs=
{
name:"sfs",/*Nameofourfilesystem*/
#if(LINUX_VERSION_CODE<KERNEL_VERSION(2,6,38))
get_sb:sfs_get_sb,
#else
#endif
mount:sfs_mount,
kill_sb:kill_block_super,
owner:THIS_MODULE
};
NotethatbeforeLinuxkernelversion2.6.38,themountfunctionpointerwasreferredasget_sb,and
also,itusedtohaveslightlydifferentparameters.Andhence,theabove#ifforittobecompatibleat
leastacross2.6.3xandpossiblywith3.xkernelversionsnoguaranteeforothers.Accordingly,the
correspondingfunctionssfs_get_sb()andsfs_mount(),arealso#ifd,asfollows:
#include<linux/kernel.h>/*Forprintk,...*/
if(LINUX_VERSION_CODE<KERNEL_VERSION(2,6,38))
staticintsfs_get_sb(structfile_system_type*fs_type,intflags,
constchar*devname,void*data,structvfsmount*vm)
printk(KERN_INFO"sfs:devname=%s\n",devname);
/*sfs_fill_superthiswillbecalledtofillthesuperblock*/
returnget_sb_bdev(fs_type,flags,devname,data,&sfs_fill_super,vm);
}
#else
staticstructdentry*sfs_mount(structfile_system_type*fs_type,
intflags,constchar*devname,void*data)
printk(KERN_INFO"sfs:devname=%s\n",devname);
/*sfs_fill_superthiswillbecalledtofillthesuperblock*/
returnmount_bdev(fs_type,flags,devname,data,&sfs_fill_super);
}
#endif
Theonlydifferenceintheabove2functionsisthatinthelater,theVFSmountpointrelatedstructure
hasbeenremoved.Theprintk()intherewoulddisplaytheunderlyingpartitionsdevicefilewhichthe
userisgoingtomount,basicallythependrivesSFSformatted
partition.get_sb_bdev()andmount_bdev()aregenericblockdevicemountfunctionsfortherespective
kernelversions,definedinfs/super.candprototypedin<linux/fs.h>.Pugsalsousedthem,asmost
otherfilesystemwritersdo.Areyouwondering:Doesallfilesystemmountablockdevice,thesame
way?Mostofityes,exceptthepartwherethemountoperationneedstofillintheVFSsuperblock
structure(structsuper_block),asperthesuperblockoftheunderlyingfilesystemobviouslythatmost
probablywouldbedifferent.Butthenhowdoesitdothat?Observecarefully,intheabovefunctions,
apartfrompassingalltheparametersasis,thereisanadditionalparametersfs_fill_super,andthatis
PugscustomfunctiontofilltheVFSsuperblock,aspertheSFSfilesystem.
Unlikethemountfunctionpointer,theunmountfunctionpointerhasbeensame(kill_sb)forquitesome
kernelversionsandinunmounting,thereisnoteventheminimaldistinctionrequiredacrossdifferent
filesystems.So,thegenericblockdeviceunmountfunctionkill_block_super()hasbeenuseddirectlyas
thefunctionpointer.
Insfs_fill_super(),Pugsisideallysupposedtoreadthesuperblockfromtheunderlyinghardwarespace
SFS,andthenaccordinglytranslateandfillthatintoVFSsuperblocktoenableVFStoprovidetheuser
spacefilesysteminterface.Butheisyettofigurethatout,ashowtoreadfromtheunderlyingblock
device,inthekernelspace.Informationofwhichblockdevicetouse,isalreadyembeddedinto
thesuper_blockstructureitself,obtainedfromtheuserissuingthemountcommand.ButasPugs
decidedtogetthebarebonerealSFSup,first,hewentaheadwritingthissfs_super_fill()functionalso
asahardcodedfillfunction.Andwiththatitself,heregisteredtheSimulafilesystemwiththeVFS.As
anyotherLinuxdriver,heresthefilesystemdriversconstructoranddestructorforthat:
#include<linux/module.h>/*Formodulerelatedmacros,...*/
staticint__initsfs_init(void)
{
interr;
err=register_filesystem(&sfs);
returnerr;
}
staticvoid__exitsfs_exit(void)
{
unregister_filesystem(&sfs);
}
module_init(sfs_init);
module_exit(sfs_exit);
Bothregister_filesystem()andunregister_filesystem()takespointertothethestructfile_system_type
sfs(filledabove),astheirparameter,torespectivelyregisterandunregisterthefilesystemdescribedby
it.
HardcodedSFSsuperblockandrootinode
Andyes,heresthehardcodedsfs_fill_super()function:
#include"real_sfs_ds.h"/*ForSFSrelateddefines,datastructures,...*/
staticintsfs_fill_super(structsuper_block*sb,void*data,intsilent)
{
printk(KERN_INFO"sfs:sfs_fill_super\n");
sb>s_blocksize=SIMULA_FS_BLOCK_SIZE;
sb>s_blocksize_bits=SIMULA_FS_BLOCK_SIZE_BITS;
sb>s_magic=SIMULA_FS_TYPE;
sb>s_type=&sfs;//file_system_type
sb>s_op=&sfs_sops;//superblockoperations
sfs_root_inode=iget_locked(sb,1);//obtainaninodefromVFS
if(!sfs_root_inode)
if(sfs_root_inode>i_state&I_NEW)//allocatedfreshnow
printk(KERN_INFO"sfs:Gotnewrootinode,let'sfillin\n");
sfs_root_inode>i_op=&sfs_iops;//inodeoperations
sfs_root_inode>i_mode=S_IFDIR|S_IRWXU|
sfs_root_inode>i_fop=&sfs_fops;//fileoperations
sfs_root_inode>i_mapping>a_ops=&sfs_aops;//addressoperations
unlock_new_inode(sfs_root_inode);
else
returnEACCES;
S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH;
printk(KERN_INFO"sfs:Gotrootinodefrominodecache\n");
#if(LINUX_VERSION_CODE<KERNEL_VERSION(3,4,0))
sb>s_root=d_alloc_root(sfs_root_inode);
#else
sb>s_root=d_make_root(sfs_root_inode);
#endif
if(!sb>s_root)
iget_failed(sfs_root_inode);
returnENOMEM;
return0;
Asmentionedearlier,thisfunctionisbasicallysupposedtoreadtheunderlyingSFSsuperblock,and
accordinglytranslateandfillthestructsuper_block,pointedtobyitsfirstparametersb.So,
understandingitissameasunderstandingtheminimalfieldsofthestructsuper_block,whichare
gettingfilledup.Thefirstthreearetheblocksize,itslogarithmbase2,andthetype/magiccodeofthe
Simulafilesystem.AsPugscodesfurther,weshallseethatoncehegetsthesuperblockfromthe
hardwarespace,hewouldinsteadgetthesevaluesfromthatsuperblock,andmoreimportantlyverify
them,toensurethatthecorrectpartitionisbeingmounted.
Afterthat,thevariousstructurepointersarepointedtotheircorrespondingstructureofthefunction
pointers.Lastbutnotleast,therootinodespointers_rootispointedtothestructinodestructure,
obtainedfromVFSinodecache,basedontheinodenumberofrootrightnow,whichhasbeenhard
codedto1itmaypossiblychange.Iftheinodestructureisobtainedfresh,i.e.forthefirsttime,itis
thenfilledaspertheunderlyingSFSrootinodescontent.Also,themodefieldisbeinghardcodedto
drwxrxrx.Apartfromthat,theusualstructurepointersarebeinginitializedbythecorresponding
structureaddresses.Andfinally,therootsinodeisbeingattachedtothesuperblock
usingd_alloc_root()ord_make_root(),asperthekernelversion.
Alltheabovecodepiecesputintogetherasthebarebonereal_sfs_bb.c,alongwith
thereal_sfs_ds.h(basedonthesamefilecreatedearlier),andasupportingMakefileareavailable
fromrsfsbb_code.tbz2.
BareboneSFSmoduleinaction
Oncecompiledusingmake,gettingthereal_sfs_bb.kodriver,Pugsdidhisusualunusualexperiments,
shownasinFigure38.
Figure38:BarebonerealSFSexperiments
Pugsexperiments(ExplanationofFigure38):
Checkedthekernelwindow/proc/filesystemsforthekernelsupportedfilesystems
Loadedthereal_sfs_bb.kodriver
Recheckedthekernelwindow/proc/filesystemsforthekernelsupportedfilesystems.Now,it
showssfslistedattheend
Didamountofhispendrivepartition/dev/sdb1onto/mntusingthesfsfilesystem.Checked
thedmesglogsontheadjacentwindow.(Keepinmind,thatrightnow,thesfs_fill_super()isnot
reallyreadingthepartition,andhencenotdoinganychecks.So,itreallydoesntmatterasto
howthe/dev/sdb1isformatted.)Butyes,themountoutputshowsthatitismountedusing
thesfsfilesystem
Oops!!!ButdfoutputshowsFunctionnotimplemented,cdgivesNotadirectory.Aha!!Pugshavent
implementedanyotherfunctionsinanyoftheotherfourfunctionpointerstructures,yet.So,thats
expected.
Note:Theaboveexperimentsareusingsudo.Insteadonemaygetintorootshellanddothesame
withoutasudo.
Okay,sonokernelcrashes,andabarebonefilesysteminactionYippee.Ya!Ya!Pugsknows
thatdf,cd,arenotyetfunctional.Forthat,heneedstostartaddingthevarioussystemcallsinthe
other(four)functionpointerstructurestobeabletodocoolcoolbrowsing,thesamewayasisdone
withallotherfilesystems,usingthevariousshellcommands.Andyes,Pugsisalreadyontohistask
afterallheneedstohaveageekydemoforhisfinalsemesterproject.
TwentythirdArticle>>
PlayingwithSystems
SysPlay'sBlogs
Menu
TheSemesterProjectPartVI:FileSystemonBlockDevice
1Reply
Thistwentythirdarticle,whichispartoftheseriesonLinuxdevicedrivers,enhancesthepreviously
writtenbarebonefilesystemmodule,toconnectwitharealhardwarepartition.
<<TwentysecondArticle
Sincethelastbarebonefilesystem,thefirstthingwhichPugsfiguredoutwashowtoreadfromthe
underlyingblockdevice.Followingisatypicalwayofdoingit:
structbuffer_head*bh;
bh=sb_bread(sb,block);/*sbisthestructsuper_blockpointer*/
//bh>b_datacontainsthedata
//Oncedone,bhshouldbereleasedusing:
brelse(bh);
TodotheaboveandvariousotherrealSFS(SimulaFileSystem)operations,Pugsfeltaneedtohave
hisownhandletobeakeyparameter,whichheaddedasfollows(inpreviousreal_sfs_ds.h):
typedefstructsfs_info
{
structsuper_block*vfs_sb;/*SuperblockstructurefromVFSforthisfs*/
sfs_super_block_tsb;/*Ourfssuperblock*/
byte1_t*used_blocks;/*Usedblockstracker*/
}sfs_info_t;
Themainideabehindthiswastoputallrequiredstaticglobalvariablesinasinglestructure,andpoint
thatbytheprivatedatapointerofthefilesystem,whichiss_fs_infopointerinthestruct
super_blockstructure.Withthat,thekeychangesinthefill_super_block()(in
previousreal_sfs_bb.cfile)becomes:
Allocatethestructureforthehandle,usingkzalloc()
Initializethestructureforthehandle(throughinit_browsing())
Readthephysicalsuperblock,verifyandtranslateinfofromitintotheVFSsuperblock
(throughinit_browsing())
Pointitbys_fs_info(throughinit_browsing())
UpdatetheVFSsuperblock,accordingly
Accordingly,theerrorhandlingcodewouldneedtodotheshut_browsing(info)andkfree(info).And,that
wouldadditionallyalsoneedtogoalongwiththefunctioncorrespondingtothekill_sbfunctionpointer,
definedinthepreviousreal_sfs_bb.cbykill_block_super,calledduringumount.
Heresthevariouscodepieces:
#include<linux/slab.h>/*Forkzalloc,...*/
...
staticintsfs_fill_super(structsuper_block*sb,void*data,intsilent)
{
sfs_info_t*info;
if(!(info=(sfs_info_t*)(kzalloc(sizeof(sfs_info_t),GFP_KERNEL))))
info>vfs_sb=sb;
if(init_browsing(info)<0)
kfree(info);
returnEIO;
/*UpdatingtheVFSsuper_block*/
sb>s_magic=info>sb.type;
sb>s_blocksize=info>sb.block_size;
sb>s_blocksize_bits=get_bit_pos(info>sb.block_size);
...
returnENOMEM;
}
staticvoidsfs_kill_sb(structsuper_block*sb)
sfs_info_t*info=(sfs_info_t*)(sb>s_fs_info);
kill_block_super(sb);
if(info)
shut_browsing(info);
kfree(info);
kzalloc()incontrasttokmalloc(),alsozeroesouttheallocatedlocation.get_bit_pos()isasimplePugs
waytocomputelogarithmbase2,asfollows:
staticintget_bit_pos(unsignedintval)
{
inti;
for(i=0;val;i++)
return(i1);
val>>=1;
Andinit_browsing(),shut_browsing()arebasicallythetransformationsoftheearlieruserspace
functionsofbrowse_real_sfs.cintokernelspacecodereal_sfs_ops.c,prototypedinreal_sfs_ops.h.
Thisbasicallyinvolvesthefollowingtransformations:
intsfs_handleintosfs_info_t*info
lseek()&read()intothereadfromtheblockdeviceusingsb_bread()
calloc()intovmalloc()&thenappropriateinitializationbyzeros.
free()intovfree()
Heresthetransformedinit_browsing()andshut_browsing()inreal_sfs_ops.c:
#include<linux/fs.h>/*Forstructsuper_block*/
#include<linux/errno.h>/*Forerrorcodes*/
#include<linux/vmalloc.h>/*Forvmalloc,...*/
#include"real_sfs_ds.h"
#include"real_sfs_ops.h"
intinit_browsing(sfs_info_t*info)
{
byte1_t*used_blocks;
inti,j;
sfs_file_entry_tfe;
intretval;
if((retval=read_sb_from_real_sfs(info,&info>sb))<0)
if(info>sb.type!=SIMULA_FS_TYPE)
printk(KERN_ERR"InvalidSFSdetected.Givingup.\n");
returnEINVAL;
/*Markusedblocks*/
used_blocks=(byte1_t*)(vmalloc(info>sb.partition_size));
if(!used_blocks)
for(i=0;i<info>sb.data_block_start;i++)
for(;i<info>sb.partition_size;i++)
for(i=0;i<info>sb.entry_count;i++)
if((retval=read_from_real_sfs(info,
info>sb.entry_table_block_start,
i*sizeof(sfs_file_entry_t),
&fe,sizeof(sfs_file_entry_t)))<0)
vfree(used_blocks);
returnretval;
if(!fe.name[0])continue;
for(j=0;j<SIMULA_FS_DATA_BLOCK_CNT;j++)
returnretval;
returnENOMEM;
used_blocks[i]=1;
used_blocks[i]=0;
if(fe.blocks[j]==0)break;
used_blocks[fe.blocks[j]]=1;
info>used_blocks=used_blocks;
info>vfs_sb>s_fs_info=info;
return0;
}
voidshut_browsing(sfs_info_t*info)
{
if(info>used_blocks)
vfree(info>used_blocks);
Similarly,allotherfunctionsinbrowse_real_sfs.cwouldalsohavetobetransformed,onebyone.Also,
notethereadfromtheunderlyingblockdeviceisbeingcapturedbythetwofunctions,
namelyread_sb_from_real_sfs()andread_from_real_sfs(),whicharecodedasfollows:
#include<linux/buffer_head.h>/*structbuffer_head,sb_bread,...*/
#include<linux/string.h>/*Formemcpy*/
#include"real_sfs_ds.h"
staticintread_sb_from_real_sfs(sfs_info_t*info,sfs_super_block_t*sb)
{
structbuffer_head*bh;
if(!(bh=sb_bread(info>vfs_sb,0/*Superblockisthe0thblock*/)))
memcpy(sb,bh>b_data,SIMULA_FS_BLOCK_SIZE);
brelse(bh);
return0;
returnEIO;
}
staticintread_from_real_sfs(sfs_info_t*info,byte4_tblock,
byte4_toffset,void*buf,byte4_tlen)
byte4_tblock_size=info>sb.block_size;
byte4_tbd_block_size=info>vfs_sb>s_bdev>bd_block_size;
byte4_tabs;
structbuffer_head*bh;
/*
*TranslatingtherealSFSblocknumberingtounderlyingblockdevice
*blocknumbering,forsb_bread()
*/
abs=block*block_size+offset;
block=abs/bd_block_size;
offset=abs%bd_block_size;
if(offset+len>bd_block_size)//Shouldneverhappen
if(!(bh=sb_bread(info>vfs_sb,block)))
memcpy(buf,bh>b_data+offset,len);
brelse(bh);
return0;
returnEINVAL;
returnEIO;
Alltheabovecodepiecesputintogetherasthereal_sfs_minimal.c(basedonthe
filereal_sfs_bb.ccreatedearlier),real_sfs_ops.c,real_sfs_ds.h(basedonthesamefilecreated
earlier),real_sfs_ops.h,andasupportingMakefile,alongwiththepreviously
createdformat_real_sfs.capplicationareavailablefromrsfs_on_block_device_code.tbz2.
RealSFSonblockdevice
Oncecompiledusingmake,gettingthereal_sfs_first.kodriver,Pugsdidntexpectittobewaydifferent
fromthepreviousreal_sfs_bb.kodriver,butatleastnowitshouldbereadingandverifyingthe
underlyingpartition.Andforthathefirsttriedmountingtheusualpartitionofapendrivetogetan
InvalidSFSdetectedmessageindmesgoutputandthenafterformattingit.Notethesameerrorof
Notadirectory,etcasinpreviousarticle,stillexistingasanywaysitisstillverysimilartotheprevious
barebonedriverthecorefunctionalitiesyettobeimplementedjustthatitisnowonarealblock
devicepartition.Figure39showstheexactcommandsforallthesesteps.
Figure39:ConnectingtheSFSmodulewiththependrivepartition
Note:./format_real_sfsandmountcommandsmaytakelotoftime(maybeinminutes),depending
onthepartitionsize.So,preferablyuseapartition,saylessthan1MB.
Withthisimportantstepofgettingthefilesystemmoduleinteractingwiththeunderlyingblockdevice,
thelaststepforPugswouldbetodotheothertransformationsfrombrowse_real_sfs.candaccordingly
usethemintheSFSmodule.
TwentyfourthArticle>>
TheSemesterProjectPartVII:FileSysteminAction
1Reply
Thistwentyfourtharticle,whichispartoftheseriesonLinuxdevicedrivers,getsthecompletereal
SIMULAfilesystemmoduleinaction,witharealhardwarepartitiononyourpendrive.
<<TwentythirdArticle
RealSFSinaction
Codeavailablefromrsfs_in_action_code.tbz2getstothefinaltestedimplementationofthefinal
semesterprojectofPugs&Shweta.Thiscontainsthefollowing:
real_sfs.ccontainsthecodeofearlierreal_sfs_minimal.cplustheremainingrealSIMULAfile
systemfunctionalities.Notethefilesystemsnamechangefromsfstoreal_sfs
real_sfs_ops.c&real_sfs_ops.hcontainstheearliercodeplustheadditionaloperations
neededfortheenhancedreal_sfs.cimplementations
real_sfs_ds.h(almostsamefileasinthepreviousarticle,plusaspinlockaddedintothereal
SFSinfostructuretobeusedforpreventingraceconditionsinaccessingtheused_blocksarray
inthesamestructure)
format_real_sfs.c(samefileasinthepreviousarticles)therealSFSformatterapplication
Makefilecontainstherulesforbuildingthedriverreal_sfs_final.kousingthereal_sfs_*.*files,
andtheformat_real_sfsapplicationusingtheformat_real_sfs.c
Withalltheseandearlierdetails,Shwetacompletedtheirprojectdocumentation.Andsofinally,Shweta
&Pugswereallsetfortheirfinalsemesterprojectdemo,presentationandviva.
Thehighlightsoftheirdemo(onrootshell)areasfollows:
Loadingreal_sfs_finaldriver:insmodreal_sfs_final.ko
Usingthepreviouslyformattedpendrivepartition/dev/sdb1orReformattingitusing
theformat_real_sfsapplication:./format_real_sfs/dev/sdb1.Caution:Pleasecheckoutthe
completedetailedstepsonthisfromthepreviousarticle,beforeyouactuallyformatit
MounttherealSFSformattedpartition:mounttreal_sfs/dev/sdb1/mnt
AndAndwhat?Browsethemountingfilesystem.Useyourusualshellcommandstooperate
onthefilesystem:ls,cd,touch,vi,rm,chmod,
Figure40showstherealSIMULAfilesysteminaction
Figure40:TherealSIMULAfilesystemmoduleinaction
Realitiesbehindtheaction
Andifyoureallywanttoknow,whataretheadditionalenhancementsPugsaddedtotheprevious
articlescodetogettothislevel,itisbasicallythefollowingcoresystemcallsaspartoftheremaining4
outof5setofstructuresoffunctionpointers(inreal_sfs.c):
1. write_inode(understructsuper_operations)sfs_write_inode()basicallygetsapointertoan
inodeintheVFSinodecache,andisexpectedtosyncthatwiththeinodeinphysicalhardware
spacefilesystem.Thatisachievedbycallingtheappropriatelymodifiedsfs_update()(defined
inreal_sfs_ops.c)(adaptedfromtheearlierbrowse_real_sfsapplication).Thekeyparameter
changesbeingpassingtheinodenumberinsteadofthefilenameandtheactualtimestamp
insteadoftheflagforitsupdatestatus.Andaccordingly,thecalltofilename
basedsfs_lookup()isbeingreplacedbyinodenumberbasedsfs_get_file_entry()(defined
inreal_sfs_ops.c),andadditionallynowthedatablocksarealsobeingfreed
(usingsfs_put_data_block()(definedinreal_sfs_ops.c)),ifthefilesizehasreduced.Notethat
sfs_put_data_block()(definedinreal_sfs_ops.c)isatransformationoftheput_data_block()from
thebrowse_real_sfsapplication.Also,notetheinterestingmappingto/fromtheVFSinode
numberfrom/toourzerobasedfileentryindices,usingthe
macrosS2V_INODE_NUM()/V2S_INODE_NUM()inreal_sfs_ops.h.
Andfinally,underlyingwriteisbeingachievedusingwrite_to_real_sfs(),afunctionadded
inreal_sfs_ops.c,verysimilartoread_from_real_sfs()(alreadythereinreal_sfs_ops.c),except
thedirectionreversalofthedatatransferandmarkingthebufferdirtytobesyncedupwiththe
physicalcontent.Alongwith,inreal_sfs_ops.c,twowrapperfunctionsread_entry_from_real_sfs()
(usingread_from_real_sfs())andwrite_entry_to_real_sfs()(usingwrite_to_real_sfs())havebeen
writtenandusedrespectivelyforthespecificrequirementsofreadingandwritingthefileentries,
toincreasethecodereadability.sfs_write_inode()andsfs_update()areshowninthecode
snippetbelow.sfs_write_inode()hasbeenwritteninthefilereal_sfs.c.Forothers,checkoutthe
filereal_sfs_ops.c
#if(LINUX_VERSION_CODE<KERNEL_VERSION(2,6,34))
staticintsfs_write_inode(structinode*inode,intdo_sync)
#else
staticintsfs_write_inode(structinode*inode,structwriteback_control*wbc)
#endif
{
sfs_info_t*info=(sfs_info_t*)(inode>i_sb>s_fs_info);
intsize,timestamp,perms;
printk(KERN_INFO"sfs:sfs_write_inode(i_ino=%ld)\n",inode>i_ino);
if(!(S_ISREG(inode>i_mode)))//RealSFSdealsonlywithregularfiles
size=i_size_read(inode);
timestamp=inode>i_mtime.tv_sec>inode>i_ctime.tv_sec?
perms=0;
perms|=(inode>i_mode&(S_IRUSR|S_IRGRP|S_IROTH))?4:0;
perms|=(inode>i_mode&(S_IWUSR|S_IWGRP|S_IWOTH))?2:0;
perms|=(inode>i_mode&(S_IXUSR|S_IXGRP|S_IXOTH))?1:0;
printk(KERN_INFO"sfs:sfs_write_inodewith%dbytes@%dsecsw/%o\n",
returnsfs_update(info,inode>i_ino,&size,×tamp,&perms);
return0;
inode>i_mtime.tv_sec:inode>i_ctime.tv_sec;
size,timestamp,perms);
}
intsfs_update(sfs_info_t*info,intvfs_ino,int*size,int*timestamp,int*perms)
{
sfs_file_entry_tfe;
inti;
intretval;
if((retval=sfs_get_file_entry(info,vfs_ino,&fe))<0)
if(size)fe.size=*size;
if(timestamp)fe.timestamp=*timestamp;
if(perms&&(*perms<=07))fe.perms=*perms;
for(i=(fe.size+info>sb.block_size1)/info>sb.block_size;
if(fe.blocks[i])
sfs_put_data_block(info,fe.blocks[i]);
fe.blocks[i]=0;
returnwrite_entry_to_real_sfs(info,V2S_INODE_NUM(vfs_ino),&fe);
returnretval;
i<SIMULA_FS_DATA_BLOCK_CNT;i++)
2. create,unlink,lookup(understructinode_operations)Allthe3
functionssfs_inode_create(),sfs_inode_unlink(),sfs_inode_lookup()havethe2common
parameters(theparentsinodepointerandthepointertothedirectoryentryforthefilein
consideration),andtheserespectivelycreate,delete,andlookupaninodecorrespondingtotheir
directoryentrypointedbytheirparameter,saydentry.
sfs_inode_lookup()basicallysearchesfortheexistenceofthefilenameunderneathusingthe
appropriatelymodifiedsfs_lookup()(definedinreal_sfs_ops.c)(adaptedfromthe
earlierbrowse_real_sfsapplicationtheadoptionbeingreplacingtheuser
spacelseek()+read()combobytheread_entry_from_real_sfs()).Iffilenameisnotfound,thenit
invokesthegenerickernelfunctiond_splice_alias()tocreateanewinodeentryintheunderlying
filesystem,forthesame,andthenattachesittothedirectoryentrypointedbydentry.Otherwise,
itjustattachestheinodefromVFSinodecache(usinggenerickernelfunctiond_add()).This
inode,ifobtainedfresh(I_NEW),needstobefilledinwiththerealSFSlookedupfileattributes.
Inalltheaboveimplementationsandinthosetocome,afewbasicassumptionshavebeen
made,namely:
RealSFSmaintainsmodeonlyforuserandthatismappedtoall3ofuser,group,otherof
theVFSinode
RealSFSmaintainsonlyonetimestampandthatismappedtoall3ofcreated,modified,
accessedtimesoftheVFSinode.
sfs_inode_create()andsfs_inode_unlink()correspondinglyinvokesthe
transformedsfs_create()andsfs_remove()(definedinreal_sfs_ops.c)(adaptedfromthe
earlierbrowse_real_sfsapplication),forrespectivelycreatingandclearingtheinodeentriesat
theunderlyinghardwarespacefilesystem,apartfromtheusualinodecacheoperations,
usingnew_inode()+insert_inode_locked(),d_instantiate()&inode_dec_link_count(),insteadof
theearlierlearntiget_locked(),d_add().Apartfromthepermissionsandfileentryparameters,
andreplacinglseek()+read()combobyread_entry_from_real_sfs(),sfs_create()hasan
interestingtransformationfromuserspacetokernelspace:time(NULL)toget_seconds().Andin
bothofsfs_create()andsfs_remove()theuserspacelseek()+write()combohasbeenreplaced
bytheobviouswrite_entry_to_real_sfs().Checkoutalltheabovementionedcodepiecesinthe
filesreal_sfs.candreal_sfs_ops.c,asmentioned.
3. readpage,write_begin,writepage,write_end(understructaddress_space_operations)All
theseaddressspaceoperationsarebasicallytoreadandwriteblocksontheunderlying
filesystem,andareachievedusingtherespectivegenerickernel
functionsmpage_readpage(),block_write_begin(),block_write_full_page(),generic_write_end().
Firstoneisprototypedin<linux/mpage.h>andremaining3in<linux/buffer_head.h>.Now,
thoughthesefunctionsaregenericenough,alittlethoughtwouldshowthatthefirstthreeof
thesewouldultimatelyhavetodoarealSFSspecifictransactionwiththeunderlyingblockdevice
(thehardwarepartition),usingthecorrespondingblocklayerAPIs.Andthatexactlyisachieved
bytherealSFSspecificfunctionsfs_get_block(),whichisbeingpassedintoandusedbythefirst
threefunctions,mentionedabove.
sfs_get_block()(definedinreal_sfs.c)isinvokedtoreadaparticularblocknumber(iblock)ofa
file(denotedbyaninode),intoabufferhead(bh_result),optionallyfetching(allocating)anew
block.Soforthat,theblockarrayofcorrespondingrealSFSinodeislookedupintoandthenthe
correspondingblockofthephysicalpartitionisfetchedusingthekernelAPImap_bh().Again
notethattofetchanewblock,weinvokethesfs_get_data_block()(definedinreal_sfs_ops.c),
whichisagainatransformationoftheget_data_block()fromthebrowse_real_sfsapplication.
Also,incaseofanewblockallocation,therealSFSinodeisalsoupdatedunderneath,
usingsfs_update_file_entry()aonelinerimplementationinreal_sfs_ops.c.Codesnippetbelow
showsthesfs_get_block()implementation.
staticintsfs_get_block(structinode*inode,sector_tiblock,
structbuffer_head*bh_result,intcreate)
structsuper_block*sb=inode>i_sb;
sfs_info_t*info=(sfs_info_t*)(sb>s_fs_info);
sfs_file_entry_tfe;
sector_tphys;
intretval;
printk(KERN_INFO"sfs:sfs_get_blockcalledforI:%ld,B:%lld,C:%d\n",
if(iblock>=SIMULA_FS_DATA_BLOCK_CNT)
if((retval=sfs_get_file_entry(info,inode>i_ino,&fe))<0)
if(!fe.blocks[iblock])
if(!create)
else
if((fe.blocks[iblock]=sfs_get_data_block(info))==
if((retval=sfs_update_file_entry(info,inode>i_ino,&fe))
inode>i_ino,iblock,create);
returnENOSPC;
returnretval;
returnEIO;
INV_BLOCK)
returnENOSPC;
<0)
phys=fe.blocks[iblock];
map_bh(bh_result,sb,phys);
return0;
returnretval;
4. open,release,read,write,aio_read/read_iter(sincekernelv3.16),aio_write/write_iter(since
kernelv3.16),fsync(underaregularfilesstructfile_operations)Alltheseoperationsshould
basicallygothroughthebuffercache,i.e.shouldbeimplementedusingtheaddressspace
operations.Andthisbeingacommonrequirement,thekernelprovidesagenericsetofkernel
APIs,namelygeneric_file_open(),do_sync_read()/new_sync_read()(sincekernel
v3.16),do_sync_write()/new_sync_write()(sincekernel
v3.16),generic_file_aio_read()/generic_file_read_iter()(sincekernel
v3.16),generic_file_aio_write()/generic_file_write_iter()(sincekernel
v3.16),simple_sync_file()/noop_fsync()(sincekernelv2.6.35).NotethatthereisnoAPIfor
release,asitisareturn0API.Checkoutthereal_sfs.cfilefortheexactdefinitionofstruct
file_operationssfs_fops.
5. readdir/iterate(sincekernelv3.11)(underadirectorysstructfile_operations)
sfs_readdir()/sfs_iterate()primarilyreadsthedirectoryentriesofanunderlyingdirectory
(denotedbyfile),andfillsitupintotheVFSdirectoryentrycache(pointedbydirentparameter)
usingtheparameterfunctionfilldir,orintothedirectorycontext(pointedbyctxparameter)(since
kernelv3.11).
AsrealSFShasonlyonedirectory,thetopone,thisfunctionbasicallyfillsupthedirectoryentry
cachewithdirectoryentriesforallthefilesintheunderlyingfilesystem,usingthe
transformedsfs_list()(definedinreal_sfs_ops.c),adaptedfromthebrowse_real_sfsapplication.
Checkoutthereal_sfs.cfileforthecompletesfs_readdir()/sfs_iterate()implementation,which
startswithfillingdirectoryentriesfor.(currentdirectory)and..(parentdirectory)using
parameterfunctionfilldir(),orgenerickernelfunctiondir_emit_dots()(sincekernelv3.11),and
thenforallthefilesoftherealSFS,usingsfs_list().
6. put_super(understructsuper_operations)Thepreviouscustom
implementationsfs_kill_sb()(pointedbykill_sb)hasbeenenhancedbyseparatingitintothe
custompartbeingputintosfs_put_super()(andnowpointedbyput_super),andthe
standardkill_block_super()beingdirectlypointedbykill_sb.Checkoutthefilereal_sfs.cforall
thesechanges.
Withalltheseinplace,onecouldseetheamazingdemobyPugsinaction,asshowninFigure40.And
dontforgetwatchingthelivelogin/var/log/messagesusingatailf/var/log/messages,matchingitwith
everycommandyouissueonthemountedrealSFSfilesystem.Thiswouldgiveyouthebestinsight
intowhendoeswhichsystemcallgetscalled.Or,inotherwordswhichapplicationinvokeswhichsystem
callfromthefilesystemfront.Fortracingallthesystemcallsinvokedbyanapplication/command,use
stracewiththecommand,e.g.typestracelsinsteadofjustls.
Notes
1. OndistroslikeUbuntu,youmayfindthelogunder/var/log/sysloginsteadof/var/log/messages