Skip to content

Instantly share code, notes, and snippets.

@corecode
Last active December 23, 2015 13:19
Show Gist options
  • Save corecode/6641720 to your computer and use it in GitHub Desktop.
Save corecode/6641720 to your computer and use it in GitHub Desktop.
class CDCDesc < FunctionDesc
TypeName = "struct cdc_function_desc"
child_block :cdc
def initialize
super()
@ctrl_iface = interface(:ctrl_iface) {
bInterfaceClass :USB_DEV_CLASS_CDC
bInterfaceSubClass :USB_DEV_SUBCLASS_CDC_ACM
bInterfaceProtocol 0
ep(:ctrl_ep) {
direction :in
type :intr
wMaxPacketSize :CDC_NOTICE_SIZE
bInterval 0xff
}
}
@data_iface = interface(:data_iface) {
bInterfaceClass :USB_DEV_CLASS_CDC_DCD
bInterfaceSubClass 0
bInterfaceProtocol 0
ep(:tx_ep) {
direction :in
type :bulk
wMaxPacketSize :CDC_TX_SIZE
bInterval 0xff
}
ep(:rx_ep) {
direction :out
type :bulk
wMaxPacketSize :CDC_RX_SIZE
bInterval 0xff
}
}
end
def gen_desc_init
<<_end_
.#@var_name = {
#{@interface.map{|i| i.gen_desc_init}.join}
.cdc_header = {
.bLength = sizeof(struct cdc_desc_function_header_t),
.bDescriptorType = {
.id = USB_DESC_IFACE,
.type_type = USB_DESC_TYPE_CLASS
},
.bDescriptorSubtype = USB_CDC_SUBTYPE_HEADER,
.bcdCDC = { .maj = 1, .min = 1 }
},
.cdc_union = {
.bLength = sizeof(struct cdc_desc_function_union_t),
.bDescriptorType = {
.id = USB_DESC_IFACE,
.type_type = USB_DESC_TYPE_CLASS
},
.bDescriptorSubtype = USB_CDC_SUBTYPE_UNION,
.bControlInterface = #{@ctrl_iface.ifacenum}
.bSubordinateInterface0 = #{@data_iface.ifacenum}
},
},
_end_
end
end
require 'dsl'
class EndpointDesc < DslItem
field :direction, :enum => {:in => 1, :out => 0}
field :type, :enum => {:intr => :USB_EP_INTR, :bulk => :USB_EP_BULK}
field :wMaxPacketSize
field :bInterval
def initialize(name)
super()
@name = name
end
def renumber!(counts)
cname = "ep_#@direction".to_sym
@epnum = counts[cname]
counts.merge cname => @epnum + 1
end
def gen_desc_init
<<_end_
.#{@name.to_loc_s} = {
.bLength = sizeof(struct usb_desc_ep_t),
.bDescriptorType = USB_DESC_EP,
.ep_num = #@epnum,
.in = #{@direction.val},
.type = #{@type.val},
.wMaxPacketSize = #{@wMaxPacketSize.to_loc_s},
.bInterval = #{@bInterval.to_loc_s}
},
_end_
end
end
class InterfaceDesc < DslItem
attr_accessor :ifacenum
field :bInterfaceClass
field :bInterfaceSubClass
field :bInterfaceProtocol
block :ep, EndpointDesc, :list => true, :optional => true
block :alternate, InterfaceDesc, :list => true, :optional => true
def initialize(name)
super()
@name = name
end
def renumber!(counts, alternatenum=0)
@ifacenum = counts[:iface]
@alternatenum = alternatenum
nc = counts.dup
@ep.each do |e|
nc = e.renumber!(nc)
end
@alternate.each_with_index do |a, i|
ac = a.renumber!(counts, i)
[:ep_in, :ep_out].each do |e|
if ac[e] > nc[e]
nc[e] = ac[e]
end
end
end
nc
end
def gen_defs
''
end
def gen_desc_init
v = <<_end_
.#{@name.to_loc_s} = {
.bLength = sizeof(struct usb_desc_iface_t),
.bDescriptorType = USB_DESC_IFACE,
.bInterfaceNumber = #@ifacenum,
.bAlternateSetting = #@alternatenum,
.bNumEndpoints = #{@ep.count},
.bInterfaceClass = #{@bInterfaceClass.to_loc_s},
.bInterfaceSubClass = #{@bInterfaceSubClass.to_loc_s},
.bInterfaceProtocol = #{@bInterfaceProtocol.to_loc_s},
.iInterface = 0
},
_end_
v + @ep.map{|e| e.gen_desc_init}.join("\n")
end
end
class FunctionDesc < DslItem
block :interface, InterfaceDesc, :list => true
def renumber!(counts)
@var_name = "usb_function_#{counts[:iface]}"
@interface.each do |iface|
counts = iface.renumber!(counts)
counts[:iface] += 1
end
counts
end
def get_desc_struct
"#{self.class::TypeName} #@var_name;"
end
def gen_defs
@interface.map{|i| i.gen_defs}.join("\n")
end
def gen_vars
''
end
end
class ConfigDesc < DslItem
field :remote_wakeup, :default => 0
field :self_powered, :default => 0
field :bMaxPower, :default => 100
field :initfun
block :function, FunctionDesc, :list => true
def renumber!(confignum)
@confignum = confignum
counts = {:iface => 0, :ep_in => 0, :ep_out => 0}
@function.each do |f|
counts = f.renumber!(counts)
end
@numinterfaces = counts[:iface]
@config_name = "usb_config_#@confignum"
@var_name = "usbd_config_#@confignum"
end
def gen_defs
@function.map{|f| f.gen_defs}.join +
<<_end_
struct #@config_name {
struct usb_desc_config_t config;
#{@function.map{|f| f.get_desc_struct}.join("\n\t")}
};
_end_
end
def gen_vars
@function.map{|f| f.gen_vars}.join("\n") +
<<_end_
static const struct #@config_name #@config_name = {
.config = {
.bLength = sizeof(struct usb_desc_config_t),
.bDescriptorType = USB_DESC_CONFIG,
.wTotalLength = sizeof(struct #@config_name),
.bNumInterfaces = #@numinterfaces,
.bConfigurationValue = #@confignum,
.iConfiguration = 0,
.one = 1,
.bMaxPower = #{@bMaxPower.to_loc_s}
},
#{@function.map{|f| f.gen_desc_init}.join}
};
static const struct usbd_config #@var_name = {
.init = #{@initfun.to_loc_s},
.desc = &#@config_name.config
};
_end_
end
def get_var
@config_name
end
end
class DeviceDesc < DslItem
field :idVendor
field :idProduct
field :bcdDevice, :default => 0.0
field :iManufacturer
field :iProduct
def initialize(name)
super()
@name = name
end
block :config, ConfigDesc, :list => true
def renumber!
@config.each_with_index do |c, i|
c.renumber!(i + 1)
end
end
def gen_defs
@config.map{|c| c.gen_defs}.join("\n")
end
def gen_vars
@config.map{|c| c.gen_vars}.join("\n") +
<<_end_
static const struct usb_desc_dev_t #{@name.to_loc_s}_dev_desc = {
.bLength = sizeof(struct usb_desc_dev_t),
.bDescriptorType = USB_DESC_DEV,
.bcdUSB = { .maj = 2 },
.bDeviceClass = USB_DEV_CLASS_SEE_IFACE,
.bDeviceSubClass = USB_DEV_SUBCLASS_SEE_IFACE,
.bDeviceProtocol = USB_DEV_PROTO_SEE_IFACE,
.bMaxPacketSize0 = EP0_BUFSIZE,
.idVendor = #{@idVendor.to_loc_s},
.idProduct = #{@idProduct.to_loc_s},
.bcdDevice = { .raw = 0 },
.iManufacturer = 1,
.iProduct = 2,
.iSerialNumber = 3,
.bNumConfigurations = #{@config.length},
};
static const struct usb_desc_string_t * const #{@name.to_loc_s}_str_desc[] = {
USB_DESC_STRING_LANG_ENUS,
USB_DESC_STRING(#{@iManufacturer.to_loc_s{|s| "w#{s.inspect}"}}),
USB_DESC_STRING(#{@iProduct.to_loc_s{|s| "w#{s.inspect}"}}),
USB_DESC_STRING_SERIALNO,
NULL
};
struct usbd #{@name.to_loc_s} = {
.dev_desc = &#{@name.to_loc_s}_dev_desc,
.string_descs = #{@name.to_loc_s}_str_desc,
.configs = {
#{@config.map{|c| "&#{c.get_var},"}.join("\n")}
NULL
}
};
_end_
end
end
class DescriptorRoot < DslItem
block :device, DeviceDesc, :list => true do |d|
@device ||= []
@device << d
d.renumber!
end
def self.load(file)
src = File.read(file)
self.eval do
eval src
end
end
def gen
@device.map{|d| d.gen_defs}.join("\n") +
@device.map{|d| d.gen_vars}.join("\n")
end
end
require 'cdc'
require 'dfu'
if $0 == __FILE__
require 'optparse'
outname = nil
OptionParser.new do |opts|
opts.on("-o", "--output FILE") do |fn|
outname = fn
end
end.parse!
r = DescriptorRoot.load(ARGV[0])
if !(warnings = r.warnings).empty?
$stderr.puts warnings.join("\n")
exit 1
end
if outname
File.open(outname, 'w') do |f|
f.puts r.gen
end
else
puts r.gen
end
end
class DFUDesc < FunctionDesc
TypeName = "struct dfu_function_desc"
child_block :dfu
def initialize
super
interface(:iface) {
bInterfaceClass :USB_DEV_CLASS_APP
bInterfaceSubClass :USB_DEV_SUBCLASS_APP_DFU
bInterfaceProtocol :USB_DEV_PROTO_DFU_DFU
}
end
def gen_desc_init
<<_end_
.#@var_name = {
#{@interface.first.gen_desc_init}
.dfu = {
.bLength = sizeof(struct dfu_desc_functional),
.bDescriptorType = {
.id = 0x1,
.type_type = USB_DESC_TYPE_CLASS
},
.will_detach = 1,
.manifestation_tolerant = 0,
.can_upload = 0,
.can_download = 1,
.wDetachTimeOut = 0,
.wTransferSize = USB_DFU_TRANSFER_SIZE,
.bcdDFUVersion = { .maj = 1, .min = 1 }
}
},
_end_
end
end
class LocationWrapper
attr_reader :location
def initialize(val, location)
@val = val
@location = location
end
def method_missing(method, *args, &block)
@val.send(method, *args, &block)
end
def to_s
@val.to_s
end
def inspect
@val.inspect
end
def to_loc_s
v = if location
"\n#line #@location\n"
else
""
end
val = self
val = yield val if block_given?
v + val.to_s
end
end
class DslItem
class << self
def add_field(name, opts)
v = {}
begin
v = class_variable_get(:@@fields)
rescue NameError
class_variable_set(:@@fields, v)
end
v[name] = opts
end
def attach_lineno(val, lineno=caller[1])
LocationWrapper.new(val, lineno)
end
def field(name, opts = {}, &action)
opts[:action] = action if action
add_field(name, opts)
define_method name do |val|
set_or_exec(name, val, opts)
end
end
def block(name, klass, opts = {}, &action)
klass.const_set(:Parent, self)
opts[:name] = name
opts[:klass] = klass
opts[:action] = action if action
add_field(name, opts)
field_alias(name, klass)
end
def field_alias(name_alias, klass)
opts = class_variable_get(:@@fields).values.find{|o| klass.ancestors.include? o[:klass]}
define_method name_alias do |*args, &block|
args = args.map do |a|
DslItem.attach_lineno(a)
end
val = klass.eval(*args, &block)
set_or_exec(opts[:name], val, opts)
end
end
def child_block(name)
superclass::Parent.field_alias(name, self)
end
def eval(*args, &block)
i = self.new(*args)
i.instance_exec(&block)
i.post_eval
i
end
end
def initialize
@warnings = []
end
def post_eval
self.class.class_variable_get(:@@fields).each do |n, o|
if !instance_variable_defined?("@#{n}")
instance_variable_set("@#{n}", DslItem.attach_lineno(o[:default] || (o[:list] ? [] : nil), nil))
end
end
rescue NameError
self.class.class_variable_set(:@@fields, {})
end
def set_or_exec(name, val, opts)
location = caller[1]
wrapped_val = DslItem.attach_lineno(val, location)
if opts[:enum]
if !opts[:enum].include? val
@warnings << "#{location}: field `#{name}' value not one of #{opts[:enum].keys.map(&:inspect).join(", ")}"
else
wrapped_val.define_singleton_method :val do
opts[:enum][val]
end
end
end
if opts[:action]
instance_exec(val, &opts[:action])
else
varname = "@#{name}".to_sym
if opts[:list]
if !instance_variable_get(varname)
instance_variable_set(varname, [])
end
instance_variable_get(varname) << wrapped_val
else
if instance_variable_defined?(varname)
@warnings << "#{location}: field `#{name}' redefined"
end
instance_variable_set(varname, wrapped_val)
end
end
wrapped_val
end
def warnings
r = @warnings.dup
self.class.class_variable_get(:@@fields).each do |name, opts|
val = instance_variable_get("@#{name}")
if !opts.has_key?(:default) && !opts[:optional] && !val
r << "#@location: required field `#{name}' undefined"
end
if val.respond_to? :warnings
r += val.warnings
elsif val.respond_to? :each
val.each do |v|
r += v.warnings if v.respond_to? :warnings
end
end
end
r
end
end
device(:foo) {
idVendor 0x2323
idProduct 0x1
iManufacturer "mchck.org"
iProduct "descriptor test"
config {
initfun :foo_init
cdc {
}
dfu {
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment