{"id":184,"date":"2015-07-21T06:12:27","date_gmt":"2015-07-21T10:12:27","guid":{"rendered":"http:\/\/72.167.111.237\/wpecsdump\/?page_id=184"},"modified":"2020-12-06T18:01:35","modified_gmt":"2020-12-06T18:01:35","slug":"os2-device-drivers-for-dummies-the-beginning","status":"publish","type":"page","link":"https:\/\/www.ecsdump.net\/?page_id=184","title":{"rendered":"OS\/2 Device Drivers for Dummies &#8211; The Beginning"},"content":{"rendered":"<p>Last change 09\/05\/2007<\/p>\n<h4>1. Introduction<\/h4>\n<p>Let me first state that I fully intend to plagiarize as much as possible, deal with it. There seems to be only a few options for the hobbyist programmer to easily learn how to build a device driver for eCS-OS\/2. Some good examples that exist are <a class=\"\" href=\"http:\/\/72.167.111.237\/wikka\/PikeOs2\">Alger Pike&#8217;s EDM\/2<\/a> articles from years ago, so they are somewhat dated. Another example is contained in the <a href=\"https:\/\/github.com\/open-watcom\/open-watcom-v2\/wiki\" data-internallinksmanager029f6b8e52c=\"1\" title=\"OPen Watcom v2\" target=\"_blank\" rel=\"noopener\">Open Watcom<\/a> directory [samples\\os2\\pdd]. Of course there are various driver examples available from many additional sources, just use Google. This text deals with 16 bit eCS-OS\/2 drivers.<\/p>\n<p>I am using several printed and electronic text references which can be be found both online or through book sites such as Amazon.com:<\/p>\n<ul>\n<li>1. OS\/2 Physical Device Driver Reference<\/li>\n<li>2. Design of OS\/2 by Harvey M. Deitel and Michael S. Kogan <em><strong>(note 1)<\/strong><\/em><\/li>\n<li>3. Writing OS &#8211; 2 2.1 Device Drivers in C by Steven J. Mastrianni <em><strong>(note 2)<\/strong><\/em><\/li>\n<li>4. Device driver information contained on the second eCS CD<\/li>\n<li>5. <a class=\"ext\" href=\"http:\/\/www.amazon.com\/Writing-Os-2-Device-Drivers\/dp\/0201522349\">Writing OS\/2 Device Drivers by Raymond Westwater<\/a><\/li>\n<\/ul>\n<p>Reference 3 and 5 are very good, but only deal with 16 bit drivers. However, there is rumored to be an unpublished version of reference 3 for OS\/2 Warp (1997) floating around the net somewhere. I will be taking parts of all the above and with luck provide something that can be used to learn what I did much quicker for a beginner.<\/p>\n<p>You need some knowledge of C and enough ASM know-how to understand some basic source. For ASM I know how to do a <a class=\"ext\" href=\"http:\/\/en.wikipedia.org\/wiki\/Hello_world\">HelloWorld<\/a> executable but that is the extent. Also, the current version of <a class=\"ext\" href=\"http:\/\/www.openwatcom.org\/index.php\/Main_Page\">Open Watcom<\/a> needs to be installed and working. Later you might want to read up on privilege levels and a good explanation exists on <a class=\"ext\" href=\"http:\/\/www.edm2.com\/0307\/32-bit-io.html\">EDM\/2 by Holger Veit<\/a> (what happened to him?).<\/p>\n<p>I have modified Pike&#8217;s Hello World example so it can be compiled with <a class=\"ext\" href=\"http:\/\/www.openwatcom.org\/index.php\/Main_Page\">Open Watcom<\/a> and does not require the OS\/2 DDK [here]. It outlines the basic parts of a device driver, but really does nothing. During system boot it displays a message which indicates it has loaded and executed initialization. I have provided a test.exe which when run opens and closes the driver. The driver will beep on open and close, not very exciting but it does provide feedback that things are working correctly.<\/p>\n<p>What is the most important thing to remember? Nothing comes fast and easy and the following information is from my continued learning about device drivers. I started with no knowledge and am still very limited. The source code for the driver and test program are <strong><a class=\"ext\" href=\"http:\/\/www.mgreene.org\/pub\/hellowdevice.zip\">HERE<\/a><\/strong>. Enjoy&#8230;<br \/>\n<em><br \/>\n<strong>Note 1:<\/strong> I believe there are PDF versions of this book floating around, I have a hard cover copy. However, I did email Dr. Kogan asking if he would release it to the masses. He reply that he had wanted to do this sometime ago, but his coauthor would not agree to it.<\/em><\/p>\n<p><strong>Note 2:<\/strong> I know there is an unpublished 3rd revision PDF floating around which covers Warp (approx 1997).<\/p>\n<h4>2. What is a Device Driver?<\/h4>\n<p>Exactly what is a device driver? Simply put, a device driver is a piece of code which is dedicated to controlling a particular device. It is the device dependent module that provides the low-level I\/O and interrupt support for a device. Also, the device driver will manage any memory that the device may use for Direct Memory Access. The device can be anything from a video card to a data acquisition board. A driver is necessary because many of the previous operations must occur at ring level 0 for them to be successful under eCS-OS\/2, and for code to run in ring 0, it must be in a device driver. So, device drivers are trusted modules that have access to the kernel. For the hobbyist programmer this means you can really crash the system now!<\/p>\n<p>Device drivers come in two flavors, block and character. Block devices are used for mass storage devices and character devices handle data one character at a time. Is this true? I have read that it is so, most of the time. However, in Gordon Letwin&#8217;s <em>Inside OS\/2<\/em> he says the following:<\/p>\n<p><em>OS\/2 device drivers are divided into two general categories: those for character mode devices and those for block mode devices. This terminology is traditional, but don\u0012t take it too literally because character mode operations can be done to block mode devices. The actual distinction is that character mode device drivers do I\/O synchronously; that is, they do operations in first in, first out order. Block mode device drivers can be asynchronous; they can perform I\/O requests in an order different from the one I which they received them.<\/em><\/p>\n<p>Device drivers operate in three different modes: INIT, Kernel (or task), and Interrupt. INIT mode is a special mode executed during at system boot with RING 3 privileges, however, it is allowed some RING 0 privileges (see OS\/2 PDD reference). The Kernel mode is in effect when the device driver is called by the kernel in response to an I\/O request. The Interrupt mode is in effect when the device driver&#8217;s interrupt handler is called in response to an external interrupt. In this example I will only be concerned with INIIT and Kernel modes.<\/p>\n<p>I intend only to deal with 16-bit driver and not some of the 32-bit additions IBM added.<\/p>\n<p>So, where does this all start? During system boot time the kernel finds a DEVICE= statement in the CONFIG.SYS file and then loads the device driver. Next, it reads the device driver header which is where I start this adventure.<\/p>\n<h4>3. The Header<\/h4>\n<p>The device driver consists of at least one code segment and one data segment, although more memory can be allocated if required. Additionally, the device driver is an EXE type program which is linked as a DLL. The header contains information used by the kernel during initialization. The data segment, which contains the Device Header, must appear as the very first data item. No data items or code can be placed before the Device Header. An OS\/2 device driver which does not adhere to this rule will not load. While I do not intend to get ASM deep, the <a class=\"\" href=\"http:\/\/72.167.111.237\/wikka\/DevDriver1devsegsasm\">devsegs.asm<\/a> source and <strong>#pragma data_seg ( &#8220;_HEADER&#8221;, &#8220;DATA&#8221; )<\/strong> in header.c keeps the segments in order.<\/p>\n<p>The initialization thread opens the driver module and reads the first segment into low memory (below 1M). This segment will be the main data segment. The second segment is loaded into low memory and will be the main code segment. Any additional segments are read into high memory (above 1M).<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" class=\"center\" title=\"Device Segments\" src=\"https:\/\/i0.wp.com\/www.mgreene.org\/graphics\/devsegs.jpg?w=790\" alt=\"device segments\"><br \/>\nThe device header is defined in <a class=\"\" href=\"http:\/\/72.167.111.237\/wikka\/DevDriver1devhdrh\">devhdr.h<\/a>:<\/p>\n<div class=\"code\">\n<pre>typedef struct DEVHEADER DEVHEADER;\nstruct DEVHEADER {\n  struct DEVHEADER FAR *next;      \/\/ next driver in chain\n  uint16_t         DAWFlags;       \/\/ device attribute word\n  NPVOID           StrategyEntry;  \/\/ offset to strategy routine\n  NPVOID           IDCEntry;       \/\/ offset to IDC routine\n  uint8_t          Name[8];        \/\/ driver name\n  uint16_t         DAWProtCS;      \/\/ * Protect-mode CS of strategy entry pt\n  uint16_t         DAWProtDS;      \/\/ * Protect-mode DS\n  uint16_t         DAWRealCS;      \/\/ * Real-mode CS of strategy entry pt\n  uint16_t         DAWRealDS;      \/\/ * Real-mode DS\n  uint32_t         Capabilities;   \/\/ Capabilities bit strip\n};<\/pre>\n<\/div>\n<form id=\"form_61092f4ded\" action=\"http:\/\/72.167.111.237\/wikka\/DevDriver\/grabcode\" method=\"post\"><input class=\"grabcode\" title=\"Download\" name=\"save\" type=\"submit\" value=\"Grab\"><\/form>\n<p>This Hello World header is defined in <a class=\"\" href=\"http:\/\/72.167.111.237\/wikka\/DevDriver1headerc\">header.c<\/a> with the following values:<\/p>\n<div class=\"code\">\n<pre>DEVHEADER DDHeader = {\n\t-1L,                                         \/\/ Link to next header in chain\n\tDAW_CHARACTER|DAW_OPENCLOSE|DAW_LEVEL1,      \/\/ device attribute word\n\tStrategy,                                    \/\/ Entry point to strategy routine\n\t0,                                           \/\/ Entry point to IDC routine\n\t{\"Hello$  \"},                                \/\/ Device driver name\n\t0,0,0,0,                                     \/\/ Reserved\n\tCAP_NULL                                     \/\/ Capabilities bit strip (for level 3 DDs)\n};<\/pre>\n<\/div>\n<form id=\"form_61092f4ded_1\" action=\"http:\/\/72.167.111.237\/wikka\/DevDriver\/grabcode\" method=\"post\"><input class=\"grabcode\" title=\"Download\" name=\"save\" type=\"submit\" value=\"Grab\"><\/form>\n<p>The &#8220;next driver in chain&#8221; link is set to -1L to mark the end of DEVHEADER chain (see <a class=\"\" href=\"http:\/\/72.167.111.237\/wikka\/DevDriver1devhdrh\">devhdr.h<\/a>) because the example device driver contains only a single device. If a second device were to be defined in this driver then the field would point to it. Next, the Device Attribute Word which is used to define the operational characteristics of the device driver. This is set to DAW_CHARACTER | DAW_OPENCLOSE | DAW_LEVEL1 which are listed and explained in <a class=\"\" href=\"http:\/\/72.167.111.237\/wikka\/DevDriver1devhdrh\">devhdr.h<\/a>. The strategy routine entry point is next and is explained in section 4 of this article. The IDC entry point offset follows and is used if the device driver supports inter-device driver communications. The Hello World driver does not support IDC so this is set to 0. The Device driver name is next and must be 8 characters in length, notice how the Hello$ name is padded with spaces. The final field is the Capabilities Bit Strip word defines additional features on level 3 drivers (OS\/2 v2.0 (support of memory above 16MB). See <a class=\"\" href=\"http:\/\/72.167.111.237\/wikka\/DevDriver1devhdrh\">devhdr.h<\/a> for Capabilities Bit Strip options, however, the Hello World driver does not utilize level 3 options.<\/p>\n<p>A more detailed description of the header is <a class=\"\" href=\"http:\/\/72.167.111.237\/wikka\/PDDHeader\">located here<\/a>.<\/p>\n<h4>4. The Strategy Section<\/h4>\n<p>When I started, the title &#8220;Strategy&#8221; kind of scared me. Like most code, the name tends to scare a hobbyist programmer until one sees what it really is! The Strategy section is just a large switch statement and from my point of view the heart of the device driver. Remember, I am not covering interrupt drivers. The device driver receives a request from the kernel on behalf of the calling application which are passed to the Strategy. Also, the Strategy is called at initialization with RP_INIT which will execute the INIT routine. In the Hello World example the <a class=\"\" href=\"http:\/\/72.167.111.237\/wikka\/DevDriver1initc\">StratInit( )<\/a> funtion is called.<\/p>\n<h4>5. The INIT Mode<\/h4>\n<p>I would like to summarize what has been presented up to now. The kernel found a DEVICE statement during system boot, it loaded and then looked for the device header. It examines the header, finding the Strategy entry point (<a class=\"\" href=\"http:\/\/72.167.111.237\/wikka\/DevDriver1strategyc\">strategy.c<\/a>) and the device name (Hello$). The kernel now calls the Strategy provided in the header with RP_INIT. To be more detailed and in Hello World context, it passes a request packet REQP_INIT (see <a class=\"\" href=\"http:\/\/72.167.111.237\/wikka\/DevDriver1devreqph\">devreqp.h<\/a>) to the strategy entry point:<\/p>\n<div class=\"code\">\n<pre>typedef struct _REQP_HEADER {\n\tuint8_t            length;        \/\/ Length of request packet\n\tuint8_t            unit;          \/\/ Unit code (B)\n\tuint8_t            command;       \/\/ Command code\n\tuint16_t           status;        \/\/ Status code\n\tuint32_t           res1;          \/\/ Flags\n\tstruct REQP_HEADER FAR *next;     \/\/ Link to next request packet in queue\n} REQP_HEADER;\ntypedef struct {\n\tREQP_HEADER header;\n\tunion{\n\t    struct{\n\t        uint8_t   res;            \/\/ Unused\n\t        uint32_t  devhlp;         \/\/ Address of Dev Help entry point\n\t        int8_t    *parms;         \/\/ Command-line arguments   PCHAR\n\t        uint8_t   drive;          \/\/ Drive number of first unit\n\t    } in;\n\t    struct  {\n\t        uint8_t   units;          \/\/ Number of supported units\n\t        uint16_t  finalcs;        \/\/ Offset to end of code\n\t        uint16_t  finalds;        \/\/ Offset of end of data\n\t        void      *<a href=\"https:\/\/en.wikipedia.org\/wiki\/BIOS_parameter_block\" data-internallinksmanager029f6b8e52c=\"4\" title=\"BPB\" target=\"_blank\" rel=\"noopener\">bpb<\/a>;           \/\/ BIOS parameter block   PVOID\n\t    } out;\n\t};\n} REQP_INIT;<\/pre>\n<\/div>\n<form id=\"form_61092f4ded_2\" action=\"http:\/\/72.167.111.237\/wikka\/DevDriver\/grabcode\" method=\"post\"><input class=\"grabcode\" title=\"Download\" name=\"save\" type=\"submit\" value=\"Grab\"><\/form>\n<p>On entry to <a class=\"\" href=\"http:\/\/72.167.111.237\/wikka\/DevDriver1strategyc\">Strategy( )<\/a> REQP_INIT rp-&gt;command will be set to RP_INIT which will call <a class=\"\" href=\"http:\/\/72.167.111.237\/wikka\/DevDriver1initc\">StratInit( )<\/a> to perform initialization. Again, it is important to remember during INIT the driver is actually at ring level 3 with some access ring level 0 functions. The following tables lists the API calls available at INIT:<\/p>\n<table class=\"data\" border=\"1\" cellspacing=\"1\" cellpadding=\"1\">\n<tbody>\n<tr>\n<td>DosBeep<\/td>\n<td>Generate sound from speaker<\/td>\n<\/tr>\n<tr>\n<td>DosCaseMap<\/td>\n<td>Perform case mapping<\/td>\n<\/tr>\n<tr>\n<td>DosChgFilePtr<\/td>\n<td>Change (move) file read\/write pointer<\/td>\n<\/tr>\n<tr>\n<td>DosClose Close<\/td>\n<td>file handle<\/td>\n<\/tr>\n<tr>\n<td>DosDelete<\/td>\n<td>Delete file<\/td>\n<\/tr>\n<tr>\n<td>DosDevConfig<\/td>\n<td>Get device configuration<\/td>\n<\/tr>\n<tr>\n<td>DosDevIOCtl<\/td>\n<td>I\/O control for devices<\/td>\n<\/tr>\n<tr>\n<td>DosFindClose<\/td>\n<td>Close find handle<\/td>\n<\/tr>\n<tr>\n<td>DosFindFirst<\/td>\n<td>Find first matching file<\/td>\n<\/tr>\n<tr>\n<td>DosFindNext<\/td>\n<td>Find next matching file<\/td>\n<\/tr>\n<tr>\n<td>DosGetEnv<\/td>\n<td>Get address of process environment string<\/td>\n<\/tr>\n<tr>\n<td>DosGetInfoSeg<\/td>\n<td>Get address of system variables segment<\/td>\n<\/tr>\n<tr>\n<td>DosGetMessage<\/td>\n<td>Get system message with variable text<\/td>\n<\/tr>\n<tr>\n<td>DosOpen<\/td>\n<td>Open file<\/td>\n<\/tr>\n<tr>\n<td>DosPutMessage<\/td>\n<td>Output message text to indicated handle<\/td>\n<\/tr>\n<tr>\n<td>DosQCurDir<\/td>\n<td>Query current directory<\/td>\n<\/tr>\n<tr>\n<td>DosQCurDisk<\/td>\n<td>Query current disk<\/td>\n<\/tr>\n<tr>\n<td>DosQFileInfo<\/td>\n<td>Query file information<\/td>\n<\/tr>\n<tr>\n<td>DosQFileMode<\/td>\n<td>Query file mode<\/td>\n<\/tr>\n<tr>\n<td>DosRead<\/td>\n<td>Read from file<\/td>\n<\/tr>\n<tr>\n<td>DosSMRegisterDD<\/td>\n<td>Register session switch notification<\/td>\n<\/tr>\n<tr>\n<td>DosWrite<\/td>\n<td>Synchronous write to file<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The initialization routine performs two important jobs. First, to save the value of the entry point for the device&#8217;s <a class=\"\" href=\"http:\/\/72.167.111.237\/wikka\/DevHlp\">DevHlp<\/a> routines (REQP_INIT rp-&gt;in.devhlp). Second, and to set the end of data and code segments (REQP_INIT rp-&gt;out.finalcs and REQP_INIT rp-&gt;out.finalds). The data and code segments after these points will be discarded after all device driver headers in the driver have been initialized. If the data length is set to zero then the driver will be unloaded.<\/p>\n<p>I found the best simple explanation of the <a class=\"\" href=\"http:\/\/72.167.111.237\/wikka\/DevHlp\">DevHlp<\/a> entry point on USENET by Holger Veit:<\/p>\n<p>&#8220;The kernel itself IS the device helper., i.e. when registering a device driver you get a 16:16 pointer to a kernel routine that is named <a class=\"\" href=\"http:\/\/72.167.111.237\/wikka\/DevHlp\">DevHlp<\/a> and all so called device helper functions call this entry point indirectly (after setting the appropriate registers).&#8221;<\/p>\n<p>The <a class=\"missingpage\" title=\"Create this page\" href=\"http:\/\/72.167.111.237\/wikka\/DevHelp\/edit\">DevHelp<\/a> entry point should be declared as:<\/p>\n<div class=\"code\">\n<pre>PFN  Device_Help  = NULL;<\/pre>\n<\/div>\n<form id=\"form_61092f4ded_3\" action=\"http:\/\/72.167.111.237\/wikka\/DevDriver\/grabcode\" method=\"post\"><input class=\"grabcode\" title=\"Download\" name=\"save\" type=\"submit\" value=\"Grab\"><\/form>\n<p>Although Resource Manager functions are not used in this Hello World example, you should plan for the future using Resource Management services. The PDD reference states: The PFN Device_Help variable must be initialized by your driver prior to calling any Resource Manager services. It is expected to contain the Device Help entry point provided in the OS\/2 Init Request Packet your driver receives.<\/p>\n<p>If the size of the segments remain zero, at the end of INIT the driver will unload.<\/p>\n<h4>6. The Kernel Mode<\/h4>\n<p>Need to add &#8211; MKG<\/p>\n<h4>7. Compiling and Testing the Driver<\/h4>\n<p>Compiling is easy. Ensure that Open Watcom is installed and working correctly. Unzip the hellowdevice.zip archive and then wmake. The result will be hello.sys and test.exe.<\/p>\n<p>Here is the part where it all comes together! Place a statement in the config.sys (example: DEVICE=C:\\hello.sys) and reboot. During boot you should see:<\/p>\n<p><strong>Hello World Driver Installed.<\/strong><br \/>\n<strong>(C) ACP Soft 1996.<\/strong><br \/>\n<strong>M Greene &lt;greenemk@cox.net&gt; 2007.<\/strong><br \/>\n<strong>All Rights Reserved.<\/strong><\/p>\n<p>So, what just happened? The kernel found our DEVICE statement, loaded the driver, read the header, and then sent a RP_INIT to the Strategy Section. The Strategy Section received the RP_INIT and called the <a class=\"\" href=\"http:\/\/72.167.111.237\/wikka\/DevDriver1initc\">StratInit( )<\/a> function. The <a class=\"\" href=\"http:\/\/72.167.111.237\/wikka\/DevDriver1initc\">StratInit( )<\/a> function displayed the above message and returned. If the system did not hang or trap then hello.sys is loaded and ready.<\/p>\n<p>As I stated, the driver does nothing spectacular. The <a class=\"\" href=\"http:\/\/72.167.111.237\/wikka\/DevDriver1testc\">test.exe<\/a> executable interfaces with hello.sys to make some noise. When <a class=\"\" href=\"http:\/\/72.167.111.237\/wikka\/DevDriver1testc\">test.exe<\/a> is run the following will be displayed with a couple beeps:<\/p>\n<p><strong>About to beep&#8230;.<\/strong><br \/>\n<strong>DosOpen return 0<\/strong><br \/>\n<strong>Sleep for 5 seconds&#8230;.<\/strong><br \/>\n<strong>About to beep&#8230;.<\/strong><br \/>\n<strong>DosClose return 0<\/strong><\/p>\n<p>Ok, here is what just happened:<\/p>\n<ul>\n<li><a class=\"\" href=\"http:\/\/72.167.111.237\/wikka\/DevDriver1testc\">test.exe<\/a> prints &#8220;About to beep&#8221; and issues a DosOpen to Hello$<\/li>\n<li>The Strategy Section receives a RP_OPEN and executes the <a class=\"\" href=\"http:\/\/72.167.111.237\/wikka\/DevDriver1openc\">StratOpen( )<\/a> function<\/li>\n<li>The <a class=\"\" href=\"http:\/\/72.167.111.237\/wikka\/DevDriver1openc\">StratOpen( )<\/a> function issues a DosBeep and RPDONE then returns<\/li>\n<li>Now back in <a class=\"\" href=\"http:\/\/72.167.111.237\/wikka\/DevDriver1testc\">test.exe<\/a> DosOpen return is printed with the return code<\/li>\n<li><a class=\"\" href=\"http:\/\/72.167.111.237\/wikka\/DevDriver1testc\">test.exe<\/a> prints Sleep for 5 seconds and sleeps for 5 seconds<\/li>\n<li>Next <a class=\"\" href=\"http:\/\/72.167.111.237\/wikka\/DevDriver1testc\">test.exe<\/a> issues a DosClose to Hello$<\/li>\n<li>The Strategy Section receives a RP_CLOSE and executes the <a class=\"\" href=\"http:\/\/72.167.111.237\/wikka\/DevDriver1closec\">StratClose( )<\/a> function<\/li>\n<li>The <a class=\"\" href=\"http:\/\/72.167.111.237\/wikka\/DevDriver1closec\">StratClose( )<\/a> function issues a DosBeep and RPDONE then returns<\/li>\n<li>Back in <a class=\"\" href=\"http:\/\/72.167.111.237\/wikka\/DevDriver1testc\">test.exe<\/a> DosClose return is printed with the return code<\/li>\n<li><a class=\"\" href=\"http:\/\/72.167.111.237\/wikka\/DevDriver1testc\">test.exe<\/a> exits<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<h4>8. Summary<\/h4>\n<p>Exciting, right??? Well maybe not, but it is a good and simple drive driver example.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Last change 09\/05\/2007 1. Introduction Let me first state that I fully intend to plagiarize as much as possible, deal with it. There seems to be only a few options for the hobbyist programmer to easily learn how to build a device driver for eCS-OS\/2. Some good examples that exist are Alger Pike&#8217;s EDM\/2 articles&hellip;<\/p>\n<p><a class=\"more-link\" href=\"https:\/\/www.ecsdump.net\/?page_id=184\" title=\"Continue reading &lsquo;OS\/2 Device Drivers for Dummies &#8211; The Beginning&rsquo;\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"parent":761,"menu_order":3,"comment_status":"open","ping_status":"open","template":"page-templates\/full-width.php","meta":{"footnotes":""},"categories":[14,10],"tags":[23,24,28],"wf_page_folders":[85],"class_list":["post-184","page","type-page","status-publish","hentry","category-driver-os2","category-os2","tag-driver","tag-ecs","tag-os2"],"jetpack_sharing_enabled":true,"jetpack-related-posts":[],"_links":{"self":[{"href":"https:\/\/www.ecsdump.net\/index.php?rest_route=\/wp\/v2\/pages\/184","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.ecsdump.net\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.ecsdump.net\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.ecsdump.net\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.ecsdump.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=184"}],"version-history":[{"count":0,"href":"https:\/\/www.ecsdump.net\/index.php?rest_route=\/wp\/v2\/pages\/184\/revisions"}],"up":[{"embeddable":true,"href":"https:\/\/www.ecsdump.net\/index.php?rest_route=\/wp\/v2\/pages\/761"}],"wp:attachment":[{"href":"https:\/\/www.ecsdump.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=184"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.ecsdump.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=184"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.ecsdump.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=184"},{"taxonomy":"wf_page_folders","embeddable":true,"href":"https:\/\/www.ecsdump.net\/index.php?rest_route=%2Fwp%2Fv2%2Fwf_page_folders&post=184"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}