Commit d1acc791 authored by Federico Vaga's avatar Federico Vaga

Merge remote-tracking branch 'sw/master'

parents b3454979 e09d458f
*.o
*.ko
*.mod.c
.*.o.cmd
.*.ko.cmd
*.mod.d
*.o.d
*.tmp
.tmp_versions
modules.order
Module.symvers
\#*
*~
# Change Log
## [0.9.0] - 2017-10-13
### Added
- unittest
### Fixes
- add missing header in the repository
- add delay in the unlocking sequence
## [0.8.0] - 2017-10-12
### Added
- char device to program the Application FPGA
- sysfs attribute to unlock programming
- documentation!
## [0.0.0] - 2017-09-28
### Added
- basic Linux device driver
\ No newline at end of file
This diff is collapsed.
-include Makefile.specific
# include parent_common.mk for buildsystem's defines
#use absolute path for REPO_PARENT
REPO_PARENT ?= $(shell /bin/pwd)/..
-include $(REPO_PARENT)/parent_common.mk
DIRS = kernel doc
.PHONY: all clean modules install modules_install $(DIRS)
all clean modules install modules_install: $(DIRS)
clean: TARGET = clean
modules: TARGET = modules
install: TARGET = install
modules_install: TARGET = modules_install
$(DIRS):
$(MAKE) -C $@ $(TARGET)
.. image:: ./doc/svec-logo.png
SVEC Driver
===========
This is the simplified version of the SVEC driver which only purpose is
to export an interface to enable the users to program their bitstream on
the SVEC FPGA.
Build Sources
=============
In order to be able to build this SVEC driver you need the VMEBUS sources
at hand, otherwise you cannot compile the kernel sources.
The documentation is written in reStructuredText and it generates HTML files
and man pages. For this you need the python docutils package installed
If the requirements are satified you can run the following commant in
the project root directory:
.. code::
make VMEBUS=/path/to/vmebus/
Futuristic Vision
=================
Actually this is not a driver for the SVEC card but for that little gateware
core in the SVEC bootloader FPGA. In the future we can think about standardize
other designs around this concept. Then this driver will become the FPGA
programmer driver for a set of devices.
*.man
*.html
*.pdf
\ No newline at end of file
MANUAL=svec-device
all: img doc
doc: html man
html:
rst2html $(MANUAL).rst > $(MANUAL).html
man:
rst2man --syntax-highlight=none $(MANUAL).rst > $(MANUAL).man
img: svec-logo.png
svec-logo.png: svec-logo.svg
inkscape -f $< -e $@
clean:
rm -f *.html *.man *.png
====
SVEC
====
---------------------------------------------
programming application FPGA
---------------------------------------------
:Author: Federico Vaga <federico.vaga@cern.ch>
:organization: CERN
:Date: 2017-10-12
:Copyright: GNU GPL, version 2 or any later version
:Version: 0.9.0
:Manual section: 4
:Manual group: CERN BECOHT Toolkit
Description
===========
The file ``/dev/svec.[0-N]`` is a character device, usually of mode 0440
and owner root:root. The number ``[0-N]`` represents a *SVEC Module* instance.
It is used to program the *Application FPGA* of a `SVEC module`_ on the VME bus.
The programming procedure consists in taking an FPGA bit-stream from the user
and writing it in the Application FPGA on the SVEC Module.
Once the device has been closed the Application FPGA become active and,
if valid, it will run the FPGA bit-stream provided by the user.
In order to prevent accidental programming, the Application FPGA programming
procedure is protected by the sysfs attribute ``AFPGA/lock``, usually of mode
0644 and owner root:root. On read it returns a string representing the current
locking status. On write it accepts only the strings:
- "unlock" to unlock the programming procedure.
- "lock" to lock the programming procedure
Once the programming procedure has been completed, the device go back to the
locking status.
The complete programming procedure is:
#. unlock the programming procedure by writing "unlock" in ``AFPGA/lock``
#. write the FPGA bit-stream in the character device ``/dev/svec.<N>``
Files
=====
/dev/svec.[0-N]
/sys/bus/vme/device/vme.[0-N]/svec/svec.[0-N]/AFPGA/lock
.. _`SVEC module`: https://www.ohwr.org/projects/svec/
Examples
========
.. code:: sh
echo "unlock" > /sys/bus/vme/devices/vme.8/svec/svec.8/AFPGA/lock
dd if=/path/to/bitstream.bin of=/dev/svec.8
.. code:: sh
echo "unlock" > /sys/bus/vme/devices/vme.8/svec/svec.8/AFPGA/lock
cat /path/to/bitstream.bin > /dev/svec.8
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="61.325069mm"
height="30.285677mm"
viewBox="0 0 61.325069 30.285677"
version="1.1"
id="svg8"
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
sodipodi:docname="svec-logo.svg">
<defs
id="defs2">
<pattern
inkscape:collect="always"
xlink:href="#Strips1_1"
id="pattern6282"
patternTransform="matrix(10.797808,0,0,10.925543,-5.9115722,-10.514179)" />
<pattern
inkscape:stockid="Stripes 1:1"
id="Strips1_1"
patternTransform="translate(0,0) scale(10,10)"
height="1"
width="2"
patternUnits="userSpaceOnUse"
inkscape:collect="always">
<rect
id="rect5563"
height="2"
width="1"
y="-0.5"
x="0"
style="fill:black;stroke:none" />
</pattern>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.98994949"
inkscape:cx="98.155692"
inkscape:cy="242.73542"
inkscape:document-units="mm"
inkscape:current-layer="layer3"
showgrid="false"
inkscape:window-width="1045"
inkscape:window-height="747"
inkscape:window-x="858"
inkscape:window-y="251"
inkscape:window-maximized="0" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="module"
style="display:inline"
transform="translate(-37.920465,-96.604592)">
<rect
style="fill:#008000;fill-opacity:1;stroke:#000000;stroke-width:0.13229166;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.06060606;paint-order:markers fill stroke"
id="rect4558"
width="61.192772"
height="15.032468"
x="37.98661"
y="111.79166" />
<g
id="g4601">
<rect
y="100.4505"
x="38.139359"
height="26.193871"
width="28.987604"
id="rect4511-0-5"
style="display:inline;fill:#008000;fill-opacity:1;stroke:#000000;stroke-width:0.09502652;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.11616163;paint-order:markers fill stroke" />
<g
id="g4593">
<rect
style="fill:#999999;fill-opacity:1;stroke:#000000;stroke-width:0.13229166;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.11616163;paint-order:markers fill stroke"
id="rect4560"
width="3.7797618"
height="3.7797618"
x="38.139359"
y="96.670738" />
<rect
style="display:inline;fill:#999999;fill-opacity:1;stroke:#000000;stroke-width:0.13229166;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.11616163;paint-order:markers fill stroke"
id="rect4560-2"
width="3.7797618"
height="3.7797618"
x="63.347206"
y="96.670738" />
<rect
style="display:inline;fill:#999999;fill-opacity:1;stroke:#000000;stroke-width:0.13229166;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.11616163;paint-order:markers fill stroke"
id="rect4560-0"
width="3.7797618"
height="3.7797618"
x="46.541973"
y="96.670738" />
<rect
style="display:inline;fill:#999999;fill-opacity:1;stroke:#000000;stroke-width:0.13229166;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.11616163;paint-order:markers fill stroke"
id="rect4560-5"
width="3.7797618"
height="3.7797618"
x="54.944592"
y="96.670738" />
</g>
</g>
<g
transform="translate(32.052422,0.17975616)"
style="display:inline"
id="g4601-5">
<rect
y="100.4505"
x="38.139359"
height="26.193871"
width="28.987604"
id="rect4511-0-5-2"
style="display:inline;fill:#008000;fill-opacity:1;stroke:#000000;stroke-width:0.09502652;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.11616163;paint-order:markers fill stroke" />
<g
id="g4593-9">
<rect
style="fill:#999999;fill-opacity:1;stroke:#000000;stroke-width:0.13229166;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.11616163;paint-order:markers fill stroke"
id="rect4560-02"
width="3.7797618"
height="3.7797618"
x="38.139359"
y="96.670738" />
<rect
style="display:inline;fill:#999999;fill-opacity:1;stroke:#000000;stroke-width:0.13229166;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.11616163;paint-order:markers fill stroke"
id="rect4560-2-8"
width="3.7797618"
height="3.7797618"
x="63.347206"
y="96.670738" />
<rect
style="display:inline;fill:#999999;fill-opacity:1;stroke:#000000;stroke-width:0.13229166;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.11616163;paint-order:markers fill stroke"
id="rect4560-0-3"
width="3.7797618"
height="3.7797618"
x="46.541973"
y="96.670738" />
<rect
style="display:inline;fill:#999999;fill-opacity:1;stroke:#000000;stroke-width:0.13229166;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.11616163;paint-order:markers fill stroke"
id="rect4560-5-8"
width="3.7797618"
height="3.7797618"
x="54.944592"
y="96.670738" />
</g>
</g>
</g>
<g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="cloud"
transform="translate(-37.920465,-96.604592)">
<path
style="opacity:0.95400002;fill:#ffffff;fill-opacity:0.92929293;stroke:url(#pattern6282);stroke-width:0.1401132;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.04545456;paint-order:markers fill stroke"
d="m 96.543104,119.10787 c 8e-6,6.10384 -0.102199,7.76776 -27.627573,7.69409 -28.442888,-0.0761 -28.494238,-1.59025 -28.49423,-7.69409 6e-6,-6.10383 12.563297,-11.05197 28.060901,-11.05197 15.497604,0 28.060896,4.94813 28.060902,11.05197 z"
id="path4639"
inkscape:connector-curvature="0"
sodipodi:nodetypes="sssss" />
</g>
<g
inkscape:label="text"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-37.920465,-96.604592)">
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:6.61458302px;font-family:monospace;-inkscape-font-specification:'monospace, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="44.979176"
y="124.03809"
id="text4506"><tspan
sodipodi:role="line"
id="tspan4504"
x="44.979176"
y="124.03809"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:16.93333244px;font-family:'Latin Modern Mono Prop Light';-inkscape-font-specification:'Latin Modern Mono Prop Light, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332px">SVEC</tspan></text>
</g>
</svg>
# add versions of supermodule. It is useful when svec-sw is included as sub-module
# of a bigger project that we want to track
ifdef CONFIG_SUPER_REPO
ifdef CONFIG_SUPER_REPO_VERSION
SUBMODULE_VERSIONS += MODULE_INFO(version_$(CONFIG_SUPER_REPO),\"$(CONFIG_SUPER_REPO_VERSION)\");
endif
endif
# add versions of used submodules
ccflags-y += -DADDITIONAL_VERSIONS="$(SUBMODULE_VERSIONS)"
ccflags-y += -DGIT_VERSION=\"$(GIT_VERSION)\"
ccflags-y += -I$(VMEBRIDGE_ABS)/include -I$(FPGA_MGR_ABS)/include
KBUILD_EXTRA_SYMBOLS += $(VMEBRIDGE_ABS)/driver/Module.symvers
KBUILD_EXTRA_SYMBOLS += $(FPGA_MGR_ABS)/drivers/fpga/Module.symvers
obj-m := svec.o
svec-objs := svec-core.o
# include parent_common.mk for buildsystem's defines
#use absolute path for REPO_PARENT
REPO_PARENT ?= $(shell /bin/pwd)/../..
-include $(REPO_PARENT)/parent_common.mk
LINUX ?= /lib/modules/$(shell uname -r)/build
VMEBRIDGE_ABS ?= $(abspath $(VMEBRIDGE))
FPGA_MGR_ABS ?= $(abspath $(FPGA_MGR))
GIT_VERSION = $(shell git describe --dirty --long --tags)
export GIT_VERSION
all: modules
.PHONY: all modules clean help install modules_install
modules help install modules_install:
$(MAKE) -C $(LINUX) M=$(shell pwd) GIT_VERSION=$(GIT_VERSION) VMEBRIDGE_ABS=$(VMEBRIDGE_ABS) FPGA_MGR_ABS=$(FPGA_MGR_ABS) $@
# be able to run the "clean" rule even if $(LINUX) is not valid
clean:
rm -rf *.o *~ .*.cmd *.ko *.mod.c .tmp_versions Module.symvers \
Module.markers modules.order
/*
Register definitions for slave core: Xilinx FPGA loader
* File : xloader_regs.h
* Author : auto-generated by wbgen2 from xloader_wb.wb
* Created : Thu Jun 21 15:17:30 2012
* Standard : ANSI C
THIS FILE WAS GENERATED BY wbgen2 FROM SOURCE FILE xloader_wb.wb
DO NOT HAND-EDIT UNLESS IT'S ABSOLUTELY NECESSARY!
*/
#ifndef __WBGEN2_REGDEFS_XLOADER_WB_WB
#define __WBGEN2_REGDEFS_XLOADER_WB_WB
#if defined( __GNUC__)
#define PACKED __attribute__ ((packed))
#else
#error "Unsupported compiler?"
#endif
#ifndef __WBGEN2_MACROS_DEFINED__
#define __WBGEN2_MACROS_DEFINED__
#define WBGEN2_GEN_MASK(offset, size) (((1<<(size))-1) << (offset))
#define WBGEN2_GEN_WRITE(value, offset, size) (((value) & ((1<<(size))-1)) << (offset))
#define WBGEN2_GEN_READ(reg, offset, size) (((reg) >> (offset)) & ((1<<(size))-1))
#define WBGEN2_SIGN_EXTEND(value, bits) (((value) & (1<<bits) ? ~((1<<(bits))-1): 0 ) | (value))
#endif
/* definitions for register: Control/status register */
/* definitions for field: Start configuration in reg: Control/status register */
#define XLDR_CSR_START WBGEN2_GEN_MASK(0, 1)
/* definitions for field: Configuration done in reg: Control/status register */
#define XLDR_CSR_DONE WBGEN2_GEN_MASK(1, 1)
/* definitions for field: Configuration error in reg: Control/status register */
#define XLDR_CSR_ERROR WBGEN2_GEN_MASK(2, 1)
/* definitions for field: Loader busy in reg: Control/status register */
#define XLDR_CSR_BUSY WBGEN2_GEN_MASK(3, 1)
/* definitions for field: Byte order select in reg: Control/status register */
#define XLDR_CSR_MSBF WBGEN2_GEN_MASK(4, 1)
/* definitions for field: Software resest in reg: Control/status register */
#define XLDR_CSR_SWRST WBGEN2_GEN_MASK(5, 1)
/* definitions for field: Exit bootloader mode in reg: Control/status register */
#define XLDR_CSR_EXIT WBGEN2_GEN_MASK(6, 1)
/* definitions for field: Serial clock divider in reg: Control/status register */
#define XLDR_CSR_CLKDIV_MASK WBGEN2_GEN_MASK(8, 6)
#define XLDR_CSR_CLKDIV_SHIFT 8
#define XLDR_CSR_CLKDIV_W(value) WBGEN2_GEN_WRITE(value, 8, 6)
#define XLDR_CSR_CLKDIV_R(reg) WBGEN2_GEN_READ(reg, 8, 6)
/* definitions for register: Bootloader Trigger Register */
/* definitions for register: GPIO Output Register */
/* definitions for register: ID Register */
/* definitions for register: FIFO 'Bitstream FIFO' data input register 0 */
/* definitions for field: Entry size in reg: FIFO 'Bitstream FIFO' data input register 0 */
#define XLDR_FIFO_R0_XSIZE_MASK WBGEN2_GEN_MASK(0, 2)
#define XLDR_FIFO_R0_XSIZE_SHIFT 0
#define XLDR_FIFO_R0_XSIZE_W(value) WBGEN2_GEN_WRITE(value, 0, 2)
#define XLDR_FIFO_R0_XSIZE_R(reg) WBGEN2_GEN_READ(reg, 0, 2)
/* definitions for field: Last xfer in reg: FIFO 'Bitstream FIFO' data input register 0 */
#define XLDR_FIFO_R0_XLAST WBGEN2_GEN_MASK(2, 1)
/* definitions for register: FIFO 'Bitstream FIFO' data input register 1 */
/* definitions for field: Data in reg: FIFO 'Bitstream FIFO' data input register 1 */
#define XLDR_FIFO_R1_XDATA_MASK WBGEN2_GEN_MASK(0, 32)
#define XLDR_FIFO_R1_XDATA_SHIFT 0
#define XLDR_FIFO_R1_XDATA_W(value) WBGEN2_GEN_WRITE(value, 0, 32)
#define XLDR_FIFO_R1_XDATA_R(reg) WBGEN2_GEN_READ(reg, 0, 32)
/* definitions for register: FIFO 'Bitstream FIFO' control/status register */
/* definitions for field: FIFO full flag in reg: FIFO 'Bitstream FIFO' control/status register */
#define XLDR_FIFO_CSR_FULL WBGEN2_GEN_MASK(16, 1)
/* definitions for field: FIFO empty flag in reg: FIFO 'Bitstream FIFO' control/status register */
#define XLDR_FIFO_CSR_EMPTY WBGEN2_GEN_MASK(17, 1)
/* definitions for field: FIFO clear in reg: FIFO 'Bitstream FIFO' control/status register */
#define XLDR_FIFO_CSR_CLEAR_BUS WBGEN2_GEN_MASK(18, 1)
/* definitions for field: FIFO counter in reg: FIFO 'Bitstream FIFO' control/status register */
#define XLDR_FIFO_CSR_USEDW_MASK WBGEN2_GEN_MASK(0, 8)
#define XLDR_FIFO_CSR_USEDW_SHIFT 0
#define XLDR_FIFO_CSR_USEDW_W(value) WBGEN2_GEN_WRITE(value, 0, 8)
#define XLDR_FIFO_CSR_USEDW_R(reg) WBGEN2_GEN_READ(reg, 0, 8)
/* [0x0]: REG Control/status register */
#define XLDR_REG_CSR 0x00000000
/* [0x4]: REG Bootloader Trigger Register */
#define XLDR_REG_BTRIGR 0x00000004
/* [0x8]: REG GPIO Output Register */
#define XLDR_REG_GPIOR 0x00000008
/* [0xc]: REG ID Register */
#define XLDR_REG_IDR 0x0000000c
/* [0x10]: REG FIFO 'Bitstream FIFO' data input register 0 */
#define XLDR_REG_FIFO_R0 0x00000010
/* [0x14]: REG FIFO 'Bitstream FIFO' data input register 1 */
#define XLDR_REG_FIFO_R1 0x00000014
/* [0x18]: REG FIFO 'Bitstream FIFO' control/status register */
#define XLDR_REG_FIFO_CSR 0x00000018
#endif
This diff is collapsed.
import os
import unittest
class SvecTestProgramming(unittest.TestCase):
def setUp(self):
slot = os.environ["VME_SLOT"]
self.file_path = "/sys/bus/vme/devices/vme.{}/svec/svec.{}/AFPGA/lock".format(slot, slot)
self.dev_path = "/dev/svec.{}".format(slot)
self.bitstream = os.environ["BITSTREAM"]
def test_01(self):
"""It writes a dummy FPGA bitstream"""
with open(self.file_path, "w") as f:
f.write("unlock")
with open(self.bitstream, "rb") as d:
with open(self.dev_path, "wb") as f:
f.write(d.read())
with open(self.file_path, "r") as f:
val = f.read().strip()
self.assertEqual(val, "locked")
import os
import random
import string
import unittest
class SvecTestProgrammingLock(unittest.TestCase):
def setUp(self):
slot = os.environ["VME_SLOT"]
self.file_path = "/sys/bus/vme/devices/vme.{}/svec/svec.{}/AFPGA/lock".format(slot, slot)
self.dev_path = "/dev/svec.{}".format(slot)
def test_01(self):
"""It flips the previous lock status"""
with open(self.file_path, "r") as f:
val = f.read().strip()
for i in range(2):
new = "lock" if val == "unlocked" else "unlock"
with open(self.file_path, "w") as f:
f.write(new)
with open(self.file_path, "r") as f:
val = f.read().strip()
self.assertEqual(val, "{}ed".format(new))
def test_02(self):
"""It writes invalid commands to the lock attribute"""
chars = (random.choice(string.ascii_uppercase) for _ in range(3))
with self.assertRaises(OSError):
with open(self.file_path) as f:
f.write(''.join(chars))
def test_03(self):
"""It unlocks the programming"""
with open(self.file_path, "w") as f:
f.write("unlock")
with open(self.file_path, "r") as f:
val = f.read().strip()
self.assertEqual(val, "unlocked")
# we should be able to open the device
with open(self.dev_path) as f:
pass
# It should lock automatically
with open(self.file_path, "r") as f:
val = f.read().strip()
self.assertEqual(val, "locked")
def test_04(self):
"""It locks the programming"""
with open(self.file_path, "w") as f:
f.write("lock")
with open(self.file_path, "r") as f:
val = f.read().strip()
self.assertEqual(val, "locked")
# we should not be able to open the device
with self.assertRaises(OSError):
f = open(self.dev_path)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment