; ; The library_vector_block macro generates the privileged library vector ; (PLV), which is found in all dispatchers. It also initializes the counters ; and index values for kernel and executive mode. The various PSECTs are ; defined with the proper names and attributes. This should be the first macro ; called in the program. ; ; library_vector_block kernel_base,executive_base,rundown ; ; ; The kernel_base and executive_base parameters are the starting system ; service indexes for kernel and executive modes respectively. These numbers ; should always be negative to avoid conflict with the regular VMS system ; services. I neglected to provide defaults for the index numbers to force the ; user to specify them. If two different dispatcher modules have the same ; range of indexes and are linked into the same executable image, the ; routines of the second dispatcher can never be accessed. ; The rundown parameter, if specified, is the address of the shareable image ; rundown routine. It is an optional parameter. ; ; The define_all_psects macro is called automatically by library_vector_block ; to define all necessary psects. However, when library_vector_block is not ; used, as in a module containing nothing but user mode transfer vectors, ; define_all_psects should be called directly. ; ; define_all_psects ; ; ; Rather than supply the access mode as a parameter to define_service, the ; author found it more convenient to define separate macros for each mode. Each ; of these macros calls define_service to generate the transfer vector, then ; calls entry_point if the routine is not external. If the routine should be ; locked into memory because it raises IPL (lock=yes), a special PSECT name ; is used. ; ; kernel_system_service name,arg_count,mask,def_mask,lock,external ; ; user_system_service name,mask,external ; ; exec_system_service name,arg_count,mask,def_mask,external ; ; ; A system service can defined within the dispatcher module itself. If the ; "external" parameter is "no" (the default), both the transfer vector and the ; .entry and .mask of the actual routine are generated. The code of the ; service follows immediately after the call to the macro. The routine is ; terminated like any subroutine, with a RET instruction. ; ; However, the routine is not required to be in the dispatcher module. Indeed, ; if the system service is written in a high level language, it can't be. In ; this case, the "external" parameter is set to "yes". Only the transfer ; vector is contained within the dispatcher module, and the actual routine is ; elsewhere. In the case of a user mode service, even the transfer vector (the ; call to user_system_service) can be in another macro module. However, kernel ; and exec services must be present in the dispatcher module because the index ; numbers must be assigned. User mode services don't have chmx instructions, ; so no index number is necessary. ; ; In a similar fashion, the arg_count and def_mask parameters are not needed ; for user mode services. The arg_count parameter is needed for privileged ; services. The default for def_mask is register 4. ; ; Since register 4 is destroyed by the change mode dispatcher for kernel and ; executive system services, the define_service macro allows specification of ; a default register save mask on the .mask instruction. This mask is ORed ; with the register save mask of the target routine, which allows saving of ; registers in addition to those specified by the called routine. This is ; primarily of benefit for system services in high level languages. There is ; usually no way to force a high level language to save registers that it ; didn't personally modify. However, since the dispatcher destroys r4, r4 ; *must* be saved somewhere. The def_mask parameter provides an override. ; ; As its name implies, the generate_dispatchers macro causes the code for the ; change-mode dispatchers to be generated, based on flags set by the ; define_service macro. It should be the last macro of the program, or at ; least after all the services are defined. ; ; generate_dispatchers ; ; The define_service macro does the work of defining a particular system ; service. It creates the transfer vector, which is a CHMK or CHME instruction ; for a privileged service or a simple branch for a user-mode one. For the ; privileged services, it assigns the index number and adds the number of ; parameters to the list for the access mode. ; In this macro package, define_service is not called directly. Instead, the ; kernel_system_service, exec_system_service, and user_system_service are ; called. These macros call define_service on behalf of the programmer. .macro define_service,name,def_mask=0,arg_count=0,mode=kernel .psect $$$transfer_vector .align quad ; align entry points on quadword boundary .transfer name ; define name as universal symbol for entry .mask name,def_mask ; use entry mask defined in main routine+default .if dif mode,user chm%extract(0,1,mode) #<'mode'_code_base+'mode'_counter> ; change to mode and execute ret ; return 'mode'_counter='mode'_counter+1 ; increment counter 'mode'_code_present=0 ; set flag indicating at least one service ; defined for this mode .align quad ; align entry points on quadword boundary .psect 'mode'_arg_count,byte,nowrt,exe,pic .byte arg_count ; the number of parameters for this service .psect services_'mode'_disp1 .signed_word 2+name-'mode'_case_base ; offset to this routine for case table .if_false brw name + 2 ; just do a regular branch .endc .endm define_service .macro library_vector_block,kernel_base,executive_base,rundown=<.> kernel_code_base=kernel_base ; set base chmk code value for this image exec_code_base=executive_base ; set base chme code value for this image kernel_counter=0 ; kernel code counter exec_counter=0 ; exec code counter $plvdef ; define plv structure .psect system_services,page,vec,pic,nowrt,exe .long plv$c_typ_cmod ; set type of vector to change mode dispatcher .long 0 ; reserved .long kernel_dispatch-. ; offset to kernel mode dispatcher .long exec_dispatch-. ; offset to executive mode dispatcher .long rundown-. ; offset to user rundown service .long 0 ; reserved. .long 0 ; no rms dispatcher .long 0 ; address check - pic image ; Now we define all the PSECTs used in the module with the proper attributes. ; We also define some important symbols. define_all_psects .psect $locka start_of_lock:: .psect $lockz end_of_lock:: .psect kernel_arg_count kernel_arg_count: ; base of byte table containing the number of parameters ; for each kernel routine .psect exec_arg_count exec_arg_count: ; base of byte table containing the number of parameters ; for each executive routine .endm library_vector_block ; ; .macro define_all_psects ; Now we define all the PSECTs used in the dispatcher, with the proper ; attributes. Later references to the PSECT require only the name. .psect $$$transfer_vector,nowrt,exe,pic,quad,shr,con .psect program_code,nowrt,exe,long,shr,con,pic .psect read_write_data,wrt,noexe,long,noshr,con,pic .psect read_only_data,nowrt,noexe,long,shr,con,pic .psect $lockcode,nowrt,exe,long,shr,con,pic .psect $locka,nowrt,exe,page,shr,con,pic .psect $lockz,nowrt,exe,long,shr,con,pic .psect kernel_arg_count,byte,nowrt,exe,pic .psect exec_arg_count,byte,nowrt,exe,pic .psect services_kernel_disp0,byte,nowrt,exe,pic .psect services_kernel_disp1,byte,nowrt,exe,pic .psect services_kernel_disp2,byte,nowrt,exe,pic .psect services_exec_disp0,byte,nowrt,exe,pic .psect services_exec_disp1,byte,nowrt,exe,pic .psect services_exec_disp2,byte,nowrt,exe,pic .endm define_all_psects .macro kernel_system_service,name,arg_count=0,- mask=0,def_mask=<<^m>>,lock=no,external=no define_service name,def_mask,arg_count ; generate kernel vector .if dif external,yes ; is code present in this module? .if dif lock,yes ; should code be locked into memory ? entry_point name,mask ; don't need to lock code .if_false entry_point name,mask,prog_sect=$lockcode ; yes, lock into memory .endc .endc .endm kernel_system_service .macro exec_system_service,name,arg_count=0,- mask=0,def_mask=<<^m>,external=no define_service name,def_mask,arg_count,mode=exec ; generate exec vector .if dif external,yes ; is code present in this module? entry_point name,mask ; generate entry point .endc .endm exec_system_service .macro user_system_service,name,mask,external=no define_service name,mode=user ; generate user transfer vector .if dif external,yes ; is code present in this module? entry_point name,mask ; generate entry point .endc .endm user_system_service ; The entry_point macro switches the PSECT to the default PSECT for code or ; whatever PSECT the caller specifies, then generates the entry mask. It is ; not called directly by the user, but is called by the xxx_system_service ; macros. ; To be on the safe side, the default mask parameter saves all registers. ; Better to save registers unnecessarily than to destroy them without saving. ; The caller of the macro can specify the actual registers destroyed by the ; routine. .macro entry_point name,mask=<^m>,- prog_sect=program_code .psect prog_sect .entry name,mask code_present_in_'prog_sect'=0 ; set flag indicating at least one ; service has code in this psect .endm entry_point ; The dispatcher macro generates the actual code of a system service ; dispatcher. The mode is an input parameter, so either the executive or kernel ; dispatcher is generated. A flag is checked to ensure that routines are ; present for that mode. If they are not, only a RSB is generated. This macro ; is not called by the programmer. The generate_dispatchers macro will do ; this. .macro dispatcher,mode=kernel .psect services_'mode'_disp0 .if defined 'mode'_code_present ; any services defined for this mode? 'mode'_accvio: ; access violation movl #ss$_accvio,r0 ; set access violation status code ret ; and return 'mode'_insfarg: ; insufficient arguments. movl #ss$_insfarg,r0 ; set status code and ret ; return 'mode'_notme: rsb ; this service not handled by this image ; return to main dispatcher .endc 'mode'_dispatch:: ; entry to dispatcher .if defined 'mode'_code_present ; any services defined for this mode? movab w^-'mode'_code_base(r0),r1 ; get base code code value blss 'mode'_notme ; branch if code value too low cmpw r1,#'mode'_counter ; check high limit bgequ 'mode'_notme ; branch if out of range ; The dispatch code has now been verified as being handled by this dispatcher. ; Now the argument list will be probed and the required number of arguments ; verified. movzbl w^'mode'_arg_count[r1],r1 ; get parameter count moval @#4[r1],r1 ; compute byte count including arg count ifnord r1,(ap),'mode'_accvio ; branch if param list not readable cmpb (ap),w^<'mode'_arg_count-'mode'_code_base>[r0] ; check param count blssu 'mode'_insfarg ; br if not enough ; Here we execute a CASE instruction, using the list of offsets accumulated ; with each call to the define_service macro. casew r0,- ; case on change mode - ; argument value #'mode'_code_base,- ; base value #<'mode'_counter-1> ; max value (number of entries) 'mode'_case_base: ; case table base address .endc .psect services_'mode'_disp2 rsb ; end of case table .endm dispatcher ; If any of the calls to kernel_system_service specified lock=yes, then two ; kernel system services are built automatically. These are lock_pages and ; unlock_pages. The lock_pages service will automatically lock all pages ; contained in psect $lockcode into the working set. The routines in $lockcode ; execute at high IPL where page faults are fatal. .macro generate_dispatchers .if defined code_present_in_$lockcode ; need lock_pages, unlock_pages .psect program_data pages_locked: .byte 0 kernel_system_service lock_pages,mask=<<<^m<>>>> moval end_of_lock,-(sp) ; get ending address of lock area moval start_of_lock,-(sp) ; get starting address movl sp,r1 ; point to two-longword array $lkwset_s inadr=(r1) retadr=(r1) ; do the lock blbc r0,5$ ; br if lock failed movb #1,pages_locked ; indicate pages now locked 5$: ret kernel_system_service unlock_pages,mask=<<<^m<>>>> moval end_of_lock,-(sp) ; get ending address of lock area moval start_of_lock,-(sp) ; get starting address movl sp,r1 ; point to two-longword array $ulwset_s inadr=(r1) retadr=(r1) ; do the unlock blbc r0,5$ ; br if unlock failed clrb pages_locked ; indicate pages now unlocked 5$: ret .endc dispatcher mode=kernel ; generate the kernel dispatcher dispatcher mode=exec ; generate the exec dispatcher .endm generate_dispatchers ; This macro can be used by a kernel system service to ensure that the ; code which executes at high-IPL is locked into the working set. ; ; lock_pages_if_necessary ; ; .macro lock_pages_if_necessary,?cont blbs pages_locked,cont ; br if pages already locked calls #0,lock_pages ; better lock them cont: .endm lock_pages_if_necessary ; ; ; This macro can be used by a kernel system service to unlock pages previously ; locked by lock_pages_if_necessary ; ; unlock_locked_pages ; ; .macro unlock_locked_pages,?cont blbc pages_locked,cont ; br if pages not locked calls #0,unlock_pages ; unlock them cont: .endm unlock_locked_pages