Initial GXFP5130 userspace prototype
This commit is contained in:
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal 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
357
LICENSES/GPL-2.0
Normal 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
509
LICENSES/LGPL-2.1
Normal 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
43
Makefile
Normal 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
99
README.md
Normal 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
267
docs/protocol.md
Normal 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
99
examples/fdt-monitor.c
Normal 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
161
examples/sensor-probe.c
Normal 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;
|
||||||
|
}
|
||||||
212
examples/session-regression.c
Normal file
212
examples/session-regression.c
Normal 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
48
include/gxfp/config.h
Normal 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
32
include/gxfp/device.h
Normal 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
34
include/gxfp/fdt.h
Normal 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
|
||||||
37
include/gxfp/goodix_ec_uapi.h
Normal file
37
include/gxfp/goodix_ec_uapi.h
Normal 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
29
include/gxfp/protocol.h
Normal 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
28
include/gxfp/request.h
Normal 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
92
include/gxfp/sensor.h
Normal 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
58
include/gxfp/session.h
Normal 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
36
include/gxfp/tls.h
Normal 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
|
||||||
26
libfprint-driver/README.md
Normal file
26
libfprint-driver/README.md
Normal 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
404
libfprint-driver/gxfp5130.c
Normal 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
262
src/config.c
Normal 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
166
src/device.c
Normal 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
155
src/fdt.c
Normal 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
100
src/protocol.c
Normal 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
149
src/request.c
Normal 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
618
src/sensor.c
Normal 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
481
src/session.c
Normal 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
356
src/tls.c
Normal 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
225
tools/analyze_capture.c
Normal 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
222
tools/capture_to_pgm.c
Normal 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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user