{- BitFieldDriver: Mackerel backend for device drivers using bitfields This driver is deprecated: please use the ShiftDriver (and associated different language bindings) instead. Functionality of this driver across different compiler revisions and/or processor architectures is not guaranteed (or, indeed, likely). Part of Mackerel: a strawman device definition DSL for Barrelfish Copyright (c) 2007-2010, ETH Zurich. All rights reserved. This file is distributed under the terms in the attached LICENSE file. If you do not find this file, copies can be found by writing to: ETH Zurich D-INFK, Universitaetstrasse 6, CH-8092 Zurich. Attn: Systems Group. -} module BitFieldDriver where import System.IO import System.Exit import Data.List import Text.Printf import MackerelParser import Checks import Attr import Space import qualified CSyntax as C import qualified TypeName as TN import qualified TypeTable as TT import qualified RegisterTable as RT import qualified Fields import qualified Dev import qualified Data.Maybe -- -- Take a list of scope names and translate to a C identifier. -- qual_trec :: TT.Rec -> [ String ] -> String qual_trec t l = qual_name (TT.tt_name t) l qual_name :: TN.Name -> [ String ] -> String qual_name (TN.Name dn tn) l = concat $ intersperse "_" ([dn, tn] ++ l) -- -- Language mapping: C type and name definitions -- dev_t = "__DN(t)" dev_init = "__DN(initialize)" dev_pr = "__DP(pr)" dev_reg_ptr n = n ++ " *" dev_ptr = dev_t ++ " *" dev_tp d = d ++ "_t" reg_nm n = "__DP(" ++ n ++ ")" -- Name of a register reg_wr n = (reg_nm (n ++ "_wr")) -- Fn to write a register field_wr n f = (reg_nm (n ++ "_" ++ f ++ "_wrf")) -- Fn to write a register reg_wr_raw n = (reg_nm (n ++ "_wr_raw")) reg_init n = (reg_nm (n ++ "_init")) reg_rd n = (reg_nm (n ++ "_rd")) -- Fn to read a register reg_rd_raw n = (reg_nm (n ++ "_rd_raw")) -- Fn to read a raw register reg_rds n = (reg_nm (n ++ "_rd_shadow")) -- Read a register's shadow reg_pv tn = qual_name tn ["prtval"] -- Fn to print a regtype type reg_pr n = (reg_nm (n ++ "_pr")) -- Fn to print a register contents reg_pri n = (reg_nm (n ++ "_pri")) -- Fn to print an array element reg_chk n = (reg_nm (n ++ "_chk")) -- Fn to check data type to be written on that register reg_fd n = (reg_nm (n ++ "_fd")) -- field read access reg_addr n = (reg_nm (n ++ "_addr")) reg_len n = (reg_nm (n ++ "_length")) reg_t tn = qual_name tn ["t"] -- Name of a register type reg_un tn = qual_name tn ["un"] -- Name of a union type enum_nm :: String -> String enum_nm n = "__DP(" ++ n ++ ")" enum_t tn = qual_name tn ["t"] enum_pr tn = qual_name tn ["prt"] enum_chk tn = qual_name tn ["chk"] shadow nm = nm ++ "_shadow" -- -- Rendering of Mackerel-specific constructs -- ------------------------------------------------------------------------- -- Top-level header file rendering code ------------------------------------------------------------------------- r_preamble dev = "/*\n * DEVICE DEFINITION: " ++ (Dev.desc dev) ++ "\n\ \ * \n\ \ * Copyright (c) 2007, ETH Zurich.\n\ \ * All rights reserved.\n\ \ * \n\ \ * This file is distributed under the terms in the attached LICENSE\n\ \ * file. If you do not find this file, copies can be found by\n\ \ * writing to:\n\ \ * ETH Zurich D-INFK, Universitaetstrasse 6, CH-8092 Zurich.\n\ \ * Attn: Systems Group.\n\ \ * \n\ \ * THIS FILE IS AUTOMATICALLY GENERATED: DO NOT EDIT!\n\ \ */\n\n" -- Top-level create-a-header-file compile infile outfile dev = let dd = device_def dev "" in (r_preamble dev) ++ (C.header_file (Dev.name dev) dd) -- translation function mapped to every argument, for generic conversion -- (eg. types or names) before rendering those arguments in C code convert_arg (Arg "addr" x) = Arg "mackerel_addr_t" x convert_arg (Arg "pci" x) = Arg "mackerel_pci_t" x convert_arg (Arg "io" x) = Arg "mackerel_io_t" x -- Body of the generated file device_def dev header = unlines ( std_header_files ++ (device_prefix_defs (Dev.name dev)) ++ -- Macros (concat [ constants_decl d | d@(TT.ConstType {}) <- (Dev.types dev)]) ++ (concat [ regtype_decl d | d@(TT.RegFormat {}) <- (Dev.types dev) ]) ++ (concat [ datatype_decl d | d@(TT.DataFormat {}) <- (Dev.types dev) ]) ++ (device_struct_def dev) ++ (device_init_def dev) ++ (space_includes dev header) ++ (concat [ register_decl d | d <- (Dev.registers dev) ] ) ++ (device_print dev) ++ (device_prefix_undefs (Dev.name dev)) -- Undefine macros ) -- Undefine macros used by the header file device_prefix_undefs :: String -> [String] device_prefix_undefs name = [ (C.undef ("__" ++ n)) | n <- [ "DN", "DP", "DP1", "DP2", "STR", "XTR" ] ] -- Define macros used by the header file device_prefix_defs :: String -> [String] device_prefix_defs name = (device_prefix_undefs name) ++ [ printf "#define __DN(x) %s ## _ ## x" name, printf "#ifdef %s_PREFIX" name, printf "#define __DP(x) __DP1(x,%s_PREFIX)" name, "#define __DP1(x1,x2) __DP2(x1,x2)", "#define __DP2(x1,x2) x2 ## x1", "#else", printf "#define __DP(x) %s##_ ##x" name, "#endif", "#define __STR(x) #x", "#define __XTR(x) __STR(x)" ] -- Header files info std_header_files = [ "#include ", "#include " ] -- Device representation structure generator device_struct_def :: Dev.Rec -> [String] device_struct_def d = [ C.multi_comment "Device representation structure", C.typedef dev_t (unlines strct) ] where args = map convert_arg (Dev.args d) strct = C.struct dev_t ( [ C.comment "Device arguments" ] ++ [ (C.struct_field n v) | Arg n v <- args ] ++ [ C.comment "Shadow registers" ] ++ [ device_struct_shadow s | s <- (Dev.shdws d)] ) device_struct_shadow (RT.Shadow n t) | TN.is_builtin_type t = C.struct_field (builtin_to_c t) (shadow n) | otherwise = C.struct_field (reg_un t) (shadow n) device_init_shadow (RT.Shadow n t) | TN.is_builtin_type t = printf "_dev->%s = %s;" (shadow n) "0x0" | otherwise = printf "_dev->%s.raw = %s;" (shadow n) "0x0" -- Device init function device_init_def :: Dev.Rec -> [String] device_init_def d = let tn = dev_tp (Dev.name d) args = map convert_arg (Dev.args d) in [ C.multi_comment "Device Initialization function\n", C.inline "void" dev_init ([(dev_ptr,"_dev")] ++ [ (n,v) | (Arg n v) <- args ] ) ([ "/* Setting up device arguments*/" ] ++ [ printf "_dev->%s = %s;" v v | (Arg n v)<- args ] ++ [ "/* Setting up shadow registers*/" ] ++ [ device_init_shadow s | s <- (Dev.shdws d)] ) ] device_print :: Dev.Rec -> [String] device_print d = [ C.inline "int" dev_pr ( [ ("char *","s"), ("size_t","sz"), (dev_t, "* _dev") ] ) ( ["int r=0;", "int _avail, _rc;" ] ++ (C.snputsq "-------------------------\\n") ++ (C.snputsq (printf "Dump of device %s (%s):\\n" (Dev.name d) (percent_escape (Dev.desc d)))) ++ (concat [ device_print_eachreg r | r <- (Dev.registers d)]) ++ (C.snputsq (printf "End of dump of device %s\\n" (Dev.name d))) ++ (C.snputsq "-------------------------\\n") ++ [ "return r;" ] ) ] device_print_eachreg reg = C.snlike (reg_pr (RT.name reg)) "_dev" space_includes d header | header /= "" = [ C.comment "Space access include file overridden by cmd line:", C.include_local header ] | all Space.is_builtin (Dev.spaces d) = [ C.comment "No user-defined spaces" ] | otherwise = [ C.comment "Include access functions for user-defined spaces", C.include_local $ printf "%s_spaces" (Dev.name d) ] ------------------------------------------------------------------------- -- Render 'constants' declarations ------------------------------------------------------------------------- constants_decl :: TT.Rec -> [String] constants_decl c = [ (constants_comment c), (constants_typedef c), (constants_print_fn c), (constants_check_fn c) ] constants_comment c = C.multi_comment (printf "Constant definition: %s (%s)" (TT.type_name c) (TT.tt_desc c)) constants_typedef c = C.enum (enum_t (TT.tt_name c)) [ (enum_nm(TT.cname v), (C.expression (TT.cval v)) ) | v <- (TT.tt_vals c) ] constants_print_fn c = let etype = enum_t (TT.tt_name c) in C.inline "int" (enum_pr (TT.tt_name c)) [ ("char *","s"), ("size_t","sz"), (etype,"e") ] (constants_print_body etype (TT.tt_vals c)) constants_print_body etype vals = C.switch "e" [ ( (enum_nm (TT.cname v)), printf "return snprintf(s, sz, \"%%s\", \"%s\");" (TT.cdesc v) ) | v <- vals ] (printf "return snprintf(s, sz, \"Unknown \" __XTR(%s) \" value 0x%%\" PRIxPTR, (uintptr_t)e);" etype ) constants_check_fn c = let etype = enum_t (TT.tt_name c) in C.inline "int" (enum_chk (TT.tt_name c)) [(etype,"e") ] (constants_check_body etype (TT.tt_vals c)) constants_check_body etype vals = C.switch "e" [ ((enum_nm (TT.cname v)), "return 1;") | v <- vals ] "return 0;" ------------------------------------------------------------------------- -- Render 'register type definitions ------------------------------------------------------------------------- builtin_to_c :: TN.Name -> String builtin_to_c tn = (TN.typeName tn) ++ "_t" round_field_size w | w <= 8 = "uint8_t" | ( w > 8 && w <= 16 ) = "uint16_t" | ( w > 16 && w <= 32 ) = "uint32_t" | (w > 32 && w <= 64) = "uint64_t" regtype_decl (TT.RegFormat tname size td desc _) = let rtype = reg_t tname rname = reg_nm $ TN.typeName tname rtype_ptr = dev_reg_ptr rtype sz = round_field_size size in [ (C.multi_comment ("Register type: " ++ desc )), (regtype_dump rtype td), (regtype_struct rtype td), (regtype_assert rtype td sz), (regtype_union tname td sz), (regtype_print_fn tname rtype td rname) ] regtype_dump rtype td = C.multi_comment ( "Dump of fields for register type: " ++ rtype ++ "\n" ++ unlines([ field_dump f | f <- td ] )) -- The type (bitfield) definition of the register type regtype_struct rtype td = C.packed_typedef rtype (unlines ( C.bitfields rtype (fields td) )) regtype_assert rtype td sz = C.assertsize rtype sz -- A union comprising the bitfield and a similarly-sized integer type regtype_union tn td sz = let un = (reg_un tn) in C.typedef un (unlines ( C.union un [C.union_field (reg_t tn) "val", C.union_field sz "raw" ] )) fields td = [ C.bitfield (Fields.name f) (Fields.size f) (field_type f) | f <- td ] field_type f | Fields.tpe f == Nothing = round_field_size $ Fields.size f | otherwise = reg_t $ Data.Maybe.fromJust $ Fields.tpe f -- Print out a value of the register type regtype_print_fn :: TN.Name -> String -> [Fields.Rec] -> String -> String regtype_print_fn tn rtype td rname = C.inline "int" (reg_pv tn) ( [ ("char *","s"), ("size_t","sz"), (rtype, "v") ] ) ( ["int r=0;", "int _avail, _rc;" ] ++ concat [ field_print f | f <- td, not $ Fields.is_anon f ] ++ ["return r;" ] ) field_print :: Fields.Rec -> [String] field_print f = case Fields.tpe f of Nothing -> C.snprintf (printf "\" %s=0x%s (%s)\\n\", (%s)(v.%s)" (Fields.name f) (field_fmt_str $ Fields.size f) (percent_escape $ Fields.desc f) (round_field_size $ Fields.size f) (Fields.name f) ) Just t -> (C.snputsq (printf " %s=" (Fields.name f))) ++ (C.snlike (enum_pr t) ("v." ++ (Fields.name f))) ++ (C.snputsq (printf " (%s)\\n" (Fields.desc f))) field_fmt_str size | size <= 8 = "%\"PRIx8\"" | size <= 16 = "%0\"PRIx16\"" | size <= 32 = "%0\"PRIx32\"" | otherwise = "%0\"PRIx64\"" percent_escape :: String -> String percent_escape s = concat [ if c == '%' then "%%" else [c] | c <- s ] ------------------------------------------------------------------------- -- Render data type definitions ------------------------------------------------------------------------- datatype_decl (TT.RegFormat tname size td desc _) = let rtype = reg_t tname rname = reg_nm $ TN.typeName tname rtype_ptr = dev_reg_ptr rtype sz = round_field_size size in [ (C.multi_comment ("Data type: " ++ desc )), (regtype_struct rtype td), (regtype_print_fn tname rtype td rname) ] datatype_decl _ = [] ------------------------------------------------------------------------- -- Render register definitions ------------------------------------------------------------------------- register_decl :: RT.Rec -> [String] register_decl r = case (RT.tpe r) of (TT.RegFormat tname sz _ tdesc _) -> [ C.multi_comment (printf "Register %s (%s); type %s (%s)" (RT.name r) (RT.desc r) (TN.toString $ RT.typename r) (TT.tt_desc (RT.tpe r))), -- (show (RT.extents r)), (register_dump r), (register_length r), (register_read_raw r), (register_read r), (register_write_raw r), (register_write r), (register_write_fields r), (register_print_fn r) ] (TT.Primitive tname sz _) -> [ (C.multi_comment (printf "Register %s (%s); type %s" (RT.name r) (RT.desc r) (TN.toString $ RT.typename r))), -- (show (RT.extents r)), (register_length r), (register_read_raw r), (register_read_builtin r), (register_write_raw r), (register_write_builtin r), (register_write_fields r), (register_print_fn r) ] (TT.ConstType {}) -> [] (TT.DataFormat {}) -> [] -- Refer to a shadow copy of the register reg_shadow_ref r = "_dev->" ++ (shadow (RT.name r)) ++ (if RT.is_array r then "[_i]" else "") -- Get the arguments right for array- and non-array registers register_args pre r post = pre ++ [ (dev_ptr, "_dev") ] ++ ( if RT.is_array r then [("int","_i")] else [] ) ++ post -- Now, code to read and write raw values. These are inlined into the -- various "real" routines below loc_read :: RT.Rec -> String loc_read r = case RT.spc r of (Builtin n _ t) -> (printf "mackerel_read_%s_%s(_dev->%s,%s)" n (show (RT.size r)) (RT.base r) (loc_array_off t (RT.offset r) (RT.arr r) (RT.size r))) (Defined n a _ _ t p) -> (printf "__DP(%s_read_%s)(_dev, %s)" n (show (RT.size r)) (loc_array_off t (RT.offset r) (RT.arr r) (RT.size r))) loc_array_off :: Space.SpaceType -> Integer -> ArrayLoc -> Integer -> String loc_array_off _ off (ArrayListLoc []) _ = printf "(0x%0x)" off loc_array_off (Space.BYTEWISE s) off (ArrayStepLoc num 0) sz = printf "(0x%0x) + (_i *(%s/8))" off (show (sz `div` s)) loc_array_off Space.VALUEWISE off (ArrayStepLoc num 0) _ = printf "(0x%0x) + (_i)" off loc_array_off _ off (ArrayStepLoc num step) _ = printf "(0x%0x) + (_i * %d)" off step loc_array_off t off (ArrayListLoc locations) sz = (show locations) loc_write :: RT.Rec -> String -> String loc_write r val = case RT.spc r of (Builtin n _ t) -> (printf "mackerel_write_%s_%s(_dev->%s,%s,%s)" n (show (RT.size r)) (RT.base r) (loc_array_off t (RT.offset r) (RT.arr r) (RT.size r)) val ) (Defined n a _ _ t p) -> (printf "__DP(%s_write_%s)(_dev, %s,%s)" n (show (RT.size r)) (loc_array_off t (RT.offset r) (RT.arr r) (RT.size r)) val ) register_length r | RT.is_array r = C.constint (reg_len (RT.name r)) (RT.num_elements r) | otherwise = "" register_dump r = C.multi_comment ( "Dump of fields for register: " ++ (RT.name r) ++ "\n" ++ unlines([ field_dump f | f <- (RT.fl r)] )) field_dump :: Fields.Rec -> String field_dump f = (printf " %s (size %d, offset %d):\t %s\t %s") (Fields.name f) (Fields.size f) (Fields.offset f) (show $ Fields.attr f) (Fields.desc f ) register_read_raw r | RT.is_readable r = C.inline (round_field_size (RT.size r)) (reg_rd_raw (RT.name r) ) (register_args [] r []) [ "return " ++ (loc_read r) ++ ";" ] | otherwise = C.comment( "Register " ++ (RT.name r) ++ " is not readable" ) register_read r | RT.is_readable r = C.inline (reg_t (RT.typename r)) (reg_rd (RT.name r)) (register_args [] r []) ( [ (reg_un (RT.typename r)) ++ " u;", "u.raw = " ++ (loc_read r) ++ ";", "return u.val;" ] ) | otherwise = C.inline (reg_t (RT.typename r)) (reg_rds (RT.name r)) (register_args [] r []) ( [ "return " ++ (reg_shadow_ref r) ++ ".val;" ] ) register_read_builtin r | RT.is_readable r = C.inline (builtin_to_c (RT.typename r)) (reg_rd (RT.name r)) (register_args [] r []) ( [ "return " ++ (loc_read r) ++ ";" ] ) | otherwise = C.inline (builtin_to_c (RT.typename r)) (reg_rds (RT.name r)) (register_args [] r []) ( [ "return " ++ (reg_shadow_ref r) ++ ";" ] ) register_write_raw r | RT.is_writeable r = C.inline "void" (reg_wr_raw (RT.name r)) (register_args [] r [ ((round_field_size (RT.size r)), "val") ]) [ (loc_write r "val") ++ ";" ] | otherwise = C.comment( "Register " ++ (RT.name r) ++ " is not writeable" ) register_write r | RT.is_writeable r = C.inline "void" (reg_wr (RT.name r)) (register_args [] r [ ((reg_t (RT.typename r)), "val") ]) ( [ (reg_un (RT.typename r)) ++ " u;" ] ++ (reg_write_init_value r) ++ (reg_write_mbz_value r) ++ (reg_write_mb1_value r) ++ [ (loc_write r "u.raw") ++ ";" ] ++ ( if RT.needs_shadow r then [ (reg_shadow_ref r) ++ ".val = u.val;" ] else [] ) ) | otherwise = [] register_write_fields r = unlines [ field_write_fn r f | f <- (RT.fl r), Fields.is_writeable f ] -- This is actually WRONG. We should actually initialize the value to -- be written properly. -- 1) If there are write-only fields (other than f) load u.raw from shadow. -- 2) If there are rw fields (other than f) load u.raw from register. -- 2a) If there are both, copy fields from one to the other. -- 3) Initialize the other fields (mbz, mb1, f) -- 4) Do the write. field_write_fn r f = C.inline "void" (field_wr (RT.name r) (Fields.name f)) (register_args [] r [ (field_type f, "val") ]) ( [ (reg_un (RT.typename r)) ++ " u;" ] ++ (if (any (\x -> ((Fields.name x) /= (Fields.name f) && field_must_be_preread x)) (RT.fl r) ) then [ "u.raw = " ++ (loc_read r) ++ ";" ] else []) ++ [ copy_shadow_field x r | x <- RT.fl r, (Fields.name x) /= (Fields.name f), Fields.is_writeonly x ] ++ (reg_write_mbz_value r) ++ (reg_write_mb1_value r) ++ [ "u.val." ++ (Fields.name f) ++ " = val;" ] ++ [ (loc_write r "u.raw") ++ ";" ] ++ ( if RT.needs_shadow r then [ (reg_shadow_ref r) ++ ".val = u.val;" ] else [] ) ) copy_shadow_field :: Fields.Rec -> RT.Rec -> String copy_shadow_field f r = printf "u.val.%s = %s.val.%s;" (Fields.name f) (reg_shadow_ref r) (Fields.name f) field_must_be_preread :: Fields.Rec -> Bool field_must_be_preread f | Fields.attr f == RSVD = True | otherwise = (Fields.is_readable f) && (Fields.is_writeable f) register_write_builtin r | RT.is_writeable r = C.inline "void" (reg_wr (RT.name r)) (register_args [] r [ ((builtin_to_c (RT.typename r)), "val") ]) ( [ (loc_write r "val") ++ ";" ] ++ (if RT.needs_shadow r then [ (reg_shadow_ref r) ++ " = val;" ] else [] ) ) | otherwise = [] reg_write_init_value r | RT.needs_read_before_write r = (if RT.is_readable r then "u.raw = " ++ (loc_read r) ++ ";" else "u.raw = " ++ (reg_shadow_ref r) ++ ".raw;" ) :[ printf "u.val.%s \t= val.%s;" (Fields.name f) (Fields.name f) | f <- (RT.fl r), not (Fields.is_rsvd f) ] | otherwise = [ "u.val = val;" ] reg_write_mbz_value r = [ (printf "u.val.%s \t= 0;") (Fields.name f) | f <- RT.fl r, (Fields.attr f) == MBZ ] reg_write_mb1_value r = [ (printf "u.val.%s \t= -1;") (Fields.name f) | f <- RT.fl r, (Fields.attr f) == MB1 ] -- Print out a value of the register type register_print_fn :: RT.Rec -> String register_print_fn r | RT.is_array r = (register_print_array_element r) ++ (register_print_array r) | otherwise = (register_print_single r) register_print_array_element r = C.inline "int" (reg_pri (RT.name r)) ( register_args [ ("char *","s"), ("size_t","sz") ] r [] ) ( ["int r=0;", "int _avail, _rc;" ] ++ (register_print_init r) ++ (C.snprintf (printf "\"Register %s[%%d] (%s):\", _i" (RT.name r) (percent_escape (RT.desc r)))) ++ (register_print_value r) ++ ["return r;" ] ) register_print_array r = C.inline "int" (reg_pr (RT.name r)) [ ("char *","s"), ("size_t","sz"), (dev_ptr, "_dev") ] ( ["int r=0;", "int _avail, _rc;" ] ++ (C.forloop "int _i=0" (printf "_i < 0x%0x" (RT.num_elements r)) "_i++" (C.snlike (reg_pri (RT.name r)) "_dev, _i" ) ) ++ ["return r;"] ) register_print_single r = C.inline "int" (reg_pr (RT.name r)) ( register_args [ ("char *","s"), ("size_t","sz") ] r [] ) ( ["int r=0;", "int _avail, _rc;" ] ++ (register_print_init r) ++ (C.snputsq (printf "Register %s (%s):" (RT.name r) (percent_escape (RT.desc r)))) ++ (register_print_value r) ++ ["return r;" ] ) register_print_value r | TT.is_primitive (RT.tpe r) = register_print_primitive r | otherwise = ( C.snputsq "\\n" ) ++ concat [ regfield_print r f | f <- (RT.fl r) ] register_print_primitive r | RT.needs_shadow r = C.snprintf (printf "\"\\t0x%s (SHADOW copy)\\n\", %s" (field_fmt_str (RT.size r)) (reg_shadow_ref r) ) | otherwise = C.snprintf (printf "\"\\t0x%s\\n\", %s" (field_fmt_str (RT.size r)) (loc_read r)) register_print_init r | not (RT.is_readable r) = [ C.comment "register is not readable" ] | TT.is_primitive (RT.tpe r) = [ C.comment "register is primitive type" ] | otherwise = [ (reg_un (RT.typename r)) ++ " u;", "u.raw = " ++ (loc_read r) ++ ";" ] regfield_print :: RT.Rec -> Fields.Rec -> [ String ] regfield_print reg f | Fields.is_anon f = [ C.comment "skipping anonymous field" ] | otherwise = C.block ( [ (printf "%s pv = (%s)%s.val.%s;" (round_field_size (Fields.size f)) (round_field_size (Fields.size f)) (if Fields.is_writeonly f then (reg_shadow_ref reg) else "u" ) (Fields.name f)) ] ++ ( C.snprintf (printf "\" %s =\\t0x%s (%s%s\", pv" (Fields.name f) (field_fmt_str (Fields.size f)) (if Fields.is_writeonly f then "SHADOW of " else "") (percent_escape (Fields.desc f))) ) ++ (case (Fields.tpe f) of Nothing -> (C.snputsq ")\\n" ) Just t -> ( (C.snputsq ": ") ++ (C.snlike (enum_pr t) "pv") ++ (C.snputsq ")\\n")) ) )