#lang scheme/base
(require "../tools.ss")
(provide usb-compile-device)
(define (t/n->n/t lst)
(map
(lambda (l)
(apply (lambda (t n v) `(,n ,t ,v)) l))
lst))
(define (expand-record map-type dict spec)
(let ((_dict (t/n->n/t dict)))
(foldr
(lambda (kar kdr)
(let ((record (assoc kar _dict))) (if record
(append (apply map-type (cdr record)) kdr)
(error 'undefined-field "~a" kar))))
'() spec)))
(define (string->numbers lst)
(bytes->list (string->bytes/utf-8 lst)))
(define (mask-byte a)
(bitwise-and a #xff))
(define (shift-byte a)
(arithmetic-shift a -8))
(define (lo+hi a)
(map mask-byte `(,a ,(shift-byte a))))
(define (lo a)
(list (mask-byte a)))
(define (dummy d) '(-1))
(define (namegen name n)
(let next ((i n)
(lst '()))
(if (zero? i)
lst
(let ((i-- (- i 1)))
(next i--
(cons (string->symbol
(format "~a~a" name i--))
lst))))))
(define (interleave lst sym)
(apply append
(map (lambda (x) (list x sym))
lst)))
(define (fstring name lst)
`(: ,name f->
,@(interleave
(cons (length lst) lst) '|,|)))
(define (route name lst error)
(let ((sep '| `(: ,name ,(length lst) route/e
,@(interleave lst sep)
,error ,sep)))
(define base-types
`((b . ,lo)
(bcd . ,lo+hi)
(id . ,lo+hi)
(w . ,lo+hi)
(bm . ,lo)
(i . ,dummy)
(l . ,dummy)
))
(define (add-length lst)
(cons (+ 1 (length lst)) lst))
(define (make-descriptor proto extended-types dict)
(let ((typeid (car proto))
(spec (map cadr (cdr proto))))
(add-length
`(,typeid
,@(expand-record
(lambda (type val)
(let ((type-map
(assoc type
(append extended-types
base-types))))
(if type-map
((cdr type-map) val)
(error 'undefined-type "~a" type))))
dict spec)))))
(define (compile-endpoint e)
(make-descriptor descr-endpoint '() e))
(define descr-device
'(1
(bcd USB)
(b DeviceClass)
(b DeviceSubClass)
(b DeviceProtocol)
(b MaxPacketSize)
(id Vendor)
(id Product)
(bcd Device)
(i Manufacturer)
(i ProductName) (i SerialNumber)
(i NumConfigurations)))
(define descr-endpoint
'(5
(b EndpointAddress)
(bm Attributes)
(w MaxPacketSize)
(b Interval)))
(define descr-interface
'(4
(b InterfaceNumber)
(b AlternateSetting)
(i NumEndpoints)
(b InterfaceClass)
(b InterfaceSubClass)
(b InterfaceProtocol)
(i Interface)))
(define descr-configuration
'(2
(w TotalLength)
(b NumInterfaces)
(b ConfigurationValue)
(i Configuration)
(bm Attributes)
(b MaxPower)))
(define (make-parent/children-bundle
concat make-string
proto dict compile-child)
(let ((collector #f))
(let ((descriptor
(make-descriptor
proto
`((i . ,make-string)
(l . ,(lambda (lst)
(set! collector
(map compile-child lst))
`(,(length lst)))))
dict)))
(concat descriptor collector))))
(define (concat-descriptors d c)
`(,@d ,@(apply append (reverse c))))
(define (make-parent/children make-string
proto dict compile-child)
(make-parent/children-bundle
concat-descriptors
make-string proto dict compile-child))
(define (compile-configuration make-string
configuration)
(define (compile-interface interface)
(make-parent/children make-string
descr-interface
interface
compile-endpoint))
(let
((config
(make-parent/children make-string
descr-configuration
configuration
compile-interface)))
(let ((total
(lo+hi
(length config))))
`(,(car config) ,(cadr config) ,@total
,@(cddddr config)))))
(define (usb-compile-device device)
(define string-stack '())
(define configurations (void))
(define device-descriptor (void))
(define (make-string s)
(let ((id (length string-stack)))
(push! string-stack `(,(+ 2 (string-length s))
3 ,@(string->numbers s)))
`(,id)))
(define (compile-device!)
(make-parent/children-bundle
(lambda (descriptor collector)
(set! configurations collector)
(set! device-descriptor descriptor))
make-string
descr-device
device
(lambda (c)
(compile-configuration
make-string c))))
(compile-device!)
(let ((string-names (namegen 'string (length string-stack)))
(config-names (namegen 'config (length configurations))))
`(,@(fstring 'device-descriptor
device-descriptor)
,@(apply append
(map fstring
string-names
(reverse string-stack)))
,@(apply append
(map fstring
config-names
configurations))
,@(route 'string-descriptor
string-names 'string-error)
,@(route 'configuration-descriptor
config-names 'config-error))))