Initial GXFP5130 userspace prototype

This commit is contained in:
2026-08-03 11:43:42 +03:00
commit 088d8ef75c
30 changed files with 5318 additions and 0 deletions

13
.gitignore vendored Normal file
View File

@@ -0,0 +1,13 @@
*.a
*.o
*.bin
*.pgm
*.variant
*.bak
*.bak[0-9]*
/analyze-capture
/capture-to-pgm
/fdt-monitor
/sensor-probe
/session-regression

357
LICENSES/GPL-2.0 Normal file
View File

@@ -0,0 +1,357 @@
Valid-License-Identifier: GPL-2.0
Valid-License-Identifier: GPL-2.0-only
Valid-License-Identifier: GPL-2.0+
Valid-License-Identifier: GPL-2.0-or-later
SPDX-URL: https://spdx.org/licenses/GPL-2.0.html
Usage-Guide:
To use this license in source code, put one of the following SPDX
tag/value pairs into a comment according to the placement
guidelines in the licensing rules documentation.
For 'GNU General Public License (GPL) version 2 only' use:
SPDX-License-Identifier: GPL-2.0
or
SPDX-License-Identifier: GPL-2.0-only
For 'GNU General Public License (GPL) version 2 or any later version' use:
SPDX-License-Identifier: GPL-2.0+
or
SPDX-License-Identifier: GPL-2.0-or-later
License-Text:
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
<https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.

509
LICENSES/LGPL-2.1 Normal file
View File

