Our customer wrote:
I am trying to modify the serial number of my system. The write test shows that Serial number can be written, but when I try to modify it an error 87h is returned. According to PnP BIOS specification, an error 87h means "System not docked". Why it happens?
This happens because of error in the BIOS of your board.

The sample below is taken from widely installed Phoenix BIOS version 6.0 to demonstrate why described error occurs.

At label "CheckLength" function compares the sum of the entire SMBIOS structures pool length (typically, 3000h - 4000h bytes) and user data length (typically 10h - 40h) with maximal value of 8000h. Clear, that this sum typically will be smaller as 8000h. Therefore 'jg' instruction marked in sample in red will almost always pass execution to label "ErrNotDock" and SetStructure function will always return an error code 87h (The system is currently not docked).

Most likely, this is an error committed by BIOS developper. The proper instruction will be 'jle' instead of 'jg'. Since DMI write interface is not used often, this routine was likely never really tested by BIOS manufacturer.

;---------------------------- PNP return codes -------------------------------

SUCCESS              equ 0
INVALID_HANDLE       equ 83h
SYSTEM_NOT_DOCKED    equ 87h
USE_ESCD_SUPPORT     equ 8dh

;------------------------ Set structure Subcommands --------------------------

CHANGE_BYTE          equ 00h
CHANGE_WORD          equ 01h
CHANGE_DWORD         equ 02h
ADD_STRUCT           equ 03h
DEL_STRUCT           equ 04h
CHANGE_STRING        equ 05h
CHANGE_BLOCK         equ 06h

;---------------------- Layout of SMBIOS table header ------------------------

@tag_DMI_HDR STRUCT
    TableId BYTE        ?
    TableLen BYTE       ?
    TableHandle WORD    ?
@tag_DMI_HDR ENDS

DMI_HDR TYPEDEF @tag_DMI_HDR

;------------------------ Layout of DMI Data Buffer --------------------------

@tag_DMI_DATA STRUCT          ; Offset
    Command     BYTE    ?     ; 00h - Identifies operation to be performed:
                              ; 00h - Change Byte
                              ; 01h - Change Word
                              ; 02h - Change Double Word
                              ; 03h - Add new structure
                              ; 04h - Delete existring structure
                              ; 05h - Change string value
                              ; 06h - Change block of information
    FieldOffset BYTE    ?     ; 01h - Starting offset within the changed structure’s
    ChangeMask  DWORD   ?     ; 02h - AND-ing mask to be applied to changed data
    ChangeValue DWORD   ?     ; 06h - Data value to be OR-ed with the existing structure data
                              ;       (after applying the ChangeMask)
    DataLength  WORD    ?     ; 0Ah - Length of StructData for commands 3, 5 and 6
    StructHdr   DMI_HDR ?     ; 0Ch - Contains the structure header of the structure to be added, changed, or deleted
;   StructData                ; 10h - Data for commands 3, 5 and 6
@tag_DMI_DATA ENDS

DMI_DATA TYPEDEF @tag_DMI_DATA

;--------------------- Stack layout on function entry ------------------------

Function             equ word  ptr [ebp + 14h]
dmiDataBuffer        equ dword ptr [ebp + 16h]
dmiWorkBuffer        equ dword ptr [ebp + 1Ah]
Control              equ word  ptr [ebp + 1Eh]
dmiSelector          equ word  ptr [ebp + 20h]
BiosSelector         equ word  ptr [ebp + 22h]

;*****************************************************************************
;*                    --- SetStructure ---
;*
;* Purpose: Modifies SMBIOS structure
;* Input:   Function      - PnP BIOS Function 52h
;*          dmiDataBuffer - Pointer to buffer with new/change data
;*          dmiWorkBuffer - Pointer to work buffer area for the BIOS
;*          Control       - Conditions for performing operation
;*                          Bit 0 = 0 - Do not set the specified structure, but
;*                                      validate its parameters
;*                          Bit 0 = 1 - Set the structure immediately
;*          dmiSelector   - SMBIOS data read/write selector
;*          BiosSelector  - PnP BIOS readable/writeable selector
;* Output: PNP return code
;*****************************************************************************
SetStructure PROC NEAR

             push bx
             push di
             push si
             push ds

;------------------------------- Check ESCD ----------------------------------

             mov  di,3960
             call 5aa2
             call 3c02             ; This subroutine in real-mode does nothing

             mov  ax, USE_ESCD_SUPPORT
             jc   Done

             xor  ax,ax
             call 3c03             ; This subroutine in real-mode does nothing
             jnc  DoneSuccess

             or   ax,ax            ; ax = SUCCESS?
             jnz  Done             ; no, return error

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;---------------------------- Check Parameters -------------------------------
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

             call CheckParameters  ; This function checks function parameters.
                                   ; If write is possible, returns NC.
                                   ; Otherwise CY and error code on AX are returned
             jc   Done             ; Return error code, if check fails


             mov  bx,Control       ; word ptr [ebp+1e]
             test bx,0001          ; Physical write is required?
             jz   Done             ; No, processing is completed.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;---------------------------- Modify structure -------------------------------
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

             push ax

             mov  ax,0300
             call GetSMBIOSPool    ; This function returns parameters of:
                                   ; SMBIOS structures pool
                                   ; ax - Full size of entire SMBIOS table pool
                                   ; di - offset of first structure relative
                                   ; to dmiSelector
             mov  di,ax            ; di - Full size of entire SMBIOS structures pool
             mov  bx,ax            ; bx - Full size of entire SMBIOS structures pool

             pop  ax

             mov  es,dmiSelector
             lds  si,dmiDataBuffer

;------------------- Calculate and check command length ----------------------

             mov  cx,SIZE DMA_DATA ; Assumed length of data structure

             cmp  byte ptr [si + DMI_DATA.Command],ADD_STRUCT
             jz   AddLength

             cmp  byte ptr [si + DMI_DATA.Command],CHANGE_BLOCK
             jz   AddLength

             cmp  byte ptr [si + DMI_DATA.Command],CHANGE_STRING
             jnz  CheckLength

AddLength:   add  cx,word ptr [si + DMI_DATA.DataLength]

CheckLength: push cx               ; cx - full length of data structure
             add  cx,di            ; di - Full size of entire SMBIOS structures pool
             cmp  cx,8000h         ; Size of new SMBIOS pool is too large?
             pop  cx               ; restore full length of data structure
             jg   ErrNotDock

;---------------- Copy command past of SMBIOS structures pool -----------------

        repe movsb                 ; Copy data from dmiDataBuffer to es:di,
                                   ; where:
                                   ;   es - dmiSelector
                                   ;   di - Size of SMBIOS structures pool

;----------------------------- Perform command -------------------------------

             mov  dx,1
             mov  si,es            ; es - dmiSelector

             mov  di,373f
             call 5aa2

             mov  di,bx
             cmp  word ptr es:[di+0e],-01
             jnz  DoneSuccess

ErrInvHnd:   mov  ax,INVALID_HANDLE
             jmp  Done

DoneSuccess: mov  ax,SUCCESS
             jmp  Done

ErrNotDock:  mov ax,SYSTEM_NOT_DOCKED

Done:        pop  ds
             pop  si
             pop  di
             pop  bx
             ret