CLI: Udev related fixes and improvements (#10736)

This commit is contained in:
Erovia 2020-11-16 21:09:32 +00:00 committed by GitHub
parent 94e94ffb5b
commit b337ba798e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 141 additions and 116 deletions

View file

@ -7,6 +7,7 @@ import re
import shutil import shutil
import subprocess import subprocess
from pathlib import Path from pathlib import Path
from enum import Enum
from milc import cli from milc import cli
from qmk import submodules from qmk import submodules
@ -14,6 +15,13 @@ from qmk.constants import QMK_FIRMWARE
from qmk.questions import yesno from qmk.questions import yesno
from qmk.commands import run from qmk.commands import run
class CheckStatus(Enum):
OK = 1
WARNING = 2
ERROR = 3
ESSENTIAL_BINARIES = { ESSENTIAL_BINARIES = {
'dfu-programmer': {}, 'dfu-programmer': {},
'avrdude': {}, 'avrdude': {},
@ -33,9 +41,12 @@ def _udev_rule(vid, pid=None, *args):
""" """
rule = "" rule = ""
if pid: if pid:
rule = 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="%s", ATTRS{idProduct}=="%s", TAG+="uaccess", RUN{builtin}+="uaccess"' % (vid, pid) rule = 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="%s", ATTRS{idProduct}=="%s", TAG+="uaccess"' % (
vid,
pid,
)
else: else:
rule = 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="%s", TAG+="uaccess", RUN{builtin}+="uaccess"' % vid rule = 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="%s", TAG+="uaccess"' % vid
if args: if args:
rule = ', '.join([rule, *args]) rule = ', '.join([rule, *args])
return rule return rule
@ -69,24 +80,25 @@ def check_arm_gcc_version():
version_number = ESSENTIAL_BINARIES['arm-none-eabi-gcc']['output'].strip() version_number = ESSENTIAL_BINARIES['arm-none-eabi-gcc']['output'].strip()
cli.log.info('Found arm-none-eabi-gcc version %s', version_number) cli.log.info('Found arm-none-eabi-gcc version %s', version_number)
return True # Right now all known arm versions are ok return CheckStatus.OK # Right now all known arm versions are ok
def check_avr_gcc_version(): def check_avr_gcc_version():
"""Returns True if the avr-gcc version is not known to cause problems. """Returns True if the avr-gcc version is not known to cause problems.
""" """
rc = CheckStatus.ERROR
if 'output' in ESSENTIAL_BINARIES['avr-gcc']: if 'output' in ESSENTIAL_BINARIES['avr-gcc']:
version_number = ESSENTIAL_BINARIES['avr-gcc']['output'].strip() version_number = ESSENTIAL_BINARIES['avr-gcc']['output'].strip()
cli.log.info('Found avr-gcc version %s', version_number)
rc = CheckStatus.OK
parsed_version = parse_gcc_version(version_number) parsed_version = parse_gcc_version(version_number)
if parsed_version['major'] > 8: if parsed_version['major'] > 8:
cli.log.error('We do not recommend avr-gcc newer than 8. Downgrading to 8.x is recommended.') cli.log.warning('{fg_yellow}We do not recommend avr-gcc newer than 8. Downgrading to 8.x is recommended.')
return False rc = CheckStatus.WARNING
cli.log.info('Found avr-gcc version %s', version_number) return rc
return True
return False
def check_avrdude_version(): def check_avrdude_version():
@ -95,7 +107,7 @@ def check_avrdude_version():
version_number = last_line.split()[2][:-1] version_number = last_line.split()[2][:-1]
cli.log.info('Found avrdude version %s', version_number) cli.log.info('Found avrdude version %s', version_number)
return True return CheckStatus.OK
def check_dfu_util_version(): def check_dfu_util_version():
@ -104,7 +116,7 @@ def check_dfu_util_version():
version_number = first_line.split()[1] version_number = first_line.split()[1]
cli.log.info('Found dfu-util version %s', version_number) cli.log.info('Found dfu-util version %s', version_number)
return True return CheckStatus.OK
def check_dfu_programmer_version(): def check_dfu_programmer_version():
@ -113,7 +125,7 @@ def check_dfu_programmer_version():
version_number = first_line.split()[1] version_number = first_line.split()[1]
cli.log.info('Found dfu-programmer version %s', version_number) cli.log.info('Found dfu-programmer version %s', version_number)
return True return CheckStatus.OK
def check_binaries(): def check_binaries():
@ -131,58 +143,56 @@ def check_binaries():
def check_submodules(): def check_submodules():
"""Iterates through all submodules to make sure they're cloned and up to date. """Iterates through all submodules to make sure they're cloned and up to date.
""" """
ok = True
for submodule in submodules.status().values(): for submodule in submodules.status().values():
if submodule['status'] is None: if submodule['status'] is None:
cli.log.error('Submodule %s has not yet been cloned!', submodule['name']) cli.log.error('Submodule %s has not yet been cloned!', submodule['name'])
ok = False return CheckStatus.ERROR
elif not submodule['status']: elif not submodule['status']:
cli.log.error('Submodule %s is not up to date!', submodule['name']) cli.log.warning('Submodule %s is not up to date!', submodule['name'])
ok = False return CheckStatus.WARNING
return ok return CheckStatus.OK
def check_udev_rules(): def check_udev_rules():
"""Make sure the udev rules look good. """Make sure the udev rules look good.
""" """
ok = True rc = CheckStatus.OK
udev_dir = Path("/etc/udev/rules.d/") udev_dir = Path("/etc/udev/rules.d/")
desired_rules = { desired_rules = {
'atmel-dfu': { 'atmel-dfu': {
_udev_rule("03EB", "2FEF"), # ATmega16U2 _udev_rule("03eb", "2fef"), # ATmega16U2
_udev_rule("03EB", "2FF0"), # ATmega32U2 _udev_rule("03eb", "2ff0"), # ATmega32U2
_udev_rule("03EB", "2FF3"), # ATmega16U4 _udev_rule("03eb", "2ff3"), # ATmega16U4
_udev_rule("03EB", "2FF4"), # ATmega32U4 _udev_rule("03eb", "2ff4"), # ATmega32U4
_udev_rule("03EB", "2FF9"), # AT90USB64 _udev_rule("03eb", "2ff9"), # AT90USB64
_udev_rule("03EB", "2FFB") # AT90USB128 _udev_rule("03eb", "2ffb") # AT90USB128
}, },
'kiibohd': {_udev_rule("1C11", "B007")}, 'kiibohd': {_udev_rule("1c11", "b007")},
'stm32': { 'stm32': {
_udev_rule("1EAF", "0003"), # STM32duino _udev_rule("1eaf", "0003"), # STM32duino
_udev_rule("0483", "DF11") # STM32 DFU _udev_rule("0483", "df11") # STM32 DFU
}, },
'bootloadhid': {_udev_rule("16C0", "05DF")}, 'bootloadhid': {_udev_rule("16c0", "05df")},
'usbasploader': {_udev_rule("16C0", "05DC")}, 'usbasploader': {_udev_rule("16c0", "05dc")},
'massdrop': {_udev_rule("03EB", "6124", 'ENV{ID_MM_DEVICE_IGNORE}="1"')}, 'massdrop': {_udev_rule("03eb", "6124", 'ENV{ID_MM_DEVICE_IGNORE}="1"')},
'caterina': { 'caterina': {
# Spark Fun Electronics # Spark Fun Electronics
_udev_rule("1B4F", "9203", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Pro Micro 3V3/8MHz _udev_rule("1b4f", "9203", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Pro Micro 3V3/8MHz
_udev_rule("1B4F", "9205", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Pro Micro 5V/16MHz _udev_rule("1b4f", "9205", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Pro Micro 5V/16MHz
_udev_rule("1B4F", "9207", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # LilyPad 3V3/8MHz (and some Pro Micro clones) _udev_rule("1b4f", "9207", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # LilyPad 3V3/8MHz (and some Pro Micro clones)
# Pololu Electronics # Pololu EleCTRONICS
_udev_rule("1FFB", "0101", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # A-Star 32U4 _udev_rule("1ffb", "0101", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # A-Star 32U4
# Arduino SA # Arduino SA
_udev_rule("2341", "0036", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Leonardo _udev_rule("2341", "0036", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Leonardo
_udev_rule("2341", "0037", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Micro _udev_rule("2341", "0037", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Micro
# Adafruit Industries LLC # Adafruit INDUSTRIES llC
_udev_rule("239A", "000C", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Feather 32U4 _udev_rule("239a", "000c", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Feather 32U4
_udev_rule("239A", "000D", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # ItsyBitsy 32U4 3V3/8MHz _udev_rule("239a", "000d", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # ItsyBitsy 32U4 3V3/8MHz
_udev_rule("239A", "000E", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # ItsyBitsy 32U4 5V/16MHz _udev_rule("239a", "000e", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # ItsyBitsy 32U4 5V/16MHz
# dog hunter AG # dog hunter ag
_udev_rule("2A03", "0036", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Leonardo _udev_rule("2a03", "0036", 'ENV{ID_MM_DEVICE_IGNORE}="1"'), # Leonardo
_udev_rule("2A03", "0037", 'ENV{ID_MM_DEVICE_IGNORE}="1"') # Micro _udev_rule("2a03", "0037", 'ENV{ID_MM_DEVICE_IGNORE}="1"') # Micro
} }
} }
@ -209,31 +219,43 @@ def check_udev_rules():
# Check if the desired rules are among the currently present rules # Check if the desired rules are among the currently present rules
for bootloader, rules in desired_rules.items(): for bootloader, rules in desired_rules.items():
# For caterina, check if ModemManager is running
if bootloader == "caterina":
if check_modem_manager():
ok = False
cli.log.warn("{bg_yellow}Detected ModemManager without the necessary udev rules. Please either disable it or set the appropriate udev rules if you are using a Pro Micro.")
if not rules.issubset(current_rules): if not rules.issubset(current_rules):
deprecated_rule = deprecated_rules.get(bootloader) deprecated_rule = deprecated_rules.get(bootloader)
if deprecated_rule and deprecated_rule.issubset(current_rules): if deprecated_rule and deprecated_rule.issubset(current_rules):
cli.log.warn("{bg_yellow}Found old, deprecated udev rules for '%s' boards. The new rules on https://docs.qmk.fm/#/faq_build?id=linux-udev-rules offer better security with the same functionality.", bootloader) cli.log.warning("{fg_yellow}Found old, deprecated udev rules for '%s' boards. The new rules on https://docs.qmk.fm/#/faq_build?id=linux-udev-rules offer better security with the same functionality.", bootloader)
else: else:
cli.log.warn("{bg_yellow}Missing udev rules for '%s' boards. See https://docs.qmk.fm/#/faq_build?id=linux-udev-rules for more details.", bootloader) # For caterina, check if ModemManager is running
if bootloader == "caterina":
if check_modem_manager():
rc = CheckStatus.WARNING
cli.log.warning("{fg_yellow}Detected ModemManager without the necessary udev rules. Please either disable it or set the appropriate udev rules if you are using a Pro Micro.")
rc = CheckStatus.WARNING
cli.log.warning("{fg_yellow}Missing or outdated udev rules for '%s' boards. Run 'sudo cp %s/util/udev/50-qmk.rules /etc/udev/rules.d/'.", bootloader, QMK_FIRMWARE)
return ok else:
cli.log.warning("{fg_yellow}'%s' does not exist. Skipping udev rule checking...", udev_dir)
return rc
def check_systemd():
"""Check if it's a systemd system
"""
return bool(shutil.which("systemctl"))
def check_modem_manager(): def check_modem_manager():
"""Returns True if ModemManager is running. """Returns True if ModemManager is running.
""" """
if shutil.which("systemctl"): if check_systemd():
mm_check = run(["systemctl", "--quiet", "is-active", "ModemManager.service"], timeout=10) mm_check = run(["systemctl", "--quiet", "is-active", "ModemManager.service"], timeout=10)
if mm_check.returncode == 0: if mm_check.returncode == 0:
return True return True
else: else:
cli.log.warn("Can't find systemctl to check for ModemManager.") """(TODO): Add check for non-systemd systems
"""
return False
def is_executable(command): def is_executable(command):
@ -263,12 +285,8 @@ def os_test_linux():
"""Run the Linux specific tests. """Run the Linux specific tests.
""" """
cli.log.info("Detected {fg_cyan}Linux.") cli.log.info("Detected {fg_cyan}Linux.")
ok = True
if not check_udev_rules(): return check_udev_rules()
ok = False
return ok
def os_test_macos(): def os_test_macos():
@ -276,7 +294,7 @@ def os_test_macos():
""" """
cli.log.info("Detected {fg_cyan}macOS.") cli.log.info("Detected {fg_cyan}macOS.")
return True return CheckStatus.OK
def os_test_windows(): def os_test_windows():
@ -284,7 +302,7 @@ def os_test_windows():
""" """
cli.log.info("Detected {fg_cyan}Windows.") cli.log.info("Detected {fg_cyan}Windows.")
return True return CheckStatus.OK
@cli.argument('-y', '--yes', action='store_true', arg_only=True, help='Answer yes to all questions.') @cli.argument('-y', '--yes', action='store_true', arg_only=True, help='Answer yes to all questions.')
@ -299,23 +317,20 @@ def doctor(cli):
* [ ] Compile a trivial program with each compiler * [ ] Compile a trivial program with each compiler
""" """
cli.log.info('QMK Doctor is checking your environment.') cli.log.info('QMK Doctor is checking your environment.')
ok = True status = CheckStatus.OK
# Determine our OS and run platform specific tests # Determine our OS and run platform specific tests
platform_id = platform.platform().lower() platform_id = platform.platform().lower()
if 'darwin' in platform_id or 'macos' in platform_id: if 'darwin' in platform_id or 'macos' in platform_id:
if not os_test_macos(): status = os_test_macos()
ok = False
elif 'linux' in platform_id: elif 'linux' in platform_id:
if not os_test_linux(): status = os_test_linux()
ok = False
elif 'windows' in platform_id: elif 'windows' in platform_id:
if not os_test_windows(): status = os_test_windows()
ok = False
else: else:
cli.log.error('Unsupported OS detected: %s', platform_id) cli.log.warning('Unsupported OS detected: %s', platform_id)
ok = False status = CheckStatus.WARNING
cli.log.info('QMK home: {fg_cyan}%s', QMK_FIRMWARE) cli.log.info('QMK home: {fg_cyan}%s', QMK_FIRMWARE)
@ -330,31 +345,41 @@ def doctor(cli):
if bin_ok: if bin_ok:
cli.log.info('All dependencies are installed.') cli.log.info('All dependencies are installed.')
else: else:
ok = False status = CheckStatus.ERROR
# Make sure the tools are at the correct version # Make sure the tools are at the correct version
ver_ok = []
for check in (check_arm_gcc_version, check_avr_gcc_version, check_avrdude_version, check_dfu_util_version, check_dfu_programmer_version): for check in (check_arm_gcc_version, check_avr_gcc_version, check_avrdude_version, check_dfu_util_version, check_dfu_programmer_version):
if not check(): ver_ok.append(check())
ok = False
if CheckStatus.ERROR in ver_ok:
status = CheckStatus.ERROR
elif CheckStatus.WARNING in ver_ok and status == CheckStatus.OK:
status = CheckStatus.WARNING
# Check out the QMK submodules # Check out the QMK submodules
sub_ok = check_submodules() sub_ok = check_submodules()
if sub_ok: if sub_ok == CheckStatus.OK:
cli.log.info('Submodules are up to date.') cli.log.info('Submodules are up to date.')
else: else:
if yesno('Would you like to clone the submodules?', default=True): if yesno('Would you like to clone the submodules?', default=True):
submodules.update() submodules.update()
sub_ok = check_submodules() sub_ok = check_submodules()
if not sub_ok: if CheckStatus.ERROR in sub_ok:
ok = False status = CheckStatus.ERROR
elif CheckStatus.WARNING in sub_ok and status == CheckStatus.OK:
status = CheckStatus.WARNING
# Report a summary of our findings to the user # Report a summary of our findings to the user
if ok: if status == CheckStatus.OK:
cli.log.info('{fg_green}QMK is ready to go') cli.log.info('{fg_green}QMK is ready to go')
return 0
elif status == CheckStatus.WARNING:
cli.log.info('{fg_yellow}QMK is ready to go, but minor problems were found')
return 1
else: else:
cli.log.info('{fg_yellow}Problems detected, please fix these problems before proceeding.') cli.log.info('{fg_red}Major problems detected, please fix these problems before proceeding.')
# FIXME(skullydazed/unclaimed): Link to a document about troubleshooting, or discord or something cli.log.info('{fg_blue}Check out the FAQ (https://docs.qmk.fm/#/faq_build) or join the QMK Discord (https://discord.gg/Uq7gcHh) for help.')
return 2
return ok

View file

@ -13,14 +13,14 @@ def check_subcommand(command, *args):
return result return result
def check_returncode(result, expected=0): def check_returncode(result, expected=[0]):
"""Print stdout if `result.returncode` does not match `expected`. """Print stdout if `result.returncode` does not match `expected`.
""" """
if result.returncode != expected: if result.returncode not in expected:
print('`%s` stdout:' % ' '.join(result.args)) print('`%s` stdout:' % ' '.join(result.args))
print(result.stdout) print(result.stdout)
print('returncode:', result.returncode) print('returncode:', result.returncode)
assert result.returncode == expected assert result.returncode in expected
def test_cformat(): def test_cformat():
@ -45,7 +45,7 @@ def test_flash():
def test_flash_bootloaders(): def test_flash_bootloaders():
result = check_subcommand('flash', '-b') result = check_subcommand('flash', '-b')
check_returncode(result, 1) check_returncode(result, [1])
def test_config(): def test_config():
@ -62,7 +62,7 @@ def test_kle2json():
def test_doctor(): def test_doctor():
result = check_subcommand('doctor', '-n') result = check_subcommand('doctor', '-n')
check_returncode(result) check_returncode(result, [0, 1])
assert 'QMK Doctor is checking your environment.' in result.stdout assert 'QMK Doctor is checking your environment.' in result.stdout
assert 'QMK is ready to go' in result.stdout assert 'QMK is ready to go' in result.stdout
@ -89,43 +89,43 @@ def test_list_keyboards():
def test_list_keymaps(): def test_list_keymaps():
result = check_subcommand('list-keymaps', '-kb', 'handwired/onekey/pytest') result = check_subcommand('list-keymaps', '-kb', 'handwired/onekey/pytest')
check_returncode(result, 0) check_returncode(result)
assert 'default' and 'test' in result.stdout assert 'default' and 'test' in result.stdout
def test_list_keymaps_long(): def test_list_keymaps_long():
result = check_subcommand('list-keymaps', '--keyboard', 'handwired/onekey/pytest') result = check_subcommand('list-keymaps', '--keyboard', 'handwired/onekey/pytest')
check_returncode(result, 0) check_returncode(result)
assert 'default' and 'test' in result.stdout assert 'default' and 'test' in result.stdout
def test_list_keymaps_kb_only(): def test_list_keymaps_kb_only():
result = check_subcommand('list-keymaps', '-kb', 'niu_mini') result = check_subcommand('list-keymaps', '-kb', 'niu_mini')
check_returncode(result, 0) check_returncode(result)
assert 'default' and 'via' in result.stdout assert 'default' and 'via' in result.stdout
def test_list_keymaps_vendor_kb(): def test_list_keymaps_vendor_kb():
result = check_subcommand('list-keymaps', '-kb', 'ai03/lunar') result = check_subcommand('list-keymaps', '-kb', 'ai03/lunar')
check_returncode(result, 0) check_returncode(result)
assert 'default' and 'via' in result.stdout assert 'default' and 'via' in result.stdout
def test_list_keymaps_vendor_kb_rev(): def test_list_keymaps_vendor_kb_rev():
result = check_subcommand('list-keymaps', '-kb', 'kbdfans/kbd67/mkiirgb/v2') result = check_subcommand('list-keymaps', '-kb', 'kbdfans/kbd67/mkiirgb/v2')
check_returncode(result, 0) check_returncode(result)
assert 'default' and 'via' in result.stdout assert 'default' and 'via' in result.stdout
def test_list_keymaps_no_keyboard_found(): def test_list_keymaps_no_keyboard_found():
result = check_subcommand('list-keymaps', '-kb', 'asdfghjkl') result = check_subcommand('list-keymaps', '-kb', 'asdfghjkl')
check_returncode(result, 1) check_returncode(result, [1])
assert 'does not exist' in result.stdout assert 'does not exist' in result.stdout
def test_json2c(): def test_json2c():
result = check_subcommand('json2c', 'keyboards/handwired/onekey/keymaps/default_json/keymap.json') result = check_subcommand('json2c', 'keyboards/handwired/onekey/keymaps/default_json/keymap.json')
check_returncode(result, 0) check_returncode(result)
assert result.stdout == '#include QMK_KEYBOARD_H\nconst uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {\t[0] = LAYOUT_ortho_1x1(KC_A)};\n\n' assert result.stdout == '#include QMK_KEYBOARD_H\nconst uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {\t[0] = LAYOUT_ortho_1x1(KC_A)};\n\n'

View file

@ -1,60 +1,60 @@
# Atmel DFU # Atmel DFU
### ATmega16U2 ### ATmega16U2
SUBSYSTEMS=="usb", ATTRS{idVendor}=="03EB", ATTRS{idProduct}=="2FEF", TAG+="uaccess", RUN{builtin}+="uaccess" SUBSYSTEMS=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2fef", TAG+="uaccess"
### ATmega32U2 ### ATmega32U2
SUBSYSTEMS=="usb", ATTRS{idVendor}=="03EB", ATTRS{idProduct}=="2FF0", TAG+="uaccess", RUN{builtin}+="uaccess" SUBSYSTEMS=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2ff0", TAG+="uaccess"
### ATmega16U4 ### ATmega16U4
SUBSYSTEMS=="usb", ATTRS{idVendor}=="03EB", ATTRS{idProduct}=="2FF3", TAG+="uaccess", RUN{builtin}+="uaccess" SUBSYSTEMS=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2ff3", TAG+="uaccess"
### ATmega32U4 ### ATmega32U4
SUBSYSTEMS=="usb", ATTRS{idVendor}=="03EB", ATTRS{idProduct}=="2FF4", TAG+="uaccess", RUN{builtin}+="uaccess" SUBSYSTEMS=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2ff4", TAG+="uaccess"
### AT90USB64 ### AT90USB64
SUBSYSTEMS=="usb", ATTRS{idVendor}=="03EB", ATTRS{idProduct}=="2FF9", TAG+="uaccess", RUN{builtin}+="uaccess" SUBSYSTEMS=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2ff9", TAG+="uaccess"
### AT90USB128 ### AT90USB128
SUBSYSTEMS=="usb", ATTRS{idVendor}=="03EB", ATTRS{idProduct}=="2FFB", TAG+="uaccess", RUN{builtin}+="uaccess" SUBSYSTEMS=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2ffb", TAG+="uaccess"
# Input Club # Input Club
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1C11", ATTRS{idProduct}=="B007", TAG+="uaccess", RUN{builtin}+="uaccess" SUBSYSTEMS=="usb", ATTRS{idVendor}=="1c11", ATTRS{idProduct}=="b007", TAG+="uaccess"
# STM32duino # STM32duino
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1EAF", ATTRS{idProduct}=="0003", TAG+="uaccess", RUN{builtin}+="uaccess" SUBSYSTEMS=="usb", ATTRS{idVendor}=="1eaf", ATTRS{idProduct}=="0003", TAG+="uaccess"
# STM32 DFU # STM32 DFU
SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="DF11", TAG+="uaccess", RUN{builtin}+="uaccess" SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="df11", TAG+="uaccess"
# BootloadHID # BootloadHID
SUBSYSTEMS=="usb", ATTRS{idVendor}=="16C0", ATTRS{idProduct}=="05DF", TAG+="uaccess", RUN{builtin}+="uaccess" SUBSYSTEMS=="usb", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="05df", TAG+="uaccess"
# USBAspLoader # USBAspLoader
SUBSYSTEMS=="usb", ATTRS{idVendor}=="16C0", ATTRS{idProduct}=="05DC", TAG+="uaccess", RUN{builtin}+="uaccess" SUBSYSTEMS=="usb", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="05dc", TAG+="uaccess"
# ModemManager should ignore the following devices # ModemManager should ignore the following devices
# Atmel SAM-BA (Massdrop) # Atmel SAM-BA (Massdrop)
SUBSYSTEMS=="usb", ATTRS{idVendor}=="03EB", ATTRS{idProduct}=="6124", TAG+="uaccess", RUN{builtin}+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1" SUBSYSTEMS=="usb", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="6124", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
# Caterina (Pro Micro) # Caterina (Pro Micro)
## Spark Fun Electronics ## Spark Fun Electronics
### Pro Micro 3V3/8MHz ### Pro Micro 3V3/8MHz
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1B4F", ATTRS{idProduct}=="9203", TAG+="uaccess", RUN{builtin}+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1" SUBSYSTEMS=="usb", ATTRS{idVendor}=="1b4f", ATTRS{idProduct}=="9203", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
### Pro Micro 5V/16MHz ### Pro Micro 5V/16MHz
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1B4F", ATTRS{idProduct}=="9205", TAG+="uaccess", RUN{builtin}+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1" SUBSYSTEMS=="usb", ATTRS{idVendor}=="1b4f", ATTRS{idProduct}=="9205", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
### LilyPad 3V3/8MHz (and some Pro Micro clones) ### LilyPad 3V3/8MHz (and some Pro Micro clones)
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1B4F", ATTRS{idProduct}=="9207", TAG+="uaccess", RUN{builtin}+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1" SUBSYSTEMS=="usb", ATTRS{idVendor}=="1b4f", ATTRS{idProduct}=="9207", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
## Pololu Electronics ## Pololu Electronics
### A-Star 32U4 ### A-Star 32U4
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1FFB", ATTRS{idProduct}=="0101", TAG+="uaccess", RUN{builtin}+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1" SUBSYSTEMS=="usb", ATTRS{idVendor}=="1ffb", ATTRS{idProduct}=="0101", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
## Arduino SA ## Arduino SA
### Leonardo ### Leonardo
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2341", ATTRS{idProduct}=="0036", TAG+="uaccess", RUN{builtin}+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1" SUBSYSTEMS=="usb", ATTRS{idVendor}=="2341", ATTRS{idProduct}=="0036", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
### Micro ### Micro
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2341", ATTRS{idProduct}=="0037", TAG+="uaccess", RUN{builtin}+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1" SUBSYSTEMS=="usb", ATTRS{idVendor}=="2341", ATTRS{idProduct}=="0037", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
## Adafruit Industries LLC ## Adafruit Industries LLC
### Feather 32U4 ### Feather 32U4
SUBSYSTEMS=="usb", ATTRS{idVendor}=="239A", ATTRS{idProduct}=="000C", TAG+="uaccess", RUN{builtin}+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1" SUBSYSTEMS=="usb", ATTRS{idVendor}=="239a", ATTRS{idProduct}=="000c", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
### ItsyBitsy 32U4 3V3/8MHz ### ItsyBitsy 32U4 3V3/8MHz
SUBSYSTEMS=="usb", ATTRS{idVendor}=="239A", ATTRS{idProduct}=="000D", TAG+="uaccess", RUN{builtin}+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1" SUBSYSTEMS=="usb", ATTRS{idVendor}=="239a", ATTRS{idProduct}=="000d", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
### ItsyBitsy 32U4 5V/16MHz ### ItsyBitsy 32U4 5V/16MHz
SUBSYSTEMS=="usb", ATTRS{idVendor}=="239A", ATTRS{idProduct}=="000E", TAG+="uaccess", RUN{builtin}+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1" SUBSYSTEMS=="usb", ATTRS{idVendor}=="239a", ATTRS{idProduct}=="000e", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
## dog hunter AG ## dog hunter AG
### Leonardo ### Leonardo
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2A03", ATTRS{idProduct}=="0036", TAG+="uaccess", RUN{builtin}+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1" SUBSYSTEMS=="usb", ATTRS{idVendor}=="2a03", ATTRS{idProduct}=="0036", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"
### Micro ### Micro
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2A03", ATTRS{idProduct}=="0037", TAG+="uaccess", RUN{builtin}+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1" SUBSYSTEMS=="usb", ATTRS{idVendor}=="2a03", ATTRS{idProduct}=="0037", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1"