@@ -0,0 +1,509 @@
Valid-License-Identifier: LGPL-2.1
Valid-License-Identifier: LGPL-2.1-only
Valid-License-Identifier: LGPL-2.1+
Valid-License-Identifier: LGPL-2.1-or-later
SPDX-URL: https://spdx.org/licenses/LGPL-2.1.html
Usage-Guide:
To use this license in source code, put one of the following SPDX
tag/value pairs into a comment according to the placement
guidelines in the licensing rules documentation.
For 'GNU Lesser General Public License (LGPL) version 2.1 only' use:
SPDX-License-Identifier: LGPL-2.1
or:
SPDX-License-Identifier: LGPL-2.1-only
For 'GNU Lesser General Public License (LGPL) version 2.1 or any later
version' use:
SPDX-License-Identifier: LGPL-2.1+
or:
SPDX-License-Identifier: LGPL-2.1-or-later
License-Text:
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
<https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts as
the successor of the GNU Library Public License, version 2, hence the
version number 2.1.]
Preamble
The licenses for most software are designed to take away your freedom to
share and change it. By contrast, the GNU General Public Licenses are
intended to guarantee your freedom to share and change free software--to
make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some specially
designated software packages--typically libraries--of the Free Software
Foundation and other authors who decide to use it. You can use it too, but
we suggest you first think carefully about whether this license or the
ordinary General Public License is the better strategy to use in any
particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use, not
price. Our General Public Licenses are designed to make sure that you have
the freedom to distribute copies of free software (and charge for this
service if you wish); that you receive source code or can get it if you
want it; that you can change the software and use pieces of it in new free
programs; and that you are informed that you can do these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for you if
you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis or for
a fee, you must give the recipients all the rights that we gave you. You
must make sure that they, too, receive or can get the source code. If you
link other code with the library, you must provide complete object files to
the recipients, so that they can relink them with the library after making
changes to the library and recompiling it. And you must show them these
terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that there is no
warranty for the free library. Also, if the library is modified by someone
else and passed on, the recipients should know that what they have is not
the original version, so that the original author's reputation will not be
affected by problems that might be introduced by others.
Finally, software patents pose a constant threat to the existence of any
free program. We wish to make sure that a company cannot effectively
restrict the users of a free program by obtaining a restrictive license
from a patent holder. Therefore, we insist that any patent license obtained
for a version of the library must be consistent with the full freedom of
use specified in this license.
Most GNU software, including some libraries, is covered by the ordinary GNU
General Public License. This license, the GNU Lesser General Public
License, applies to certain designated libraries, and is quite different
from the ordinary General Public License. We use this license for certain
libraries in order to permit linking those libraries into non-free
programs.
When a program is linked with a library, whether statically or using a
shared library, the combination of the two is legally speaking a combined
work, a derivative of the original library. The ordinary General Public
License therefore permits such linking only if the entire combination fits
its criteria of freedom. The Lesser General Public License permits more lax
criteria for linking other code with the library.
We call this license the "Lesser" General Public License because it does
Less to protect the user's freedom than the ordinary General Public
License. It also provides other free software developers Less of an
advantage over competing non-free programs. These disadvantages are the
reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to encourage
the widest possible use of a certain library, so that it becomes a de-facto
standard. To achieve this, non-free programs must be allowed to use the
library. A more frequent case is that a free library does the same job as
widely used non-free libraries. In this case, there is little to gain by
limiting the free library to free software only, so we use the Lesser
General Public License.
In other cases, permission to use a particular library in non-free programs
enables a greater number of people to use a large body of free
software. For example, permission to use the GNU C Library in non-free
programs enables many more people to use the whole GNU operating system, as
well as its variant, the GNU/Linux operating system.
Although the Lesser General Public License is Less protective of the users'
freedom, it does ensure that the user of a program that is linked with the
Library has the freedom and the wherewithal to run that program using a
modified version of the Library.
The precise terms and conditions for copying, distribution and modification
follow. Pay close attention to the difference between a "work based on the
library" and a "work that uses the library". The former contains code
derived from the library, whereas the latter must be combined with the
library in order to run.
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other program
which contains a notice placed by the copyright holder or other
authorized party saying it may be distributed under the terms of this
Lesser General Public License (also called "this License"). Each
licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work which
has been distributed under these terms. A "work based on the Library"
means either the Library or any derivative work under copyright law:
that is to say, a work containing the Library or a portion of it, either
verbatim or with modifications and/or translated straightforwardly into
another language. (Hereinafter, translation is included without
limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for making
modifications to it. For a library, complete source code means all the
source code for all modules it contains, plus any associated interface
definition files, plus the scripts used to control compilation and
installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of running
a program using the Library is not restricted, and output from such a
program is covered only if its contents constitute a work based on the
Library (independent of the use of the Library in a tool for writing
it). Whether that is true depends on what the Library does and what the
program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's complete
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the notices
that refer to this License and to the absence of any warranty; and
distribute a copy of this License along with the Library.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Library or any portion of it,
thus forming a work based on the Library, and copy and distribute such
modifications or work under the terms of Section 1 above, provided that
you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices stating
that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no charge to
all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a table
of data to be supplied by an application program that uses the
facility, other than as an argument passed when the facility is
invoked, then you must make a good faith effort to ensure that, in
the event an application does not supply such function or table, the
facility still operates, and performs whatever part of its purpose
remains meaningful.
(For example, a function in a library to compute square roots has a
purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must be
optional: if the application does not supply it, the square root
function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library, and
can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based on
the Library, the distribution of the whole must be on the terms of this
License, whose permissions for other licensees extend to the entire
whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of a
storage or distribution medium does not bring the other work under the
scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so that
they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in these
notices.
Once this change is made in a given copy, it is irreversible for that
copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of the
Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or derivative of
it, under Section 2) in object code or executable form under the terms
of Sections 1 and 2 above provided that you accompany it with the
complete corresponding machine-readable source code, which must be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange.
If distribution of object code is made by offering access to copy from a
designated place, then offering equivalent access to copy the source
code from the same place satisfies the requirement to distribute the
source code, even though third parties are not compelled to copy the
source along with the object code.
5. A program that contains no derivative of any portion of the Library, but
is designed to work with the Library by being compiled or linked with
it, is called a "work that uses the Library". Such a work, in isolation,
is not a derivative work of the Library, and therefore falls outside the
scope of this License.
However, linking a "work that uses the Library" with the Library creates
an executable that is a derivative of the Library (because it contains
portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License. Section 6
states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is
not. Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data structure
layouts and accessors, and small macros and small inline functions (ten
lines or less in length), then the use of the object file is
unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section
6. Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or link a
"work that uses the Library" with the Library to produce a work
containing portions of the Library, and distribute that work under terms
of your choice, provided that the terms permit modification of the work
for the customer's own use and reverse engineering for debugging such
modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work during
execution displays copyright notices, you must include the copyright
notice for the Library among them, as well as a reference directing the
user to the copy of this License. Also, you must do one of these things:
a) Accompany the work with the complete corresponding machine-readable
source code for the Library including whatever changes were used in
the work (which must be distributed under Sections 1 and 2 above);
and, if the work is an executable linked with the Library, with the
complete machine-readable "work that uses the Library", as object
code and/or source code, so that the user can modify the Library and
then relink to produce a modified executable containing the modified
Library. (It is understood that the user who changes the contents of
definitions files in the Library will not necessarily be able to
recompile the application to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a copy
of the library already present on the user's computer system, rather
than copying library functions into the executable, and (2) will
operate properly with a modified version of the library, if the user
installs one, as long as the modified version is interface-compatible
with the version that the work was made with.
c) Accompany the work with a written offer, valid for at least three
years, to give the same user the materials specified in Subsection
6a, above, for a charge no more than the cost of performing this
distribution.
d) If distribution of the work is made by offering access to copy from a
designated place, offer equivalent access to copy the above specified
materials from the same place.
e) Verify that the user has already received a copy of these materials
or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the Library"
must include any data and utility programs needed for reproducing the
executable from it. However, as a special exception, the materials to be
distributed need not include anything that is normally distributed (in
either source or binary form) with the major components (compiler,
kernel, and so on) of the operating system on which the executable runs,
unless that component itself accompanies the executable.
It may happen that this requirement contradicts the license restrictions
of other proprietary libraries that do not normally accompany the
operating system. Such a contradiction means you cannot use both them
and the Library together in an executable that you distribute.
7. You may place library facilities that are a work based on the Library
side-by-side in a single library together with other library facilities
not covered by this License, and distribute such a combined library,
provided that the separate distribution of the work based on the Library
and of the other library facilities is otherwise permitted, and provided
that you do these two things:
a) Accompany the combined library with a copy of the same work based on
the Library, uncombined with any other library facilities. This must
be distributed under the terms of the Sections above.
b) Give prominent notice with the combined library of the fact that part
of it is a work based on the Library, and explaining where to find
the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute the
Library except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense, link with, or distribute the
Library is void, and will automatically terminate your rights under this
License. However, parties who have received copies, or rights, from you
under this License will not have their licenses terminated so long as
such parties remain in full compliance.
9. You are not required to accept this License, since you have not signed
it. However, nothing else grants you permission to modify or distribute
the Library or its derivative works. These actions are prohibited by law
if you do not accept this License. Therefore, by modifying or
distributing the Library (or any work based on the Library), you
indicate your acceptance of this License to do so, and all its terms and
conditions for copying, distributing or modifying the Library or works
based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted
herein. You are not responsible for enforcing compliance by third
parties with this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent license
would not permit royalty-free redistribution of the Library by all
those who receive copies directly or indirectly through you, then the
only way you could satisfy both it and this License would be to refrain
entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply, and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is implemented
by public license practices. Many people have made generous
contributions to the wide range of software distributed through that
system in reliance on consistent application of that system; it is up
to the author/donor to decide if he or she is willing to distribute
software through any other system and a licensee cannot impose that
choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in certain
countries either by patents or by copyrighted interfaces, the original
copyright holder who places the Library under this License may add an
explicit geographical distribution limitation excluding those
countries, so that distribution is permitted only in or among countries
not thus excluded. In such case, this License incorporates the
limitation as if written in the body of this License.
13. The Free Software Foundation may publish revised and/or new versions of
the Lesser General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in
detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a license
version number, you may choose any version ever published by the Free
Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free Software
Foundation; we sometimes make exceptions for this. Our decision will be
guided by the two goals of preserving the free status of all
derivatives of our free software and of promoting the sharing and reuse
of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH
YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR
DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL
DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY
(INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED
INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF
THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR
OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
one line to give the library's name and an idea of what it does.
Copyright (C) year name of author
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or (at
your option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
for more details.
You should have received a copy of the GNU Lesser General Public License
along with this library; if not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in
the library `Frob' (a library for tweaking knobs) written
by James Random Hacker.
signature of Ty Coon, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!

43
Makefile Normal file
View File

@@ -0,0 +1,43 @@
CC ?= gcc
AR ?= ar
MBEDTLS_CFLAGS := $(shell pkg-config --cflags mbedtls 2>/dev/null)
MBEDTLS_LIBS := $(shell pkg-config --libs mbedtls 2>/dev/null) -lmbedx509 -lmbedcrypto
CPPFLAGS += -Iinclude $(MBEDTLS_CFLAGS)
CFLAGS ?= -O2 -Wall -Wextra -Wpedantic -std=c11 -D_POSIX_C_SOURCE=200809L
LIB_OBJECTS = src/device.o src/protocol.o src/request.o src/fdt.o src/sensor.o src/config.o src/tls.o src/session.o
LIBRARY = libgxfp.a
all: $(LIBRARY) fdt-monitor sensor-probe session-regression analyze-capture capture-to-pgm
$(LIBRARY): $(LIB_OBJECTS)
$(AR) rcs $@ $^
fdt-monitor: examples/fdt-monitor.o $(LIBRARY)
$(CC) $(CFLAGS) -o $@ examples/fdt-monitor.o $(LIBRARY) $(MBEDTLS_LIBS)
sensor-probe: examples/sensor-probe.o $(LIBRARY)
$(CC) $(CFLAGS) -o $@ examples/sensor-probe.o $(LIBRARY) $(MBEDTLS_LIBS)
session-regression: examples/session-regression.o $(LIBRARY)
$(CC) $(CFLAGS) -o $@ examples/session-regression.o $(LIBRARY) $(MBEDTLS_LIBS) -pthread
analyze-capture: tools/analyze_capture.o
$(CC) $(CFLAGS) -o $@ $^
capture-to-pgm: tools/capture_to_pgm.o
$(CC) $(CFLAGS) -o $@ $^
src/%.o: src/%.c
$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
examples/%.o: examples/%.c
$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
tools/%.o: tools/%.c
$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
clean:
rm -f $(LIB_OBJECTS) examples/*.o tools/*.o $(LIBRARY) fdt-monitor sensor-probe session-regression analyze-capture capture-to-pgm

99
README.md Normal file
View File

@@ -0,0 +1,99 @@
# Goodix GXFP5130 userspace prototype
Experimental Linux userspace prototype for the Goodix GXFP5130 fingerprint
sensor transported through the `/dev/gxfp` EC mailbox ABI.
The low-level initialization, TLS session, finger detection and image capture
paths work on the tested hardware. The project is **not yet fully integrated
into libfprint**, and enrollment/verification is not production-ready.
The implementation has been tested on one GXFP5130 device with chip ID
`0x2504` and the ChicagoHU profile.
## Hardware-validated functionality
- mailbox record I/O through `/dev/gxfp`
- Goodix request and response framing
- chip-ID and 64-byte OTP reads
- OTP CRC validation and DAC calibration extraction
- OTP-derived 224-byte ChicagoHU configuration generation
- TLS 1.2 PSK session establishment with the sensor MCU
- finger-down and finger-up detection
- repeated image capture in one TLS session
- 7680-byte packed 12-bit image decoding into 5120 samples
- CRC-32/MPEG-2 image validation
- 64x80 12-bit image output and deterministic 8-bit conversion
- timeout, cancellation and session recovery regression paths
An experimental libfprint image-device skeleton is included under
`libfprint-driver/`. It can capture images through libfprint, but the final
enroll/verify pipeline and matching policy remain work in progress.
## Requirements
- Linux with the Goodix EC mailbox transport and `/dev/gxfp`
- a C11 compiler and GNU Make
- pkg-config
- mbedTLS development libraries
The corresponding kernel transport patch is archived at:
<https://lore.kernel.org/platform-driver-x86/20260803074454.49474-1-ertugtopcu0@gmail.com/>
## Build and test
```sh
make
sudo ./sensor-probe /dev/gxfp 10
```
Enable protocol/TLS debug output only when needed:
```sh
sudo env GXFP_DEBUG=1 ./sensor-probe /dev/gxfp 1
```
Run cancellation, timeout-recovery and normal-cycle regression tests:
```sh
sudo ./session-regression /dev/gxfp
```
## Layout
```text
include/gxfp/ protocol and session headers
src/ transport, configuration, TLS, capture and session code
examples/ hardware probes and session regression program
tools/ offline capture-analysis utilities
libfprint-driver/ experimental, incomplete libfprint integration
docs/ verified protocol documentation
```
See [docs/protocol.md](docs/protocol.md) for the protocol and capture format.
## Security and compatibility
The TLS material in `src/tls.c` was recovered for interoperability with the
tested hardware and forms part of this experimental protocol implementation.
It is not a user password or repository credential. Do not assume that the
protocol, configuration, TLS material or image geometry applies to another
Goodix model.
The prototype exposes raw biometric images. Captures, PGM files and enrollment
templates are ignored by Git and should not be shared without the subject's
consent.
## Reverse-engineering notes
Protocol and configuration behavior was reconstructed primarily from the
Windows implementation and verified against live hardware traces. Existing
community GXFP5130 experiments were used for comparison. The
`capture-to-pgm` utility is an offline diagnostic tool and is not used by the
capture/session implementation.
## License
The userspace library, examples and tools are GPL-2.0-only. The experimental
libfprint integration file carries an LGPL-2.1-or-later SPDX identifier. Full
license texts are available under `LICENSES/`.

267
docs/protocol.md Normal file
View File

@@ -0,0 +1,267 @@
# GXFP5130 protocol notes
This document describes the protocol implemented by this repository for the
tested Goodix GXFP5130, chip ID `0x2504`, using the ChicagoHU configuration
profile. It replaces the earlier point-in-time PDF report with documentation
that can evolve together with the source.
The labels below have precise meanings:
- **Hardware verified:** repeatedly observed on the physical sensor.
- **Windows-derived:** recovered from the Windows implementation and then used
to guide the implementation.
- **Unknown:** retained as opaque data without assigning semantics.
## 1. Layering
```text
application / libfprint prototype
|
v
GXFP session, configuration, TLS and image parser
|
v
/dev/gxfp record-oriented userspace ABI
|
v
kernel EC mailbox transport
|
v
Goodix GXFP5130
```
The kernel driver transports opaque mailbox records, manages MMIO/GPIO/IRQ and
owns the userspace ABI. Sensor commands, configuration policy, TLS and image
processing remain in userspace.
## 2. `/dev/gxfp` records
The UAPI structures are mirrored in `include/gxfp/goodix_ec_uapi.h`.
Userspace writes a `goodix_ec_tx_header` followed by exactly `payload_len`
bytes. A read returns one complete `goodix_ec_record_header` followed by its
payload; records are never split across reads.
Observed normalized MP types:
| MP type | Purpose | Status |
| --- | --- | --- |
| `0x0a` | Normal Goodix commands, replies, ACKs and FDT | Hardware verified |
| `0x0b` | TLS records exchanged with the sensor MCU | Hardware verified |
| `0xb0` | Host-to-sensor TLS transmit flag | Hardware verified |
The kernel may receive a raw normal MP value represented as `0xa0` at the
mailbox level. The userspace ABI exposes its normalized type as `0x0a`.
## 3. Normal Goodix frame
Normal command traffic uses this byte layout:
```text
offset size field
0 1 command
1 2 declared length, little-endian
3 n payload
3+n 1 Goodix checksum
```
The declared length covers the payload and checksum. The checksum is selected
so the eight-bit sum of the command, both length bytes, payload and checksum is
zero.
Responses may use the direction bit in the command byte. The request layer
matches the normalized command and skips unrelated asynchronous records while
continuing to wait for the expected response.
## 4. Identification and OTP
Register `0x0000` returns the chip identification. The tested response is:
```text
command: 0x82
payload: a2 04 25 00
chip ID: 0x2504
```
The sensor exposes a 64-byte OTP block. The implementation validates the CP,
FT and MT sections independently using the recovered eight-bit CRC algorithm.
The tested sensor produces valid values for all three sections.
The OTP is also used to select and patch the DAC calibration values rather
than applying a single machine-wide constant. A captured example yielded:
```text
raw DAC values: 0x00ba 0x00bc 0x00ba 0x00ba
0x0220: 0x0ba8
0x0236: 0x00bc
0x0238: 0x00ba
0x023a: 0x00ba
```
These exact values are device calibration data, not universal constants.
## 5. ChicagoHU configuration
For chip ID `0x2504`, userspace constructs a 224-byte ChicagoHU configuration,
patches its OTP-derived fields and recomputes the 16-bit configuration
checksum. The tested device used:
```text
t-code: 0x0100
FDT delta: 0x1d
FDT offset: 0x00
size: 224 bytes
```
The exact checksum depends on the patched device calibration values. The
configuration is downloaded only after reset and is acknowledged by the
sensor.
## 6. TLS transport
The host userspace implementation acts as a TLS server and the sensor MCU acts
as a TLS client.
| Parameter | Value |
| --- | --- |
| Protocol | TLS 1.2 |
| Verified cipher suite | `TLS-PSK-WITH-AES-256-GCM-SHA384` (`0x00a9`) |
| PSK identity | `Client_identity` |
| MCU-to-host MP type | `0x0b` |
| Host-to-MCU MP flag | `0xb0` |
A hardware-verified handshake contains ClientHello, ClientKeyExchange,
ChangeCipherSpec and Finished. Capture data subsequently arrives as TLS
application data and is decrypted through `mbedtls_ssl_read()`.
Normal `0x0a` ACKs can be interleaved with TLS traffic. The TLS receive path
must ignore non-TLS MP records, while the normal request path must not consume
TLS records intended for mbedTLS.
## 7. Finger detection
The verified FDT commands are implemented in `src/fdt.c`:
| Command | Function |
| --- | --- |
| `0x36` | Set/arm FDT mode |
| `0x32` | Finger-down path |
| `0x34` | Finger-up path |
| `0xda` | FDT status/event report |
A typical finger-down sequence reports:
```text
cmd=0x36 status=0x0100 -> ready
cmd=0x32 status=0x0002 -> finger down
```
Capture must begin while the finger remains present. After a successful image,
userspace arms finger-up, waits for removal and returns the session to ACTIVE.
## 8. Image capture
The hardware-verified capture frame is sent through the normal MP path:
```text
20 03 00 01 00 86
```
Decoded:
| Bytes | Meaning |
| --- | --- |
| `20` | Image-capture command |
| `03 00` | Payload plus checksum length |
| `01 00` | Capture payload |
| `86` | Goodix checksum |
The command ACK arrives as a normal `0x0a` record. The image does not arrive
there; it arrives encrypted as TLS application data over MP `0x0b`.
After TLS stream reassembly, one capture has this verified layout:
```text
offset size field
0 1 command (0x20)
1 2 declared length (7690, little-endian)
3 5 opaque image header
8 7680 packed 12-bit pixels
7688 4 CRC-32/MPEG-2, Goodix byte order
7692 1 opaque trailer/status
total 7693 bytes
```
The Windows-derived parser relationship is consistent with this layout:
```c
image_data = frame_payload + 5;
image_data_length = frame_payload_length - 6;
```
The CRC covers the 7680 packed-pixel bytes. It does not cover the five-byte
opaque header or the final trailer byte. Stored and calculated CRC values have
matched across repeated live captures.
## 9. Packed 12-bit pixels
Six packed bytes decode to four 12-bit samples. A complete image contains:
```text
7680 packed bytes
5120 samples
64 x 80 pixels
```
The session API exposes the decoded samples as `uint16_t`. Conversion to an
8-bit `FpImage` is intentionally separate from protocol parsing so that image
orientation, normalization and matching policy can evolve without changing
the validated capture layer.
## 10. Session state sequence
```text
OPEN
-> activate (identification, OTP, config, TLS)
ACTIVE
-> arm finger down
WAITING_FINGER_DOWN
-> finger detected
FINGER_PRESENT
-> capture
CAPTURING
-> CRC-valid image
FINGER_PRESENT
-> arm/wait finger up
WAITING_FINGER_UP
-> finger removed
ACTIVE
```
Timeout and cancellation paths deactivate and reinitialize the hardware rather
than leaving FDT armed across the next process invocation. The regression tool
tests cancellation during finger-down wait, finger-up timeout recovery and a
normal capture after both recoveries.
## 11. Known unknowns
The following values are deliberately kept opaque:
- semantics of the five-byte image header; it is consistently all zero on the
tested profile
- meaning of trailer value `0x88`
- whether command `0x21` selects a separate image or calibration mode
- orientation/inversion policy appropriate for final libfprint matching
- fixed-pattern-noise and dark-frame correction policy
- applicability of this profile and TLS material to another chip ID
No resynchronization heuristic, guessed checksum or alternate capture command
is used in the validated session path.
## 12. Current project boundary
The transport, sensor setup, TLS, FDT, capture, CRC and image decoding layers
are hardware-validated prototypes. The libfprint driver remains experimental:
final enroll/verify behavior, multi-capture policy, image enhancement and
matcher quality still require validation before production use.

99
examples/fdt-monitor.c Normal file
View File

@@ -0,0 +1,99 @@
// SPDX-License-Identifier: GPL-2.0-only
#include "gxfp/device.h"
#include "gxfp/fdt.h"
#include "gxfp/protocol.h"
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static const char *event_name(enum gxfp_fdt_event event)
{
switch (event) {
case GXFP_FDT_EVENT_READY:
return "ready";
case GXFP_FDT_EVENT_FINGER_DOWN:
return "finger-down";
case GXFP_FDT_EVENT_FINGER_UP:
return "finger-up";
case GXFP_FDT_EVENT_REVERSE:
return "reverse";
default:
return "none";
}
}
int main(int argc, char **argv)
{
const char *path = argc > 1 ? argv[1] : "/dev/gxfp";
struct gxfp_device device = { .fd = -1 };
struct gxfp_fdt fdt;
int ret;
ret = gxfp_device_open(&device, path);
if (ret) {
fprintf(stderr, "open failed: %s\n", strerror(-ret));
return EXIT_FAILURE;
}
ret = gxfp_device_flush_rx(&device);
if (ret)
fprintf(stderr, "warning: flush failed: %s\n", strerror(-ret));
gxfp_fdt_init(&fdt);
ret = gxfp_fdt_arm_down(&device);
if (ret) {
fprintf(stderr, "FDT arm failed: %s\n", strerror(-ret));
gxfp_device_close(&device);
return EXIT_FAILURE;
}
printf("FDT monitor active. Touch and release the sensor.\n");
for (;;) {
struct gxfp_rx_record record;
struct gxfp_frame_view frame;
enum gxfp_fdt_event event;
uint16_t status = 0;
ret = gxfp_device_receive(&device, &record, -1);
if (ret == -EINTR)
break;
if (ret) {
fprintf(stderr, "receive failed: %s\n", strerror(-ret));
break;
}
ret = gxfp_frame_parse(record.payload, record.payload_len, &frame);
if (ret) {
fprintf(stderr, "bad frame: %s\n", strerror(-ret));
gxfp_rx_record_release(&record);
continue;
}
if (frame.command == GXFP_GOODIX_ACK) {
gxfp_rx_record_release(&record);
continue;
}
event = gxfp_fdt_decode(&frame, &status);
printf("cmd=0x%02x status=0x%04x event=%s\n",
frame.command, status, event_name(event));
ret = gxfp_fdt_handle_event(&fdt, &device, event);
gxfp_rx_record_release(&record);
if (ret) {
fprintf(stderr, "FDT transition failed: %s\n",
strerror(-ret));
break;
}
}
gxfp_device_close(&device);
return EXIT_SUCCESS;
}

161
examples/sensor-probe.c Normal file
View File

@@ -0,0 +1,161 @@
// SPDX-License-Identifier: GPL-2.0-only
#include "gxfp/session.h"
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static const char *error_string(int error)
{
if (error >= 0)
return "non-negative API error";
return strerror(-error);
}
static int print_image_stats(unsigned int index,
const struct gxfp_image12 *image)
{
size_t count;
uint16_t minimum = UINT16_MAX;
uint16_t maximum = 0;
uint64_t sum = 0;
size_t i;
if (!image || !image->pixels || !image->width || !image->height) {
fprintf(stderr, "invalid image returned by capture\n");
return -EINVAL;
}
if (image->width > SIZE_MAX / image->height) {
fprintf(stderr, "image dimensions overflow\n");
return -EOVERFLOW;
}
count = image->width * image->height;
for (i = 0; i < count; i++) {
uint16_t value = image->pixels[i];
if (value < minimum)
minimum = value;
if (value > maximum)
maximum = value;
sum += value;
}
printf("capture_index: %u\n", index);
printf("image: %zux%zu, samples=%zu\n",
image->width, image->height, count);
printf("CRC: stored=0x%08x calculated=0x%08x valid=yes\n",
image->crc_stored, image->crc_calculated);
printf("header: %02x %02x %02x %02x %02x\n",
image->header[0], image->header[1], image->header[2],
image->header[3], image->header[4]);
printf("frame_trailer: 0x%02x\n", image->trailer);
printf("pixel_min=%u pixel_max=%u pixel_mean=%.2f\n",
minimum, maximum, (double)sum / count);
return 0;
}
int main(int argc, char **argv)
{
const char *path = argc > 1 ? argv[1] : "/dev/gxfp";
unsigned int capture_count = 1u;
struct gxfp_session *session = NULL;
const char *stage = "open";
unsigned int completed = 0;
unsigned int index;
int active = 0;
int ret;
if (argc > 3) {
fprintf(stderr, "usage: %s [device] [capture-count]\n", argv[0]);
return EXIT_FAILURE;
}
if (argc > 2) {
char *end = NULL;
unsigned long value;
errno = 0;
value = strtoul(argv[2], &end, 10);
if (errno == ERANGE || end == argv[2] || *end != '\0' ||
value == 0 || value > 100) {
fprintf(stderr, "capture count must be in range 1..100\n");
return EXIT_FAILURE;
}
capture_count = (unsigned int)value;
}
ret = gxfp_session_open(&session, path);
if (ret) {
fprintf(stderr, "session open failed: %s (%d)\n",
error_string(ret), ret);
return EXIT_FAILURE;
}
stage = "activate";
ret = gxfp_session_activate(session, 10000);
if (ret) {
fprintf(stderr, "session activate failed: %s (%d)\n",
error_string(ret), ret);
goto out;
}
active = 1;
printf("Session active; TLS established.\n");
for (index = 1; index <= capture_count; index++) {
struct gxfp_image12 image = { 0 };
printf("\n=== Capture %u/%u ===\n", index, capture_count);
stage = "arm-finger-down";
ret = gxfp_session_arm_finger_down(session);
if (ret)
goto capture_error;
printf("FDT down armed. Put finger on sensor...\n");
stage = "wait-finger-down";
ret = gxfp_session_wait_finger_down(session, 10000);
if (ret)
goto capture_error;
stage = "capture";
ret = gxfp_session_capture(session, &image, 20000);
if (ret)
goto capture_error;
stage = "print-image-stats";
ret = print_image_stats(index, &image);
if (ret)
goto capture_error;
gxfp_image12_clear(&image);
stage = "arm-finger-up";
ret = gxfp_session_arm_finger_up(session);
if (ret)
goto capture_error;
printf("Remove your finger...\n");
stage = "wait-finger-up";
ret = gxfp_session_wait_finger_up(session, 10000);
if (ret)
goto capture_error;
completed++;
continue;
capture_error:
gxfp_image12_clear(&image);
fprintf(stderr,
"capture %u failed during %s, state=%d: %s (%d)\n",
index, stage,
(int)gxfp_session_get_state(session),
error_string(ret), ret);
break;
}
out:
printf("\ncompleted captures: %u/%u\n", completed, capture_count);
if (session) {
if (active) {
int deactivate_ret = gxfp_session_deactivate(session);
if (!ret && deactivate_ret)
ret = deactivate_ret;
}
gxfp_session_close(session);
}
return ret ? EXIT_FAILURE : EXIT_SUCCESS;
}

View File

@@ -0,0 +1,212 @@
// SPDX-License-Identifier: GPL-2.0-only
#include "gxfp/session.h"
#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
struct wait_context {
struct gxfp_session *session;
int result;
};
static const char *error_string(int error)
{
return error < 0 ? strerror(-error) : "non-negative API error";
}
static void sleep_ms(long milliseconds)
{
struct timespec delay = {
.tv_sec = milliseconds / 1000,
.tv_nsec = (milliseconds % 1000) * 1000000L,
};
while (nanosleep(&delay, &delay) < 0 && errno == EINTR)
;
}
static void *wait_down_thread(void *opaque)
{
struct wait_context *context = opaque;
context->result = gxfp_session_wait_finger_down(context->session,
30000);
return NULL;
}
static int activate(struct gxfp_session *session)
{
int ret = gxfp_session_activate(session, 10000);
if (ret)
fprintf(stderr, "activate failed: %s (%d), state=%d\n",
error_string(ret), ret,
(int)gxfp_session_get_state(session));
return ret;
}
static int recover(struct gxfp_session *session)
{
int ret;
ret = gxfp_session_deactivate(session);
if (ret) {
fprintf(stderr, "deactivate/reset failed: %s (%d)\n",
error_string(ret), ret);
return ret;
}
if (gxfp_session_get_state(session) != GXFP_SESSION_OPEN) {
fprintf(stderr, "deactivate did not reach OPEN\n");
return -EPROTO;
}
return activate(session);
}
static int test_cancel_wait_down(struct gxfp_session *session)
{
struct wait_context context = { .session = session, .result = 0 };
pthread_t thread;
int pthread_ret;
int ret;
puts("\n[1/3] cross-thread cancel during wait-finger-down");
ret = gxfp_session_arm_finger_down(session);
if (ret)
return ret;
pthread_ret = pthread_create(&thread, NULL, wait_down_thread, &context);
if (pthread_ret)
return -pthread_ret;
sleep_ms(500);
gxfp_session_cancel(session);
pthread_ret = pthread_join(thread, NULL);
if (pthread_ret)
return -pthread_ret;
if (context.result != -ECANCELED) {
fprintf(stderr, "expected -ECANCELED, got %d\n",
context.result);
return -EPROTO;
}
if (gxfp_session_get_state(session) != GXFP_SESSION_ERROR) {
fprintf(stderr, "cancel did not leave session in ERROR\n");
return -EPROTO;
}
puts("PASS: waiter returned -ECANCELED; recovering hardware");
return recover(session);
}
static int capture_while_present(struct gxfp_session *session,
struct gxfp_image12 *image)
{
int ret;
ret = gxfp_session_arm_finger_down(session);
if (ret)
return ret;
puts("Put finger on sensor and keep it there...");
ret = gxfp_session_wait_finger_down(session, 10000);
if (ret)
return ret;
ret = gxfp_session_capture(session, image, 20000);
if (!ret)
printf("capture CRC: stored=0x%08x calculated=0x%08x\n",
image->crc_stored, image->crc_calculated);
return ret;
}
static int test_finger_up_timeout(struct gxfp_session *session)
{
struct gxfp_image12 image = { 0 };
int ret;
puts("\n[2/3] wait-finger-up timeout and hardware recovery");
ret = capture_while_present(session, &image);
if (ret)
goto out;
ret = gxfp_session_arm_finger_up(session);
if (ret)
goto out;
puts("KEEP finger pressed for 3 seconds (timeout is intentional)...");
ret = gxfp_session_wait_finger_up(session, 3000);
if (ret != -ETIMEDOUT) {
fprintf(stderr, "expected -ETIMEDOUT, got %d\n", ret);
ret = -EPROTO;
goto out;
}
if (gxfp_session_get_state(session) != GXFP_SESSION_ERROR) {
fprintf(stderr, "timeout did not leave session in ERROR\n");
ret = -EPROTO;
goto out;
}
puts("PASS: finger-up timed out; remove finger now");
sleep_ms(1000);
ret = recover(session);
out:
gxfp_image12_clear(&image);
return ret;
}
static int test_normal_cycle(struct gxfp_session *session)
{
struct gxfp_image12 image = { 0 };
int ret;
puts("\n[3/3] normal capture after both recoveries");
ret = capture_while_present(session, &image);
if (ret)
goto out;
ret = gxfp_session_arm_finger_up(session);
if (ret)
goto out;
puts("Remove finger...");
ret = gxfp_session_wait_finger_up(session, 10000);
if (!ret && gxfp_session_get_state(session) != GXFP_SESSION_ACTIVE)
ret = -EPROTO;
if (!ret)
puts("PASS: normal cycle completed in ACTIVE state");
out:
gxfp_image12_clear(&image);
return ret;
}
int main(int argc, char **argv)
{
const char *path = argc == 2 ? argv[1] : "/dev/gxfp";
struct gxfp_session *session = NULL;
int ret;
if (argc > 2) {
fprintf(stderr, "usage: %s [device]\n", argv[0]);
return EXIT_FAILURE;
}
ret = gxfp_session_open(&session, path);
if (ret)
goto out;
ret = activate(session);
if (ret)
goto out;
ret = test_cancel_wait_down(session);
if (ret)
goto out;
ret = test_finger_up_timeout(session);
if (ret)
goto out;
ret = test_normal_cycle(session);
out:
if (ret)
fprintf(stderr, "regression failed: %s (%d), state=%d\n",
error_string(ret), ret,
session ? (int)gxfp_session_get_state(session) : -1);
if (session) {
int cleanup_ret = gxfp_session_deactivate(session);
if (!ret && cleanup_ret)
ret = cleanup_ret;
gxfp_session_close(session);
}
if (!ret)
puts("\nALL SESSION REGRESSIONS PASSED");
return ret ? EXIT_FAILURE : EXIT_SUCCESS;
}

48
include/gxfp/config.h Normal file
View File

@@ -0,0 +1,48 @@
// SPDX-License-Identifier: GPL-2.0-only
#ifndef GXFP_CONFIG_H
#define GXFP_CONFIG_H
#include <stddef.h>
#include <stdint.h>
#define GXFP_CONFIG_SIZE 0xE0u
#define GXFP_CONFIG_SECTION_COUNT 8u
enum gxfp_config_write_mode {
GXFP_CONFIG_WRITE_WORD = 0,
GXFP_CONFIG_WRITE_LOW_BYTE = 1,
GXFP_CONFIG_WRITE_HIGH_BYTE = 2,
};
struct gxfp_dac_info;
struct gxfp_chicagohu_config_info {
uint8_t tcode_diff;
uint16_t tcode;
uint8_t fdt_delta;
uint8_t fdt_offset;
uint16_t checksum;
};
uint16_t gxfp_config_checksum(const uint8_t config[GXFP_CONFIG_SIZE]);
int gxfp_config_update_checksum(uint8_t config[GXFP_CONFIG_SIZE]);
int gxfp_config_modify_register(uint8_t config[GXFP_CONFIG_SIZE],
uint16_t reg,
uint16_t value,
unsigned int section,
enum gxfp_config_write_mode mode,
uint16_t *old_value);
int gxfp_config_patch_dac(uint8_t config[GXFP_CONFIG_SIZE],
const uint16_t register_values[4]);
int gxfp_config_build_chicagohu_2504(
uint8_t config[GXFP_CONFIG_SIZE],
const void *otp,
size_t otp_len,
const struct gxfp_dac_info *dac,
struct gxfp_chicagohu_config_info *info);
#endif

32
include/gxfp/device.h Normal file
View File

@@ -0,0 +1,32 @@
// SPDX-License-Identifier: GPL-2.0-only
#ifndef GXFP_DEVICE_H
#define GXFP_DEVICE_H
#include <stddef.h>
#include <stdint.h>
struct gxfp_device {
int fd;
};
struct gxfp_rx_record {
uint32_t mp_type;
uint64_t timestamp_ns;
uint8_t *payload;
size_t payload_len;
};
int gxfp_device_open(struct gxfp_device *device, const char *path);
void gxfp_device_close(struct gxfp_device *device);
int gxfp_device_flush_rx(struct gxfp_device *device);
int gxfp_device_send_mp(struct gxfp_device *device, uint8_t mp_flags,
const void *payload, size_t payload_len);
int gxfp_device_receive(struct gxfp_device *device,
struct gxfp_rx_record *record,
int timeout_ms);
void gxfp_rx_record_release(struct gxfp_rx_record *record);
#endif

34
include/gxfp/fdt.h Normal file
View File

@@ -0,0 +1,34 @@
// SPDX-License-Identifier: GPL-2.0-only
#ifndef GXFP_FDT_H
#define GXFP_FDT_H
#include <stdbool.h>
#include <stdint.h>
struct gxfp_device;
struct gxfp_frame_view;
enum gxfp_fdt_event {
GXFP_FDT_EVENT_NONE = 0,
GXFP_FDT_EVENT_READY,
GXFP_FDT_EVENT_FINGER_DOWN,
GXFP_FDT_EVENT_FINGER_UP,
GXFP_FDT_EVENT_REVERSE,
};
struct gxfp_fdt {
bool waiting_for_up;
};
void gxfp_fdt_init(struct gxfp_fdt *fdt);
int gxfp_fdt_arm_down(struct gxfp_device *device);
int gxfp_fdt_arm_up(struct gxfp_device *device);
enum gxfp_fdt_event
gxfp_fdt_decode(const struct gxfp_frame_view *frame, uint16_t *status);
int gxfp_fdt_handle_event(struct gxfp_fdt *fdt,
struct gxfp_device *device,
enum gxfp_fdt_event event);
#endif

View File

@@ -0,0 +1,37 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
#ifndef _UAPI_GOODIX_EC_H_
#define _UAPI_GOODIX_EC_H_
#include <linux/ioctl.h>
#include <linux/types.h>
#define GOODIX_EC_UAPI_MAGIC 'G'
#define GOODIX_EC_UAPI_TX_MAX 500u
#define GOODIX_EC_UAPI_RX_MAX (128u * 1024u)
/*
* read(2) returns one record:
* struct goodix_ec_record_header
* followed by len bytes of MP payload (normally one Goodix frame).
*/
struct goodix_ec_record_header {
__u32 len;
__u32 mp_type;
__u64 timestamp_ns;
};
/*
* write(2) accepts:
* struct goodix_ec_tx_header
* followed by payload_len bytes used as the MP payload.
*/
struct goodix_ec_tx_header {
__u8 mp_flags;
__u8 reserved;
__u16 payload_len;
__u32 flags;
};
#define GOODIX_EC_IOCTL_FLUSH_RX _IO(GOODIX_EC_UAPI_MAGIC, 0x11)
#endif /* _UAPI_GOODIX_EC_H_ */

29
include/gxfp/protocol.h Normal file
View File

@@ -0,0 +1,29 @@
// SPDX-License-Identifier: GPL-2.0-only
#ifndef GXFP_PROTOCOL_H
#define GXFP_PROTOCOL_H
#include <stddef.h>
#include <stdint.h>
#define GXFP_MP_COMMAND 0xA0
#define GXFP_MP_RX_COMMAND 0x0A
#define GXFP_GOODIX_CHECKSUM_TARGET 0xAA
#define GXFP_GOODIX_ACK 0xB0
#define GXFP_CMD_IMAGE_CAPTURE 0x20
struct gxfp_frame_view {
uint8_t command;
const uint8_t *payload;
size_t payload_len;
uint8_t checksum;
};
int gxfp_frame_build(uint8_t command,
const void *payload, size_t payload_len,
uint8_t *output, size_t output_capacity,
size_t *output_len);
int gxfp_frame_parse(const void *frame, size_t frame_len,
struct gxfp_frame_view *view);
#endif

28
include/gxfp/request.h Normal file
View File

@@ -0,0 +1,28 @@
// SPDX-License-Identifier: GPL-2.0-only
#ifndef GXFP_REQUEST_H
#define GXFP_REQUEST_H
#include <stddef.h>
#include <stdint.h>
struct gxfp_device;
struct gxfp_response {
uint8_t command;
uint8_t *payload;
size_t payload_len;
uint64_t timestamp_ns;
};
int gxfp_request(struct gxfp_device *device,
uint8_t command,
const void *payload,
size_t payload_len,
uint8_t expected_command,
unsigned int max_frames,
int timeout_ms,
struct gxfp_response *response);
void gxfp_response_release(struct gxfp_response *response);
#endif

92
include/gxfp/sensor.h Normal file
View File

@@ -0,0 +1,92 @@
// SPDX-License-Identifier: GPL-2.0-only
#ifndef GXFP_SENSOR_H
#define GXFP_SENSOR_H
#include <stddef.h>
#include <stdint.h>
struct gxfp_device;
struct gxfp_otp_crc_info {
int cp_valid;
int ft_valid;
int mt_valid;
uint8_t cp_calculated;
uint8_t ft_calculated;
uint8_t mt_calculated;
uint8_t cp_stored;
uint8_t ft_stored;
uint8_t mt_stored;
};
struct gxfp_dac_info {
uint16_t raw[4];
uint16_t register_value[4];
unsigned int match_count;
int used_ft;
int used_mt;
int used_fallback;
struct gxfp_otp_crc_info crc;
};
struct gxfp_mcu_state {
uint8_t version;
uint8_t flags;
int pov_image_valid;
int tls_connected;
int tls_used;
int locked;
uint8_t reserved[18];
};
int gxfp_sensor_read_version(struct gxfp_device *device,
char *version, size_t version_capacity);
int gxfp_sensor_query_mcu_state(struct gxfp_device *device,
struct gxfp_mcu_state *state);
int gxfp_sensor_recover(struct gxfp_device *device, int unstick_tls);
int gxfp_sensor_reset(struct gxfp_device *device);
int gxfp_sensor_start_tls(struct gxfp_device *device);
int gxfp_sensor_download_config(struct gxfp_device *device,
const void *config,
size_t config_len);
int gxfp_sensor_reset_and_download_config(
struct gxfp_device *device,
const void *config,
size_t config_len);
int gxfp_sensor_capture_oneframe(struct gxfp_device *device,
void *output,
size_t output_capacity,
size_t *output_len);
int gxfp_sensor_read_register(struct gxfp_device *device,
uint16_t address,
void *output,
uint16_t output_len);
int gxfp_sensor_read_chip_id(struct gxfp_device *device,
uint16_t *chip_id);
int gxfp_sensor_read_otp(struct gxfp_device *device,
void *output,
size_t output_capacity,
size_t *output_len);
int gxfp_sensor_check_chicagohu_otp_crc(
const void *otp,
size_t otp_len,
struct gxfp_otp_crc_info *info);
int gxfp_sensor_parse_dac(const void *otp,
size_t otp_len,
int force_ft,
int force_mt,
struct gxfp_dac_info *info);
#endif

58
include/gxfp/session.h Normal file
View File

@@ -0,0 +1,58 @@
// SPDX-License-Identifier: GPL-2.0-only
#ifndef GXFP_SESSION_H
#define GXFP_SESSION_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
struct gxfp_session;
enum gxfp_session_state {
GXFP_SESSION_OPEN = 0,
GXFP_SESSION_ACTIVE,
GXFP_SESSION_WAITING_FINGER_DOWN,
GXFP_SESSION_FINGER_PRESENT,
GXFP_SESSION_CAPTURING,
GXFP_SESSION_WAITING_FINGER_UP,
GXFP_SESSION_ERROR,
};
struct gxfp_image12 {
uint16_t *pixels;
size_t width;
size_t height;
uint8_t header[5];
uint8_t trailer;
uint32_t crc_stored;
uint32_t crc_calculated;
};
int gxfp_session_open(struct gxfp_session **out, const char *device_path);
int gxfp_session_activate(struct gxfp_session *session, int timeout_ms);
int gxfp_session_deactivate(struct gxfp_session *session);
int gxfp_session_arm_finger_down(struct gxfp_session *session);
int gxfp_session_wait_finger_down(struct gxfp_session *session, int timeout_ms);
int gxfp_session_capture(struct gxfp_session *session,
struct gxfp_image12 *image, int timeout_ms);
int gxfp_session_arm_finger_up(struct gxfp_session *session);
int gxfp_session_wait_finger_up(struct gxfp_session *session, int timeout_ms);
int gxfp_image12_to_u8(const struct gxfp_image12 *source,
uint8_t *destination, size_t destination_size);
void gxfp_image12_clear(struct gxfp_image12 *image);
void gxfp_session_cancel(struct gxfp_session *session);
enum gxfp_session_state
gxfp_session_get_state(const struct gxfp_session *session);
void gxfp_session_close(struct gxfp_session *session);
#ifdef __cplusplus
}
#endif
#endif

36
include/gxfp/tls.h Normal file
View File

@@ -0,0 +1,36 @@
// SPDX-License-Identifier: GPL-2.0-only
#ifndef GXFP_TLS_H
#define GXFP_TLS_H
#include <stddef.h>
#include <stdint.h>
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/entropy.h>
#include <mbedtls/ssl.h>
#include "gxfp/device.h"
struct gxfp_tls {
struct gxfp_device *device;
mbedtls_ssl_context ssl;
mbedtls_ssl_config config;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_entropy_context entropy;
unsigned char *rx_buffer;
size_t rx_length;
size_t rx_offset;
int initialized;
};
int gxfp_tls_init(struct gxfp_tls *tls, struct gxfp_device *device);
int gxfp_tls_handshake(struct gxfp_tls *tls);
int gxfp_tls_handshake_timeout(struct gxfp_tls *tls, int timeout_ms);
int gxfp_tls_write_all(struct gxfp_tls *tls, const void *data, size_t len);
int gxfp_tls_read(struct gxfp_tls *tls, void *data, size_t capacity,
size_t *data_len);
int gxfp_tls_read_timeout(struct gxfp_tls *tls, void *data, size_t capacity,
size_t *data_len, int timeout_ms);
void gxfp_tls_close(struct gxfp_tls *tls);
#endif

View File

@@ -0,0 +1,26 @@
# libfprint GXFP5130 integration skeleton
This driver connects libfprint's `FpImageDevice` state machine to the
hardware-tested `gxfp_session` API. Blocking sensor operations run in a GLib
worker thread; all libfprint callbacks run in the main context.
Current discovery is intentionally explicit because the sensor is an ACPI
platform/character device, while upstream libfprint has no generic ACPI char
device discovery type. Build the driver as an optional virtual-type driver and
start libfprint/fprintd with:
```sh
FP_GXFP5130=/dev/gxfp
```
The integration must add `gxfp5130` to `drivers_info` and
`libfprint_drivers_sources` in upstream Meson, include this file, add the GXFP
headers, and link the PIC-built `libgxfp.a` plus mbedTLS libraries.
The first image path uses the verified 64x80 sample order and the deterministic
`gxfp_image12_to_u8()` conversion. No transpose, flip, inversion, percentile
clipping, or unverified `0x21` command is applied.
Before upstreaming, replace the environment-backed discovery with a dedicated
udev/ACPI character-device discovery mechanism and install permissions for
`/dev/gxfp`.

404
libfprint-driver/gxfp5130.c Normal file
View File

@@ -0,0 +1,404 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#define FP_COMPONENT "gxfp5130"
#include "drivers_api.h"
#include "gxfp/session.h"
#include <errno.h>
#include <limits.h>
#include <string.h>
#include <glib/gstdio.h>
#include <unistd.h>
#define GXFP_DEVICE_PATH_DEFAULT "/dev/gxfp"
#define GXFP_ACTIVATE_TIMEOUT_MS 10000
#define GXFP_FINGER_TIMEOUT_MS INT_MAX
#define GXFP_CAPTURE_TIMEOUT_MS 20000
#define GXFP_IMAGE_WIDTH 64
#define GXFP_IMAGE_HEIGHT 80
#define GXFP_IMAGE_PPMM (500.0 / 25.4)
typedef enum {
GXFP_JOB_OPEN,
GXFP_JOB_ACTIVATE,
GXFP_JOB_WAIT_FINGER_DOWN,
GXFP_JOB_CAPTURE,
GXFP_JOB_WAIT_FINGER_UP,
GXFP_JOB_DEACTIVATE,
GXFP_JOB_CLOSE,
} GxfpJob;
typedef struct {
GxfpJob job;
gint result;
guint8 *image_data;
gsize image_size;
} GxfpJobResult;
struct _FpiDeviceGxfp5130
{
FpImageDevice parent;
struct gxfp_session *session;
gchar *device_path;
gchar *debug_image_dir;
guint64 capture_sequence;
gboolean worker_running;
gboolean deactivating;
};
G_DECLARE_FINAL_TYPE (FpiDeviceGxfp5130, fpi_device_gxfp5130,
FPI, DEVICE_GXFP5130, FpImageDevice)
G_DEFINE_TYPE (FpiDeviceGxfp5130, fpi_device_gxfp5130, FP_TYPE_IMAGE_DEVICE)
static void
gxfp_save_debug_image (FpiDeviceGxfp5130 *self,
const guint8 *data,
gsize size)
{
g_autofree gchar *filename = NULL;
g_autofree gchar *contents = NULL;
g_autoptr(GError) error = NULL;
gsize header_size;
if (!self->debug_image_dir || !self->debug_image_dir[0])
return;
if (size != GXFP_IMAGE_WIDTH * GXFP_IMAGE_HEIGHT)
{
fp_warn ("Refusing to save debug image with invalid size %" G_GSIZE_FORMAT,
size);
return;
}
if (g_mkdir_with_parents (self->debug_image_dir, 0700) < 0)
{
fp_warn ("Failed to create debug image directory %s: %s",
self->debug_image_dir, g_strerror (errno));
return;
}
self->capture_sequence++;
filename = g_strdup_printf ("%s/capture-%u-%06" G_GUINT64_FORMAT ".pgm",
self->debug_image_dir,
(guint) getpid (),
self->capture_sequence);
contents = g_malloc (32 + size);
header_size = g_snprintf (contents, 32, "P5\n%d %d\n255\n",
GXFP_IMAGE_WIDTH, GXFP_IMAGE_HEIGHT);
memcpy (contents + header_size, data, size);
if (!g_file_set_contents (filename, contents, header_size + size, &error))
fp_warn ("Failed to save debug image %s: %s", filename, error->message);
else
fp_dbg ("Saved raw debug image to %s", filename);
}
static void gxfp_start_job (FpiDeviceGxfp5130 *self, GxfpJob job);
static GError *
gxfp_error_new (gint result)
{
gint error_number = result < 0 ? -result : EIO;
return g_error_new (G_IO_ERROR, g_io_error_from_errno (error_number),
"GXFP session failed: %s (%d)",
g_strerror (error_number), result);
}
static void
gxfp_job_result_free (GxfpJobResult *result)
{
if (!result)
return;
g_free (result->image_data);
g_free (result);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (GxfpJobResult, gxfp_job_result_free)
static void
gxfp_job_thread (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
FpiDeviceGxfp5130 *self = FPI_DEVICE_GXFP5130 (source_object);
GxfpJob job = GPOINTER_TO_INT (task_data);
GxfpJobResult *result = g_new0 (GxfpJobResult, 1);
(void) cancellable;
result->job = job;
switch (job)
{
case GXFP_JOB_OPEN:
result->result = gxfp_session_open (&self->session, self->device_path);
break;
case GXFP_JOB_ACTIVATE:
result->result = gxfp_session_activate (self->session,
GXFP_ACTIVATE_TIMEOUT_MS);
break;
case GXFP_JOB_WAIT_FINGER_DOWN:
result->result = gxfp_session_arm_finger_down (self->session);
if (!result->result)
result->result = gxfp_session_wait_finger_down (
self->session, GXFP_FINGER_TIMEOUT_MS);
break;
case GXFP_JOB_CAPTURE:
{
struct gxfp_image12 image = { 0 };
result->result = gxfp_session_capture (self->session, &image,
GXFP_CAPTURE_TIMEOUT_MS);
if (!result->result)
{
result->image_size = image.width * image.height;
result->image_data = g_malloc (result->image_size);
result->result = gxfp_image12_to_u8 (&image,
result->image_data,
result->image_size);
}
gxfp_image12_clear (&image);
}
break;
case GXFP_JOB_WAIT_FINGER_UP:
result->result = gxfp_session_arm_finger_up (self->session);
if (!result->result)
result->result = gxfp_session_wait_finger_up (
self->session, GXFP_FINGER_TIMEOUT_MS);
break;
case GXFP_JOB_DEACTIVATE:
result->result = gxfp_session_deactivate (self->session);
break;
case GXFP_JOB_CLOSE:
gxfp_session_close (self->session);
self->session = NULL;
result->result = 0;
break;
default:
result->result = -EINVAL;
break;
}
g_task_return_pointer (task, result, (GDestroyNotify) gxfp_job_result_free);
}
static void
gxfp_finish_deactivate (FpiDeviceGxfp5130 *self, gint result)
{
self->deactivating = FALSE;
fpi_image_device_deactivate_complete (
FP_IMAGE_DEVICE (self), result ? gxfp_error_new (result) : NULL);
}
static void
gxfp_job_done (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
FpiDeviceGxfp5130 *self = FPI_DEVICE_GXFP5130 (source_object);
g_autoptr(GxfpJobResult) result = g_task_propagate_pointer (G_TASK (res), NULL);
FpImageDevice *image_device = FP_IMAGE_DEVICE (self);
(void) user_data;
self->worker_running = FALSE;
if (self->deactivating && result->job != GXFP_JOB_DEACTIVATE)
{
gxfp_start_job (self, GXFP_JOB_DEACTIVATE);
return;
}
if (result->result)
{
GError *error = gxfp_error_new (result->result);
switch (result->job)
{
case GXFP_JOB_OPEN:
fpi_image_device_open_complete (image_device, error);
break;
case GXFP_JOB_ACTIVATE:
fpi_image_device_activate_complete (image_device, error);
break;
case GXFP_JOB_DEACTIVATE:
gxfp_finish_deactivate (self, result->result);
g_error_free (error);
break;
case GXFP_JOB_CLOSE:
fpi_image_device_close_complete (image_device, error);
break;
case GXFP_JOB_WAIT_FINGER_DOWN:
case GXFP_JOB_CAPTURE:
case GXFP_JOB_WAIT_FINGER_UP:
default:
fpi_image_device_session_error (image_device, error);
break;
}
return;
}
switch (result->job)
{
case GXFP_JOB_OPEN:
fpi_image_device_open_complete (image_device, NULL);
break;
case GXFP_JOB_ACTIVATE:
fpi_image_device_activate_complete (image_device, NULL);
break;
case GXFP_JOB_WAIT_FINGER_DOWN:
fpi_image_device_report_finger_status (image_device, TRUE);
break;
case GXFP_JOB_CAPTURE:
{
FpImage *image;
g_assert (result->image_size == GXFP_IMAGE_WIDTH * GXFP_IMAGE_HEIGHT);
image = fp_image_new (GXFP_IMAGE_WIDTH, GXFP_IMAGE_HEIGHT);
memcpy (image->data, result->image_data, result->image_size);
image->ppmm = GXFP_IMAGE_PPMM;
image->flags = FPI_IMAGE_NONE;
gxfp_save_debug_image (self, result->image_data, result->image_size);
fpi_image_device_image_captured (image_device, image);
}
break;
case GXFP_JOB_WAIT_FINGER_UP:
fpi_image_device_report_finger_status (image_device, FALSE);
break;
case GXFP_JOB_DEACTIVATE:
gxfp_finish_deactivate (self, 0);
break;
case GXFP_JOB_CLOSE:
g_clear_pointer (&self->device_path, g_free);
g_clear_pointer (&self->debug_image_dir, g_free);
fpi_image_device_close_complete (image_device, NULL);
break;
}
}
static void
gxfp_start_job (FpiDeviceGxfp5130 *self, GxfpJob job)
{
g_autoptr(GTask) task = NULL;
g_assert (!self->worker_running);
self->worker_running = TRUE;
task = g_task_new (self, NULL, gxfp_job_done, NULL);
g_task_set_task_data (task, GINT_TO_POINTER (job), NULL);
g_task_run_in_thread (task, gxfp_job_thread);
}
static void
gxfp_dev_open (FpImageDevice *device)
{
FpiDeviceGxfp5130 *self = FPI_DEVICE_GXFP5130 (device);
const gchar *path = fpi_device_get_virtual_env (FP_DEVICE (device));
self->device_path = g_strdup (path && path[0] ? path
: GXFP_DEVICE_PATH_DEFAULT);
self->debug_image_dir = g_strdup (g_getenv ("GXFP_DEBUG_IMAGE_DIR"));
self->capture_sequence = 0;
gxfp_start_job (self, GXFP_JOB_OPEN);
}
static void
gxfp_dev_close (FpImageDevice *device)
{
FpiDeviceGxfp5130 *self = FPI_DEVICE_GXFP5130 (device);
g_assert (!self->worker_running);
gxfp_start_job (self, GXFP_JOB_CLOSE);
}
static void
gxfp_dev_activate (FpImageDevice *device)
{
FpiDeviceGxfp5130 *self = FPI_DEVICE_GXFP5130 (device);
self->deactivating = FALSE;
gxfp_start_job (self, GXFP_JOB_ACTIVATE);
}
static void
gxfp_dev_deactivate (FpImageDevice *device)
{
FpiDeviceGxfp5130 *self = FPI_DEVICE_GXFP5130 (device);
self->deactivating = TRUE;
if (self->worker_running)
{
gxfp_session_cancel (self->session);
return;
}
gxfp_start_job (self, GXFP_JOB_DEACTIVATE);
}
static void
gxfp_dev_change_state (FpImageDevice *device, FpiImageDeviceState state)
{
FpiDeviceGxfp5130 *self = FPI_DEVICE_GXFP5130 (device);
if (self->deactivating || self->worker_running)
return;
switch (state)
{
case FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON:
gxfp_start_job (self, GXFP_JOB_WAIT_FINGER_DOWN);
break;
case FPI_IMAGE_DEVICE_STATE_CAPTURE:
gxfp_start_job (self, GXFP_JOB_CAPTURE);
break;
case FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF:
gxfp_start_job (self, GXFP_JOB_WAIT_FINGER_UP);
break;
case FPI_IMAGE_DEVICE_STATE_INACTIVE:
case FPI_IMAGE_DEVICE_STATE_ACTIVATING:
case FPI_IMAGE_DEVICE_STATE_DEACTIVATING:
case FPI_IMAGE_DEVICE_STATE_IDLE:
default:
break;
}
}
static const FpIdEntry id_table[] = {
{ .virtual_envvar = "FP_GXFP5130" },
{ .virtual_envvar = NULL },
};
static void
fpi_device_gxfp5130_init (FpiDeviceGxfp5130 *self)
{
self->session = NULL;
}
static void
fpi_device_gxfp5130_class_init (FpiDeviceGxfp5130Class *klass)
{
FpDeviceClass *device_class = FP_DEVICE_CLASS (klass);
FpImageDeviceClass *image_class = FP_IMAGE_DEVICE_CLASS (klass);
device_class->id = FP_COMPONENT;
device_class->full_name = "Goodix GXFP5130 (ChicagoHU 0x2504)";
device_class->type = FP_DEVICE_TYPE_VIRTUAL;
device_class->id_table = id_table;
device_class->scan_type = FP_SCAN_TYPE_PRESS;
image_class->img_open = gxfp_dev_open;
image_class->img_close = gxfp_dev_close;
image_class->activate = gxfp_dev_activate;
image_class->deactivate = gxfp_dev_deactivate;
image_class->change_state = gxfp_dev_change_state;
image_class->img_width = GXFP_IMAGE_WIDTH;
image_class->img_height = GXFP_IMAGE_HEIGHT;
}

262
src/config.c Normal file
View File

@@ -0,0 +1,262 @@
// SPDX-License-Identifier: GPL-2.0-only
#include "gxfp/config.h"
#include "gxfp/sensor.h"
#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#define GXFP_CONFIG_CHECKSUM_WORDS 0x6fu
#define GXFP_CONFIG_CHECKSUM_OFFSET 0xdeu
#define GXFP_OTP_FDT_OFFSET 0x1bu
#define GXFP_OTP_TCODE_A_OFFSET 0x2au
#define GXFP_OTP_TCODE_INV_OFFSET 0x2bu
#define GXFP_OTP_TCODE_B_OFFSET 0x2du
#define GXFP_CHICAGOHU_OTP_MIN_SIZE 0x36u
static const uint8_t chicagohu_2504_base_config[GXFP_CONFIG_SIZE] = {
0x70, 0x11, 0x74, 0x85, 0x00, 0x85, 0x2c, 0xb1, 0x18, 0xc9, 0x14, 0xdd,
0x00, 0xdd, 0x00, 0xdd, 0x00, 0xba, 0x00, 0x01, 0x80, 0xca, 0x00, 0x04,
0x00, 0x84, 0x00, 0x15, 0xb3, 0x86, 0x00, 0x00, 0xc4, 0x88, 0x00, 0x00,
0xba, 0x8a, 0x00, 0x00, 0xb2, 0x8c, 0x00, 0x00, 0xaa, 0x8e, 0x00, 0x00,
0xc1, 0x90, 0x00, 0xbb, 0xbb, 0x92, 0x00, 0xb1, 0xb1, 0x94, 0x00, 0x00,
0xa8, 0x96, 0x00, 0x00, 0xb6, 0x98, 0x00, 0x00, 0x00, 0x9a, 0x00, 0x00,
0x00, 0xd2, 0x00, 0x00, 0x00, 0xd4, 0x00, 0x00, 0x00, 0xd6, 0x00, 0x00,
0x00, 0xd8, 0x00, 0x00, 0x00, 0x50, 0x00, 0x01, 0x05, 0xd0, 0x00, 0x00,
0x00, 0x70, 0x00, 0x00, 0x00, 0x72, 0x00, 0x78, 0x56, 0x74, 0x00, 0x34,
0x12, 0x20, 0x00, 0x10, 0x40, 0x5c, 0x00, 0x80, 0x01, 0x20, 0x02, 0x08,
0x08, 0x36, 0x02, 0x80, 0x00, 0x38, 0x02, 0x80, 0x00, 0x3a, 0x02, 0x80,
0x00, 0x2a, 0x01, 0x82, 0x03, 0x22, 0x00, 0x01, 0x20, 0x24, 0x00, 0x14,
0x00, 0x80, 0x00, 0x01, 0x00, 0x5c, 0x00, 0x00, 0x01, 0x56, 0x00, 0x04,
0x20, 0x58, 0x00, 0x03, 0x02, 0x32, 0x00, 0x0c, 0x02, 0x66, 0x00, 0x03,
0x00, 0x7c, 0x00, 0x00, 0x58, 0x82, 0x00, 0x80, 0x15, 0x2a, 0x01, 0x08,
0x00, 0x54, 0x00, 0x10, 0x01, 0x62, 0x00, 0x04, 0x03, 0x64, 0x00, 0x19,
0x00, 0x66, 0x00, 0x03, 0x00, 0x7c, 0x00, 0x00, 0x58, 0x2a, 0x01, 0x08,
0x00, 0x52, 0x00, 0x08, 0x00, 0x54, 0x00, 0x00, 0x01, 0x66, 0x00, 0x03,
0x00, 0x7c, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00,
};
static uint16_t get_le16(const uint8_t *data)
{
return (uint16_t)data[0] | ((uint16_t)data[1] << 8);
}
static void put_le16(uint8_t *data, uint16_t value)
{
data[0] = (uint8_t)(value & 0xff);
data[1] = (uint8_t)(value >> 8);
}
uint16_t gxfp_config_checksum(const uint8_t config[GXFP_CONFIG_SIZE])
{
uint16_t sum = 0xa5a5u;
size_t i;
for (i = 0; i < GXFP_CONFIG_CHECKSUM_WORDS; i++)
sum = (uint16_t)(sum + get_le16(config + i * 2u));
return (uint16_t)(0u - sum);
}
int gxfp_config_update_checksum(uint8_t config[GXFP_CONFIG_SIZE])
{
uint16_t checksum;
if (!config)
return -EINVAL;
checksum = gxfp_config_checksum(config);
put_le16(config + GXFP_CONFIG_CHECKSUM_OFFSET, checksum);
return 0;
}
int gxfp_config_modify_register(uint8_t config[GXFP_CONFIG_SIZE],
uint16_t reg,
uint16_t value,
unsigned int section,
enum gxfp_config_write_mode mode,
uint16_t *old_value)
{
size_t start;
size_t length;
size_t offset;
if (!config || section >= GXFP_CONFIG_SECTION_COUNT)
return -EINVAL;
start = config[1u + section * 2u];
length = config[2u + section * 2u];
if (start >= GXFP_CONFIG_CHECKSUM_OFFSET ||
length > GXFP_CONFIG_CHECKSUM_OFFSET - start ||
(length % 4u) != 0u)
return -EBADMSG;
for (offset = 0; offset < length; offset += 4u) {
uint8_t *entry = config + start + offset;
uint16_t entry_reg = get_le16(entry);
uint16_t current;
if (entry_reg != reg)
continue;
current = get_le16(entry + 2u);
if (old_value)
*old_value = current;
switch (mode) {
case GXFP_CONFIG_WRITE_WORD:
put_le16(entry + 2u, value);
break;
case GXFP_CONFIG_WRITE_LOW_BYTE:
entry[2] = (uint8_t)value;
break;
case GXFP_CONFIG_WRITE_HIGH_BYTE:
entry[3] = (uint8_t)(value >> 8);
break;
default:
return -EINVAL;
}
return gxfp_config_update_checksum(config);
}
return -ENOENT;
}
int gxfp_config_patch_dac(uint8_t config[GXFP_CONFIG_SIZE],
const uint16_t register_values[4])
{
static const uint16_t registers[4] = {
0x0220, 0x0236, 0x0238, 0x023a
};
unsigned int i;
int ret;
if (!config || !register_values)
return -EINVAL;
for (i = 0; i < 4; i++) {
ret = gxfp_config_modify_register(config,
registers[i],
register_values[i],
0,
GXFP_CONFIG_WRITE_WORD,
NULL);
if (ret)
return ret;
}
return 0;
}
static int parse_tcode_diff(const uint8_t *otp, uint8_t *tcode_diff)
{
uint8_t a = otp[GXFP_OTP_TCODE_A_OFFSET];
uint8_t inv = otp[GXFP_OTP_TCODE_INV_OFFSET];
uint8_t b = otp[GXFP_OTP_TCODE_B_OFFSET];
if (a != 0 && a == (uint8_t)~inv) {
*tcode_diff = a;
return 0;
}
if (b != 0 && b == (uint8_t)~inv) {
*tcode_diff = b;
return 0;
}
if (a != 0 && a == b) {
*tcode_diff = a;
return 0;
}
return -EBADMSG;
}
static uint8_t parse_fdt_offset(uint8_t value)
{
uint8_t low = value & 0x03u;
uint8_t high = (value >> 4) & 0x03u;
uint8_t inverted = ((uint8_t)~value >> 2) & 0x03u;
if (low == high || low == inverted)
return low;
if (high == inverted)
return high;
return 0;
}
int gxfp_config_build_chicagohu_2504(
uint8_t config[GXFP_CONFIG_SIZE],
const void *otp_data,
size_t otp_len,
const struct gxfp_dac_info *dac,
struct gxfp_chicagohu_config_info *info)
{
const uint8_t *otp = otp_data;
uint8_t tcode_diff;
uint16_t tcode;
uint16_t fdt_tmp;
uint8_t fdt_delta;
uint8_t fdt_offset;
int ret;
if (!config || !otp || !dac || !info)
return -EINVAL;
if (otp_len < GXFP_CHICAGOHU_OTP_MIN_SIZE)
return -EBADMSG;
memset(info, 0, sizeof(*info));
memcpy(config, chicagohu_2504_base_config, GXFP_CONFIG_SIZE);
ret = gxfp_config_patch_dac(config, dac->register_value);
if (ret)
return ret;
ret = parse_tcode_diff(otp, &tcode_diff);
if (ret)
return ret;
tcode = (uint16_t)((((uint16_t)(tcode_diff >> 4) + 1u) * 0x10u) + 0x40u);
if (tcode == 0)
return -ERANGE;
ret = gxfp_config_modify_register(config, 0x005c, tcode, 0,
GXFP_CONFIG_WRITE_WORD, NULL);
if (ret)
return ret;
fdt_tmp = (uint16_t)(((tcode_diff & 0x0fu) + 2u) * 100u);
fdt_delta = (uint8_t)(((((uint32_t)fdt_tmp * 0x100u) / tcode) / 3u) >> 4);
ret = gxfp_config_modify_register(config, 0x0082,
(uint16_t)fdt_delta << 8, 2,
GXFP_CONFIG_WRITE_HIGH_BYTE, NULL);
if (ret)
return ret;
fdt_offset = parse_fdt_offset(otp[GXFP_OTP_FDT_OFFSET]);
if (fdt_offset != 0) {
ret = gxfp_config_modify_register(config, 0x0056,
(uint16_t)(fdt_offset + 4u), 2,
GXFP_CONFIG_WRITE_LOW_BYTE, NULL);
if (ret)
return ret;
}
ret = gxfp_config_update_checksum(config);
if (ret)
return ret;
info->tcode_diff = tcode_diff;
info->tcode = tcode;
info->fdt_delta = fdt_delta;
info->fdt_offset = fdt_offset;
info->checksum = get_le16(config + GXFP_CONFIG_CHECKSUM_OFFSET);
return 0;
}

166
src/device.c Normal file
View File

@@ -0,0 +1,166 @@
// SPDX-License-Identifier: GPL-2.0-only
#define _POSIX_C_SOURCE 200809L
#include "gxfp/device.h"
#include "gxfp/goodix_ec_uapi.h"
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
int gxfp_device_open(struct gxfp_device *device, const char *path)
{
if (!device || !path)
return -EINVAL;
device->fd = open(path, O_RDWR | O_NONBLOCK);
if (device->fd < 0)
return -errno;
return 0;
}
void gxfp_device_close(struct gxfp_device *device)
{
if (!device)
return;
if (device->fd >= 0)
close(device->fd);
device->fd = -1;
}
int gxfp_device_flush_rx(struct gxfp_device *device)
{
if (!device || device->fd < 0)
return -EINVAL;
if (ioctl(device->fd, GOODIX_EC_IOCTL_FLUSH_RX) < 0)
return -errno;
return 0;
}
int gxfp_device_send_mp(struct gxfp_device *device, uint8_t mp_flags,
const void *payload, size_t payload_len)
{
struct goodix_ec_tx_header header;
uint8_t *record;
size_t total_len;
ssize_t written;
if (!device || device->fd < 0 || (payload_len && !payload))
return -EINVAL;
if (payload_len > GOODIX_EC_UAPI_TX_MAX)
return -EMSGSIZE;
total_len = sizeof(header) + payload_len;
record = calloc(1, total_len);
if (!record)
return -ENOMEM;
memset(&header, 0, sizeof(header));
header.mp_flags = mp_flags;
header.payload_len = (uint16_t)payload_len;
memcpy(record, &header, sizeof(header));
if (payload_len)
memcpy(record + sizeof(header), payload, payload_len);
written = write(device->fd, record, total_len);
free(record);
if (written < 0)
return -errno;
if ((size_t)written != total_len)
return -EIO;
return 0;
}
int gxfp_device_receive(struct gxfp_device *device,
struct gxfp_rx_record *record,
int timeout_ms)
{
struct goodix_ec_record_header *header;
struct pollfd poll_fd;
uint8_t *buffer;
size_t capacity;
ssize_t bytes_read;
int ret;
if (!device || device->fd < 0 || !record)
return -EINVAL;
memset(record, 0, sizeof(*record));
poll_fd.fd = device->fd;
poll_fd.events = POLLIN;
do {
ret = poll(&poll_fd, 1, timeout_ms);
} while (ret < 0 && errno == EINTR);
if (ret < 0)
return -errno;
if (ret == 0)
return -ETIMEDOUT;
if (!(poll_fd.revents & POLLIN))
return -EIO;
capacity = sizeof(*header) + GOODIX_EC_UAPI_RX_MAX;
buffer = malloc(capacity);
if (!buffer)
return -ENOMEM;
bytes_read = read(device->fd, buffer, capacity);
if (bytes_read < 0) {
ret = -errno;
free(buffer);
return ret;
}
if ((size_t)bytes_read < sizeof(*header)) {
free(buffer);
return -EBADMSG;
}
header = (struct goodix_ec_record_header *)buffer;
if (header->len > (uint32_t)(bytes_read - sizeof(*header))) {
free(buffer);
return -EBADMSG;
}
record->payload = malloc(header->len);
if (!record->payload) {
free(buffer);
return -ENOMEM;
}
memcpy(record->payload, buffer + sizeof(*header), header->len);
record->payload_len = header->len;
record->mp_type = header->mp_type;
record->timestamp_ns = header->timestamp_ns;
free(buffer);
return 0;
}
void gxfp_rx_record_release(struct gxfp_rx_record *record)
{
if (!record)
return;
free(record->payload);
memset(record, 0, sizeof(*record));
}

155
src/fdt.c Normal file
View File

@@ -0,0 +1,155 @@
// SPDX-License-Identifier: GPL-2.0-only
#define _POSIX_C_SOURCE 200809L
#include "gxfp/fdt.h"
#include "gxfp/device.h"
#include "gxfp/protocol.h"
#include <errno.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#define GXFP_CMD_FDT_DOWN 0x32
#define GXFP_CMD_FDT_UP 0x34
#define GXFP_CMD_FDT_MODE 0x36
#define GXFP_CMD_FDT_STATUS 0xDA
#define GXFP_FDT_TABLE_LEN 24
#define GXFP_FDT_TRAILER_LEN 8
#define GXFP_FDT_PAYLOAD_LEN (2 + GXFP_FDT_TABLE_LEN + GXFP_FDT_TRAILER_LEN)
static void sleep_ms(unsigned int milliseconds)
{
struct timespec delay = {
.tv_sec = milliseconds / 1000,
.tv_nsec = (long)(milliseconds % 1000) * 1000000L,
};
while (nanosleep(&delay, &delay) < 0 && errno == EINTR)
;
}
static uint16_t get_le16(const uint8_t *data)
{
return (uint16_t)data[0] | ((uint16_t)data[1] << 8);
}
static int send_fdt_command(struct gxfp_device *device,
uint8_t command,
uint8_t magic0,
uint8_t magic1)
{
uint8_t payload[GXFP_FDT_PAYLOAD_LEN] = { 0 };
uint8_t frame[64];
size_t frame_len;
int ret;
payload[0] = magic0;
payload[1] = magic1;
ret = gxfp_frame_build(command, payload, sizeof(payload),
frame, sizeof(frame), &frame_len);
if (ret)
return ret;
return gxfp_device_send_mp(device, GXFP_MP_COMMAND,
frame, frame_len);
}
static int arm_mode(struct gxfp_device *device)
{
return send_fdt_command(device, GXFP_CMD_FDT_MODE, 0x09, 0x01);
}
void gxfp_fdt_init(struct gxfp_fdt *fdt)
{
if (!fdt)
return;
memset(fdt, 0, sizeof(*fdt));
}
int gxfp_fdt_arm_down(struct gxfp_device *device)
{
int ret;
ret = arm_mode(device);
if (ret)
return ret;
sleep_ms(10);
return send_fdt_command(device, GXFP_CMD_FDT_DOWN, 0x08, 0x01);
}
int gxfp_fdt_arm_up(struct gxfp_device *device)
{
int ret;
ret = arm_mode(device);
if (ret)
return ret;
sleep_ms(10);
return send_fdt_command(device, GXFP_CMD_FDT_UP, 0x0a, 0x01);
}
enum gxfp_fdt_event
gxfp_fdt_decode(const struct gxfp_frame_view *frame, uint16_t *status)
{
uint16_t value;
if (status)
*status = 0;
if (!frame || frame->payload_len < 2)
return GXFP_FDT_EVENT_NONE;
if (frame->command != GXFP_CMD_FDT_STATUS &&
frame->command != GXFP_CMD_FDT_DOWN &&
frame->command != GXFP_CMD_FDT_MODE &&
frame->command != GXFP_CMD_FDT_UP)
return GXFP_FDT_EVENT_NONE;
value = get_le16(frame->payload);
if (status)
*status = value;
switch (value) {
case 0x0100:
return GXFP_FDT_EVENT_READY;
case 0x0002:
return GXFP_FDT_EVENT_FINGER_DOWN;
case 0x0200:
return GXFP_FDT_EVENT_FINGER_UP;
case 0x0080:
case 0x0082:
return GXFP_FDT_EVENT_REVERSE;
default:
return GXFP_FDT_EVENT_NONE;
}
}
int gxfp_fdt_handle_event(struct gxfp_fdt *fdt,
struct gxfp_device *device,
enum gxfp_fdt_event event)
{
if (!fdt || !device)
return -EINVAL;
switch (event) {
case GXFP_FDT_EVENT_FINGER_DOWN:
fdt->waiting_for_up = true;
return gxfp_fdt_arm_up(device);
case GXFP_FDT_EVENT_FINGER_UP:
case GXFP_FDT_EVENT_REVERSE:
fdt->waiting_for_up = false;
return gxfp_fdt_arm_down(device);
case GXFP_FDT_EVENT_NONE:
case GXFP_FDT_EVENT_READY:
default:
return 0;
}
}

100
src/protocol.c Normal file
View File

@@ -0,0 +1,100 @@
// SPDX-License-Identifier: GPL-2.0-only
#include "gxfp/protocol.h"
#include <errno.h>
#include <stdint.h>
#include <string.h>
static uint16_t get_le16(const uint8_t *data)
{
return (uint16_t)data[0] | ((uint16_t)data[1] << 8);
}
static void put_le16(uint8_t *data, uint16_t value)
{
data[0] = (uint8_t)(value & 0xff);
data[1] = (uint8_t)(value >> 8);
}
static uint8_t checksum_for(uint8_t command, uint16_t declared_len,
const uint8_t *payload, size_t payload_len)
{
uint8_t sum = command +
(uint8_t)(declared_len & 0xff) +
(uint8_t)(declared_len >> 8);
size_t index;
for (index = 0; index < payload_len; index++)
sum = (uint8_t)(sum + payload[index]);
return (uint8_t)(GXFP_GOODIX_CHECKSUM_TARGET - sum);
}
int gxfp_frame_build(uint8_t command,
const void *payload, size_t payload_len,
uint8_t *output, size_t output_capacity,
size_t *output_len)
{
const uint8_t *bytes = payload;
uint16_t declared_len;
size_t total_len;
if (!output || !output_len || (payload_len && !payload))
return -EINVAL;
if (payload_len > UINT16_MAX - 1)
return -EOVERFLOW;
declared_len = (uint16_t)(payload_len + 1);
total_len = 3 + payload_len + 1;
if (total_len > output_capacity)
return -EMSGSIZE;
output[0] = command;
put_le16(output + 1, declared_len);
if (payload_len)
memcpy(output + 3, bytes, payload_len);
output[3 + payload_len] =
checksum_for(command, declared_len, bytes, payload_len);
*output_len = total_len;
return 0;
}
int gxfp_frame_parse(const void *frame, size_t frame_len,
struct gxfp_frame_view *view)
{
const uint8_t *bytes = frame;
uint16_t declared_len;
size_t payload_len;
size_t total_len;
uint8_t checksum;
if (!frame || !view || frame_len < 4)
return -EINVAL;
declared_len = get_le16(bytes + 1);
if (declared_len < 1)
return -EBADMSG;
payload_len = declared_len - 1;
total_len = 3 + payload_len + 1;
if (total_len > frame_len)
return -EBADMSG;
checksum = bytes[3 + payload_len];
if (checksum_for(bytes[0], declared_len, bytes + 3, payload_len) !=
checksum)
return -EBADMSG;
view->command = bytes[0];
view->payload = bytes + 3;
view->payload_len = payload_len;
view->checksum = checksum;
return 0;
}

149
src/request.c Normal file
View File

@@ -0,0 +1,149 @@
// SPDX-License-Identifier: GPL-2.0-only
#include "gxfp/request.h"
#include "gxfp/device.h"
#include "gxfp/protocol.h"
#include "gxfp/goodix_ec_uapi.h"
#include <stdio.h>
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#define GXFP_TLS_RX_MP_TYPE 0x0bu
static int gxfp_debug_enabled(void)
{
const char *value = getenv("GXFP_DEBUG");
return value && value[0] && strcmp(value, "0") != 0;
}
static void debug_dump(const uint8_t *data, size_t length, size_t limit)
{
size_t dump_len = length < limit ? length : limit;
size_t i;
for (i = 0; i < dump_len; i++)
fprintf(stderr, " %02x", data[i]);
if (length > dump_len)
fprintf(stderr, " ...");
fputc('\n', stderr);
}
int gxfp_request(struct gxfp_device *device,
uint8_t command,
const void *payload,
size_t payload_len,
uint8_t expected_command,
unsigned int max_frames,
int timeout_ms,
struct gxfp_response *response)
{
uint8_t frame[GOODIX_EC_UAPI_TX_MAX];
size_t frame_len;
unsigned int frame_index;
int ret;
if (!device || !response || max_frames == 0)
return -EINVAL;
memset(response, 0, sizeof(*response));
ret = gxfp_frame_build(command, payload, payload_len,
frame, sizeof(frame), &frame_len);
if (ret)
return ret;
ret = gxfp_device_send_mp(device, GXFP_MP_COMMAND, frame, frame_len);
if (ret)
return ret;
for (frame_index = 0; frame_index < max_frames; frame_index++) {
struct gxfp_rx_record record;
struct gxfp_frame_view view;
ret = gxfp_device_receive(device, &record, timeout_ms);
if (ret)
return ret;
if (record.mp_type == GXFP_TLS_RX_MP_TYPE) {
if (gxfp_debug_enabled()) {
fprintf(stderr,
"request RX: skipping TLS record len=%zu\n",
record.payload_len);
fprintf(stderr, "payload:");
debug_dump(record.payload, record.payload_len, 64);
}
gxfp_rx_record_release(&record);
continue;
}
ret = gxfp_frame_parse(record.payload, record.payload_len, &view);
if (ret) {
if (gxfp_debug_enabled()) {
fprintf(stderr,
"request RX parse failed: ret=%d len=%zu "
"mp=0x%02x\n", ret, record.payload_len,
record.mp_type);
fprintf(stderr, "raw payload:");
debug_dump(record.payload, record.payload_len, 64);
}
gxfp_rx_record_release(&record);
continue;
}
/*
* B0 may be an intermediate ACK. It is final only when the caller
* explicitly expects B0.
*/
if (view.command == GXFP_GOODIX_ACK &&
expected_command != GXFP_GOODIX_ACK) {
gxfp_rx_record_release(&record);
continue;
}
if (view.command != expected_command) {
if (gxfp_debug_enabled()) {
fprintf(stderr,
"request RX unmatched: cmd=0x%02x "
"expected=0x%02x len=%zu\n",
view.command, expected_command,
view.payload_len);
if (view.payload_len) {
fprintf(stderr, "payload:");
debug_dump(view.payload, view.payload_len, 32);
}
}
gxfp_rx_record_release(&record);
continue;
}
if (view.payload_len) {
response->payload = malloc(view.payload_len);
if (!response->payload) {
gxfp_rx_record_release(&record);
return -ENOMEM;
}
memcpy(response->payload, view.payload, view.payload_len);
}
response->command = view.command;
response->payload_len = view.payload_len;
response->timestamp_ns = record.timestamp_ns;
gxfp_rx_record_release(&record);
return 0;
}
return -ETIMEDOUT;
}
void gxfp_response_release(struct gxfp_response *response)
{
if (!response)
return;
free(response->payload);
memset(response, 0, sizeof(*response));
}

618
src/sensor.c Normal file
View File

@@ -0,0 +1,618 @@
// SPDX-License-Identifier: GPL-2.0-only
#include "gxfp/sensor.h"
#include "gxfp/config.h"
#include "gxfp/device.h"
#include "gxfp/request.h"
#include "gxfp/protocol.h"
#define GXFP_MP_COMMAND 0xA0
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#define GXFP_CMD_REG_READ 0x82
#define GXFP_CMD_READ_OTP 0xA6
#define GXFP_CMD_SENSOR_RESET 0xA2
#define GXFP_CMD_CONFIG_DOWNLOAD 0x90
#define GXFP_CMD_IMAGE_CAPTURE 0x20
#define GXFP_CMD_ACK 0xB0
#define GXFP_CMD_TLS_INIT 0xD0
#define GXFP_CMD_PROTOCOL_INIT 0x01
#define GXFP_CMD_NOTIFY_POWER 0x0E
#define GXFP_CMD_GET_VERSION 0xA8
#define GXFP_CMD_QUERY_MCU_STATE 0xAE
#define GXFP_CMD_TRIGGER_MCU_STATE 0xAF
#define GXFP_CMD_SLEEP_CLEANUP 0xD2
#define GXFP_CMD_TLS_UNLOCK 0xD4
#define GXFP_MCU_STATE_SIZE 20u
#define GXFP_RESET_SETTLE_MS 200L
#define GXFP_OTP_SIZE 0x40u
#define GXFP_OTP_MT_DAC_OFFSET 0x2eu
#define GXFP_OTP_FT_DAC_OFFSET 0x32u
#define GXFP_OTP_DAC_COUNT 4u
#define GXFP_OTP_CP_CRC_OFFSET 0x3cu
#define GXFP_OTP_FT_CRC_OFFSET 0x3du
#define GXFP_OTP_MT_CRC_OFFSET 0x3fu
static void put_le16(uint8_t *data, uint16_t value)
{
data[0] = (uint8_t)(value & 0xff);
data[1] = (uint8_t)(value >> 8);
}
static uint8_t crc8_poly07_not(const uint8_t *data, size_t len)
{
uint8_t crc = 0;
size_t i;
unsigned int bit;
for (i = 0; i < len; i++) {
crc ^= data[i];
for (bit = 0; bit < 8; bit++) {
if (crc & 0x80u)
crc = (uint8_t)((crc << 1) ^ 0x07u);
else
crc <<= 1;
}
}
return (uint8_t)~crc;
}
int gxfp_sensor_read_register(struct gxfp_device *device,
uint16_t address,
void *output,
uint16_t output_len)
{
uint8_t request_payload[5];
struct gxfp_response response;
int ret;
if (!device || !output || output_len == 0)
return -EINVAL;
request_payload[0] = 0x00;
put_le16(request_payload + 1, address);
put_le16(request_payload + 3, output_len);
ret = gxfp_request(device,
GXFP_CMD_REG_READ,
request_payload,
sizeof(request_payload),
GXFP_CMD_REG_READ,
4,
500,
&response);
if (ret)
return ret;
if (response.payload_len < output_len) {
gxfp_response_release(&response);
return -EBADMSG;
}
fprintf(stderr,
"REG 0x%04x response cmd=0x%02x len=%zu payload:",
address,
response.command,
response.payload_len);
for (size_t i = 0; i < response.payload_len; i++)
fprintf(stderr, " %02x", response.payload[i]);
fputc('\n', stderr);
memcpy(output, response.payload, output_len);
gxfp_response_release(&response);
return 0;
}
static int sleep_ms(long milliseconds)
{
struct timespec request;
request.tv_sec = milliseconds / 1000;
request.tv_nsec = (milliseconds % 1000) * 1000000L;
while (nanosleep(&request, &request) != 0) {
if (errno != EINTR)
return -errno;
}
return 0;
}
static int sensor_expect_ack(struct gxfp_device *device, uint8_t command,
const void *payload, size_t payload_len,
int timeout_ms)
{
struct gxfp_response response = { 0 };
int ret;
ret = gxfp_request(device, command, payload, payload_len,
GXFP_CMD_ACK, 8, timeout_ms, &response);
if (!ret)
gxfp_response_release(&response);
return ret;
}
int gxfp_sensor_read_version(struct gxfp_device *device,
char *version, size_t version_capacity)
{
static const uint8_t payload[] = { 0x01 };
struct gxfp_response response = { 0 };
size_t length;
int ret;
if (!device || !version || version_capacity < 2)
return -EINVAL;
version[0] = '\0';
ret = gxfp_request(device, GXFP_CMD_GET_VERSION,
payload, sizeof(payload), GXFP_CMD_GET_VERSION,
4, 1000, &response);
if (ret)
return ret;
length = response.payload_len;
if (length >= version_capacity)
length = version_capacity - 1;
memcpy(version, response.payload, length);
version[length] = '\0';
gxfp_response_release(&response);
return length ? 0 : -ENODATA;
}
int gxfp_sensor_query_mcu_state(struct gxfp_device *device,
struct gxfp_mcu_state *state)
{
struct timespec now;
struct gxfp_response response = { 0 };
uint8_t payload[5];
uint16_t timestamp;
int ret;
if (!device || !state)
return -EINVAL;
if (clock_gettime(CLOCK_REALTIME, &now) != 0)
return -errno;
timestamp = (uint16_t)(((uint64_t)(now.tv_sec % 60) * 1000u) +
(uint64_t)now.tv_nsec / 1000000u);
payload[0] = 0x55;
payload[1] = (uint8_t)timestamp;
payload[2] = (uint8_t)(timestamp >> 8);
payload[3] = 0;
payload[4] = 0;
ret = gxfp_request(device, GXFP_CMD_TRIGGER_MCU_STATE,
payload, sizeof(payload), GXFP_CMD_QUERY_MCU_STATE,
4, 1000, &response);
if (ret)
return ret;
if (response.payload_len < GXFP_MCU_STATE_SIZE) {
gxfp_response_release(&response);
return -EBADMSG;
}
memset(state, 0, sizeof(*state));
state->version = response.payload[0];
state->flags = response.payload[1];
state->pov_image_valid = !!(state->flags & (1u << 0));
state->tls_connected = !!(state->flags & (1u << 1));
state->tls_used = !!(state->flags & (1u << 2));
state->locked = !!(state->flags & (1u << 3));
memcpy(state->reserved, response.payload + 2,
sizeof(state->reserved));
gxfp_response_release(&response);
return 0;
}
int gxfp_sensor_recover(struct gxfp_device *device, int unstick_tls)
{
static const uint8_t power_on[] = { 0x01 };
static const uint8_t zero2[] = { 0x00, 0x00 };
static const uint8_t zero4[] = { 0x00, 0x00, 0x00, 0x00 };
int first_error = 0;
int ret;
if (!device)
return -EINVAL;
ret = sensor_expect_ack(device, GXFP_CMD_NOTIFY_POWER,
power_on, sizeof(power_on), 500);
if (ret)
first_error = ret;
(void)sleep_ms(GXFP_RESET_SETTLE_MS);
if (unstick_tls) {
ret = sensor_expect_ack(device, GXFP_CMD_PROTOCOL_INIT,
zero4, sizeof(zero4), 500);
if (ret && !first_error)
first_error = ret;
ret = sensor_expect_ack(device, GXFP_CMD_TLS_UNLOCK,
zero2, sizeof(zero2), 500);
if (ret && !first_error)
first_error = ret;
}
ret = sensor_expect_ack(device, GXFP_CMD_SLEEP_CLEANUP,
zero2, sizeof(zero2), 500);
if (ret && !first_error)
first_error = ret;
(void)sleep_ms(GXFP_RESET_SETTLE_MS);
ret = gxfp_sensor_reset(device);
if (ret)
return ret;
return first_error;
}
int gxfp_sensor_start_tls(struct gxfp_device *device)
{
static const uint8_t payload[2] = { 0x00, 0x00 };
uint8_t frame[32];
size_t frame_len;
int ret;
if (!device)
return -EINVAL;
ret = gxfp_frame_build(GXFP_CMD_TLS_INIT,
payload,
sizeof(payload),
frame,
sizeof(frame),
&frame_len);
if (ret)
return ret;
/*
* The TLS ClientHello may arrive before the normal B0 ACK. Do not
* wait for a response in the request layer; the mbedTLS receive
* callback will consume the incoming TLS records.
*/
return gxfp_device_send_mp(device,
GXFP_MP_COMMAND,
frame,
frame_len);
}
int gxfp_sensor_reset(struct gxfp_device *device)
{
static const uint8_t payload[2] = { 0x01, 0x14 };
struct gxfp_response response;
int ret;
if (!device)
return -EINVAL;
ret = gxfp_request(device,
GXFP_CMD_SENSOR_RESET,
payload,
sizeof(payload),
GXFP_CMD_ACK,
8,
1000,
&response);
if (ret)
return ret;
gxfp_response_release(&response);
return sleep_ms(GXFP_RESET_SETTLE_MS);
}
int gxfp_sensor_download_config(struct gxfp_device *device,
const void *config,
size_t config_len)
{
struct gxfp_response response;
int ret;
if (!device || !config)
return -EINVAL;
if (config_len != GXFP_CONFIG_SIZE)
return -EINVAL;
ret = gxfp_request(device,
GXFP_CMD_CONFIG_DOWNLOAD,
config,
config_len,
GXFP_CMD_CONFIG_DOWNLOAD,
32,
5000,
&response);
if (ret)
return ret;
gxfp_response_release(&response);
return 0;
}
int gxfp_sensor_reset_and_download_config(
struct gxfp_device *device,
const void *config,
size_t config_len)
{
int ret;
ret = gxfp_sensor_reset(device);
if (ret)
return ret;
return gxfp_sensor_download_config(device, config, config_len);
}
int gxfp_sensor_capture_oneframe(struct gxfp_device *device,
void *output,
size_t output_capacity,
size_t *output_len)
{
static const uint8_t payload[2] = { 0x01, 0x00 };
struct gxfp_response response = { 0 };
int ret;
if (!device || !output || output_capacity == 0 || !output_len)
return -EINVAL;
*output_len = 0;
ret = gxfp_request(device,
GXFP_CMD_IMAGE_CAPTURE,
payload,
sizeof(payload),
GXFP_CMD_IMAGE_CAPTURE,
8,
5000,
&response);
if (ret)
return ret;
if (response.payload_len > output_capacity) {
gxfp_response_release(&response);
return -EMSGSIZE;
}
memcpy(output, response.payload, response.payload_len);
*output_len = response.payload_len;
gxfp_response_release(&response);
return 0;
}
int gxfp_sensor_read_chip_id(struct gxfp_device *device,
uint16_t *chip_id)
{
static const long retry_delay_ms[] = { 0, 25, 50 };
uint8_t value[4];
unsigned int attempt;
int ret;
if (!device || !chip_id)
return -EINVAL;
*chip_id = 0;
for (attempt = 0; attempt < 3; attempt++) {
if (retry_delay_ms[attempt] != 0) {
ret = sleep_ms(retry_delay_ms[attempt]);
if (ret)
return ret;
}
ret = gxfp_sensor_read_register(device, 0x0000,
value, sizeof(value));
if (ret)
return ret;
*chip_id = (uint16_t)value[1] | ((uint16_t)value[2] << 8);
if (*chip_id != 0x0000u &&
*chip_id != 0x8000u &&
*chip_id != 0xffffu)
return 0;
}
return -EAGAIN;
}
int gxfp_sensor_read_otp(struct gxfp_device *device,
void *output,
size_t output_capacity,
size_t *output_len)
{
static const uint8_t request_payload[2] = { 0x00, 0x00 };
struct gxfp_response response;
int ret;
if (!device || !output || output_capacity == 0 || !output_len)
return -EINVAL;
*output_len = 0;
ret = gxfp_request(device,
GXFP_CMD_READ_OTP,
request_payload,
sizeof(request_payload),
GXFP_CMD_READ_OTP,
10,
750,
&response);
if (ret)
return ret;
if (response.payload_len > output_capacity) {
gxfp_response_release(&response);
return -EMSGSIZE;
}
memcpy(output, response.payload, response.payload_len);
*output_len = response.payload_len;
gxfp_response_release(&response);
return 0;
}
int gxfp_sensor_check_chicagohu_otp_crc(
const void *otp_data,
size_t otp_len,
struct gxfp_otp_crc_info *info)
{
const uint8_t *otp = otp_data;
uint8_t cp_data[15];
uint8_t ft_data[19];
uint8_t mt_data[27];
size_t pos;
if (!otp || !info)
return -EINVAL;
if (otp_len < GXFP_OTP_SIZE)
return -EBADMSG;
memset(info, 0, sizeof(*info));
pos = 0;
memcpy(cp_data + pos, otp + 0x00, 11);
pos += 11;
memcpy(cp_data + pos, otp + 0x24, 4);
pos = 0;
memcpy(ft_data + pos, otp + 0x0b, 9);
pos += 9;
ft_data[pos++] = otp[0x1c];
memcpy(ft_data + pos, otp + 0x32, 4);
pos += 4;
memcpy(ft_data + pos, otp + 0x38, 4);
pos += 4;
ft_data[pos] = otp[0x3e];
pos = 0;
memcpy(mt_data + pos, otp + 0x14, 8);
pos += 8;
memcpy(mt_data + pos, otp + 0x1d, 7);
pos += 7;
memcpy(mt_data + pos, otp + 0x28, 10);
pos += 10;
memcpy(mt_data + pos, otp + 0x36, 2);
info->cp_calculated = crc8_poly07_not(cp_data, sizeof(cp_data));
info->ft_calculated = crc8_poly07_not(ft_data, sizeof(ft_data));
info->mt_calculated = crc8_poly07_not(mt_data, sizeof(mt_data));
info->cp_stored = otp[GXFP_OTP_CP_CRC_OFFSET];
info->ft_stored = otp[GXFP_OTP_FT_CRC_OFFSET];
info->mt_stored = otp[GXFP_OTP_MT_CRC_OFFSET];
info->cp_valid = info->cp_calculated == info->cp_stored;
info->ft_valid = info->ft_calculated == info->ft_stored;
info->mt_valid = info->mt_calculated == info->mt_stored;
return 0;
}
int gxfp_sensor_parse_dac(const void *otp_data,
size_t otp_len,
int force_ft,
int force_mt,
struct gxfp_dac_info *info)
{
const uint8_t *otp = otp_data;
unsigned int i;
unsigned int matches = 0;
int ret;
if (!otp || !info)
return -EINVAL;
if (otp_len < GXFP_OTP_FT_DAC_OFFSET + GXFP_OTP_DAC_COUNT)
return -EBADMSG;
memset(info, 0, sizeof(*info));
ret = gxfp_sensor_check_chicagohu_otp_crc(otp, otp_len, &info->crc);
if (ret)
return ret;
if (force_ft || info->crc.ft_valid) {
for (i = 0; i < GXFP_OTP_DAC_COUNT; i++)
info->raw[i] = otp[GXFP_OTP_FT_DAC_OFFSET + i];
info->used_ft = 1;
goto finalize;
}
if (force_mt || info->crc.mt_valid) {
for (i = 0; i < GXFP_OTP_DAC_COUNT; i++)
info->raw[i] = otp[GXFP_OTP_MT_DAC_OFFSET + i];
info->used_mt = 1;
goto finalize;
}
for (i = 0; i < GXFP_OTP_DAC_COUNT; i++) {
uint8_t mt = otp[GXFP_OTP_MT_DAC_OFFSET + i];
uint8_t ft = otp[GXFP_OTP_FT_DAC_OFFSET + i];
if (mt == ft && ft != 0)
matches++;
}
info->match_count = matches;
if (matches < 3)
return -EBADMSG;
if (matches == 4) {
for (i = 0; i < GXFP_OTP_DAC_COUNT; i++)
info->raw[i] = otp[GXFP_OTP_FT_DAC_OFFSET + i];
info->used_ft = 1;
info->used_fallback = 1;
goto finalize;
}
for (i = 0; i < GXFP_OTP_DAC_COUNT; i++) {
uint8_t mt = otp[GXFP_OTP_MT_DAC_OFFSET + i];
uint8_t ft = otp[GXFP_OTP_FT_DAC_OFFSET + i];
if (mt == ft) {
info->raw[i] = ft;
} else {
unsigned int a =
otp[GXFP_OTP_MT_DAC_OFFSET + ((i + 1u) & 3u)];
unsigned int b =
otp[GXFP_OTP_MT_DAC_OFFSET + ((i + 3u) & 3u)];
unsigned int c =
otp[GXFP_OTP_MT_DAC_OFFSET + ((i + 2u) & 3u)];
info->raw[i] = (uint16_t)((a + b + c) / 3u);
info->used_fallback = 1;
}
}
finalize:
if (info->match_count == 0) {
for (i = 0; i < GXFP_OTP_DAC_COUNT; i++) {
uint8_t mt = otp[GXFP_OTP_MT_DAC_OFFSET + i];
uint8_t ft = otp[GXFP_OTP_FT_DAC_OFFSET + i];
if (mt == ft && ft != 0)
info->match_count++;
}
}
info->register_value[0] =
(uint16_t)((info->raw[0] << 4) | 0x0008u);
info->register_value[1] = info->raw[1];
info->register_value[2] = info->raw[2];
info->register_value[3] = info->raw[3];
return 0;
}

481
src/session.c Normal file
View File

@@ -0,0 +1,481 @@
// SPDX-License-Identifier: GPL-2.0-only
#include "gxfp/session.h"
#include "gxfp/config.h"
#include "gxfp/device.h"
#include "gxfp/fdt.h"
#include "gxfp/protocol.h"
#include "gxfp/sensor.h"
#include "gxfp/tls.h"
#include <errno.h>
#include <stdatomic.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define GXFP_PROFILE_CHIP_ID 0x2504u
#define GXFP_CAPTURE_COMMAND 0x20u
#define GXFP_CAPTURE_DECLARED 7690u
#define GXFP_CAPTURE_FRAME_SIZE (3u + GXFP_CAPTURE_DECLARED)
#define GXFP_IMAGE_HEADER_SIZE 5u
#define GXFP_PACKED_SIZE 7680u
#define GXFP_IMAGE_CRC_SIZE 4u
#define GXFP_IMAGE_REGION_SIZE (GXFP_PACKED_SIZE + GXFP_IMAGE_CRC_SIZE)
#define GXFP_IMAGE_WIDTH 64u
#define GXFP_IMAGE_HEIGHT 80u
#define GXFP_IMAGE_SAMPLES (GXFP_IMAGE_WIDTH * GXFP_IMAGE_HEIGHT)
#define GXFP_TLS_READ_SLICE_MS 250
struct gxfp_session {
struct gxfp_device device;
struct gxfp_tls tls;
enum gxfp_session_state state;
atomic_bool cancel_requested;
int tls_ready;
};
static int64_t monotonic_ms(void)
{
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
return -1;
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
static uint32_t crc32_mpeg2(const uint8_t *data, size_t length)
{
uint32_t crc = UINT32_MAX;
size_t i;
for (i = 0; i < length; i++) {
unsigned int bit;
crc ^= (uint32_t)data[i] << 24;
for (bit = 0; bit < 8; bit++)
crc = (crc & UINT32_C(0x80000000))
? (crc << 1) ^ UINT32_C(0x04c11db7)
: crc << 1;
}
return crc;
}
static uint32_t get_goodix_crc(const uint8_t *p)
{
return ((uint32_t)p[2] << 24) | ((uint32_t)p[3] << 16) |
((uint32_t)p[0] << 8) | (uint32_t)p[1];
}
static void unpack_four(const uint8_t *p, uint16_t out[4])
{
out[0] = (uint16_t)(((p[0] & 0x0fu) << 8) | p[1]);
out[1] = (uint16_t)((p[3] << 4) | (p[0] >> 4));
out[2] = (uint16_t)(((p[5] & 0x0fu) << 8) | p[2]);
out[3] = (uint16_t)((p[4] << 4) | (p[5] >> 4));
}
static int session_fail(struct gxfp_session *session, int error)
{
session->state = GXFP_SESSION_ERROR;
return error;
}
static int wait_fdt(struct gxfp_session *session,
enum gxfp_fdt_event wanted, int timeout_ms)
{
int64_t deadline;
if (timeout_ms <= 0)
return -EINVAL;
deadline = monotonic_ms();
if (deadline < 0)
return -EIO;
deadline += timeout_ms;
for (;;) {
struct gxfp_rx_record record = { 0 };
struct gxfp_frame_view frame;
enum gxfp_fdt_event event;
int64_t now;
int slice;
int ret;
if (atomic_load(&session->cancel_requested))
return -ECANCELED;
now = monotonic_ms();
if (now < 0)
return -EIO;
if (now >= deadline)
return -ETIMEDOUT;
slice = (int)(deadline - now);
if (slice > 100)
slice = 100;
ret = gxfp_device_receive(&session->device, &record, slice);
if (ret == -ETIMEDOUT || ret == -EAGAIN)
continue;
if (ret)
return ret;
if (record.mp_type != GXFP_MP_RX_COMMAND) {
gxfp_rx_record_release(&record);
continue;
}
ret = gxfp_frame_parse(record.payload, record.payload_len, &frame);
if (!ret) {
event = gxfp_fdt_decode(&frame, NULL);
gxfp_rx_record_release(&record);
if (event == wanted)
return 0;
} else {
gxfp_rx_record_release(&record);
}
}
}
int gxfp_session_open(struct gxfp_session **out, const char *device_path)
{
struct gxfp_session *session;
int ret;
if (!out || !device_path)
return -EINVAL;
*out = NULL;
session = calloc(1, sizeof(*session));
if (!session)
return -ENOMEM;
session->device.fd = -1;
ret = gxfp_device_open(&session->device, device_path);
if (ret) {
free(session);
return ret;
}
atomic_init(&session->cancel_requested, 0);
session->state = GXFP_SESSION_OPEN;
*out = session;
return 0;
}
int gxfp_session_activate(struct gxfp_session *session, int timeout_ms)
{
struct gxfp_dac_info dac;
struct gxfp_chicagohu_config_info info;
uint8_t otp[256];
uint8_t config[GXFP_CONFIG_SIZE];
size_t otp_length = 0;
uint16_t chip_id = 0;
int ret;
if (!session || timeout_ms <= 0)
return -EINVAL;
if (session->state != GXFP_SESSION_OPEN)
return -EINVAL;
atomic_store(&session->cancel_requested, 0);
ret = gxfp_device_flush_rx(&session->device);
if (ret)
return session_fail(session, ret);
/*
* Recover from a previous process that exited while the MCU was
* armed for FDT or capture. Register reads are not reliable until
* that persistent sensor state has been reset.
*/
ret = gxfp_sensor_reset(&session->device);
if (ret) {
/* A stale MCU TLS/FDT state is sensor policy, so recover here. */
ret = gxfp_sensor_recover(&session->device, 1);
if (ret) {
/* Recovery helpers are best-effort; the reset is authoritative. */
ret = gxfp_sensor_reset(&session->device);
if (ret)
return session_fail(session, ret);
}
}
ret = gxfp_device_flush_rx(&session->device);
if (ret)
return session_fail(session, ret);
ret = gxfp_sensor_read_chip_id(&session->device, &chip_id);
if (ret)
return session_fail(session, ret);
if (chip_id != GXFP_PROFILE_CHIP_ID)
return session_fail(session, -ENODEV);
ret = gxfp_sensor_read_otp(&session->device, otp, sizeof(otp),
&otp_length);
if (ret)
return session_fail(session, ret);
ret = gxfp_sensor_parse_dac(otp, otp_length, 0, 0, &dac);
if (ret)
return session_fail(session, ret);
ret = gxfp_config_build_chicagohu_2504(config, otp, otp_length,
&dac, &info);
if (ret)
return session_fail(session, ret);
ret = gxfp_sensor_reset_and_download_config(&session->device, config,
sizeof(config));
if (ret)
return session_fail(session, ret);
ret = gxfp_tls_init(&session->tls, &session->device);
if (ret)
return session_fail(session, ret);
session->tls_ready = 1;
ret = gxfp_sensor_start_tls(&session->device);
if (ret)
return session_fail(session, ret);
ret = gxfp_tls_handshake_timeout(&session->tls, timeout_ms);
if (ret)
return session_fail(session, ret);
session->state = GXFP_SESSION_ACTIVE;
return 0;
}
int gxfp_session_arm_finger_down(struct gxfp_session *session)
{
int ret;
if (!session || session->state != GXFP_SESSION_ACTIVE)
return -EINVAL;
atomic_store(&session->cancel_requested, 0);
ret = gxfp_fdt_arm_down(&session->device);
if (ret)
return session_fail(session, ret);
session->state = GXFP_SESSION_WAITING_FINGER_DOWN;
return 0;
}
int gxfp_session_wait_finger_down(struct gxfp_session *session, int timeout_ms)
{
int ret;
if (!session || session->state != GXFP_SESSION_WAITING_FINGER_DOWN)
return -EINVAL;
ret = wait_fdt(session, GXFP_FDT_EVENT_FINGER_DOWN, timeout_ms);
if (ret)
return session_fail(session, ret);
session->state = GXFP_SESSION_FINGER_PRESENT;
return 0;
}
int gxfp_session_capture(struct gxfp_session *session,
struct gxfp_image12 *image, int timeout_ms)
{
static const uint8_t capture_payload[2] = { 0x01, 0x00 };
uint8_t request[16];
uint8_t *stream = NULL;
size_t request_length = 0;
size_t stream_length = 0;
int64_t deadline;
int ret;
if (!session || !image || timeout_ms <= 0)
return -EINVAL;
if (session->state != GXFP_SESSION_FINGER_PRESENT || image->pixels)
return -EINVAL;
atomic_store(&session->cancel_requested, 0);
session->state = GXFP_SESSION_CAPTURING;
ret = gxfp_frame_build(GXFP_CAPTURE_COMMAND, capture_payload, 2u,
request, sizeof(request), &request_length);
if (ret)
return session_fail(session, ret);
ret = gxfp_device_send_mp(&session->device, GXFP_MP_COMMAND,
request, request_length);
if (ret)
return session_fail(session, ret);
stream = malloc(GXFP_CAPTURE_FRAME_SIZE);
if (!stream)
return session_fail(session, -ENOMEM);
deadline = monotonic_ms();
if (deadline < 0) {
ret = -EIO;
goto fail;
}
deadline += timeout_ms;
while (stream_length < GXFP_CAPTURE_FRAME_SIZE) {
size_t chunk = 0;
int64_t now;
int remaining;
if (atomic_load(&session->cancel_requested)) {
ret = -ECANCELED;
goto fail;
}
now = monotonic_ms();
if (now < 0) {
ret = -EIO;
goto fail;
}
if (now >= deadline) {
ret = -ETIMEDOUT;
goto fail;
}
remaining = (int)(deadline - now);
if (remaining > GXFP_TLS_READ_SLICE_MS)
remaining = GXFP_TLS_READ_SLICE_MS;
ret = gxfp_tls_read_timeout(&session->tls,
stream + stream_length,
GXFP_CAPTURE_FRAME_SIZE -
stream_length, &chunk, remaining);
if (ret == -ETIMEDOUT || ret == -EAGAIN)
continue;
if (ret)
goto fail;
stream_length += chunk;
if (stream_length >= 3u) {
uint16_t declared = (uint16_t)stream[1] |
((uint16_t)stream[2] << 8);
if (stream[0] != GXFP_CAPTURE_COMMAND ||
declared != GXFP_CAPTURE_DECLARED) {
ret = -EPROTO;
goto fail;
}
}
}
{
const uint8_t *payload = stream + 3u;
const uint8_t *packed = payload + GXFP_IMAGE_HEADER_SIZE;
const uint8_t *crc_bytes = packed + GXFP_PACKED_SIZE;
uint32_t calculated = crc32_mpeg2(packed, GXFP_PACKED_SIZE);
uint32_t stored = get_goodix_crc(crc_bytes);
uint16_t *pixels;
size_t offset;
size_t sample = 0;
if (calculated != stored) {
ret = -EBADMSG;
goto fail;
}
pixels = calloc(GXFP_IMAGE_SAMPLES, sizeof(*pixels));
if (!pixels) {
ret = -ENOMEM;
goto fail;
}
for (offset = 0; offset < GXFP_PACKED_SIZE; offset += 6u) {
uint16_t values[4];
size_t i;
unpack_four(packed + offset, values);
for (i = 0; i < 4u; i++)
pixels[sample++] = values[i];
}
if (sample != GXFP_IMAGE_SAMPLES) {
free(pixels);
ret = -EPROTO;
goto fail;
}
memset(image, 0, sizeof(*image));
image->pixels = pixels;
image->width = GXFP_IMAGE_WIDTH;
image->height = GXFP_IMAGE_HEIGHT;
memcpy(image->header, payload, GXFP_IMAGE_HEADER_SIZE);
image->trailer = stream[GXFP_CAPTURE_FRAME_SIZE - 1u];
image->crc_stored = stored;
image->crc_calculated = calculated;
}
free(stream);
session->state = GXFP_SESSION_FINGER_PRESENT;
return 0;
fail:
free(stream);
return session_fail(session, ret);
}
int gxfp_session_arm_finger_up(struct gxfp_session *session)
{
int ret;
if (!session || session->state != GXFP_SESSION_FINGER_PRESENT)
return -EINVAL;
atomic_store(&session->cancel_requested, 0);
ret = gxfp_fdt_arm_up(&session->device);
if (ret)
return session_fail(session, ret);
session->state = GXFP_SESSION_WAITING_FINGER_UP;
return 0;
}
int gxfp_session_wait_finger_up(struct gxfp_session *session, int timeout_ms)
{
int ret;
if (!session || session->state != GXFP_SESSION_WAITING_FINGER_UP)
return -EINVAL;
ret = wait_fdt(session, GXFP_FDT_EVENT_FINGER_UP, timeout_ms);
if (ret)
return session_fail(session, ret);
session->state = GXFP_SESSION_ACTIVE;
return 0;
}
int gxfp_session_deactivate(struct gxfp_session *session)
{
int reset_ret = 0;
if (!session)
return -EINVAL;
if (session->state == GXFP_SESSION_OPEN)
return 0;
atomic_store(&session->cancel_requested, 1);
/* FDT/capture mode survives closing /dev/gxfp; reset it explicitly. */
reset_ret = gxfp_sensor_reset(&session->device);
if (session->tls_ready) {
gxfp_tls_close(&session->tls);
session->tls_ready = 0;
}
(void)gxfp_device_flush_rx(&session->device);
atomic_store(&session->cancel_requested, 0);
session->state = GXFP_SESSION_OPEN;
return reset_ret;
}
int gxfp_image12_to_u8(const struct gxfp_image12 *source,
uint8_t *destination, size_t destination_size)
{
size_t count;
size_t i;
uint16_t minimum = UINT16_MAX;
uint16_t maximum = 0;
if (!source || !source->pixels || !destination)
return -EINVAL;
if (source->width && source->height > SIZE_MAX / source->width)
return -EOVERFLOW;
count = source->width * source->height;
if (!count || destination_size < count)
return -EMSGSIZE;
for (i = 0; i < count; i++) {
if (source->pixels[i] < minimum)
minimum = source->pixels[i];
if (source->pixels[i] > maximum)
maximum = source->pixels[i];
}
if (minimum == maximum)
return -EBADMSG;
for (i = 0; i < count; i++)
destination[i] = (uint8_t)(((uint32_t)(source->pixels[i] - minimum)
* 255u) / (maximum - minimum));
return 0;
}
void gxfp_image12_clear(struct gxfp_image12 *image)
{
if (!image)
return;
free(image->pixels);
memset(image, 0, sizeof(*image));
}
void gxfp_session_cancel(struct gxfp_session *session)
{
if (session)
atomic_store(&session->cancel_requested, 1);
}
enum gxfp_session_state
gxfp_session_get_state(const struct gxfp_session *session)
{
return session ? session->state : GXFP_SESSION_ERROR;
}
void gxfp_session_close(struct gxfp_session *session)
{
if (!session)
return;
(void)gxfp_session_deactivate(session);
gxfp_device_close(&session->device);
free(session);
}

356
src/tls.c Normal file
View File

@@ -0,0 +1,356 @@
// SPDX-License-Identifier: GPL-2.0-only
#include "gxfp/tls.h"
#include <errno.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <mbedtls/error.h>
#define GXFP_TLS_MP_FLAGS 0xb0u
#define GXFP_TLS_RX_MP_TYPE 0x0bu
#define GXFP_TLS_IO_TIMEOUT_MS 250
static int gxfp_debug_enabled(void)
{
const char *value = getenv("GXFP_DEBUG");
return value && value[0] && strcmp(value, "0") != 0;
}
static const unsigned char gxfp_tls_psk[32] = {
0xB9, 0xE7, 0x19, 0x74, 0xBC, 0x72, 0xA0, 0xE9,
0x0C, 0x53, 0x96, 0x59, 0x43, 0x60, 0x18, 0x9C,
0xF9, 0xF5, 0x79, 0x33, 0x6F, 0xBB, 0x69, 0x00,
0x89, 0x62, 0x79, 0xE5, 0x16, 0xF4, 0x2B, 0xAC
};
static const unsigned char gxfp_tls_identity[] = "Client_identity";
static int gxfp_tls_mbed_error(int ret)
{
if (ret >= 0)
return ret;
if (ret == MBEDTLS_ERR_SSL_TIMEOUT)
return -ETIMEDOUT;
if (ret == MBEDTLS_ERR_SSL_WANT_READ ||
ret == MBEDTLS_ERR_SSL_WANT_WRITE)
return -EAGAIN;
return -EPROTO;
}
static void gxfp_tls_log_error(const char *operation, int ret)
{
char message[160];
mbedtls_strerror(ret, message, sizeof(message));
fprintf(stderr, "%s failed: -0x%04x (%s)\n",
operation, (unsigned int)-ret, message);
}
static int gxfp_tls_send(void *context, const unsigned char *buffer,
size_t len)
{
struct gxfp_tls *tls = context;
int ret;
if (!tls || !tls->device || (!buffer && len != 0))
return MBEDTLS_ERR_SSL_INTERNAL_ERROR;
if (len > INT_MAX)
return MBEDTLS_ERR_SSL_BAD_INPUT_DATA;
ret = gxfp_device_send_mp(tls->device, GXFP_TLS_MP_FLAGS, buffer, len);
if (ret == -EAGAIN || ret == -EINTR)
return MBEDTLS_ERR_SSL_WANT_WRITE;
if (ret)
return MBEDTLS_ERR_SSL_INTERNAL_ERROR;
return (int)len;
}
static int gxfp_tls_recv(void *context, unsigned char *buffer, size_t len)
{
struct gxfp_tls *tls = context;
if (!tls || !tls->device || !buffer || len == 0)
return MBEDTLS_ERR_SSL_BAD_INPUT_DATA;
for (;;) {
size_t available;
size_t copy_len;
available = tls->rx_length - tls->rx_offset;
if (available != 0) {
copy_len = available < len ? available : len;
memcpy(buffer, tls->rx_buffer + tls->rx_offset, copy_len);
tls->rx_offset += copy_len;
if (tls->rx_offset == tls->rx_length) {
free(tls->rx_buffer);
tls->rx_buffer = NULL;
tls->rx_length = 0;
tls->rx_offset = 0;
}
return (int)copy_len;
}
for (;;) {
struct gxfp_rx_record record = { 0 };
int ret;
ret = gxfp_device_receive(tls->device, &record,
GXFP_TLS_IO_TIMEOUT_MS);
if (ret == -ETIMEDOUT || ret == -EAGAIN)
return MBEDTLS_ERR_SSL_WANT_READ;
if (ret == -EINTR)
continue;
if (ret)
return MBEDTLS_ERR_SSL_INTERNAL_ERROR;
if (record.mp_type != GXFP_TLS_RX_MP_TYPE) {
if (gxfp_debug_enabled())
fprintf(stderr,
"TLS: ignoring non-TLS MP frame "
"type=0x%02x len=%zu\n",
(unsigned int)record.mp_type,
record.payload_len);
gxfp_rx_record_release(&record);
continue;
}
if (record.payload_len == 0) {
gxfp_rx_record_release(&record);
continue;
}
if (gxfp_debug_enabled()) {
size_t i;
fprintf(stderr, "TLS RX MP=0x%02x len=%zu:\n",
(unsigned int)record.mp_type,
record.payload_len);
for (i = 0; i < record.payload_len; i++)
fprintf(stderr, "%02x%s", record.payload[i],
((i + 1) % 16 == 0 ||
i + 1 == record.payload_len)
? "\n" : " ");
}
tls->rx_buffer = malloc(record.payload_len);
if (!tls->rx_buffer) {
gxfp_rx_record_release(&record);
return MBEDTLS_ERR_SSL_ALLOC_FAILED;
}
memcpy(tls->rx_buffer, record.payload, record.payload_len);
tls->rx_length = record.payload_len;
tls->rx_offset = 0;
gxfp_rx_record_release(&record);
break;
}
}
}
int gxfp_tls_init(struct gxfp_tls *tls, struct gxfp_device *device)
{
static const int cipher_suites[] = {
MBEDTLS_TLS_PSK_WITH_AES_256_GCM_SHA384,
0,
};
static const unsigned char personalization[] = "gxfp-tls-server";
int ret;
if (!tls || !device || device->fd < 0)
return -EINVAL;
memset(tls, 0, sizeof(*tls));
tls->device = device;
mbedtls_ssl_init(&tls->ssl);
mbedtls_ssl_config_init(&tls->config);
mbedtls_ctr_drbg_init(&tls->ctr_drbg);
mbedtls_entropy_init(&tls->entropy);
ret = mbedtls_ctr_drbg_seed(&tls->ctr_drbg,
mbedtls_entropy_func,
&tls->entropy,
personalization,
sizeof(personalization) - 1);
if (ret)
goto fail;
ret = mbedtls_ssl_config_defaults(&tls->config,
MBEDTLS_SSL_IS_SERVER,
MBEDTLS_SSL_TRANSPORT_STREAM,
MBEDTLS_SSL_PRESET_DEFAULT);
if (ret)
goto fail;
mbedtls_ssl_conf_rng(&tls->config,
mbedtls_ctr_drbg_random,
&tls->ctr_drbg);
mbedtls_ssl_conf_ciphersuites(&tls->config, cipher_suites);
mbedtls_ssl_conf_min_version(&tls->config,
MBEDTLS_SSL_MAJOR_VERSION_3,
MBEDTLS_SSL_MINOR_VERSION_3);
mbedtls_ssl_conf_max_version(&tls->config,
MBEDTLS_SSL_MAJOR_VERSION_3,
MBEDTLS_SSL_MINOR_VERSION_3);
ret = mbedtls_ssl_conf_psk(&tls->config,
gxfp_tls_psk,
sizeof(gxfp_tls_psk),
gxfp_tls_identity,
sizeof(gxfp_tls_identity) - 1);
if (ret)
goto fail;
ret = mbedtls_ssl_setup(&tls->ssl, &tls->config);
if (ret)
goto fail;
mbedtls_ssl_set_bio(&tls->ssl, tls,
gxfp_tls_send, gxfp_tls_recv, NULL);
tls->initialized = 1;
return 0;
fail:
gxfp_tls_log_error("TLS initialization", ret);
gxfp_tls_close(tls);
return gxfp_tls_mbed_error(ret);
}
int gxfp_tls_handshake_timeout(struct gxfp_tls *tls, int timeout_ms)
{
struct timespec ts;
int64_t deadline;
if (!tls || !tls->initialized || timeout_ms <= 0)
return -EINVAL;
if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
return -EIO;
deadline = (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000 +
timeout_ms;
for (;;) {
int ret = mbedtls_ssl_handshake(&tls->ssl);
if (ret == 0)
return 0;
if (ret == MBEDTLS_ERR_SSL_WANT_READ ||
ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
int64_t now;
if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
return -EIO;
now = (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
if (now >= deadline)
return -ETIMEDOUT;
continue;
}
gxfp_tls_log_error("TLS handshake", ret);
return gxfp_tls_mbed_error(ret);
}
}
int gxfp_tls_handshake(struct gxfp_tls *tls)
{
return gxfp_tls_handshake_timeout(tls, INT_MAX);
}
int gxfp_tls_write_all(struct gxfp_tls *tls, const void *data, size_t len)
{
const unsigned char *cursor = data;
size_t written = 0;
if (!tls || !tls->initialized || (len != 0 && !data))
return -EINVAL;
while (written < len) {
int ret = mbedtls_ssl_write(&tls->ssl,
cursor + written,
len - written);
if (ret == MBEDTLS_ERR_SSL_WANT_READ ||
ret == MBEDTLS_ERR_SSL_WANT_WRITE)
continue;
if (ret < 0) {
gxfp_tls_log_error("TLS write", ret);
return gxfp_tls_mbed_error(ret);
}
if (ret == 0)
return -EIO;
written += (size_t)ret;
}
return 0;
}
int gxfp_tls_read_timeout(struct gxfp_tls *tls, void *data, size_t capacity,
size_t *data_len, int timeout_ms)
{
struct timespec ts;
int64_t deadline;
if (!tls || !tls->initialized || !data || capacity == 0 || !data_len ||
timeout_ms <= 0)
return -EINVAL;
if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
return -EIO;
deadline = (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000 +
timeout_ms;
*data_len = 0;
for (;;) {
int ret = mbedtls_ssl_read(&tls->ssl, data, capacity);
if (ret == MBEDTLS_ERR_SSL_WANT_READ ||
ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
int64_t now;
if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
return -EIO;
now = (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
if (now >= deadline)
return -ETIMEDOUT;
continue;
}
if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY || ret == 0)
return -ECONNRESET;
if (ret < 0) {
gxfp_tls_log_error("TLS read", ret);
return gxfp_tls_mbed_error(ret);
}
*data_len = (size_t)ret;
return 0;
}
}
int gxfp_tls_read(struct gxfp_tls *tls, void *data, size_t capacity,
size_t *data_len)
{
return gxfp_tls_read_timeout(tls, data, capacity, data_len, INT_MAX);
}
void gxfp_tls_close(struct gxfp_tls *tls)
{
if (!tls)
return;
if (tls->initialized)
(void)mbedtls_ssl_close_notify(&tls->ssl);
free(tls->rx_buffer);
tls->rx_buffer = NULL;
mbedtls_ssl_free(&tls->ssl);
mbedtls_ssl_config_free(&tls->config);
mbedtls_ctr_drbg_free(&tls->ctr_drbg);
mbedtls_entropy_free(&tls->entropy);
memset(tls, 0, sizeof(*tls));
}

225
tools/analyze_capture.c Normal file
View File

@@ -0,0 +1,225 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static uint16_t get_le16(const uint8_t *p)
{
return (uint16_t)p[0] | ((uint16_t)p[1] << 8);
}
static uint32_t get_le32(const uint8_t *p)
{
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) |
((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
}
static uint32_t get_be32(const uint8_t *p)
{
return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) |
((uint32_t)p[2] << 8) | (uint32_t)p[3];
}
static uint32_t get_goodix_image_crc(const uint8_t *p)
{
return ((uint32_t)p[2] << 24) | ((uint32_t)p[3] << 16) |
((uint32_t)p[0] << 8) | (uint32_t)p[1];
}
static uint32_t crc32_mpeg2(const uint8_t *data, size_t length)
{
uint32_t crc = UINT32_MAX;
size_t i;
for (i = 0; i < length; i++) {
unsigned int bit;
crc ^= (uint32_t)data[i] << 24;
for (bit = 0; bit < 8; bit++)
crc = (crc & UINT32_C(0x80000000))
? (crc << 1) ^ UINT32_C(0x04c11db7)
: crc << 1;
}
return crc;
}
static void analyze_crc_candidate(const char *name, const uint8_t *data,
size_t size, size_t offset, size_t span)
{
const uint8_t *stored;
uint32_t calculated;
uint32_t be;
uint32_t le;
uint32_t goodix;
printf("CRC candidate %s: offset=%zu length=%zu", name, offset, span);
if (span < 4u || offset > size || span > size - offset) {
printf(" OUT_OF_RANGE\n");
return;
}
stored = data + offset + span - 4u;
calculated = crc32_mpeg2(data + offset, span - 4u);
be = get_be32(stored);
le = get_le32(stored);
goodix = get_goodix_image_crc(stored);
printf(" data=%zu raw=%02x%02x%02x%02x calc=%08x",
span - 4u, stored[0], stored[1], stored[2], stored[3],
calculated);
if (calculated == be)
printf(" MATCH_BE");
if (calculated == le)
printf(" MATCH_LE");
if (calculated == goodix)
printf(" MATCH_GOODIX_2301");
if (calculated != be && calculated != le && calculated != goodix)
printf(" NO_MATCH");
putchar('\n');
}
static void dump_edge(const char *label, const uint8_t *data, size_t length)
{
size_t i;
printf("%s (%zu bytes):", label, length);
for (i = 0; i < length; i++) {
if ((i % 16) == 0)
printf("\n ");
printf("%02x%c", data[i], (i % 16) == 15 ? '\n' : ' ');
}
if ((length % 16) != 0)
putchar('\n');
}
static int read_file(const char *path, uint8_t **out, size_t *out_length)
{
FILE *file;
long end;
uint8_t *data;
file = fopen(path, "rb");
if (!file)
return -errno;
if (fseek(file, 0, SEEK_END) != 0 || (end = ftell(file)) < 0 ||
fseek(file, 0, SEEK_SET) != 0) {
fclose(file);
return -EIO;
}
data = malloc(end > 0 ? (size_t)end : 1u);
if (!data) {
fclose(file);
return -ENOMEM;
}
if (end > 0 && fread(data, 1, (size_t)end, file) != (size_t)end) {
free(data);
fclose(file);
return -EIO;
}
if (fclose(file) != 0) {
free(data);
return -EIO;
}
*out = data;
*out_length = (size_t)end;
return 0;
}
int main(int argc, char **argv)
{
const char *path = argc > 1 ? argv[1] : "capture_plaintext.bin";
uint8_t *data = NULL;
size_t size = 0;
uint16_t declared;
size_t expected_total;
size_t edge;
int ret;
ret = read_file(path, &data, &size);
if (ret) {
fprintf(stderr, "%s: %s\n", path, strerror(-ret));
return EXIT_FAILURE;
}
if (size < 3) {
fprintf(stderr, "%s: file is shorter than the 3-byte header\n", path);
free(data);
return EXIT_FAILURE;
}
declared = get_le16(data + 1);
expected_total = 3u + (size_t)declared;
printf("file: %s\n", path);
printf("command: 0x%02x\n", data[0]);
printf("declared_length: %u (0x%04x)\n", declared, declared);
printf("file_size: %zu\n", size);
printf("expected_total: %zu\n", expected_total);
printf("length_match: %s\n", size == expected_total ? "yes" : "no");
edge = size < 32u ? size : 32u;
dump_edge("first", data, edge);
dump_edge("last", data + size - edge, edge);
printf("controlled CRC scope candidates:\n");
analyze_crc_candidate("A", data, size, 3u, 7685u);
analyze_crc_candidate("B", data, size, 8u, 7680u);
analyze_crc_candidate("C", data, size, 3u, 7686u);
analyze_crc_candidate("D", data, size, 8u, 7684u);
if (declared >= 6u && expected_total <= size) {
const uint8_t *frame_payload = data + 3u;
size_t frame_payload_length = declared;
const uint8_t *image_data = frame_payload + 5u;
size_t image_data_length = frame_payload_length - 6u;
const uint8_t *frame_trailer = frame_payload +
frame_payload_length - 1u;
uint8_t sum = 0;
size_t i;
printf("frame_payload_offset: 3\n");
printf("frame_payload_length: %zu\n", frame_payload_length);
printf("image_data_offset: 8\n");
printf("image_data_length (DLL length - 6): %zu\n",
image_data_length);
printf("frame_trailer: 0x%02x\n", *frame_trailer);
for (i = 0; i < expected_total - 1u; i++)
sum = (uint8_t)(sum + data[i]);
printf("checksum_candidate target_AA: 0x%02x\n",
(uint8_t)(UINT8_C(0xaa) - sum));
printf("checksum_candidate target_00: 0x%02x\n",
(uint8_t)(0u - sum));
printf("sum_with_trailer_mod256: 0x%02x\n",
(uint8_t)(sum + *frame_trailer));
if (image_data_length >= 4u) {
const uint8_t *crc_bytes = image_data +
image_data_length - 4u;
uint32_t calculated = crc32_mpeg2(
image_data, image_data_length - 4u);
printf("image_bytes_before_crc: %zu\n",
image_data_length - 4u);
printf("image_crc_raw: %02x %02x %02x %02x\n",
crc_bytes[0], crc_bytes[1],
crc_bytes[2], crc_bytes[3]);
printf("crc32_mpeg2_calculated: 0x%08x\n", calculated);
printf("crc_candidate_be: 0x%08x%s\n",
get_be32(crc_bytes),
calculated == get_be32(crc_bytes) ? " MATCH" : "");
printf("crc_candidate_le: 0x%08x%s\n",
get_le32(crc_bytes),
calculated == get_le32(crc_bytes) ? " MATCH" : "");
printf("crc_candidate_goodix_2301: 0x%08x%s\n",
get_goodix_image_crc(crc_bytes),
calculated == get_goodix_image_crc(crc_bytes)
? " MATCH" : "");
}
} else {
printf("frame analysis skipped: declared length is incomplete\n");
}
free(data);
return EXIT_SUCCESS;
}

222
tools/capture_to_pgm.c Normal file
View File

@@ -0,0 +1,222 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Experimental offline decoder only.
* The 12-bit unpack and transpose candidates are adapted from Metrohan's
* gxfp5130-linux decoder. They are not part of the capture/library path and
* are not yet verified for this 0x2504 ChicagoHU device.
*/
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define IMAGE_OFFSET 8u
#define PACKED_LENGTH 7680u
#define CRC_LENGTH 4u
#define SAMPLE_COUNT 5120u
#define RAW_WIDTH 64u
#define RAW_HEIGHT 80u
#define TRANSPOSED_WIDTH 80u
#define TRANSPOSED_HEIGHT 64u
#define SAMPLE_MAX 4095u
static uint16_t get_le16(const uint8_t *p)
{
return (uint16_t)p[0] | ((uint16_t)p[1] << 8);
}
static uint32_t crc32_mpeg2(const uint8_t *data, size_t length)
{
uint32_t crc = UINT32_MAX;
size_t i;
for (i = 0; i < length; i++) {
unsigned int bit;
crc ^= (uint32_t)data[i] << 24;
for (bit = 0; bit < 8; bit++)
crc = (crc & UINT32_C(0x80000000))
? (crc << 1) ^ UINT32_C(0x04c11db7)
: crc << 1;
}
return crc;
}
static uint32_t get_goodix_crc(const uint8_t *p)
{
return ((uint32_t)p[2] << 24) | ((uint32_t)p[3] << 16) |
((uint32_t)p[0] << 8) | (uint32_t)p[1];
}
static int read_file(const char *path, uint8_t **out, size_t *out_length)
{
FILE *file = fopen(path, "rb");
long end;
uint8_t *data;
if (!file)
return -errno;
if (fseek(file, 0, SEEK_END) != 0 || (end = ftell(file)) < 0 ||
fseek(file, 0, SEEK_SET) != 0) {
fclose(file);
return -EIO;
}
data = malloc(end > 0 ? (size_t)end : 1u);
if (!data) {
fclose(file);
return -ENOMEM;
}
if (end > 0 && fread(data, 1, (size_t)end, file) != (size_t)end) {
free(data);
fclose(file);
return -EIO;
}
if (fclose(file) != 0) {
free(data);
return -EIO;
}
*out = data;
*out_length = (size_t)end;
return 0;
}
static void unpack_four(const uint8_t *p, uint16_t out[4])
{
out[0] = (uint16_t)(((p[0] & 0x0fu) << 8) | p[1]);
out[1] = (uint16_t)((p[3] << 4) | (p[0] >> 4));
out[2] = (uint16_t)(((p[5] & 0x0fu) << 8) | p[2]);
out[3] = (uint16_t)((p[4] << 4) | (p[5] >> 4));
}
static int write_pgm(const char *path, const uint16_t *pixels,
size_t width, size_t height, int inverted)
{
FILE *file = fopen(path, "wb");
size_t i;
if (!file)
return -errno;
if (fprintf(file, "P5\n%zu %zu\n%u\n",
width, height, SAMPLE_MAX) < 0) {
fclose(file);
return -EIO;
}
for (i = 0; i < width * height; i++) {
uint16_t value = inverted
? (uint16_t)(SAMPLE_MAX - pixels[i])
: pixels[i];
uint8_t encoded[2] = {
(uint8_t)(value >> 8), (uint8_t)value
};
if (fwrite(encoded, 1, sizeof(encoded), file) !=
sizeof(encoded)) {
fclose(file);
return -EIO;
}
}
return fclose(file) == 0 ? 0 : -EIO;
}
int main(int argc, char **argv)
{
const char *path = argc > 1 ? argv[1] : "capture_plaintext.bin";
uint8_t *frame = NULL;
size_t frame_length = 0;
uint16_t *raw = NULL;
uint16_t *transposed = NULL;
uint16_t declared;
const uint8_t *packed;
const uint8_t *crc_bytes;
uint32_t calculated_crc;
uint32_t stored_crc;
size_t offset;
size_t sample = 0;
int ret;
ret = read_file(path, &frame, &frame_length);
if (ret) {
fprintf(stderr, "%s: %s\n", path, strerror(-ret));
return EXIT_FAILURE;
}
if (frame_length < 3u) {
fprintf(stderr, "input is shorter than the frame header\n");
ret = -EBADMSG;
goto out;
}
declared = get_le16(frame + 1);
if (frame[0] != 0x20u || frame_length != 3u + declared ||
frame_length < IMAGE_OFFSET + PACKED_LENGTH + CRC_LENGTH + 1u) {
fprintf(stderr,
"unexpected frame: cmd=0x%02x declared=%u size=%zu\n",
frame[0], declared, frame_length);
ret = -EBADMSG;
goto out;
}
packed = frame + IMAGE_OFFSET;
crc_bytes = packed + PACKED_LENGTH;
calculated_crc = crc32_mpeg2(packed, PACKED_LENGTH);
stored_crc = get_goodix_crc(crc_bytes);
if (calculated_crc != stored_crc) {
fprintf(stderr,
"image CRC mismatch: calculated=%08x stored=%08x\n",
calculated_crc, stored_crc);
ret = -EBADMSG;
goto out;
}
raw = calloc(SAMPLE_COUNT, sizeof(*raw));
transposed = calloc(SAMPLE_COUNT, sizeof(*transposed));
if (!raw || !transposed) {
ret = -ENOMEM;
goto out;
}
for (offset = 0; offset < PACKED_LENGTH; offset += 6u) {
uint16_t values[4];
size_t i;
unpack_four(packed + offset, values);
for (i = 0; i < 4u; i++, sample++) {
size_t destination;
raw[sample] = values[i];
destination = (sample % RAW_WIDTH) *
TRANSPOSED_WIDTH +
sample / RAW_WIDTH;
transposed[destination] = values[i];
}
}
ret = write_pgm("candidate_raw.pgm", raw,
RAW_WIDTH, RAW_HEIGHT, 0);
if (!ret)
ret = write_pgm("candidate_transposed.pgm", transposed,
TRANSPOSED_WIDTH, TRANSPOSED_HEIGHT, 0);
if (!ret)
ret = write_pgm("candidate_inverted.pgm", raw,
RAW_WIDTH, RAW_HEIGHT, 1);
if (!ret)
ret = write_pgm("candidate_transposed_inverted.pgm",
transposed, TRANSPOSED_WIDTH,
TRANSPOSED_HEIGHT, 1);
if (ret) {
fprintf(stderr, "PGM write failed: %s\n", strerror(-ret));
goto out;
}
printf("Input CRC valid: 0x%08x\n", calculated_crc);
printf("Experimental outputs written:\n");
printf(" candidate_raw.pgm (64x80)\n");
printf(" candidate_transposed.pgm (80x64)\n");
printf(" candidate_inverted.pgm (64x80)\n");
printf(" candidate_transposed_inverted.pgm (80x64)\n");
out:
free(transposed);
free(raw);
free(frame);
return ret ? EXIT_FAILURE : EXIT_SUCCESS;
}