aboutsummaryrefslogtreecommitdiffstats
path: root/quantum/serial_link/protocol/byte_stuffer.c
blob: 2c87d64c29c14b395e600ec528ccbdf1891c8d7f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/*
The MIT License (MIT)

Copyright (c) 2016 Fred Sundvik

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#include "serial_link/protocol/byte_stuffer.h"
#include "serial_link/protocol/frame_validator.h"
#include "serial_link/protocol/physical.h"
#include <stdbool.h>

// This implements the "Consistent overhead byte stuffing protocol"
// https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing
// http://www.stuartcheshire.org/papers/COBSforToN.pdf

typedef struct byte_stuffer_state {
    uint16_t next_zero;
    uint16_t data_pos;
    bool long_frame;
    uint8_t data[MAX_FRAME_SIZE];
}byte_stuffer_state_t;

static byte_stuffer_state_t states[NUM_LINKS];

void init_byte_stuffer_state(byte_stuffer_state_t* state) {
    state->next_zero = 0;
    state->data_pos = 0;
    state->long_frame = false;
}

void init_byte_stuffer(void) {
    int i;
    for (i=0;i<NUM_LINKS;i++) {
        init_byte_stuffer_state(&states[i]);
    }
}

void byte_stuffer_recv_byte(uint8_t link, uint8_t data) {
    byte_stuffer_state_t* state = &states[link];
    // Start of a new frame
    if (state->next_zero == 0) {
        state->next_zero = data;
        state->long_frame = data == 0xFF;
        state->data_pos = 0;
        return;
    }

    state->next_zero--;
    if (data == 0) {
        if (state->next_zero == 0) {
            // The frame is completed
            if (state->data_pos > 0) {
                validator_recv_frame(link, state->data, state->data_pos);
            }
        }
        else {
            // The frame is invalid, so reset
            init_byte_stuffer_state(state);
        }
    }
    else {
        if (state->data_pos == MAX_FRAME_SIZE) {
            // We exceeded our maximum frame size
            // therefore there's nothing else to do than reset to a new frame
            state->next_zero = data;
            state->long_frame = data == 0xFF;
            state->data_pos = 0;
        }
        else if (state->next_zero == 0) {
            if (state->long_frame) {
                // This is part of a long frame, so continue
                state->next_zero = data;
                state->long_frame = data == 0xFF;
            }
            else {
                // Special case for zeroes
                state->next_zero = data;
                state->data[state->data_pos++] = 0;
            }
        }
        else {
            state->data[state->data_pos++] = data;
        }
    }
}

static void send_block(uint8_t link, uint8_t* start, uint8_t* end, uint8_t num_non_zero) {
    send_data(link, &num_non_zero, 1);
    if (end > start) {
        send_data(link, start, end-start);
    }
}

void byte_stuffer_send_frame(uint8_t link, uint8_t* data, uint16_t size) {
    const uint8_t zero = 0;
    if (size > 0) {
        uint16_t num_non_zero = 1;
        uint8_t* end = data + size;
        uint8_t* start = data;
        while (data < end) {
            if (num_non_zero == 0xFF) {
                // There's more data after big non-zero block
                // So send it, and start a new block
                send_block(link, start, data, num_non_zero);
                start = data;
                num_non_zero = 1;
            }
            else {
                if (*data == 0) {
                    // A zero encountered, so send the block
                    send_block(link, start, data, num_non_zero);
                    start = data + 1;
                    num_non_zero = 1;
                }
                else {
                    num_non_zero++;
                }
                ++data;
            }
        }
        send_block(link, start, data, num_non_zero);
        send_data(link, &zero, 1);
    }
}
onfig.get('devs', []): domain = parse_hex(pci_config.get('domain', 0)) bus = parse_hex(pci_config.get('bus', 0)) slot = parse_hex(pci_config.get('slot', 0)) func = parse_hex(pci_config.get('func', 0)) vslt = pci_config.get('vslt') if vslt is not None: vslots = vslots + vslt + ";" back['dev-%i' % pcidevid] = "%04x:%02x:%02x.%02x" % \ (domain, bus, slot, func) pcidevid += 1 if vslots != "": back['vslots'] = vslots back['num_devs']=str(pcidevid) back['uuid'] = config.get('uuid','') return (0, back, {}) def reconfigureDevice(self, _, config): """@see DevController.reconfigureDevice""" (devid, back, front) = self.getDeviceDetails(config) num_devs = int(back['num_devs']) states = config.get('states', []) old_vslots = self.readBackend(devid, 'vslots') if old_vslots is None: old_vslots = '' num_olddevs = int(self.readBackend(devid, 'num_devs')) for i in range(num_devs): try: dev = back['dev-%i' % i] state = states[i] except: raise XendError('Error reading config') if state == 'Initialising': # PCI device attachment for j in range(num_olddevs): if dev == self.readBackend(devid, 'dev-%i' % j): raise XendError('Device %s is already connected.' % dev) log.debug('Attaching PCI device %s.' % dev) (domain, bus, slotfunc) = dev.split(':') (slot, func) = slotfunc.split('.') domain = parse_hex(domain) bus = parse_hex(bus) slot = parse_hex(slot) func = parse_hex(func) self.setupOneDevice(domain, bus, slot, func) self.writeBackend(devid, 'dev-%i' % (num_olddevs + i), dev) self.writeBackend(devid, 'state-%i' % (num_olddevs + i), str(xenbusState['Initialising'])) self.writeBackend(devid, 'num_devs', str(num_olddevs + i + 1)) # Update vslots if back['vslots'] is not None: vslots = old_vslots + back['vslots'] self.writeBackend(devid, 'vslots', vslots) elif state == 'Closing': # PCI device detachment found = False for j in range(num_olddevs): if dev == self.readBackend(devid, 'dev-%i' % j): found = True log.debug('Detaching device %s' % dev) self.writeBackend(devid, 'state-%i' % j, str(xenbusState['Closing'])) if not found: raise XendError('Device %s is not connected' % dev) # Update vslots if back['vslots'] is not None: vslots = old_vslots for vslt in back['vslots'].split(';'): if vslt != '': vslots = vslots.replace(vslt + ';', '', 1) if vslots == '': self.removeBackend(devid, 'vslots') else: self.writeBackend(devid, 'vslots', vslots) else: raise XendError('Error configuring device %s: invalid state %s' % (dev,state)) self.writeBackend(devid, 'state', str(xenbusState['Reconfiguring'])) return self.readBackend(devid, 'uuid') def getDeviceConfiguration(self, devid, transaction = None): result = DevController.getDeviceConfiguration(self, devid, transaction) num_devs = self.readBackend(devid, 'num_devs') pci_devs = [] vslots = self.readBackend(devid, 'vslots') if vslots is not None: if vslots[-1] == ";": vslots = vslots[:-1] slot_list = vslots.split(';') for i in range(int(num_devs)): dev_config = self.readBackend(devid, 'dev-%d' % i) pci_match = re.match(r"((?P<domain>[0-9a-fA-F]{1,4})[:,])?" + r"(?P<bus>[0-9a-fA-F]{1,2})[:,]" + r"(?P<slot>[0-9a-fA-F]{1,2})[.,]" + r"(?P<func>[0-7]{1,2})$", dev_config) if pci_match!=None: pci_dev_info = pci_match.groupdict() dev_dict = {'domain': '0x%(domain)s' % pci_dev_info, 'bus': '0x%(bus)s' % pci_dev_info, 'slot': '0x%(slot)s' % pci_dev_info, 'func': '0x%(func)s' % pci_dev_info} #append vslot info if vslots is not None: try: dev_dict['vslt'] = slot_list[i] except IndexError: dev_dict['vslt'] = '0x0' pci_devs.append(dev_dict) result['devs'] = pci_devs result['uuid'] = self.readBackend(devid, 'uuid') return result def configuration(self, devid, transaction = None): """Returns SXPR for devices on domain. @note: we treat this dict especially to convert to SXP because it is not a straight dict of strings.""" configDict = self.getDeviceConfiguration(devid, transaction) sxpr = [self.deviceClass] # remove devs devs = configDict.pop('devs', []) for dev in devs: dev_sxpr = ['dev'] for dev_item in dev.items(): dev_sxpr.append(list(dev_item)) sxpr.append(dev_sxpr) for key, val in configDict.items(): if type(val) == type(list()): for v in val: sxpr.append([key, v]) else: sxpr.append([key, val]) return sxpr def CheckSiblingDevices(self, domid, dev): """ Check if all sibling devices of dev are owned by pciback """ if not self.vm.info.is_hvm(): return group_str = xc.get_device_group(domid, dev.domain, dev.bus, dev.slot, dev.func) if group_str == "": return #group string format xx:xx.x,xx:xx.x, devstr_len = group_str.find(',') for i in range(0, len(group_str), devstr_len + 1): (bus, slotfunc) = group_str[i:i + devstr_len].split(':') (slot, func) = slotfunc.split('.') b = parse_hex(bus) d = parse_hex(slot) f = parse_hex(func) try: sdev = PciDevice(dev.domain, b, d, f) except Exception, e: #no dom0 drivers bound to sdev continue if sdev.driver!='pciback': raise VmError(("pci: PCI Backend does not own\n "+ \ "sibling device %s of device %s\n"+ \ "See the pciback.hide kernel "+ \ "command-line parameter or\n"+ \ "bind your slot/device to the PCI backend using sysfs" \ )%(sdev.name, dev.name)) return def setupOneDevice(self, domain, bus, slot, func): """ Attach I/O resources for device to frontend domain """ fe_domid = self.getDomid() try: dev = PciDevice(domain, bus, slot, func) except Exception, e: raise VmError("pci: failed to locate device and "+ "parse it's resources - "+str(e)) if dev.driver!='pciback': raise VmError(("pci: PCI Backend does not own device "+ \ "%s\n"+ \ "See the pciback.hide kernel "+ \ "command-line parameter or\n"+ \ "bind your slot/device to the PCI backend using sysfs" \ )%(dev.name)) self.CheckSiblingDevices(fe_domid, dev) PCIQuirk(dev.vendor, dev.device, dev.subvendor, dev.subdevice, domain, bus, slot, func) if not self.vm.info.is_hvm(): # Setup IOMMU device assignment pci_str = "0x%x, 0x%x, 0x%x, 0x%x" % (domain, bus, slot, func) bdf = xc.assign_device(fe_domid, pci_str) if bdf > 0: raise VmError("Failed to assign device to IOMMU (%x:%x.%x)" % (bus, slot, func)) log.debug("pci: assign device %x:%x.%x" % (bus, slot, func)) for (start, size) in dev.ioports: log.debug('pci: enabling ioport 0x%x/0x%x'%(start,size)) rc = xc.domain_ioport_permission(domid = fe_domid, first_port = start, nr_ports = size, allow_access = True) if rc<0: raise VmError(('pci: failed to configure I/O ports on device '+ '%s - errno=%d')%(dev.name,rc)) for (start, size) in dev.iomem: # Convert start/size from bytes to page frame sizes start_pfn = start>>PAGE_SHIFT # Round number of pages up to nearest page boundary (if not on one) nr_pfns = (size+(PAGE_SIZE-1))>>PAGE_SHIFT log.debug('pci: enabling iomem 0x%x/0x%x pfn 0x%x/0x%x'% \ (start,size,start_pfn,nr_pfns)) rc = xc.domain_iomem_permission(domid = fe_domid, first_pfn = start_pfn, nr_pfns = nr_pfns, allow_access = True) if rc<0: raise VmError(('pci: failed to configure I/O memory on device '+ '%s - errno=%d')%(dev.name,rc)) rc = xc.physdev_map_pirq(domid = fe_domid, index = dev.irq, pirq = dev.irq) if rc < 0: raise VmError(('pci: failed to map irq on device '+ '%s - errno=%d')%(dev.name,rc)) if dev.msix: for (start, size) in dev.msix_iomem: start_pfn = start>>PAGE_SHIFT nr_pfns = (size+(PAGE_SIZE-1))>>PAGE_SHIFT log.debug('pci-msix: remove permission for 0x%x/0x%x 0x%x/0x%x' % \ (start,size, start_pfn, nr_pfns)) rc = xc.domain_iomem_permission(domid = fe_domid, first_pfn = start_pfn, nr_pfns = nr_pfns, allow_access = False) if rc<0: raise VmError(('pci: failed to remove msi-x iomem')) if dev.irq>0: log.debug('pci: enabling irq %d'%dev.irq) rc = xc.domain_irq_permission(domid = fe_domid, pirq = dev.irq, allow_access = True) if rc<0: raise VmError(('pci: failed to configure irq on device '+ '%s - errno=%d')%(dev.name,rc)) def setupDevice(self, config): """Setup devices from config """ for pci_config in config.get('devs', []): domain = parse_hex(pci_config.get('domain', 0)) bus = parse_hex(pci_config.get('bus', 0)) slot = parse_hex(pci_config.get('slot', 0)) func = parse_hex(pci_config.get('func', 0)) self.setupOneDevice(domain, bus, slot, func) return def cleanupOneDevice(self, domain, bus, slot, func): """ Detach I/O resources for device from frontend domain """ fe_domid = self.getDomid() try: dev = PciDevice(domain, bus, slot, func) except Exception, e: raise VmError("pci: failed to locate device and "+ "parse it's resources - "+str(e)) if dev.driver!='pciback': raise VmError(("pci: PCI Backend does not own device "+ \ "%s\n"+ \ "See the pciback.hide kernel "+ \ "command-line parameter or\n"+ \ "bind your slot/device to the PCI backend using sysfs" \ )%(dev.name)) if not self.vm.info.is_hvm(): pci_str = "0x%x, 0x%x, 0x%x, 0x%x" % (domain, bus, slot, func) bdf = xc.deassign_device(fe_domid, pci_str) if bdf > 0: raise VmError("Failed to deassign device from IOMMU (%x:%x.%x)" % (bus, slot, func)) log.debug("pci: deassign device %x:%x.%x" % (bus, slot, func)) for (start, size) in dev.ioports: log.debug('pci: disabling ioport 0x%x/0x%x'%(start,size)) rc = xc.domain_ioport_permission(domid = fe_domid, first_port = start, nr_ports = size, allow_access = False) if rc<0: raise VmError(('pci: failed to configure I/O ports on device '+ '%s - errno=%d')%(dev.name,rc)) for (start, size) in dev.iomem: # Convert start/size from bytes to page frame sizes start_pfn = start>>PAGE_SHIFT # Round number of pages up to nearest page boundary (if not on one) nr_pfns = (size+(PAGE_SIZE-1))>>PAGE_SHIFT log.debug('pci: disabling iomem 0x%x/0x%x pfn 0x%x/0x%x'% \ (start,size,start_pfn,nr_pfns)) rc = xc.domain_iomem_permission(domid = fe_domid, first_pfn = start_pfn, nr_pfns = nr_pfns, allow_access = False) if rc<0: raise VmError(('pci: failed to configure I/O memory on device '+ '%s - errno=%d')%(dev.name,rc)) if dev.irq>0: log.debug('pci: disabling irq %d'%dev.irq) rc = xc.domain_irq_permission(domid = fe_domid, pirq = dev.irq, allow_access = False) if rc<0: raise VmError(('pci: failed to configure irq on device '+ '%s - errno=%d')%(dev.name,rc)) def cleanupDevice(self, devid): """ Detach I/O resources for device and cleanup xenstore nodes after reconfigure. @param devid: The device ID @type devid: int @return: Return the number of devices connected @rtype: int """ num_devs = int(self.readBackend(devid, 'num_devs')) new_num_devs = 0 for i in range(num_devs): state = int(self.readBackend(devid, 'state-%i' % i)) if state == xenbusState['Closing']: # Detach I/O resources. dev = self.readBackend(devid, 'dev-%i' % i) (domain, bus, slotfunc) = dev.split(':') (slot, func) = slotfunc.split('.') domain = parse_hex(domain) bus = parse_hex(bus) slot = parse_hex(slot) func = parse_hex(func) # In HVM case, I/O resources are disabled in ioemu. self.cleanupOneDevice(domain, bus, slot, func) # Remove xenstore nodes. self.removeBackend(devid, 'dev-%i' % i) self.removeBackend(devid, 'vdev-%i' % i) self.removeBackend(devid, 'state-%i' % i) else: if new_num_devs != i: tmpdev = self.readBackend(devid, 'dev-%i' % i) self.writeBackend(devid, 'dev-%i' % new_num_devs, tmpdev) self.removeBackend(devid, 'dev-%i' % i) tmpvdev = self.readBackend(devid, 'vdev-%i' % i) if tmpvdev is not None: self.writeBackend(devid, 'vdev-%i' % new_num_devs, tmpvdev) self.removeBackend(devid, 'vdev-%i' % i) tmpstate = self.readBackend(devid, 'state-%i' % i) self.writeBackend(devid, 'state-%i' % new_num_devs, tmpstate) self.removeBackend(devid, 'state-%i' % i) new_num_devs = new_num_devs + 1 self.writeBackend(devid, 'num_devs', str(new_num_devs)) return new_num_devs def waitForBackend(self,devid): return (0, "ok - no hotplug") def migrate(self, config, network, dst, step, domName): raise XendError('Migration not permitted with assigned PCI device.')