port to pika os 4
Some checks failed
PikaOS Package Build Only (Canary) (amd64-v3) / build (push) Failing after 45s
PikaOS Package Build Only (amd64-v3) / build (push) Failing after 1s
PikaOS Package Build & Release (Canary) (amd64-v3) / build (push) Failing after 31s
PikaOS Package Build & Release (amd64-v3) / build (push) Failing after 0s

This commit is contained in:
Ward from fusion-voyager-3 2024-07-28 14:25:59 +03:00
parent 28b340ca7c
commit c19d626ade
19 changed files with 1477 additions and 66 deletions

1
.github/build-canary-v3 vendored Normal file
View File

@ -0,0 +1 @@
1

1
.github/build-nest-v3 vendored Normal file
View File

@ -0,0 +1 @@
1

1
.github/release-canary-v3 vendored Normal file
View File

@ -0,0 +1 @@
1

1
.github/release-nest-v3 vendored Normal file
View File

@ -0,0 +1 @@
1

37
.github/workflows/build-canaryv3.yml vendored Normal file
View File

@ -0,0 +1,37 @@
name: PikaOS Package Build Only (Canary) (amd64-v3)
on:
push:
branches:
- main
paths:
- '.github/build-canary-v3'
jobs:
build:
runs-on: ubuntu-latest
container:
image: ghcr.io/pikaos-linux/pikaos-builder:canaryv3
volumes:
- /proc:/proc
options: --privileged -it
steps:
- uses: actions/checkout@v3
- name: Install SSH key
uses: shimataro/ssh-key-action@v2
with:
key: ${{ secrets.SSH_KEY }}
name: id_rsa
known_hosts: ${{ secrets.KNOWN_HOSTS }}
if_key_exists: replace
- name: Update APT Cache
run: apt-get update -y
- name: Set Build Config
run: cp -vf ./pika-build-config/amd64-v3.sh ./pika-build-config.sh
- name: Build Package
run: ./main.sh

37
.github/workflows/build-nestv3.yml vendored Normal file
View File

@ -0,0 +1,37 @@
name: PikaOS Package Build Only (amd64-v3)
on:
push:
branches:
- main
paths:
- '.github/build-nest-v3'
jobs:
build:
runs-on: ubuntu-latest
container:
image: ghcr.io/pikaos-linux/pikaos-builder:nestv3
volumes:
- /proc:/proc
options: --privileged -it
steps:
- uses: actions/checkout@v3
- name: Install SSH key
uses: shimataro/ssh-key-action@v2
with:
key: ${{ secrets.SSH_KEY }}
name: id_rsa
known_hosts: ${{ secrets.KNOWN_HOSTS }}
if_key_exists: replace
- name: Update APT Cache
run: apt-get update -y
- name: Set Build Config
run: cp -vf ./pika-build-config/amd64-v3.sh ./pika-build-config.sh
- name: Build Package
run: ./main.sh

40
.github/workflows/release-canaryv3.yml vendored Normal file
View File

@ -0,0 +1,40 @@
name: PikaOS Package Build & Release (Canary) (amd64-v3)
on:
push:
branches:
- main
paths:
- '.github/release-canary-v3'
jobs:
build:
runs-on: ubuntu-latest
container:
image: ghcr.io/pikaos-linux/pikaos-builder:canaryv3
volumes:
- /proc:/proc
options: --privileged -it
steps:
- uses: actions/checkout@v3
- name: Install SSH key
uses: shimataro/ssh-key-action@v2
with:
key: ${{ secrets.SSH_KEY }}
name: id_rsa
known_hosts: ${{ secrets.KNOWN_HOSTS }}
if_key_exists: replace
- name: Update APT Cache
run: apt-get update -y
- name: Set Build Config
run: cp -vf ./pika-build-config/amd64-v3.sh ./pika-build-config.sh
- name: Build Package
run: ./main.sh
- name: Release Package
run: ./release.sh

40
.github/workflows/release-nestv3.yml vendored Normal file
View File

@ -0,0 +1,40 @@
name: PikaOS Package Build & Release (amd64-v3)
on:
push:
branches:
- main
paths:
- '.github/release-nest-v3'
jobs:
build:
runs-on: ubuntu-latest
container:
image: ghcr.io/pikaos-linux/pikaos-builder:nestv3
volumes:
- /proc:/proc
options: --privileged -it
steps:
- uses: actions/checkout@v3
- name: Install SSH key
uses: shimataro/ssh-key-action@v2
with:
key: ${{ secrets.SSH_KEY }}
name: id_rsa
known_hosts: ${{ secrets.KNOWN_HOSTS }}
if_key_exists: replace
- name: Update APT Cache
run: apt-get update -y
- name: Set Build Config
run: cp -vf ./pika-build-config/amd64-v3.sh ./pika-build-config.sh
- name: Build Package
run: ./main.sh
- name: Release Package
run: ./release.sh

View File

@ -1,50 +0,0 @@
name: PikaOS Package Release
on:
workflow_dispatch
jobs:
build:
runs-on: self-hosted
container:
image: ghcr.io/pikaos-linux/pika-package-container:latest
volumes:
- /proc:/proc
options: --privileged -it
steps:
- uses: actions/checkout@v3
- name: Import GPG key
id: import_gpg
uses: crazy-max/ghaction-import-gpg@v5
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.PASSPHRASE }}
- name: Install SSH key
uses: shimataro/ssh-key-action@v2
with:
key: ${{ secrets.SSH_KEY }}
name: id_rsa
known_hosts: ${{ secrets.KNOWN_HOSTS }}
if_key_exists: replace
- name: Update apt cache
run: apt-get update -y
- name: Build Package
run: ./main.sh
- name: Release Package
run: ./release.sh
- name: Purge cache
uses: strrife/cloudflare-chunked-purge-action@master
env:
# Zone is required by both authentication methods
CLOUDFLARE_ZONE: ${{ secrets.CLOUDFLARE_ZONE }}
CLOUDFLARE_TOKEN: ${{ secrets.CLOUDFLARE_TOKEN }}
PURGE_URLS: ${{ vars.PURGE_URLS }}

View File

@ -1,6 +1,6 @@
MIT License MIT License (With DPKG packaging compatibility)
Copyright (c) 2023 PikaOS Copyright (c) 2024 PikaOS
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@ -19,3 +19,6 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
Notes:
The files covered by this license are any files and directories in the root of this repository (including but not limited to: `main.sh`, `release.sh`, and `.github`), with the exception of the `debian` directory and its contents if `debian/copyright` exists, and declares any files or directories as a different LICENSE/COPYRIGHT.

6
debian/changelog vendored
View File

