home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / CDEVDR.ZIP / DEMODD.DOC < prev    next >
Text File  |  1991-06-01  |  26KB  |  476 lines

  1. 1.0 What this is about
  2. ------------------------------------
  3. This document describes how to write OS/2 V1.X device drivers in C.  While they
  4. do require a small amount of assembler code, the majority of the device driver
  5. can be coded in C.  Only the interfaces to OS/2 and the functions that need
  6. to access the CPU directly need be written in assembler.
  7.  
  8. When writing device drivers in assembler, the programmer has complete control
  9. over his code.  He can control the order of segments, what goes in which
  10. segment, and in what order.  He can pass parameters using any mechanism he
  11. wishes.  He can control exactly what gets linked in and what doesn't.  A C
  12. programmer, however, does not have quite that level of control over his final
  13. executable.  The compiler has made many decisions for him, or provides fairly
  14. remote control of these parameters.  The problem is how to regain the control
  15. that the assembler programmer enjoys, yet keep the efficiencies afforded by
  16. programming in a high level language.  This document describes how to gain
  17. that control.
  18.  
  19. Also included in this package is a sample device driver with source code.  The
  20. sample does not do anything useful, except display an installation message and
  21. install.  The only commands it accepts are INIT, OPEN, CLOSE, WRITE, OUTPUT
  22. FLUSH, IOCTL, and DEINSTALL.  On all of these, except INIT, it does nothing,
  23. but return SUCCESS.  INIT installs the device driver.  It does demonstrate the
  24. techniques described here.  Anyone receiving this package is free to use the
  25. source code in any way they see fit.
  26.  
  27. The text will reference the sample code as part of the explanation of the
  28. techniques.
  29.  
  30.  
  31. 1.1 Scope
  32. ------------------------------------
  33. This document assumes the reader is familiar with the organization and design
  34. of OS/2 device drivers.  It does not attempt to teach how to write device
  35. drivers, but how to implement them in C.  It also assumes a fairly solid
  36. background in Intel 80286 and 80386 CPU architecture and assembler language
  37. programming.  Finally, it assumes that the reader understands the use of
  38. Microsoft programming tools - LINK, LIB, and MAKE, and IBM's C/2 compiler.
  39.  
  40.  
  41. 1.2 Tools used
  42. ------------------------------------
  43. The tools used to build the sample code are:
  44.    IBM C/2  V1.1
  45.    IBM MAKE V2.0
  46.    OS/2 Library Manager/2 V1.01
  47.    OS/2 Linker/2 V1.20
  48.    Microsoft MASM V5.1
  49.  
  50. The sample code will also compile with Microsoft C6.00A.
  51.  
  52.  
  53. 2.0 Problems to be solved
  54. ------------------------------------
  55.  
  56. Segment Control - How do you control the order of segments in the final
  57.   executable?  How you do control which segments get grouped together, and
  58.   in what order?  How do you control which segments are kept loaded after
  59.   INIT time?  How do you control which segment library functions end up in?
  60.   How do you make sure the first thing in the Data Segment is the Device
  61.   Driver header?
  62.  
  63. Compiler and Library Control - How do you make sure stack probes are disabled
  64.   in library functions?  What library functions can you use and why? What
  65.   compiler memory models do you use and why?  How do you guarantee access to
  66.   variables?
  67.  
  68. Passing Parameters - How do you get the pointer to the request packet into a
  69.   C variable?  How do you load registers when calling the DevHelp facility?
  70.  
  71. DOS Box Memory Usage - How do you minimize the amount of memory the device
  72.   driver consumes of the Real Mode DOS Compatibility session?  How do you
  73.   gain access to memory above the 1Meg line when entered in Real Mode?
  74.  
  75.  
  76. 3.0 Segment Control
  77. ------------------------------------
  78. Unlike programs, where you can organize your segments anyway you see fit, OS/2
  79. requires a specific organization for its device drivers.  The first segment
  80. in the executable (.SYS), must the main DATA segment.  The second segment must
  81. be the main CODE segment - the one that contains all the device driver's entry
  82. points.  All other segments, both CODE and DATA, will be loaded at INIT time,
  83. but they will be discarded after INIT is complete unless you have marked them
  84. as having IOPL privilege at LINK time and LOCKed them down at INIT time.
  85. Finally, the first thing in the main DATA segment must be the device driver
  86. header, which points to the strategy entry point, defines the name and
  87. characteristics of the device driver and has a pointer to the next device
  88. driver in the chain.
  89.  
  90. The Demo Device driver uses 5 methods, in concert, to control segment
  91. organization and placement.  These are:
  92. 1. The MASM .SEQ directive
  93. 2. The MASM GROUP directive
  94. 3. Knowledge of the compiler's naming conventions
  95. 4. The SEGMENTS statement in the LINKer's definition (.DEF) file
  96. 5. The /NT option of the compiler to name the code segment created
  97.  
  98. The .SEQ directive tells the linker to place the segments in the executable in
  99. the order encountered.  This allows, along with knowledge of the compiler's
  100. naming conventions, allows us to place the main DATA segment before the main
  101. CODE segment.  We do this by defining empty segments with the same name and
  102. class as the segments defined by the compiler.  Combine this with the MASM
  103. GROUP directive and we now have our logical segments combined into physical
  104. segments in the order we want, and the physical segments placed in the
  105. executable in the order we need.  The use of this method can be seen in
  106. DEMO.ASM.
  107.  
  108. We use the same idea for the CODE segments, but with a twist.  We are not
  109. constrained to using the compiler's default segment names.  The /NT option
  110. when invoking the compiler allows us to give the CODE segment generated any
  111. name we wish.  The names you will see in the sample code are: MAINSEG, INITSEG,
  112. and KEEPSEG.  Their purpose is obvious.  This step is seen in DEMO.MAK, in
  113. the definitions of the optXXXX variables.
  114.  
  115. The next step to segment control is done by using the SEGMENTS statement in the
  116. definition file (.DEF) of the linker.  The logical segment names are placed in
  117. the order we want them to appear.  It is optional whether you wish to specify
  118. IOPL for the main DATA and CODE segments, but required for any others you wish
  119. to remain accessible after INIT time.  The ordering must match that
  120. encountered by the .SEQ statement, or extra segments with the same name and
  121. class will be generated and you will have no control over which of the 2
  122. segments stuff will go into.  This is seen in the file DEMO.DEF.
  123.  
  124. One final step is needed.  At INIT time, a device driver is required to tell
  125. OS/2 how much memory the CODE and DATA segments require after INIT is done.
  126. Essentially, this means finding the offset of the last instruction in the
  127. CODE segment and the offset of the last byte used in the DATA segment.  It
  128. is easier to just create a new segment with a single global variable and force
  129. this logical segment to be the last in the GROUP that comprises the physical
  130. segment.  It is a simple matter, then, at INIT time to get the offset of these
  131. variables and store them in the INIT request packet on returning to OS/2.
  132. This is seen in the files DEMO.ASM, for the definition of these segments -
  133. LAST_D and END_TEXT.  The loading of the offsets into the request packet is
  134. done (in C) in INIT.C.
  135.  
  136.  
  137. 4.0 Compiler and Library Control
  138. ------------------------------------
  139. Other problems with using a compiled language compared to assembler are that
  140. you have less control over the 'memory model' used.  Essentially, this means
  141. what kind of pointers - NEAR or FAR are used as well as the way functions are
  142. called and how they return - again NEAR or FAR.  You also have less control
  143. over the inclusion of startup code and which segment holds the library
  144. functions linked in.
  145.  
  146. The first thing is to decide on memory model.  My feeling is that LARGE is the
  147. way to go.  This lets you place your functions in any segment that is
  148. convenient, or that makes sense for your design, and be able to reach them
  149. from any other segment.  This means that functions that are used ONLY at
  150. INIT time can reach utility functions that are used later.  It means that
  151. library functions can be placed in a segment of their own, and called by any
  152. functions.
  153.  
  154. The LARGE model also implies FAR data pointers.  This is almost
  155. a given.  All pointers passed by OS/2 to the device driver are FAR pointers.
  156. Most of the pointers you will construct will be FAR pointers.  To handle these,
  157. you need the LARGE or COMPACT model.
  158.  
  159. Next, there is the question of Stack Segment and Data Segment.  Most modern
  160. compilers generate code that assumes SS equals DS.  In device drivers, this is
  161. emphatically false.  Fortunately, the Microsoft family of C compilers has an
  162. option that lets the programmer control whether the code generated makes this
  163. assumption or not.  Just say no.  These compilers also have an option that lets
  164. the programmer control whether DS will be loaded at the entrance to each
  165. function or not.  Since OS/2 loads DS, SS and CS before entering the device
  166. driver, just say no to this one as well.
  167.  
  168. The net of this is that the memory model to be used is LARGE code, FAR pointers
  169. and don't assume DS=SS.  For IBM C/2, that translates to the option /Alfw.
  170. This can be seen in the optXXXX definitions in DEMO.MAK.
  171.  
  172. Next is the problem of stack probes.  Microsoft compilers allow you to turn
  173. off stack probes in your code and it needs to be done.  OS/2 controls your
  174. stack size, not you.  The problem is what about library functions that were
  175. compiled with the stack probes turned on.  To solve this problem, we replace
  176. the stack probe function with a NULL function.  Unfortunately, Microsoft
  177. included the adjustment of the stack pointer for local variables in the same
  178. function.  To solve this problem, we need to write our own stack check
  179. function that does the variable allocation, but doesn't do the stack pointer
  180. comparisons.  This function is __chkstk, in DEMO.ASM.  The number of bytes
  181. to reserve for local variables is passed in AX, and a FAR call to made to
  182. __chkstk.  This function pops the return address, adjusts the stack pointer
  183. for the local variables and the returns.  It needs to use a bit of trickery
  184. in that adjusting the stack pointer causes the return address to be lost, so
  185. it save the address and pushes it back on the stack after the adjustment is
  186. made.
  187.  
  188. Now we come to variable access.  Microsoft compilers put STATIC variables in
  189. different segments depending on whether they are initialized or not, when
  190. using the LARGE model.  They then store the segment of that variable in a
  191. static variable called $T2000n, where n is different for each static variable
  192. being referenced.  This causes untold problems in accessing the variable
  193. because the segment pointed to by $T2000n is valid at INIT time when the
  194. device driver is at ring 3, but it is no longer valid later, when the device
  195. driver is at ring 0.  To solve this problem, we force all the logical data
  196. segments into one physical segment using the techniques described above.  Then,
  197. we tell the compiler to use DS to access the variable with the near keyword.
  198. In general, all global and static variables should be defined with this
  199. keyword.  A simple hint - if you look into the .COD listing and see a $T2000n
  200. variable defined, you didn't get what you wanted and need to put a near on
  201. a variable.  A classic example of the use of the near keyword in this respect
  202. can be seen in LOCK.C.
  203.  
  204. Finally, we come to the question of library functions.  Which ones can and
  205. cannot be used?  The general answer is that you cannot use those that will
  206. cause a call to the OS or will use the coprocessor.  These include all the
  207. floating point operations, file I/O, memory management functions, process
  208. control and time functions.  What remains are things like string and buffer
  209. management, searching and sorting, character classification and some data
  210. conversion (atoi(), for instance).  Fortunately, the kinds of things
  211. forbidden are either not the kinds of things done by device drivers or are
  212. provided by the DevHelp functions.
  213.  
  214. 5.0 Passing Parameters
  215. ------------------------------------
  216. OS/2 uses registers to pass parameters back and forth with device drivers, but
  217. Microsoft compilers use the stack for most of these.  To solve this problem,
  218. we need a layer of assembler between the device driver and OS/2.  All entry
  219. points are in assembler.  All they do is push their parameters on the stack,
  220. making a stack based parameter, and call the C code.  The C code returns its
  221. results (usually an integer, in AX), and the assembler converts it to a form
  222. suitable to OS/2 (a particular flag set or cleared, for instance).  This can
  223. be seen in DEMO.ASM, in the form of the procedure _strategy.  It pushes the
  224. request packet pointer onto the stack and calls the real strategy function
  225. written in C.
  226.  
  227. Calling the DevHelp facility is a bit different.  One method is based on the
  228. methods used for calling DOS in DOS based compilers.  A structure is defined
  229. that holds all the values that will be loaded into the registers.  This
  230. structure is loaded with the necessary values and a call made to an assembler
  231. function that loads the registers from the structure and then calls DevHelp.
  232. On return, it stores the register values back into the structure, allowing
  233. the C code to examine the results.
  234.  
  235. There are at least 2 problems with this method that need to be overcome.
  236. One is that not all values are allowed to be loaded into segment registers.
  237. Put the wrong value in and you will get a TRAP 000D and the system will lock
  238. up.  The only recourse is to power off.  This is not exactly user friendly.
  239. Therefore, either a way must be divides to tell the assembler function to not
  240. load a segment value from the structure, or the structure must always have
  241. valid values in the segment register fields.  0 is always a valid value to
  242. load into a segment register.  It cannot be used, but it can be loaded.  The
  243. demo device driver uses the first method.  It is in DEVHLP.ASM.
  244.  
  245. The second problem is that some DevHelp functions use DS to hold a selector
  246. and others use ES.  How can you make a general DevHelp caller when it has to
  247. use a segment register to point to the DevHelp entry point to make the
  248. indirect call?  The solution is to use CS.  This means that at INIT time,
  249. the DevHelp function needs to create an alias to the CODE segment that is
  250. writeable.  To do this, it needs the DevHelp functions.  The classic Catch-22.
  251. The solution to this is to create 2 new DevHelp functions - one that uses
  252. DS to call DevHelp and another that uses ES.  These live in the INIT segment
  253. and are discarded after INIT time.  These functions are also in DEVHLP.ASM.
  254. the C code to store the DevHelp entry point is in INIT.C.
  255.  
  256. 6.0 DOS Box memory Usage
  257. ------------------------------------
  258. The final problem addressed by this document is reducing the amount fo memory
  259. used by the device driver in the DOS Compatibility Session (aka the DOS Box).
  260. OS/2 loads the main CODE and DATA segments into memory below the 1Meg line,
  261. using an already scarce resource - the amount of memory available for DOS
  262. programs.  The key to solving this problem is the fact that all other segments
  263. are loaded ABOVE the 1Meg line.  What this means is that you put only those
  264. functions that absolutely need to be there in the main CODE segment.  These
  265. include the DevHelp caller and all assembler entry points and the main
  266. strategy function.  All other code can be moved to above the 1Meg line.
  267.  
  268. Moving the DATA segment is a little trickier.  The problem is that there is
  269. no way to easily define a second DATA segment at compile/link time.  It needs
  270. to be dynamically allocated.  All memory allocated via the AllocPhys DevHelp
  271. call comes from above the 1Meg line.  All of your system level global variables
  272. can be allocated and initialized at INIT time and accessed by pointer later.
  273. This lets you reduce the amount of DOS Box memory consumed to around 1K or so.
  274.  
  275. The final problem with moving these memory blocks above the 1Meg line is that
  276. when in REAL mode, the CPU cannot access memory above the 1Meg line.  How can
  277. it get to the functions and data there?  The solution to this is don't run in
  278. REAL mode.  At every entry point - there are only 5 possible, Strategy, Timer,
  279. Interrupt, Notify, and IDC - check to see if the CPU is in REAL mode.  If it
  280. is, make a call to the ReadToProt DevHelp function, and note in a local
  281. variable that the mode change was made.  On exit, if the mode was changed,
  282. go back via ProtToReal.  Also note, and this is REAL IMPORTANT - if you enter
  283. in REAL mode, you cannot do a FAR call until you have changed to PROT mode;
  284. and after you change back to REAL mode, you cannot do any FAR returns.  That
  285. means that the mode changing functions must all be in the main segment.
  286.  
  287. Note that this path may be seldom taken, depending on the number of interrupts
  288. taken and how much time the CPU spends in the DOS box.  The chances of hitting
  289. it are reduced as OS/2 switches the CPU to PROT mode when entering the Strategy
  290. function and Notify cannot be called fro a DOS program.  This leaves the Timer
  291. and Interrupt and IDC calls as the only ones to worry about.  Since OS/2 is
  292. always timeslicing into PROT mode, even when the DOS box is the active session,
  293. the chances are reduced that the CPU will be in REAL mode when one of these
  294. entry points are activated.  Regardless, that possibility needs to be accounted
  295. for.
  296.  
  297. An example of this method can be seen in STRATEGY.C.  This call is not needed
  298. here, but since the sample device driver has no other entry point, the
  299. mechanism is shown here.
  300.  
  301. 7.0 Some other ideas
  302. ------------------------------------
  303. There are other ways to call DevHelp.  Some people write a separate assembler
  304. level function for each DevHelp call.  Others pass the DevHelp entry point
  305. on the stack instead of storing it in the CODE segment.  Some write several
  306. generic DevHelper callers.  The point is that there are lots of solutions to
  307. this problem.  You need to make sure that the one you use is suitable to your
  308. needs.  You also need to make sure that they always call OS/2 from the main
  309. CODE segment as any function that registers an entry point will use that CODE
  310. segment as the selector portion of the entry point.
  311.  
  312. Another thing to watch out for is changing from REAL to PROT mode or when
  313. going back.  After the mode change has been made, the only valid address
  314. on the stack is the return address.  That is why those functions do not use
  315. the normal DevHelper call in the sample code.  They can be found in
  316. ASMUTILS.ASM.
  317.  
  318. It is useful to have the compiler generate combined listings - that is, the
  319. listing should contain the ASM listing along with the C code.  The option
  320. to make Microsoft compilers do this is /Fc.  Another thing should be to tell
  321. the compiler to pack data structures.  OS/2 request packets have no extra
  322. space in them.  Another useful item is the MAP file.  It will help you make
  323. sure what is being generated is what you really want and it will help when
  324. using some debuggers.
  325.  
  326. 8.0 Putting it Together
  327. ------------------------------------
  328. This section explains the sample device driver included in this package, in
  329. light of the above discussion.
  330.  
  331. Put
  332.  
  333. DEVICE=<path>\DEMO.SYS
  334.  
  335. in your CONFIG.SYS and reboot.  You will see the loading message come up.
  336. After re-booting is done, you can copy files to the $DEMO and the system will
  337. say all went well.  If, however, you say copy $demo to some file, you'll get
  338. an error saying that $DEMO doesn't like that command.  You are seeing the
  339. difference between READ and WRITE.  READ is not recognized, while WRITE is
  340. answered OK, we did it.
  341.  
  342. 8.1 Directory Structure and the Make File
  343. ------------------------------------
  344. The demo can be compiled by running MAKE against DEMO.MAK.  This make file
  345. will generate the entire device driver, the MAP, and all listings or .COD
  346. files.  It creates a library with all the .OBJ files stored in it, as well
  347. as a MSG file used by INIT to display the loading commercial.
  348.  
  349. Currently, it stores the .LST and .COD files in a separate subdirectory off
  350. the current one, the .OBJ in another, and all messages from the compile and
  351. masm and link in a third.  These directories are defined in the variables
  352. lst, obj and msg, defined at the beginning of the make file.  Note that they
  353. use . as the current directory, making them relative references, not absolute.
  354. Other variables are defined to tell MAKE where the library file is (and should
  355. go) and where the source and include files can be found.  Also note that the
  356. .ARF file has a reference to both the.\obj and .\lst subdirectories.  If you
  357. change the .MAK file, you need to change this too.
  358.  
  359. There are 4 variables defined that put all the compiler options in one semi-
  360. user friendly block.  The first is OPTMAIN.  This will cause the code from
  361. this file to be included in the MAIN code segment (named MAINSEG).  The second,
  362. OPTSIZE, does the same, except it has size optimization turned on instead of
  363. none like the others.  OPTKEEP causes the code to go into the KEEPSEG segment
  364. and OPTINIT puts it into the INITSEG segment (surprise!).
  365.  
  366. Each set of options has the link disabled (/c), stack probes disabled (/Gs),
  367. structure packing (/Zp), warning level 3 (/W3), and the model as described
  368. above (/Alfw).  It causes a .COD listing to be generated (/Fc), puts the .OBJ
  369. file in a specific subdirectory (/Fo), renames the code segment generated
  370. (/NT) and redirects the error messages to the msg subdirectory.
  371.  
  372. The rest of the MAKE file is standard stuff.  Each separate file is given
  373. a separate set of dependencies so I can control the options on each.  They
  374. then are added to the library.
  375.  
  376.  
  377. 8.2 Assembler files
  378. ------------------------------------
  379. There are 4 assembler files:
  380.    ASMUTILS.ASM
  381.    BRKPOINT.ASM
  382.    DEMO.ASM
  383.    DEVHLP.ASM
  384.  
  385. These contain the entry points for the device driver, the DevHelp caller, the
  386. segment controlling stuff described in Section 3.0, and a bunch of utility
  387. functions.
  388.  
  389. ASMUTILS.ASM has a bunch of utility functions.  Not all are used by the DEMO
  390. device driver, but they are useful as examples of the kinds of things you
  391. resort to assembler for.  Included are the calls to DevHelp to change the CPU
  392. to REAL mode a back.
  393.  
  394. BRKPOINT.ASM holds the code to do an INT3 on demand.  This function causes
  395. a breakpoint in many debuggers.  When a call to this function is placed in
  396. INIT code, you can trace your initialization code.  I also make it a habit
  397. of creating an IOCTL call that invokes this function.  That lets me get into
  398. my device driver with a debugger without recompiling.  This is a great boon
  399. when you want to debug a particular version without re-compiling.
  400.  
  401. DEMO.ASM is the main assembler code file.  It holds the strategy entry point,
  402. it does all the segment and group definition stuff described in Section 3, and
  403. it holds the main DATA segment, with the device driver header.
  404.  
  405. DEVHLP.ASM has the DevHelp caller, along with the temporary one used at INIT
  406. time to get the DevHelp entry point stored in the main CODE segment.  This
  407. variable is also defined in this file.
  408.  
  409.  
  410. 8.3 C files
  411. ------------------------------------
  412. There are 8 C files.  These are the meat of the function to the demo device
  413. driver.  They include a bunch of utility functions as well as the necessary
  414. stuff.  They are:
  415.    INIT.C
  416.    PRTMSG.C
  417.    STRATEGY.C
  418.    BADCMD.C
  419.    DDUTILS.C
  420.    GDTMEM.C
  421.    LDTMEM.C
  422.    LOCK.C
  423.  
  424. INIT.C holds the function to process the INIT command from OS/2.  Basically, it
  425. sets up the DevHelp entry point, figures out the name of the message file,
  426. prints the loading message, sets up a pointer to the milliseconds since IPL
  427. timer, tells OS/2 how much code and data to keep loaded and exits.
  428.  
  429. PRTMSG.C is used for printing messages at INIT time.  It won't work at any
  430. other time as it uses DOSGetMessage and DOSPutMessage.
  431.  
  432. STRATEGY.C is the main C function that figures out what OS/2 wants it to do
  433. and calls the proper function to do it.  It also makes sure that the CPU is
  434. in PROT mode before proceeding.  As explained above, this is unnecessary, but
  435. it is instructive.  Most of the functions just set the status to Command Not
  436. Recognized.  Others say 'Yes, we did it', when they really didn't.  INIT is
  437. the only command that really calls another function.  After we get past all
  438. this, dev_done() is called (except for INIT, when it isn't valid), to set the
  439. request packet status.
  440.  
  441. BAD_CMD.C just returns the value needed to set into the request packet status
  442. to reflect that fact that we don't recognize the command.
  443.  
  444. The rest of the files hold all sorts of utility functions.  There is stuff to
  445. allocate and free memory, allocate GDT slots, Lock and unlock segments, block
  446. a task and yield the CPU temporarily, and do all sorts of other things.  Some
  447. of these are used by the demo device driver, others are included for
  448. instructional purposes (actually, I was too lazy to take them out).
  449.  
  450. 8.4 H files
  451. ------------------------------------
  452. There are 4 include files.  Three that do the actual work and a fourth to
  453. gather them all together in one include line.  The 3 are broken into constant
  454. definitions, structure definitions and function prototypes.
  455.  
  456. 8.5 Other files
  457. ------------------------------------
  458. DEMO.ARF - Automatic Response file for the link stage.  Note that this file
  459.            assumes that the map file is to go to the .\lst subdirectory and
  460.            the main .obj file comes from .\obj.  If you change the subdir
  461.            structure, be sure to change these as well.
  462.  
  463. DEMO.DEF - Definition file for the link stage.  Here is where you set the IOPL
  464.            bit on for a segment.
  465.  
  466. DEMO.TXT - Source for the message file.
  467.  
  468. DEMO.LIB - Library file made of all the .OBJs
  469.  
  470. DEMO.MSG - The message file.  This is where the text for messages displayed
  471.            during INIT time are kept.
  472.  
  473. DEMO.SYS - The device driver (ta da!)
  474.  
  475.  
  476.