@ -1,3 +1,9 @@
libfprint (1:1.94.7.git-101pika1) pika; urgency=medium
* more egismoc support + new release + pika os 4 port
-- Marco Trevisan (Treviño) <marco@ubuntu.com> Wed, 23 Aug 2023 01:11:51 +0200
libfprint (1:1.94.7-100pika1) pikauwu; urgency=medium libfprint (1:1.94.7-100pika1) pikauwu; urgency=medium
* New upstream release * New upstream release

View File

@ -1,3 +1,4 @@
lib/udev/hwdb.d/ lib/udev/hwdb.d/
lib/udev/rules.d/ lib/udev/rules.d/
usr/lib/${DEB_HOST_MULTIARCH}/libfprint-[0-9].so.* usr/lib/${DEB_HOST_MULTIARCH}/libfprint-[0-9].so.*
egismoc-1c7a-0582.py usr/bin/

1
debian/source/format vendored Normal file
View File

@ -0,0 +1 @@
3.0 (quilt)

436
egismoc-1c7a-0582.py Executable file
View File

@ -0,0 +1,436 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
EGIS 1C7A:0582 Match-on-Chip fingerprint reader driver PoC.
Usage:
ARGV0 -h | --help
ARGV0 info
ARGV0 verify
ARGV0 enroll
ARGV0 wipe
Options:
-h, --help Show help
Commands:
info Get device info
verify Verify finger
enroll Enroll a new finger
wipe Wipe all enrolled fingers
"""
import sys
from docopt import docopt
import usb.core
import usb.util
import re
import secrets
import struct
# find our device
dev = usb.core.find(idVendor=0x1c7a, idProduct=0x0582)
if dev is None:
raise ValueError('Device not found')
# USB URB Device IDs for 1c7a:0582 device
USB_BULK_OUT = 0x02
USB_BULK_IN = 0x81
USB_INTERRUPT_IN = 0x83
# Reading/Writing to the bulk in/out seems to have this kind of payload structure:
# 1) 8 bytes hard-coded prefix depending on out vs in (see below)
# 2) 2 bytes which are a kind of "check bytes" for the payload
# 3) hardcoded "00 00 00"
# 4) 2 bytes that seem to be some kind of type/subtype for different processes/events
# 5) then it seems to vary a bit more with additional payload data depending on different type of events; either some kind of payload directly or more "type" kind of stuff either before or after the payload
# TODO: Probably better/easier/nicer to move to the struct package for building payloads, but for now we will do it all super-hard-coded with bytes...
# Bytes prefix for every payload sent to and from device
WRITE_PREFIX = b'EGIS\x00\x00\x00\x01'
READ_PREFIX = b'SIGE\x00\x00\x00\x01'
# Derive the 2 "check bytes" for write payloads
# 32-bit big-endian sum of all 16-bit words (including check bytes) MOD 0xFFFF should be 0
def get_check_bytes(payload):
full_payload = WRITE_PREFIX + b'\x00\x00\x00\x00\x00' + payload
full_payload_h_be_ints = []
# Combine every 2 bytes (32 bits) into 2-byte big-endian "words"
# Example: '0x53 0x49' should become '0x4953'
i = 0
while i < len(full_payload):
if i+1 >= len(full_payload):
full_payload_h_be_ints.append(struct.unpack('>H', full_payload[i].to_bytes(length=1) + b'\x00')[0])
else:
full_payload_h_be_ints.append(struct.unpack('>H', full_payload[i:i+2])[0])
i += 2
# we can derive the "first occurence" of possible check bytes as `0xFFFF - (sum_of_32bit_words % 0xFFFF)`
# and then pack it so it is split into 2 individual bytes and in the right order
return struct.pack('>H', 0xFFFF - (sum(full_payload_h_be_ints) % 0xFFFF))
# Standard method to make it easier to write to USB_BULK_OUT
# This method will create and send a "full" payload which looks like this:
# E G I S 00 00 00 01 {cb1} {cb2} 00 00 00 {payload}
# (where cb1 and cb2 are some check bytes generated by the get_check_bytes() method and payload is what is passed via the parameter)
def write(payload):
full_payload = WRITE_PREFIX + get_check_bytes(payload) + b'\x00\x00\x00' + payload
print(f"Sending: {full_payload}")
return dev.write(USB_BULK_OUT, full_payload)
# Standard method to make it easier to read from USB_BULK_IN
def read(length=4096, timeout=5000):
return dev.read(USB_BULK_IN, length, timeout)
# Standard method to make it easier to read from USB_INTERRUPT_IN (wait for response from fingerprint on physical device)
def wait_for_finger(length=64, timeout=10000): # 10s default timeout to put finger on sensor
return dev.read(USB_INTERRUPT_IN, length, timeout)
### Setup ###
# required every time before the device is used; the Windows driver seems to work that the driver is always running in the background so this setup is only done once on Windows startup
# Put the identifier for each fingerprint in an array in case it is helpful later? We can also use len(fingers_enrolled) to see how many fingerprints are enrolled
fingers_enrolled = []
def setup():
# Reset the device in case it was not closed out properly last time
dev.reset()
# set the active configuration. With no arguments, the first
# configuration will be the active one
dev.set_configuration(1)
# get an endpoint instance
cfg = dev.get_active_configuration()
intf = cfg[(0,0)]
## Initialization sequence (attempting to match from traces) ##
# First send some control transfers
print(dev.ctrl_transfer(0xc0, 32, 0x0000, 4, 16).tobytes())
print(dev.ctrl_transfer(0xc0, 32, 0x0000, 4, 40).tobytes())
print(dev.ctrl_transfer(0x80, 0, 0x0000, 0, 2).tobytes())
print(dev.ctrl_transfer(0x80, 0, 0x0000, 0, 2).tobytes())
print(dev.ctrl_transfer(0xc0, 82, 0x0000, 0, 8).tobytes())
cfg = dev.get_active_configuration()
intf = cfg[(0,0)]
# Then send a series of "initilization" packets
write(b'\x07\x50\x7f\x00\x00\x00\x00\x0c')
print(f"Read: {read().tobytes()}")
write(b'\x07\x50\x43\x00\x00\x00\x00\x04')
print(f"Read: {read().tobytes()}")
write(b'\x07\x50\x07\x00\x02\x00\x00\x1d')
# this response seems to have slightly different payload if there are fingers regd already vs not? not sure logic, but for now we can instead use the next sequence instead for this detection?
print(f"Read: {read().tobytes()}")
write(b'\x07\x50\x19\x04\x00\x00\x01\x40')
# This response seems to return a payload with an identifier/signature of all fingers registered, so we can use it to know if we are ready to verify or not and provide other info
# The payload portion of this packet (listed as #5 in description above) seems to be composed like this:
# - 32 bytes per finger
# - seem to be some kind of identifier/hash of the finger?
# - The 32 byte block sequences can come in different order when fingers are added/removed?
# - then 2 bytes "90 00"
fingers_enrolled_response = read()
print(f"Read: {fingers_enrolled_response.tobytes()}")
if len(fingers_enrolled_response) >= 48:
for i in range(int((len(fingers_enrolled_response) - 16) / 32)):
fingers_enrolled.append(fingers_enrolled_response[14+(i*32):14+(i*32)+32])
print(f"Device initialization is complete. Number of fingers already enrolled: {len(fingers_enrolled)}")
# Payload that needs to be built for multiple methods below (both enroll and verify)
def fingers_enrolled_payload():
# This payload needs to be built based on existing registered fingerprints and seems to be used in multiple different processes
# ("num fingers regd * 20" + "9") # TODO based on this logic does it mean only max 7 fingers per user?
payload = (((len(fingers_enrolled) + 1) * 0x20) + 0x09).to_bytes()
# then 50 17 03 00 00 00
payload += b'\x50\x17\x03\x00\x00\x00'
# then ("num fingers regd * 20") # TODO based on this logic does it mean only max 7 fingers per user?
payload += ((len(fingers_enrolled) + 1) * 0x20).to_bytes()
# Then hard-coded 32 x 00s
payload += b'\x00\x00\x00\x00\x00\x00\x00\x00'
payload += b'\x00\x00\x00\x00\x00\x00\x00\x00'
payload += b'\x00\x00\x00\x00\x00\x00\x00\x00'
payload += b'\x00\x00\x00\x00\x00\x00\x00\x00'
# Then each of finger identifiers already registered
for f in reversed(fingers_enrolled): #TODO not sure if reversed is required? but seems to be so in trace anyway
payload += f
# then 00 40
payload += b'\x00\x40'
return payload
### End Setup ###
# Different methods for performing different operations: info, enroll, verify, and wipe
### INFO ###
def info():
print("Device information:")
print(dev)
print(f"Number of fingers enrolled: {len(fingers_enrolled)}")
### ENROLL ###
# Sensor read response checks during enrollment
# TODO: In the Windows driver they seem to be able to tell difference between Move Lower / Higher / Left / Right but could not find any difference in payload for each of these conditions (it was all the same payload pattern for these "not in center" conditions)
# also as a "hack" python has trouble if the byte is 0x0a because it interprets this as a linebreak (char 0a) and will sometimes not match this as "any character" (".") so will use a hack (.|\n) as the placeholder to pick up just in case the byte is 0x0a
FINGER_NOT_YET_ENROLLED_REGEX = re.compile(READ_PREFIX + b'\xd5(.|\n)\x00\x00\x00\x02\x90(.|\n)')
NOT_IN_CENTER_REGEX = re.compile(READ_PREFIX + b'(.|\n)\xd0\x00\x00\x00\x04(.|\n)\x0a\x64\x91')
SENSOR_DIRTY_REGEX = re.compile(READ_PREFIX + b'\x00(.|\n)\x00\x00\x00\x02\x64(.|\n)')
PARTIAL_READ_SUCCESS_REGEX = re.compile(READ_PREFIX + b'(.|\n)\x61\x00\x00\x00\x04(.|\n)\x0a\x90\x00') # Maybe we don't need to care about this one? also some bytes seem to increment/decrement based on which valid_read this is, we could use this if we did not want to keep track of read number in our code
def enroll():
# In the trace it seems like the Windows driver actually kicks off a read to the INTERRUPT IN in a background thread much earlier in the process and it always "stays on" in the background
# Then you can sort of control what will be done using different commands to the BULK OUT and read results using the BULK IN
# But here as a temporary test, we will instead just have a single thread where we kick off the read of the sensor (the INTERRUPT IN) only when we need it instead of this read always running/waiting in the background
# Setup to read a print
write(b'\x04\x50\x1a\x00\x00')
print(f"Read: {read().tobytes()}")
write(b'\x04\x50\x17\x01\x00')
print(f"Read: {read().tobytes()}")
print("Waiting for fingerprint on sensor. Please touch the power button...")
wait_for_finger()
print("Fingerprint detected!")
write(b'\x04\x50\x17\x02\x00')
print(f"Read: {read().tobytes()}")
# Next payload from existing registered fingerprints
write(fingers_enrolled_payload())
# this packet returns specific hard-coded bytes if this is a good new finger to add, or some kind of longer payload if it is not
new_finger_response = read()
print(f"Read: {new_finger_response.tobytes()}")
if not FINGER_NOT_YET_ENROLLED_REGEX.fullmatch(new_finger_response):
raise ValueError('That fingerprint is already enrolled. Try a different finger.')
# TODO: if this result then it is considered a bad packet payload from fingers_enrolled_payload() (bad CRC etc?)?
#b'\xf5\x8c\x00\x00\x00\x02\x6f\xe1'
# TODO: skip this for now and see if it works without it? otherwise wonder if this might have something to do with associated the current current user to these enrollments?
## If first time a new finger is being enrolled for this device session, thenn some kind of client info is exchanged? not sure but will just copy what was in the trace for now...
#if len(fingers_enrolled) == 0:
# # TODO is this next payload hardcoded or same every time?
# payload = b'\x6b\x50\x57'
# payload += b'\x01\x00\x00\x00\x62\x20\x76\x6b'
# payload += b'\x30\x62\xb2\xc1\xc7\x18\x55\xeb'
# payload += b'\x3a\xf3\x90\x70\xf9\x7f\x8b\x8b'
# payload += b'\xda\x93\x10\xa8\x87\xc8\x75\xa0'
# payload += b'\xe9\x73\xbf\x70\x31\x8f\x04\xc5'
# payload += b'\x00\xbf\x04\x77\xe8\xd3\x09\x0f'
# payload += b'\x5f\xa3\x1a\x20\x23\x60\x43\x78'
# payload += b'\x39\xf5\x73\x49\xbd\x25\xe2\x7c'
# payload += b'\xe3\xa9\xc9\x07\x18\xb5\x62\xb1'
# payload += b'\xa5\x2b\x83\x97\x84\x17\x43\x4b'
# payload += b'\xd1\x1a\x47\x5b\x94\x82\xa7\x3e'
# payload += b'\x7d\x26\xc0\xab\x38\x57\x59\xb3'
# payload += b'\x0d\x86\x59\x00\xb3\x6d\xe6\x00'
# payload += b'\x00'
# write(payload)
# print(f"Read: {read().tobytes()}") # TODO this response is quite larger and seems to have a bit of Windows / device / cert info with it?
write(b'\x07\x50\x16\x01\x00\x00\x00\x20')
print(f"Read: {read().tobytes()}") # TODO this response has some kind of identifier in it? not sure what it is used for (is this the user ID?)
write(b'\x04\x50\x1a\x00\x00')
print(f"Read: {read().tobytes()}")
write(b'\x04\x50\x16\x02\x01')
print(f"Read: {read().tobytes()}")
read_prompt = "Began registration of a new fingerprint. Please touch the sensor again..."
valid_reads = 0
while valid_reads < 10:
print(read_prompt)
wait_for_finger()
print("Fingerprint detected!")
write(b'\x07\x50\x16\x02\x02\x00\x00\x02')
finger_read_response = read()
print(f"Read: {finger_read_response.tobytes()}")
# Check read success; retry failures without incrementing valid_reads
is_valid = False
if NOT_IN_CENTER_REGEX.fullmatch(finger_read_response):
read_prompt = "Finger was not centered on the sensor. Please try to move to the center and try again..." #TODO how does Windows driver tell difference between higher/lower/left/right?
elif SENSOR_DIRTY_REGEX.fullmatch(finger_read_response):
read_prompt = "The sensor appears dirty or cannot recognize you, please try again..."
elif not PARTIAL_READ_SUCCESS_REGEX.fullmatch(finger_read_response):
raise ValueError("Unknown response from partial read.")
else:
is_valid = True
if valid_reads < 9 or not is_valid:
write(b'\x04\x50\x1a\x00\x00')
print(f"Read: {read().tobytes()}")
write(b'\x04\x50\x16\x02\x01')
print(f"Read: {read().tobytes()}")
else:
write(b'\x07\x50\x16\x05\x00\x00\x00\x20')
print(f"Read: {read().tobytes()}")
# Build new enrolled fingerprint identifier payload
# first is a hardcoded string of bytes
payload = b'\x27\x50\x16\x03\x00\x00\x00\x20'
# Then the rest is actually the "identifier" for the fingerprint (which is sent back from device later) - how should this be generated?
# I think the driver is actually creating and assigning these here? Not sure how it is generated, maybe start with just generating a new 32 byte token?
new_finger_id = secrets.token_bytes(32)
payload += new_finger_id
write(payload)
print(f"Read: {read().tobytes()}")
# TODO: In theory this should be "per user" e.g. per os.getlogin() and then that these IDs per user are saved somewhere like a file or database?
fingers_enrolled.append(new_finger_id)
print("Success! New fingerprint added.")
if is_valid:
valid_reads += 1
read_prompt = f"Great job! Please touch the sensor again... ({valid_reads}/10)"
### VERIFY ###
# Sensor read response checks during verification
# "Verified" seems to be a prefix of 2 bytes (?), then "00 00 00 00 42", then 32 bytes of "something?" (is this the user ID?), then a 32 byte id of the fingerprint which was verified, then "90 00"
# So I assume you can figure out which user is associated with the verification based on which users has this fingerprint ID
VERIFIED_REGEX = re.compile(READ_PREFIX + b'(.|\n)(.|\n)\x00\x00\x00\x42(.|\n){32}((.|\n){32})\x90\x00') # TODO: think this is still not working exactly right to capture the ID?
# If "not verified," instead of matching the above pattern it seems like it is usually a hardcoded byte string on the same read?
NOT_VERIFIED_REGEX = re.compile(READ_PREFIX + b'\xd5\x69\x00\x00\x00\x02\x90\x04')
def verify():
if len(fingers_enrolled) == 0:
raise ValueError('No fingers are enrolled!')
# setup to read a print - in the trace this seems to be done after the read is kicked off on INTERRUPT IN but is it ok to do single-threaded like this?
write(b'\x04\x50\x17\x01\x01')
print(f"Read: {read().tobytes()}")
print("Waiting for fingerprint on sensor. Please touch the power button...")
wait_for_finger()
print("Fingerprint detected!")
# post-processing of some kind?
write(b'\x04\x50\x17\x02\x00')
print(f"Read: {read().tobytes()}")
# Next payload from existing registered fingerprints
write(fingers_enrolled_payload())
verify_finger_payload = read()
print(f"Read: {verify_finger_payload.tobytes()}")
result = False
mtch = VERIFIED_REGEX.match(verify_finger_payload)
if not mtch:
result = False
print('Your fingerprint could not be recognized. Please try a different finger.')
else:
result = True
print("Matched fingerprint! TODO: capture ID and match vs user")
## TODO: This capture / check may not be needed but also seems to be an issue with the regex/logic -- comment out for now...
#finger_id = mtch.group(4) # needs to be 4 due to workaround with (.|\n), so the finger ID is the 5th capture group we can get from the match
#if finger_id in fingers_enrolled:
# print(f"Found and matched fingerprint {finger_id} !")
#else:
# raise ValueError('Fingerprint seemed to be valid but could not be found in existing list of enrolled prints; what happened???')
write(b'\x04\x50\x1a\x00\x00')
print(f"Read: {read().tobytes()}")
write(b'\x04\x50\x17\x01\x01')
print(f"Read: {read().tobytes()}")
write(b'\x04\x50\x04\x01\x00')
print(f"Read: {read().tobytes()}")
# Get a response from the sensor a second time - why?
print("Getting second response from sensor...")
second_finger_response = wait_for_finger()
# TODO: does this need to be checked for valid/invalid ?
print("Second response detected!")
print(f"Read: {second_finger_response.tobytes()}")
write(b'\x04\x50\x04\x02\x00')
print(f"Read: {read().tobytes()}")
return result
### WIPE ###
def wipe():
if len(fingers_enrolled) == 0:
raise ValueError('No fingers are enrolled!')
# Build the full payload for the packet
# ("num fingers regd * 20" + "7")
payload = (((len(fingers_enrolled)) * 0x20) + 0x07).to_bytes()
# then 50 18 04 00 00 00
payload += b'\x50\x18\x04\x00\x00\x00'
# then ("num fingers regd * 20")
payload += ((len(fingers_enrolled)) * 0x20).to_bytes()
# Then the fingerprint IDs from above
for f in reversed(fingers_enrolled):
payload += f
write(payload)
print(f"Read: {read().tobytes()}") # TODO check for success? is this always READ_PREFIX + '\xd5\x6d\x00\x00\x00\x02\x90\x00' when success?
# TODO: how to know it was "valid" or not? The read() above seems to throw a device I/O exception if there was a failure, maybe that is good enough?
print("Wipe successful!")
return True
def main(args):
if args["info"]:
setup()
info()
if args["enroll"]:
setup()
enroll()
if args["verify"]:
setup()
verify()
if args["wipe"]:
setup()
wipe()
if __name__ == '__main__':
args = docopt(__doc__.replace("ARGV0", sys.argv[0]))
main(args)

17
main.sh
View File

@ -1,11 +1,19 @@
#! /bin/bash #! /bin/bash
DEBIAN_FRONTEND=noninteractive set -e
VERSION="1.94.7.git"
source ./pika-build-config.sh
echo "$PIKA_BUILD_ARCH" > pika-build-arch
# Clone Upstream # Clone Upstream
git clone -b egismoc-0587 https://gitlab.freedesktop.org/thameruddin/libfprint ./libfprintd-2-2 #git clone https://gitlab.freedesktop.org/libfprint/libfprint -b v"$VERSION"
cp -rvf ./debian ./libfprintd-2-2/ git clone https://gitlab.freedesktop.org/libfprint/libfprint
cd ./libfprintd-2-2/ cp -rvf ./debian ./libfprint/
cp -rvf ./egismoc-1c7a-0582.py ./libfprint/
cd ./libfprint/
for i in $(cat ../patches/series | grep -v '^#') ; do echo "Applying Patch: $i" && patch -Np1 -i ../patches/$i || bash -c "echo "Applying Patch $i Failed!" && exit 2"; done for i in $(cat ../patches/series | grep -v '^#') ; do echo "Applying Patch: $i" && patch -Np1 -i ../patches/$i || bash -c "echo "Applying Patch $i Failed!" && exit 2"; done
@ -13,6 +21,7 @@ for i in $(cat ../patches/series | grep -v '^#') ; do echo "Applying Patch: $i"
apt-get build-dep ./ -y apt-get build-dep ./ -y
# Build package # Build package
LOGNAME=root dh_make --createorig -y -l -p libfprint_"$VERSION" || echo "dh-make: Ignoring Last Error"
dpkg-buildpackage --no-sign dpkg-buildpackage --no-sign
# Move the debs to output # Move the debs to output

View File

@ -1,7 +1,7 @@
From d6334a7a4ce57092d426c5016aed286c61b3bffb Mon Sep 17 00:00:00 2001 From d8eb9844f52b72cbb7ffed51d8c1dc49f68356c6 Mon Sep 17 00:00:00 2001
From: Tamer Hassan <thameruddin@gmail.com> From: Tamer Hassan <thameruddin@gmail.com>
Date: Sat, 9 Mar 2024 12:20:15 +0400 Date: Sat, 9 Mar 2024 12:20:15 +0400
Subject: [PATCH] egismoc: add 0587 support (also supports 0586 but missing Subject: [PATCH 1/2] egismoc: add 0587 support (also supports 0586 but missing
device file) device file)
--- ---
@ -18,7 +18,7 @@ Subject: [PATCH] egismoc: add 0587 support (also supports 0586 but missing
create mode 100644 tests/egismoc-0587/device create mode 100644 tests/egismoc-0587/device
diff --git a/data/autosuspend.hwdb b/data/autosuspend.hwdb diff --git a/data/autosuspend.hwdb b/data/autosuspend.hwdb
index 74ac65b0..d8a112b0 100644 index 8e0e6eab..f9842ae4 100644
--- a/data/autosuspend.hwdb --- a/data/autosuspend.hwdb
+++ b/data/autosuspend.hwdb +++ b/data/autosuspend.hwdb
@@ -79,6 +79,7 @@ usb:v1C7Ap0571* @@ -79,6 +79,7 @@ usb:v1C7Ap0571*
@ -1000,3 +1000,841 @@ index dc3b70e2..7a7afc03 100644
-- --
GitLab GitLab
From 4c50d9b6a0cb45681e1cd08411df477772f6821c Mon Sep 17 00:00:00 2001
From: Tamer Hassan <thameruddin@gmail.com>
Date: Wed, 17 Apr 2024 16:09:36 +0400
Subject: [PATCH 2/2] egismoc: add 0586 support
---
data/autosuspend.hwdb | 1 +
libfprint/drivers/egismoc/egismoc.c | 6 +-
tests/egismoc-0586/custom.pcapng | Bin 0 -> 92980 bytes
tests/egismoc-0586/custom.py | 156 +++++++++++++++++
tests/egismoc-0586/device | 255 ++++++++++++++++++++++++++++
tests/meson.build | 1 +
6 files changed, 414 insertions(+), 5 deletions(-)
create mode 100644 tests/egismoc-0586/custom.pcapng
create mode 100755 tests/egismoc-0586/custom.py
create mode 100644 tests/egismoc-0586/device
diff --git a/data/autosuspend.hwdb b/data/autosuspend.hwdb
index f9842ae4..0e4c30ef 100644
--- a/data/autosuspend.hwdb
+++ b/data/autosuspend.hwdb
@@ -79,6 +79,7 @@ usb:v1C7Ap0571*
# Supported by libfprint driver egismoc
usb:v1C7Ap0582*
+usb:v1C7Ap0586*
usb:v1C7Ap0587*
usb:v1C7Ap05A1*
ID_AUTOSUSPEND=1
diff --git a/libfprint/drivers/egismoc/egismoc.c b/libfprint/drivers/egismoc/egismoc.c
index 97997a2d..de5ec765 100644
--- a/libfprint/drivers/egismoc/egismoc.c
+++ b/libfprint/drivers/egismoc/egismoc.c
@@ -51,11 +51,7 @@ G_DEFINE_TYPE (FpiDeviceEgisMoc, fpi_device_egismoc, FP_TYPE_DEVICE);
static const FpIdEntry egismoc_id_table[] = {
{ .vid = 0x1c7a, .pid = 0x0582, .driver_data = EGISMOC_DRIVER_CHECK_PREFIX_TYPE1 },
- /*
- * 0x0586 is supported in the same way as 0587 per user report, but missing submission of device file to be included
- *
- * { .vid = 0x1c7a, .pid = 0x0586, .driver_data = EGISMOC_DRIVER_CHECK_PREFIX_TYPE1 | EGISMOC_DRIVER_MAX_ENROLL_STAGES_20 },
- */
+ { .vid = 0x1c7a, .pid = 0x0586, .driver_data = EGISMOC_DRIVER_CHECK_PREFIX_TYPE1 | EGISMOC_DRIVER_MAX_ENROLL_STAGES_20 },
{ .vid = 0x1c7a, .pid = 0x0587, .driver_data = EGISMOC_DRIVER_CHECK_PREFIX_TYPE1 | EGISMOC_DRIVER_MAX_ENROLL_STAGES_20 },
{ .vid = 0x1c7a, .pid = 0x05a1, .driver_data = EGISMOC_DRIVER_CHECK_PREFIX_TYPE2 },
{ .vid = 0, .pid = 0, .driver_data = 0 }
diff --git a/tests/egismoc-0586/custom.pcapng b/tests/egismoc-0586/custom.pcapng
new file mode 100644
index 0000000000000000000000000000000000000000..f43dc6f4d1c06801e083c37eaab5e45729444ebe
GIT binary patch
literal 92980
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
literal 0
HcmV?d00001
diff --git a/tests/egismoc-0586/custom.py b/tests/egismoc-0586/custom.py
new file mode 100755
index 00000000..3a662380
--- /dev/null
+++ b/tests/egismoc-0586/custom.py
@@ -0,0 +1,156 @@
+#!/usr/bin/python3
+
+import traceback
+import sys
+import time
+import gi
+
+gi.require_version('FPrint', '2.0')
+from gi.repository import FPrint, GLib
+
+# Exit with error on any exception, included those happening in async callbacks
+sys.excepthook = lambda *args: (traceback.print_exception(*args), sys.exit(1))
+
+ctx = GLib.main_context_default()
+
+c = FPrint.Context()
+c.enumerate()
+devices = c.get_devices()
+
+d = devices[0]
+del devices
+
+d.open_sync()
+
+assert d.get_driver() == "egismoc"
+assert not d.has_feature(FPrint.DeviceFeature.CAPTURE)
+assert d.has_feature(FPrint.DeviceFeature.IDENTIFY)
+assert d.has_feature(FPrint.DeviceFeature.VERIFY)
+assert d.has_feature(FPrint.DeviceFeature.DUPLICATES_CHECK)
+assert d.has_feature(FPrint.DeviceFeature.STORAGE)
+assert d.has_feature(FPrint.DeviceFeature.STORAGE_LIST)
+assert d.has_feature(FPrint.DeviceFeature.STORAGE_DELETE)
+assert d.has_feature(FPrint.DeviceFeature.STORAGE_CLEAR)
+
+def enroll_progress(*args):
+ print("finger status: ", d.get_finger_status())
+ print('enroll progress: ' + str(args))
+
+def identify_done(dev, res):
+ global identified
+ identified = True
+ identify_match, identify_print = dev.identify_finish(res)
+ print('indentification_done: ', identify_match, identify_print)
+ assert identify_match.equal(identify_print)
+
+# Beginning with list and clear assumes you begin with >0 prints enrolled before capturing
+
+print("listing - device should have prints")
+stored = d.list_prints_sync()
+assert len(stored) > 0
+del stored
+
+print("clear device storage")
+d.clear_storage_sync()
+print("clear done")
+
+print("listing - device should be empty")
+stored = d.list_prints_sync()
+assert len(stored) == 0
+del stored
+
+print("enrolling")
+template = FPrint.Print.new(d)
+template.set_finger(FPrint.Finger.LEFT_INDEX)
+assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE
+p1 = d.enroll_sync(template, None, enroll_progress, None)
+assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE
+print("enroll done")
+del template
+
+print("listing - device should have 1 print")
+stored = d.list_prints_sync()
+assert len(stored) == 1
+assert stored[0].equal(p1)
+
+print("verifying")
+assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE
+verify_res, verify_print = d.verify_sync(p1)
+assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE
+print("verify done")
+assert verify_res == True
+
+identified = False
+deserialized_prints = []
+for p in stored:
+ deserialized_prints.append(FPrint.Print.deserialize(p.serialize()))
+ assert deserialized_prints[-1].equal(p)
+del stored
+
+print('async identifying')
+d.identify(deserialized_prints, callback=identify_done)
+del deserialized_prints
+
+while not identified:
+ ctx.iteration(True)
+
+print("try to enroll duplicate")
+template = FPrint.Print.new(d)
+template.set_finger(FPrint.Finger.RIGHT_INDEX)
+assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE
+try:
+ d.enroll_sync(template, None, enroll_progress, None)
+except GLib.Error as error:
+ assert error.matches(FPrint.DeviceError.quark(),
+ FPrint.DeviceError.DATA_DUPLICATE)
+except Exception as exc:
+ raise
+assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE
+print("duplicate enroll attempt done")
+
+print("listing - device should still only have 1 print")
+stored = d.list_prints_sync()
+assert len(stored) == 1
+assert stored[0].equal(p1)
+del stored
+
+print("enroll new finger")
+template = FPrint.Print.new(d)
+template.set_finger(FPrint.Finger.RIGHT_INDEX)
+assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE
+p2 = d.enroll_sync(template, None, enroll_progress, None)
+assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE
+print("enroll new finger done")
+del template
+
+print("listing - device should have 2 prints")
+stored = d.list_prints_sync()
+assert len(stored) == 2
+assert (stored[0].equal(p1) and stored[1].equal(p2)) or (stored[0].equal(p2) and stored[1].equal(p1))
+del stored
+
+print("deleting first print")
+d.delete_print_sync(p1)
+print("delete done")
+del p1
+
+print("listing - device should only have second print")
+stored = d.list_prints_sync()
+assert len(stored) == 1
+assert stored[0].equal(p2)
+del stored
+del p2
+
+print("clear device storage")
+d.clear_storage_sync()
+print("clear done")
+
+print("listing - device should be empty")
+stored = d.list_prints_sync()
+assert len(stored) == 0
+del stored
+
+d.close_sync()
+
+del d
+del c
diff --git a/tests/egismoc-0586/device b/tests/egismoc-0586/device
new file mode 100644
index 00000000..fb41aeea
--- /dev/null
+++ b/tests/egismoc-0586/device
@@ -0,0 +1,255 @@
+P: /devices/pci0000:00/0000:00:14.0/usb1/1-7
+N: bus/usb/001/021=12010002FF0000407A1C860556620102030109022700010100A0320904000003FF000000070581020002000705020200020007058303400005
+E: BUSNUM=001
+E: DEVNAME=/dev/bus/usb/001/021
+E: DEVNUM=021
+E: DEVTYPE=usb_device
+E: DRIVER=usb
+E: ID_AUTOSUSPEND=1
+E: ID_BUS=usb
+E: ID_MODEL=ETU905A88-E
+E: ID_MODEL_ENC=ETU905A88-E
+E: ID_MODEL_ID=0586
+E: ID_PATH=pci-0000:00:14.0-usb-0:7
+E: ID_PATH_TAG=pci-0000_00_14_0-usb-0_7
+E: ID_PATH_WITH_USB_REVISION=pci-0000:00:14.0-usbv2-0:7
+E: ID_PERSIST=0
+E: ID_REVISION=6256
+E: ID_SERIAL=EGIS_ETU905A88-E_0A5743PCU834
+E: ID_SERIAL_SHORT=0A5743PCU834
+E: ID_USB_INTERFACES=:ff0000:
+E: ID_USB_MODEL=ETU905A88-E
+E: ID_USB_MODEL_ENC=ETU905A88-E
+E: ID_USB_MODEL_ID=0586
+E: ID_USB_REVISION=6256
+E: ID_USB_SERIAL=EGIS_ETU905A88-E_0A5743PCU834
+E: ID_USB_SERIAL_SHORT=0A5743PCU834
+E: ID_USB_VENDOR=EGIS
+E: ID_USB_VENDOR_ENC=EGIS
+E: ID_USB_VENDOR_ID=1c7a
+E: ID_VENDOR=EGIS
+E: ID_VENDOR_ENC=EGIS
+E: ID_VENDOR_FROM_DATABASE=LighTuning Technology Inc.
+E: ID_VENDOR_ID=1c7a
+E: MAJOR=189
+E: MINOR=20
+E: PRODUCT=1c7a/586/6256
+E: SUBSYSTEM=usb
+E: TYPE=255/0/0
+A: authorized=1\n
+A: avoid_reset_quirk=0\n
+A: bConfigurationValue=1\n
+A: bDeviceClass=ff\n
+A: bDeviceProtocol=00\n
+A: bDeviceSubClass=00\n
+A: bMaxPacketSize0=64\n
+A: bMaxPower=100mA\n
+A: bNumConfigurations=1\n
+A: bNumInterfaces= 1\n
+A: bcdDevice=6256\n
+A: bmAttributes=a0\n
+A: busnum=1\n
+A: configuration=
+H: descriptors=12010002FF0000407A1C860556620102030109022700010100A0320904000003FF000000070581020002000705020200020007058303400005
+A: dev=189:20\n
+A: devnum=21\n
+A: devpath=7\n
+L: driver=../../../../../bus/usb/drivers/usb
+L: firmware_node=../../../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:4e/device:4f/device:56
+A: idProduct=0586\n
+A: idVendor=1c7a\n
+A: ltm_capable=no\n
+A: manufacturer=EGIS\n
+A: maxchild=0\n
+A: physical_location/dock=no\n
+A: physical_location/horizontal_position=center\n
+A: physical_location/lid=no\n
+A: physical_location/panel=front\n
+A: physical_location/vertical_position=center\n
+L: port=../1-0:1.0/usb1-port7
+A: power/active_duration=12644\n
+A: power/autosuspend=2\n
+A: power/autosuspend_delay_ms=2000\n
+A: power/connected_duration=230907\n
+A: power/control=auto\n
+A: power/level=auto\n
+A: power/persist=0\n
+A: power/runtime_active_time=12929\n
+A: power/runtime_status=active\n
+A: power/runtime_suspended_time=217715\n
+A: power/wakeup=disabled\n
+A: power/wakeup_abort_count=\n
+A: power/wakeup_active=\n
+A: power/wakeup_active_count=\n
+A: power/wakeup_count=\n
+A: power/wakeup_expire_count=\n
+A: power/wakeup_last_time_ms=\n
+A: power/wakeup_max_time_ms=\n
+A: power/wakeup_total_time_ms=\n
+A: product=ETU905A88-E\n
+A: quirks=0x0\n
+A: removable=fixed\n
+A: rx_lanes=1\n
+A: serial=0A5743PCU834\n
+A: speed=480\n
+A: tx_lanes=1\n
+A: urbnum=18\n
+A: version= 2.00\n
+
+P: /devices/pci0000:00/0000:00:14.0/usb1
+N: bus/usb/001/001=12010002090001406B1D020008060302010109021900010100E0000904000001090000000705810304000C
+E: BUSNUM=001
+E: CURRENT_TAGS=:seat:
+E: DEVNAME=/dev/bus/usb/001/001
+E: DEVNUM=001
+E: DEVTYPE=usb_device
+E: DRIVER=usb
+E: ID_AUTOSUSPEND=1
+E: ID_BUS=usb
+E: ID_FOR_SEAT=usb-pci-0000_00_14_0
+E: ID_MODEL=xHCI_Host_Controller
+E: ID_MODEL_ENC=xHCI\x20Host\x20Controller
+E: ID_MODEL_FROM_DATABASE=2.0 root hub
+E: ID_MODEL_ID=0002
+E: ID_PATH=pci-0000:00:14.0
+E: ID_PATH_TAG=pci-0000_00_14_0
+E: ID_REVISION=0608
+E: ID_SERIAL=Linux_6.8.5-arch1-1_xhci-hcd_xHCI_Host_Controller_0000:00:14.0
+E: ID_SERIAL_SHORT=0000:00:14.0
+E: ID_USB_INTERFACES=:090000:
+E: ID_USB_MODEL=xHCI_Host_Controller
+E: ID_USB_MODEL_ENC=xHCI\x20Host\x20Controller
+E: ID_USB_MODEL_ID=0002
+E: ID_USB_REVISION=0608
+E: ID_USB_SERIAL=Linux_6.8.5-arch1-1_xhci-hcd_xHCI_Host_Controller_0000:00:14.0
+E: ID_USB_SERIAL_SHORT=0000:00:14.0
+E: ID_USB_VENDOR=Linux_6.8.5-arch1-1_xhci-hcd
+E: ID_USB_VENDOR_ENC=Linux\x206.8.5-arch1-1\x20xhci-hcd
+E: ID_USB_VENDOR_ID=1d6b
+E: ID_VENDOR=Linux_6.8.5-arch1-1_xhci-hcd
+E: ID_VENDOR_ENC=Linux\x206.8.5-arch1-1\x20xhci-hcd
+E: ID_VENDOR_FROM_DATABASE=Linux Foundation
+E: ID_VENDOR_ID=1d6b
+E: MAJOR=189
+E: MINOR=0
+E: PRODUCT=1d6b/2/608
+E: SUBSYSTEM=usb
+E: TAGS=:seat:
+E: TYPE=9/0/1
+A: authorized=1\n
+A: authorized_default=1\n
+A: avoid_reset_quirk=0\n
+A: bConfigurationValue=1\n
+A: bDeviceClass=09\n
+A: bDeviceProtocol=01\n
+A: bDeviceSubClass=00\n
+A: bMaxPacketSize0=64\n
+A: bMaxPower=0mA\n
+A: bNumConfigurations=1\n
+A: bNumInterfaces= 1\n
+A: bcdDevice=0608\n
+A: bmAttributes=e0\n
+A: busnum=1\n
+A: configuration=
+H: descriptors=12010002090001406B1D020008060302010109021900010100E0000904000001090000000705810304000C
+A: dev=189:0\n
+A: devnum=1\n
+A: devpath=0\n
+L: driver=../../../../bus/usb/drivers/usb
+L: firmware_node=../../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:4e/device:4f
+A: idProduct=0002\n
+A: idVendor=1d6b\n
+A: interface_authorized_default=1\n
+A: ltm_capable=no\n
+A: manufacturer=Linux 6.8.5-arch1-1 xhci-hcd\n
+A: maxchild=12\n
+A: power/active_duration=73066477\n
+A: power/autosuspend=0\n
+A: power/autosuspend_delay_ms=0\n
+A: power/connected_duration=73071614\n
+A: power/control=auto\n
+A: power/level=auto\n
+A: power/runtime_active_time=73070027\n
+A: power/runtime_status=active\n
+A: power/runtime_suspended_time=0\n
+A: power/wakeup=disabled\n
+A: power/wakeup_abort_count=\n
+A: power/wakeup_active=\n
+A: power/wakeup_active_count=\n
+A: power/wakeup_count=\n
+A: power/wakeup_expire_count=\n
+A: power/wakeup_last_time_ms=\n
+A: power/wakeup_max_time_ms=\n
+A: power/wakeup_total_time_ms=\n
+A: product=xHCI Host Controller\n
+A: quirks=0x0\n
+A: removable=unknown\n
+A: rx_lanes=1\n
+A: serial=0000:00:14.0\n
+A: speed=480\n
+A: tx_lanes=1\n
+A: urbnum=1111\n
+A: version= 2.00\n
+
+P: /devices/pci0000:00/0000:00:14.0
+E: DRIVER=xhci_hcd
+E: ID_AUTOSUSPEND=1
+E: ID_MODEL_FROM_DATABASE=Alder Lake PCH USB 3.2 xHCI Host Controller
+E: ID_PATH=pci-0000:00:14.0
+E: ID_PATH_TAG=pci-0000_00_14_0
+E: ID_PCI_CLASS_FROM_DATABASE=Serial bus controller
+E: ID_PCI_INTERFACE_FROM_DATABASE=XHCI
+E: ID_PCI_SUBCLASS_FROM_DATABASE=USB controller
+E: ID_VENDOR_FROM_DATABASE=Intel Corporation
+E: MODALIAS=pci:v00008086d000051EDsv00001043sd0000201Fbc0Csc03i30
+E: PCI_CLASS=C0330
+E: PCI_ID=8086:51ED
+E: PCI_SLOT_NAME=0000:00:14.0
+E: PCI_SUBSYS_ID=1043:201F
+E: SUBSYSTEM=pci
+A: ari_enabled=0\n
+A: broken_parity_status=0\n
+A: class=0x0c0330\n
+H: config=8680ED51060490020130030C000080000400220560000000000000000000000000000000000000000000000043101F20000000007000000000000000FF010000FD0134A089C27F8000000000000000003F6DD80F000000000000000000000000316000000000000000000000000000000180C2C10800000000000000000000000590B7001805E0FE000000000000000009B014F01000400100000000C10A080000080E00001800008F50020000010000090000018680C00009001014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B50F110112000000
+A: consistent_dma_mask_bits=64\n
+A: d3cold_allowed=1\n
+A: device=0x51ed\n
+A: dma_mask_bits=64\n
+L: driver=../../../bus/pci/drivers/xhci_hcd
+A: driver_override=(null)\n
+A: enable=1\n
+L: firmware_node=../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:4e
+A: irq=143\n
+A: local_cpulist=0-15\n
+A: local_cpus=ffff\n
+A: modalias=pci:v00008086d000051EDsv00001043sd0000201Fbc0Csc03i30\n
+A: msi_bus=1\n
+A: msi_irqs/143=msi\n
+A: msi_irqs/144=msi\n
+A: msi_irqs/145=msi\n
+A: msi_irqs/146=msi\n
+A: msi_irqs/147=msi\n
+A: msi_irqs/148=msi\n
+A: msi_irqs/149=msi\n
+A: msi_irqs/150=msi\n
+A: numa_node=-1\n
+A: pools=poolinfo - 0.1\nbuffer-2048 0 0 2048 0\nbuffer-512 0 0 512 0\nbuffer-128 0 0 128 0\nbuffer-32 0 0 32 0\nxHCI 1KB stream ctx arrays 0 0 1024 0\nxHCI 256 byte stream ctx arrays 0 0 256 0\nxHCI input/output contexts 8 9 2112 9\nxHCI ring segments 43 53 4096 53\nbuffer-2048 0 16 2048 8\nbuffer-512 0 0 512 0\nbuffer-128 0 0 128 0\nbuffer-32 0 128 32 1\n
+A: power/control=auto\n
+A: power/runtime_active_time=73070690\n
+A: power/runtime_status=active\n
+A: power/runtime_suspended_time=0\n
+A: power/wakeup=enabled\n
+A: power/wakeup_abort_count=0\n
+A: power/wakeup_active=0\n
+A: power/wakeup_active_count=0\n
+A: power/wakeup_count=0\n
+A: power/wakeup_expire_count=0\n
+A: power/wakeup_last_time_ms=0\n
+A: power/wakeup_max_time_ms=0\n
+A: power/wakeup_total_time_ms=0\n
+A: power_state=D0\n
+A: resource=0x0000006005220000 0x000000600522ffff 0x0000000000140204\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n
+A: revision=0x01\n
+A: subsystem_device=0x201f\n
+A: subsystem_vendor=0x1043\n
+A: vendor=0x8086\n
diff --git a/tests/meson.build b/tests/meson.build
index 7a7afc03..2b9691ee 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -53,6 +53,7 @@ drivers_tests = [
'egis0570',
'egismoc',
'egismoc-05a1',
+ 'egismoc-0586',
'egismoc-0587',
'fpcmoc',
'realtek',
--
GitLab

10
pika-build-config/amd64-v3.sh Executable file
View File

@ -0,0 +1,10 @@
#! /bin/bash
export PIKA_BUILD_ARCH="amd64-v3"
export DEBIAN_FRONTEND="noninteractive"
export DEB_BUILD_MAINT_OPTIONS="optimize=+lto -march=x86-64-v3 -O3 -flto -fuse-linker-plugin -falign-functions=32"
export DEB_CFLAGS_MAINT_APPEND="-march=x86-64-v3 -O3 -flto -fuse-linker-plugin -falign-functions=32"
export DEB_CPPFLAGS_MAINT_APPEND="-march=x86-64-v3 -O3 -flto -fuse-linker-plugin -falign-functions=32"
export DEB_CXXFLAGS_MAINT_APPEND="-march=x86-64-v3 -O3 -flto -fuse-linker-plugin -falign-functions=32"
export DEB_LDFLAGS_MAINT_APPEND="-march=x86-64-v3 -O3 -flto -fuse-linker-plugin -falign-functions=32"
export DEB_BUILD_OPTIONS="nocheck notest terse"
export DPKG_GENSYMBOLS_CHECK_LEVEL=0

5
pika-build-config/i386.sh Executable file
View File

@ -0,0 +1,5 @@
#! /bin/bash
export PIKA_BUILD_ARCH="i386"
export DEBIAN_FRONTEND="noninteractive"
export DEB_BUILD_OPTIONS="nocheck notest terse"
export DPKG_GENSYMBOLS_CHECK_LEVEL=0

View File

@ -1,8 +1,2 @@
# send debs to server # send debs to server
rsync -azP --include './' --include '*.deb' --exclude '*' ./output/ ferreo@direct.pika-os.com:/srv/www/incoming/ rsync -azP --include './' --include '*.deb' --exclude '*' ./output/ ferreo@direct.pika-os.com:/srv/www/cockatiel-incoming/
# add debs to repo
ssh ferreo@direct.pika-os.com 'aptly repo add -force-replace -remove-files pikauwu-main /srv/www/incoming/'
# publish the repo
ssh ferreo@direct.pika-os.com 'aptly publish update -batch -skip-contents -force-overwrite pikauwu filesystem:pikarepo:'