├── .gitignore ├── COPYING ├── Makefile ├── README ├── dev.c ├── dev.h ├── erase.c ├── flashbench.c ├── vm.c └── vm.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | erase 3 | flashbench 4 | 5 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC ?= gcc 2 | CFLAGS ?= -O2 -Wall -Wextra -Wno-missing-field-initializers -Wno-unused-parameter -g2 3 | LDFLAGS ?= -lrt 4 | 5 | all: flashbench erase 6 | 7 | dev.o: dev.c dev.h 8 | vm.o: vm.c vm.h dev.h 9 | flashbench.o: flashbench.c vm.h dev.h 10 | 11 | flashbench: flashbench.o dev.o vm.o 12 | $(CC) -o $@ flashbench.o dev.o vm.o $(LDFLAGS) 13 | 14 | 15 | erase: erase.o 16 | 17 | clean: 18 | rm -f flashbench flashbench.o erase erase.o dev.o vm.o 19 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | = flashbench -- identify characteristics of flash media = 2 | 3 | This is the tool used to identify the properties of 4 | SD cards and other media for the Linaro flash memory 5 | survey at [1]. The latest version should be available 6 | at [2]. Please also check out the article on lwn.net [3]. 7 | 8 | A short introduction to the most useful commands follows. 9 | 10 | == Guess erase block and page sizes == 11 | 12 | ''flashbench -a '' 13 | 14 | This is a simple read-only test doing small reads 15 | across boundaries of various sizes. Example: 16 | 17 | $ sudo ./flashbench -a /dev/mmcblk0 --blocksize=1024 18 | align 134217728 pre 735µs on 1.08ms post 780µs diff 324µs 19 | align 67108864 pre 736µs on 1.05ms post 763µs diff 300µs 20 | align 33554432 pre 722µs on 1.04ms post 763µs diff 294µs 21 | align 16777216 pre 727µs on 1.05ms post 772µs diff 302µs 22 | align 8388608 pre 724µs on 1.04ms post 768µs diff 299µs 23 | align 4194304 pre 741µs on 1.08ms post 788µs diff 317µs 24 | align 2097152 pre 745µs on 950µs post 811µs diff 171µs 25 | align 1048576 pre 745µs on 945µs post 807µs diff 169µs 26 | align 524288 pre 743µs on 936µs post 799µs diff 165µs 27 | align 262144 pre 746µs on 948µs post 809µs diff 171µs 28 | align 131072 pre 737µs on 935µs post 804µs diff 165µs 29 | align 65536 pre 735µs on 925µs post 796µs diff 159µs 30 | align 32768 pre 735µs on 925µs post 800µs diff 157µs 31 | align 16384 pre 745µs on 911µs post 781µs diff 148µs 32 | align 8192 pre 785µs on 808µs post 725µs diff 53.3µs 33 | align 4096 pre 784µs on 788µs post 779µs diff 5.85µs 34 | align 2048 pre 787µs on 793µs post 789µs diff 4.65µs 35 | 36 | This shows the access times to do two 1024 byte reads around 37 | the boundaries of power-of-two aligned blocks. Reading at 38 | the end of a 128 MB unit takes around 735 microseconds, reading x 39 | the last block of this unit together with the first block of 40 | the next one takes about 1080 microseconds and reading the first 41 | two blocks in a 128 MB unit takes around 780 microseconds. 42 | 43 | The most interesting number here is the last one, the difference 44 | between the second number and the average of the first and the third 45 | is 324 microseconds. These numbers all stay roughly the same for 46 | all units between 4 MB and 128 MB. 47 | 48 | However, from 2 MB down to 16 KB, the last column has a much lower 49 | value. This indicates that whatever the memory card does on a 4 MB 50 | boundary does not happen at other boundaries. The educated guess 51 | here is that 4 MB is the erase block size, also called the segment 52 | or allocation unit size. This erase blocksize will need to be 53 | used in other tests following this one. 54 | 55 | Similarly, both 16 KB and 8 KB boundaries are special. The logical 56 | explanation for this is that the card has 8 KB pages, but can use 57 | multi-plane accesses to read two 8 KB pages simultaneously. 58 | 59 | Some cards only show a clear pattern using accesses with certain 60 | block sizes, other cards do not show any pattern, which means 61 | that the numbers need to be determined differently. 62 | 63 | Also, cards that were never fully written may show a different 64 | behaviour because access times on pre-erased segments are different 65 | from those that have been written. 66 | 67 | == Create a scatter plot of access times == 68 | 69 | ''flashbench -s --scatter-order= --scatter-span= -o '' 70 | 71 | Writes a scatter plot into a file that can be used as input 72 | for a ''gnuplot -p -e 'plot "file"' '' 73 | 74 | == Finding the number of open erase blocks == 75 | 76 | ''flashbench --open-au --open-au-nr= --erasesize= [--random]'' 77 | 78 | Example: 79 | 80 | $ sudo ./flashbench -O --erasesize=$[4 * 1024 * 1024] \ 81 | --blocksize=$[256 * 1024] /dev/mmcblk0 --open-au-nr=2 82 | 4MiB 8.79M/s 83 | 2MiB 7.41M/s 84 | 1MiB 6.87M/s 85 | 512KiB 6.39M/s 86 | 256KiB 6.27M/s 87 | $ sudo ./flashbench -O --erasesize=$[4 * 1024 * 1024] \ 88 | --blocksize=$[256 * 1024] /dev/mmcblk0 --open-au-nr=3 89 | 4MiB 7.75M/s 90 | 2MiB 5.03M/s 91 | 1MiB 3.24M/s 92 | 512KiB 1.76M/s 93 | 256KiB 912K/s 94 | 95 | In this case, trying 2 open AUs shows fast accesses for small 96 | block sizes, but trying 3 open AUs is much slower, and degrades 97 | further at smaller sizes. 98 | 99 | Try varying numbers until hitting the cut-off point. 100 | For cards that are fast when using --random, this will find 101 | the cut-off more reliably. 102 | 103 | Some cards can do more open segments in linear mode than they 104 | can in random mode. 105 | 106 | == References == 107 | 108 | [1] https://wiki.linaro.org/WorkingGroups/KernelArchived/Projects/FlashCardSurvey 109 | [2] git clone git://git.linaro.org/people/arnd/flashbench.git 110 | [3] http://lwn.net/Articles/428584 111 | 112 | Feel free to reach the author by email for any questions 113 | about the latest version, Arnd Bergmann , 114 | or use the linaro-dev@lists.linaro.org mailing list for discussions. 115 | 116 | If you use this tool to measure memory cards, USB sticks 117 | or SSDs and get useful results, please share them 118 | at flashbench-results@lists.linaro.org. 119 | -------------------------------------------------------------------------------- /dev.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #define _FILE_OFFSET_BITS 64 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | 23 | #include "dev.h" 24 | 25 | #define MAX_BUFSIZE (64 * 1024 * 1024) 26 | 27 | static inline long long time_to_ns(struct timespec *ts) 28 | { 29 | return (long long)ts->tv_sec * 1000 * 1000 * 1000 + ts->tv_nsec; 30 | } 31 | 32 | static long long get_ns(void) 33 | { 34 | struct timespec ts; 35 | clock_gettime(CLOCK_REALTIME, &ts); 36 | return time_to_ns(&ts); 37 | } 38 | 39 | long long time_read(struct device *dev, off_t pos, size_t size) 40 | { 41 | long long now = get_ns(); 42 | ssize_t ret; 43 | 44 | if (size > MAX_BUFSIZE) 45 | return -ENOMEM; 46 | 47 | do { 48 | ret = pread(dev->fd, dev->readbuf, size, pos % dev->size); 49 | if (ret > 0) { 50 | size -= ret; 51 | pos += ret; 52 | } 53 | } while (ret > 0 || errno == -EAGAIN); 54 | 55 | if (ret) { 56 | perror("time_read"); 57 | return 0; 58 | } 59 | 60 | return get_ns() - now; 61 | } 62 | 63 | long long time_write(struct device *dev, off_t pos, size_t size, enum writebuf which) 64 | { 65 | long long now = get_ns(); 66 | ssize_t ret; 67 | unsigned long *p; 68 | 69 | if (size > MAX_BUFSIZE) 70 | return -ENOMEM; 71 | p = dev->writebuf[which]; 72 | 73 | do { 74 | ret = pwrite(dev->fd, p, size, pos % dev->size); 75 | if (ret > 0) { 76 | size -= ret; 77 | pos += ret; 78 | } 79 | } while (ret > 0 || errno == -EAGAIN); 80 | 81 | if (ret) { 82 | perror("time_write"); 83 | return 0; 84 | } 85 | 86 | return get_ns() - now; 87 | } 88 | 89 | long long time_erase(struct device *dev, off_t pos, size_t size) 90 | { 91 | long long now = get_ns(); 92 | ssize_t ret; 93 | unsigned long long args[2] = { size, pos % dev->size }; 94 | 95 | if (size > MAX_BUFSIZE) 96 | return -ENOMEM; 97 | 98 | ret = ioctl(dev->fd, BLKDISCARD, &args); 99 | 100 | if (ret) { 101 | perror("time_erase"); 102 | } 103 | 104 | return get_ns() - now; 105 | } 106 | 107 | static void set_rtprio(void) 108 | { 109 | int ret; 110 | struct sched_param p = { 111 | .sched_priority = 10, 112 | }; 113 | ret = sched_setscheduler(0, SCHED_FIFO, &p); 114 | if (ret) 115 | perror("sched_setscheduler"); 116 | } 117 | 118 | 119 | int setup_dev(struct device *dev, const char *filename) 120 | { 121 | int err; 122 | void *p; 123 | set_rtprio(); 124 | 125 | dev->fd = open(filename, O_RDWR | O_DIRECT | O_SYNC | O_NOATIME); 126 | if (dev->fd < 0) { 127 | perror(filename); 128 | return -errno; 129 | } 130 | 131 | dev->size = lseek(dev->fd, 0, SEEK_END); 132 | if (dev->size < 0) { 133 | perror("seek"); 134 | return -errno; 135 | } 136 | 137 | err = posix_memalign(&dev->readbuf, 4096, MAX_BUFSIZE); 138 | if (err) 139 | return -err; 140 | 141 | err = posix_memalign(&p, 4096, MAX_BUFSIZE); 142 | if (err) 143 | return -err; 144 | memset(p, 0, MAX_BUFSIZE); 145 | dev->writebuf[WBUF_ZERO] = p; 146 | 147 | err = posix_memalign(&p, 4096, MAX_BUFSIZE); 148 | if (err) 149 | return -err; 150 | memset(p, 0xff, MAX_BUFSIZE); 151 | dev->writebuf[WBUF_ONE] = p; 152 | 153 | err = posix_memalign(&p , 4096, MAX_BUFSIZE); 154 | if (err) 155 | return -err; 156 | memset(p, 0x5a, MAX_BUFSIZE); 157 | dev->writebuf[WBUF_RAND] = p; 158 | 159 | return 0; 160 | } 161 | -------------------------------------------------------------------------------- /dev.h: -------------------------------------------------------------------------------- 1 | #ifndef FLASHBENCH_DEV_H 2 | #define FLASHBENCH_DEV_H 3 | 4 | #include 5 | 6 | struct device { 7 | void *readbuf; 8 | void *writebuf[3]; 9 | int fd; 10 | off_t size; 11 | }; 12 | 13 | enum writebuf { 14 | WBUF_ZERO, 15 | WBUF_ONE, 16 | WBUF_RAND, 17 | }; 18 | 19 | extern int setup_dev(struct device *dev, const char *filename); 20 | 21 | long long time_write(struct device *dev, off_t pos, size_t size, enum writebuf which); 22 | 23 | long long time_read(struct device *dev, off_t pos, size_t size); 24 | 25 | long long time_erase(struct device *dev, off_t pos, size_t size); 26 | 27 | #endif /* FLASHBENCH_DEV_H */ 28 | -------------------------------------------------------------------------------- /erase.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | int main(int argc, char *argv[]) 13 | { 14 | int fd = open(argv[1], O_RDWR | O_DIRECT); 15 | int ret; 16 | unsigned long long range[2]; 17 | 18 | if (argc != 4) { 19 | fprintf(stderr, "usage: %s \n", 20 | argv[0]); 21 | } 22 | 23 | if (fd < 0) { 24 | perror("open"); 25 | return errno; 26 | } 27 | 28 | range[0] = atoll(argv[2]); 29 | range[1] = atoll(argv[3]); 30 | 31 | printf("erasing %lld to %lld on %s\n", range[0], range[0] + range[1], argv[1]); 32 | 33 | ret = ioctl(fd, BLKDISCARD, range); 34 | if (ret) 35 | perror("ioctl"); 36 | 37 | return errno; 38 | } 39 | -------------------------------------------------------------------------------- /flashbench.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #define _FILE_OFFSET_BITS 64 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "dev.h" 18 | #include "vm.h" 19 | 20 | typedef long long ns_t; 21 | 22 | #define returnif(x) do { typeof(x) __x = (x); if (__x < 0) return (__x); } while (0) 23 | 24 | static ns_t ns_min(int count, ns_t data[]) 25 | { 26 | int i; 27 | ns_t min = LLONG_MAX; 28 | 29 | for (i=0; i max) 56 | max = data[i]; 57 | } 58 | 59 | return max; 60 | } 61 | 62 | static ns_t ns_avg(int count, ns_t data[]) 63 | { 64 | int i; 65 | ns_t sum = 0; 66 | 67 | for (i=0; i ret) 134 | results[i] = ret; 135 | } 136 | 137 | return 0; 138 | } 139 | 140 | static void print_one_blocksize(int count, ns_t *times, off_t blocksize) 141 | { 142 | char min[8], avg[8], max[8]; 143 | 144 | format_ns(min, ns_min(count, times)); 145 | format_ns(avg, ns_avg(count, times)); 146 | format_ns(max, ns_max(count, times)); 147 | 148 | printf("%lld bytes: min %s avg %s max %s: %g MB/s\n", (long long)blocksize, 149 | min, avg, max, blocksize / (ns_min(count, times) / 1000.0)); 150 | } 151 | 152 | static int try_interval(struct device *dev, long blocksize, ns_t *min_time, int count) 153 | { 154 | int ret; 155 | ns_t times[count]; 156 | memset(times, 0, sizeof(times)); 157 | 158 | ret = time_read_interval(dev, count, times, blocksize, 0, blocksize * 9); 159 | returnif (ret); 160 | 161 | print_one_blocksize(count, times, blocksize); 162 | *min_time = ns_min(count, times); 163 | 164 | return 0; 165 | } 166 | 167 | static int try_intervals(struct device *dev, int count, int rounds) 168 | { 169 | const int ignore = 3; 170 | ns_t min[rounds]; 171 | off_t bytes[rounds]; 172 | ns_t atime; 173 | float throughput; 174 | int i; 175 | 176 | for (i=0; i= (1 << bits)) { 206 | fprintf(stderr, "flashbench: internal error\n"); 207 | exit(-EINVAL); 208 | } 209 | 210 | if (v == 0) 211 | v = ((1 << bits) - 1) & 0xace1; 212 | 213 | switch (bits) { 214 | case 8: /* x^8 + x^6 + x^5 + x^4 + 1 */ 215 | bit = ((v >> 0) ^ (v >> 2) ^ (v >> 3) ^ (v >> 4)) & 1; 216 | break; 217 | case 9: /* x9 + x5 + 1 */ 218 | bit = ((v >> 0) ^ (v >> 4)) & 1; 219 | break; 220 | case 10: /* x10 + x7 + 1 */ 221 | bit = ((v >> 0) ^ (v >> 3)) & 1; 222 | break; 223 | case 11: /* x11 + x9 + 1 */ 224 | bit = ((v >> 0) ^ (v >> 2)) & 1; 225 | break; 226 | case 12: 227 | bit = ((v >> 0) ^ (v >> 1) ^ (v >> 2) ^ (v >> 8)) & 1; 228 | break; 229 | case 13: /* x^13 + x^12 + x^11 + x^8 + 1 */ 230 | bit = ((v >> 0) ^ (v >> 1) ^ (v >> 2) ^ (v >> 5)) & 1; 231 | break; 232 | case 14: /* x^14 + x^13 + x^12 + x^2 + 1 */ 233 | bit = ((v >> 0) ^ (v >> 1) ^ (v >> 2) ^ (v >> 12)) & 1; 234 | break; 235 | case 15: /* x^15 + x^14 + 1 */ 236 | bit = ((v >> 0) ^ (v >> 1) ) & 1; 237 | break; 238 | case 16: /* x^16 + x^14 + x^13 + x^11 + 1 */ 239 | bit = ((v >> 0) ^ (v >> 2) ^ (v >> 3) ^ (v >> 5) ) & 1; 240 | break; 241 | default: 242 | fprintf(stderr, "flashbench: internal error\n"); 243 | exit(-EINVAL); 244 | } 245 | 246 | return v >> 1 | bit << (bits - 1); 247 | } 248 | 249 | static int try_scatter_io(struct device *dev, int tries, int scatter_order, 250 | int scatter_span, int blocksize, FILE *out) 251 | { 252 | int i, j; 253 | const int count = 1 << scatter_order; 254 | ns_t time; 255 | ns_t min[count]; 256 | unsigned long pos; 257 | 258 | memset(min, 0, sizeof(min)); 259 | for (i = 0; i < tries; i++) { 260 | pos = 0; 261 | for (j = 0; j < count; j++) { 262 | time = time_read(dev, (pos * blocksize), scatter_span * blocksize); 263 | returnif (time); 264 | 265 | if (i == 0 || time < min[pos]) 266 | min[pos] = time; 267 | 268 | pos = lfsr(pos, scatter_order); 269 | } 270 | } 271 | 272 | for (j = 0; j < count; j++) { 273 | fprintf(out, "%f %f\n", j * blocksize / (1024 * 1024.0), min[j] / 1000000.0); 274 | } 275 | 276 | return 0; 277 | } 278 | 279 | static unsigned int find_order(unsigned int large, unsigned int small) 280 | { 281 | unsigned int o; 282 | 283 | for (o=1; small < large; small <<= 1) 284 | o++; 285 | 286 | return o; 287 | } 288 | 289 | static int try_read_alignment(struct device *dev, int tries, int count, 290 | off_t maxalign, off_t align, size_t blocksize) 291 | { 292 | ns_t pre[count], on[count], post[count]; 293 | char pre_s[8], on_s[8], post_s[8], diff_s[8]; 294 | int i, ret; 295 | 296 | memset(pre, 0, sizeof(pre)); 297 | memset(on, 0, sizeof(on)); 298 | memset(post, 0, sizeof(post)); 299 | 300 | for (i = 0; i < tries; i++) { 301 | ret = time_read_interval(dev, count, pre, blocksize, 302 | align - blocksize, maxalign); 303 | returnif(ret); 304 | 305 | ret = time_read_interval(dev, count, on, blocksize, 306 | align - blocksize / 2, maxalign); 307 | returnif(ret); 308 | 309 | ret = time_read_interval(dev, count, post, blocksize, 310 | align, maxalign); 311 | returnif(ret); 312 | } 313 | 314 | format_ns(pre_s, ns_avg(count, pre)); 315 | format_ns(on_s, ns_avg(count, on)); 316 | format_ns(post_s, ns_avg(count, post)); 317 | format_ns(diff_s, ns_avg(count, on) - (ns_avg(count, pre) + ns_avg(count, post)) / 2); 318 | printf("align %lld\tpre %s\ton %s\tpost %s\tdiff %s\n", (long long)align, pre_s, on_s, post_s, diff_s); 319 | 320 | return 0; 321 | } 322 | 323 | static int try_read_alignments(struct device *dev, int tries, int blocksize) 324 | { 325 | const int count = 7; 326 | int ret; 327 | off_t align, maxalign; 328 | 329 | /* make sure we can fit eight power-of-two blocks in the device */ 330 | for (maxalign = blocksize * 2; maxalign < dev->size / count; maxalign *= 2) 331 | ; 332 | 333 | for (align = maxalign; align >= blocksize * 2; align /= 2) { 334 | ret = try_read_alignment(dev, tries, count, maxalign, align, blocksize); 335 | returnif (ret); 336 | } 337 | 338 | return 0; 339 | } 340 | 341 | static int try_program(struct device *dev) 342 | { 343 | #if 0 344 | struct operation program[] = { 345 | {O_REPEAT, 4}, 346 | {O_SEQUENCE, 3}, 347 | {O_PRINT, .string = "Hello, World!\n"}, 348 | {O_DROP}, 349 | {O_PRINTF}, 350 | {O_FORMAT}, 351 | {O_REDUCE, 8, .aggregate = A_AVERAGE}, 352 | {O_LEN_POW2, 8, 512}, 353 | {O_OFF_LIN, 8, 4096 }, 354 | {O_SEQUENCE, 3}, 355 | {O_PRINTF}, 356 | {O_READ}, 357 | {O_NEWLINE}, 358 | {O_DROP}, 359 | {O_READ}, 360 | {O_END}, 361 | {O_NEWLINE}, 362 | {O_END}, 363 | {O_END}, 364 | }; 365 | #endif 366 | 367 | #if 0 368 | struct operation program[] = { 369 | {O_SEQUENCE, 3}, 370 | {O_PRINT, .string="read by size\n"}, 371 | {O_LEN_POW2, 12, 512}, 372 | {O_DROP}, 373 | {O_SEQUENCE, 4}, 374 | {O_PRINTF}, 375 | {O_FORMAT}, 376 | {O_LENGTH}, 377 | {O_PRINT, .string = ": \t"}, 378 | {O_PRINTF}, 379 | {O_FORMAT}, 380 | {O_REDUCE, .aggregate = A_MINIMUM}, 381 | {O_OFF_LIN, 8, 4096 * 1024}, 382 | {O_READ}, 383 | {O_NEWLINE}, 384 | {O_END}, 385 | {O_NEWLINE}, 386 | {O_END}, 387 | {O_END}, 388 | }; 389 | #endif 390 | 391 | #if 1 392 | /* show effect of type of access within AU */ 393 | struct operation program[] = { 394 | /* loop through power of two multiple of one sector */ 395 | {O_LEN_POW2, 13, -512}, 396 | {O_SEQUENCE, 3}, 397 | /* print block size */ 398 | {O_DROP}, 399 | {O_PRINTF}, 400 | {O_FORMAT}, 401 | {O_LENGTH}, 402 | /* start four units into the device, to skip FAT */ 403 | {O_OFF_FIXED, .val = 1024 * 4096 * 4}, {O_DROP}, 404 | /* print one line of aggregated 405 | per second results */ 406 | {O_PRINTF}, {O_SEQUENCE, 8}, 407 | /* write one block to clear state of block, 408 | ignore result */ 409 | // {O_DROP}, {O_LEN_FIXED, .val = 1024 * 4096}, 410 | // {O_WRITE_RAND}, 411 | #if 1 /* linear write zeroes */ 412 | {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS}, 413 | {O_OFF_LIN, 8192, -1}, {O_WRITE_ZERO}, 414 | /* linear write ones */ 415 | {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS}, 416 | {O_OFF_LIN, 8192, -1}, {O_WRITE_ONE}, 417 | #endif 418 | #if 0 419 | /* Erase */ 420 | {O_FORMAT},{O_REDUCE, .aggregate = A_TOTAL},// {O_BPS}, 421 | {O_OFF_LIN, 8192, -1}, {O_ERASE}, 422 | {O_FORMAT},{O_REDUCE, .aggregate = A_TOTAL},// {O_BPS}, 423 | {O_OFF_LIN, 8192, -1}, {O_ERASE}, 424 | /* linear write 0x5a */ 425 | {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS}, 426 | {O_OFF_LIN, 8192, -1}, {O_WRITE_RAND}, 427 | #endif 428 | /* linear write 0x5a again */ 429 | {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS}, 430 | {O_OFF_LIN, 8192, -1}, {O_WRITE_RAND}, 431 | /* linear read */ 432 | {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS}, 433 | {O_OFF_LIN, 8192, -1}, {O_READ}, 434 | #if 1 435 | /* random write zeroes */ 436 | {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS}, 437 | {O_OFF_RAND, 8192, -1}, {O_WRITE_ZERO}, 438 | /* random write ones */ 439 | {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS}, 440 | {O_OFF_RAND, 8192, -1}, {O_WRITE_ONE}, 441 | #endif 442 | #if 0 443 | /* random erase */ 444 | {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE},// {O_BPS}, 445 | {O_OFF_RAND, 8192, -1}, {O_ERASE}, 446 | {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE},// {O_BPS}, 447 | {O_OFF_RAND, 8192, -1}, {O_ERASE}, 448 | /* random write 0x5a */ 449 | {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS}, 450 | {O_OFF_RAND, 8192, -1}, {O_WRITE_RAND}, 451 | #endif 452 | /* random write 0x5a again */ 453 | {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS}, 454 | {O_OFF_RAND, 8192, -1}, {O_WRITE_RAND}, 455 | /* random read */ 456 | {O_FORMAT},{O_REDUCE, .aggregate = A_AVERAGE}, {O_BPS}, 457 | {O_OFF_RAND, 8192, -1}, {O_READ}, 458 | {O_END}, 459 | {O_NEWLINE}, 460 | {O_END}, 461 | {O_END}, 462 | }; 463 | call(program, dev, 0, 1 * 4096 * 1024, 0); 464 | #endif 465 | 466 | return 0; 467 | } 468 | 469 | #if 0 470 | static int try_open_au_oob(struct device *dev, unsigned int erasesize, 471 | unsigned int blocksize, 472 | unsigned int count, 473 | bool random) 474 | { 475 | /* find maximum number of open AUs */ 476 | struct operation program[] = { 477 | /* loop through power of two multiple of one sector */ 478 | {O_LEN_POW2, find_order(erasesize, blocksize), -(long long)blocksize}, 479 | {O_SEQUENCE, 4}, 480 | /* print block size */ 481 | {O_DROP}, 482 | {O_PRINTF}, 483 | {O_FORMAT}, 484 | {O_LENGTH}, 485 | /* start 16 MB into the device, to skip FAT */ 486 | {O_OFF_FIXED, .val = 4 * 1024 * 4128}, {O_DROP}, 487 | /* print one line of aggregated 488 | per second results */ 489 | {O_PRINTF}, {O_FORMAT}, {O_BPS}, 490 | /* linear write 0x5a */ 491 | {O_REDUCE, .aggregate = A_MAXIMUM}, {O_REPEAT, 1}, 492 | {O_REDUCE, .aggregate = A_AVERAGE}, 493 | { (random ? O_OFF_RAND : O_OFF_LIN), 494 | erasesize / blocksize, -1}, 495 | {O_REDUCE, .aggregate = A_AVERAGE}, // {O_BPS}, 496 | {O_OFF_RAND, count, /* 2 * erasesize */ 2 * 4128 * 1024}, {O_WRITE_RAND}, 497 | {O_DROP}, 498 | {O_OFF_FIXED, .val = 4 * 1024 * 4128 + 4 * 1024 * 1024}, {O_DROP}, 499 | {O_LEN_FIXED, .val = 32 * 1024}, 500 | {O_OFF_RAND, count, 2 * 4128 * 1024}, {O_WRITE_RAND}, 501 | {O_NEWLINE}, 502 | {O_END}, 503 | {O_END}, 504 | }; 505 | call(program, dev, 0, erasesize, 0); 506 | 507 | return 0; 508 | } 509 | #endif 510 | 511 | static int try_open_au(struct device *dev, unsigned int erasesize, 512 | unsigned int blocksize, 513 | unsigned int count, 514 | unsigned long long offset, 515 | bool random) 516 | { 517 | /* start 16 MB into the device, to skip FAT, round up to full erase blocks */ 518 | if (offset == -1ull) 519 | offset = (1024 * 1024 * 16 + erasesize - 1) / erasesize * erasesize; 520 | 521 | /* find maximum number of open AUs */ 522 | struct operation program[] = { 523 | /* loop through power of two multiple of one sector */ 524 | {O_LEN_POW2, find_order(erasesize, blocksize), -(long long)blocksize}, 525 | {O_SEQUENCE, 3}, 526 | /* print block size */ 527 | {O_DROP}, 528 | {O_PRINTF}, 529 | {O_FORMAT}, 530 | {O_LENGTH}, 531 | {O_OFF_FIXED, .val = offset}, {O_DROP}, 532 | /* print one line of aggregated 533 | per second results */ 534 | {O_PRINTF}, {O_FORMAT}, {O_BPS}, 535 | /* linear write 0x5a */ 536 | {O_REDUCE, .aggregate = A_MAXIMUM}, {O_REPEAT, 1}, 537 | {O_REDUCE, .aggregate = A_AVERAGE}, 538 | { (random ? O_OFF_RAND : O_OFF_LIN), 539 | erasesize / blocksize, -1}, 540 | {O_REDUCE, .aggregate = A_AVERAGE}, 541 | {O_OFF_RAND, count, 12 * erasesize}, {O_WRITE_RAND}, 542 | {O_NEWLINE}, 543 | {O_END}, 544 | {O_END}, 545 | }; 546 | call(program, dev, 0, erasesize, 0); 547 | 548 | return 0; 549 | } 550 | 551 | static int try_find_fat(struct device *dev, unsigned int erasesize, 552 | unsigned int blocksize, 553 | unsigned int count, 554 | bool random) 555 | { 556 | /* Find FAT Units */ 557 | struct operation program[] = { 558 | /* loop through power of two multiple of one sector */ 559 | {O_LEN_POW2, find_order(erasesize, blocksize), - (long long)blocksize}, 560 | {O_SEQUENCE, 3}, 561 | /* print block size */ 562 | {O_DROP}, 563 | {O_PRINTF}, 564 | {O_FORMAT}, 565 | {O_LENGTH}, 566 | /* print one line of aggregated 567 | per second results */ 568 | {O_PRINTF}, {O_FORMAT}, 569 | {O_OFF_LIN, count, erasesize}, 570 | /* linear write 0x5a */ 571 | {O_REDUCE, .aggregate = A_MAXIMUM}, {O_REPEAT, 1}, 572 | {O_BPS}, {O_REDUCE, .aggregate = A_AVERAGE}, 573 | {random ? O_OFF_RAND : O_OFF_LIN, erasesize / blocksize, -1}, 574 | {O_WRITE_RAND}, 575 | {O_NEWLINE}, 576 | {O_END}, 577 | {O_END}, 578 | }; 579 | call(program, dev, 0, erasesize, 0); 580 | 581 | return 0; 582 | } 583 | 584 | 585 | static void print_help(const char *name) 586 | { 587 | printf("%s [OPTION]... [DEVICE]\n", name); 588 | printf("run tests on DEVICE, pointing to a flash storage medium.\n\n"); 589 | printf("-o, --out=FILE write output to FILE instead of stdout\n"); 590 | printf("-s, --scatter run scatter read test\n"); 591 | printf(" --scatter-order=N scatter across 2^N blocks (default:9)\n"); 592 | printf(" --scatter-span=N span each write across N blocks (default:1)\n"); 593 | printf("-f, --find-fat analyse first few erase blocks\n"); 594 | printf(" --fat-nr=N look through first N erase blocks (default:6)\n"); 595 | printf("-O, --open-au find number of open erase blocks\n"); 596 | printf(" --open-au-nr=N try N open erase blocks (default:2)\n"); 597 | printf(" --offset=N start at position N\n"); 598 | printf("-r, --random use pseudorandom access with erase block\n"); 599 | printf("-v, --verbose increase verbosity of output\n"); 600 | printf("-c, --count=N run each test N times (default:8)\n"); 601 | printf("-b, --blocksize=N use a blocksize of N (default:16K)\n"); 602 | printf("-e, --erasesize=N use a eraseblock size of N (default:4M)\n"); 603 | } 604 | 605 | struct arguments { 606 | const char *dev; 607 | const char *out; 608 | bool scatter, interval, program, fat, open_au, align; 609 | bool random; 610 | int count; 611 | int blocksize; 612 | int erasesize; 613 | unsigned long long offset; 614 | int scatter_order; 615 | int scatter_span; 616 | int interval_order; 617 | int fat_nr; 618 | int open_au_nr; 619 | }; 620 | 621 | static int parse_arguments(int argc, char **argv, struct arguments *args) 622 | { 623 | static const struct option long_options[] = { 624 | { "out", 1, NULL, 'o' }, 625 | { "scatter", 0, NULL, 's' }, 626 | { "scatter-order", 1, NULL, 'S' }, 627 | { "scatter-span", 1, NULL, '$' }, 628 | { "align", 0, NULL, 'a' }, 629 | { "interval", 0, NULL, 'i' }, 630 | { "interval-order", 1, NULL, 'I' }, 631 | { "find-fat", 0, NULL, 'f' }, 632 | { "fat-nr", 1, NULL, 'F' }, 633 | { "open-au", 0, NULL, 'O' }, 634 | { "open-au-nr", 1, NULL, '0' }, 635 | { "offset", 1, NULL, 't' }, 636 | { "random", 0, NULL, 'r' }, 637 | { "verbose", 0, NULL, 'v' }, 638 | { "count", 1, NULL, 'c' }, 639 | { "blocksize", 1, NULL, 'b' }, 640 | { "erasesize", 1, NULL, 'e' }, 641 | { NULL, 0, NULL, 0 }, 642 | }; 643 | 644 | memset(args, 0, sizeof(*args)); 645 | args->count = 8; 646 | args->scatter_order = 9; 647 | args->scatter_span = 1; 648 | args->blocksize = 16384; 649 | args->offset = -1ull; 650 | args->erasesize = 4 * 1024 * 1024; 651 | args->fat_nr = 6; 652 | args->open_au_nr = 2; 653 | 654 | while (1) { 655 | int c; 656 | 657 | c = getopt_long(argc, argv, "o:siafF:Ovrc:b:e:p", long_options, &optind); 658 | 659 | if (c == -1) 660 | break; 661 | 662 | switch (c) { 663 | case 'o': 664 | args->out = optarg; 665 | break; 666 | 667 | case 's': 668 | args->scatter = 1; 669 | break; 670 | 671 | case 'S': 672 | args->scatter_order = atoi(optarg); 673 | break; 674 | 675 | case '$': 676 | args->scatter_span = atoi(optarg); 677 | break; 678 | 679 | case 'a': 680 | args->align = 1; 681 | break; 682 | 683 | case 'i': 684 | args->interval = 1; 685 | break; 686 | 687 | case 'I': 688 | args->interval_order = atoi(optarg); 689 | break; 690 | 691 | case 'f': 692 | args->fat = 1; 693 | break; 694 | 695 | case 'F': 696 | args->fat_nr = atoi(optarg); 697 | break; 698 | 699 | case 'O': 700 | args->open_au = 1; 701 | break; 702 | 703 | case '0': 704 | args->open_au_nr = atoi(optarg); 705 | break; 706 | 707 | case 'r': 708 | args->random = 1; 709 | break; 710 | 711 | case 'p': 712 | args->program = 1; 713 | break; 714 | 715 | case 'v': 716 | verbose++; 717 | break; 718 | 719 | case 'c': 720 | args->count = atoi(optarg); 721 | break; 722 | 723 | case 'b': 724 | args->blocksize = atoi(optarg); 725 | break; 726 | 727 | case 'e': 728 | args->erasesize = atoi(optarg); 729 | break; 730 | 731 | case 't': 732 | args->offset = strtoull(optarg, NULL, 0); 733 | break; 734 | 735 | case '?': 736 | print_help(argv[0]); 737 | return -EINVAL; 738 | break; 739 | } 740 | } 741 | 742 | if (optind != (argc - 1)) { 743 | fprintf(stderr, "%s: invalid arguments\n", argv[0]); 744 | return -EINVAL; 745 | } 746 | 747 | args->dev = argv[optind]; 748 | 749 | if (!(args->scatter || args->interval || args->program || 750 | args->fat || args->open_au || args->align)) { 751 | fprintf(stderr, "%s: need at least one action\n", argv[0]); 752 | return -EINVAL; 753 | } 754 | 755 | if (args->scatter && (args->scatter_order > 16)) { 756 | fprintf(stderr, "%s: scatter_order must be at most 16\n", argv[0]); 757 | return -EINVAL; 758 | } 759 | 760 | return 0; 761 | } 762 | 763 | static FILE *open_output(const char *filename) 764 | { 765 | if (!filename || !strcmp(filename, "-")) 766 | return fdopen(1, "w"); /* write to stdout */ 767 | 768 | return fopen(filename, "w+"); 769 | } 770 | 771 | int main(int argc, char **argv) 772 | { 773 | struct device dev; 774 | struct arguments args; 775 | FILE *output; 776 | int ret; 777 | 778 | returnif(parse_arguments(argc, argv, &args)); 779 | 780 | returnif(setup_dev(&dev, args.dev)); 781 | 782 | output = open_output(args.out); 783 | if (!output) { 784 | perror(args.out); 785 | return -errno; 786 | } 787 | 788 | if (verbose > 1) { 789 | printf("filename: \"%s\"\n", argv[1]); 790 | printf("filesize: 0x%llx\n", (unsigned long long)dev.size); 791 | } 792 | 793 | if (args.scatter) { 794 | ret = try_scatter_io(&dev, args.count, args.scatter_order, 795 | args.scatter_span, args.blocksize, output); 796 | if (ret < 0) { 797 | errno = -ret; 798 | perror("try_scatter_io"); 799 | return ret; 800 | } 801 | } 802 | 803 | if (args.fat) { 804 | ret = try_find_fat(&dev, args.erasesize, args.blocksize, 805 | args.fat_nr, args.random); 806 | if (ret < 0) { 807 | errno = -ret; 808 | perror("try_find_fat"); 809 | } 810 | } 811 | 812 | if (args.align) { 813 | ret = try_read_alignments(&dev, args.count, args.blocksize); 814 | if (ret < 0) { 815 | errno = -ret; 816 | perror("try_read_alignments"); 817 | return ret; 818 | } 819 | } 820 | 821 | if (args.open_au) { 822 | ret = try_open_au(&dev, args.erasesize, args.blocksize, 823 | args.open_au_nr, args.offset, args.random); 824 | if (ret < 0) { 825 | errno = -ret; 826 | perror("try_open_au"); 827 | return ret; 828 | } 829 | } 830 | 831 | if (args.interval) { 832 | ret = try_intervals(&dev, args.count, args.interval_order); 833 | if (ret < 0) { 834 | errno = -ret; 835 | perror("try_intervals"); 836 | return ret; 837 | } 838 | } 839 | 840 | if (args.program) { 841 | try_program(&dev); 842 | } 843 | 844 | return 0; 845 | } 846 | -------------------------------------------------------------------------------- /vm.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #define _FILE_OFFSET_BITS 64 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "dev.h" 11 | #include "vm.h" 12 | 13 | static inline res_t *res_ptr(res_t r) 14 | { 15 | return (res_t *)((unsigned long)r._p & ~7); 16 | } 17 | 18 | static inline enum resulttype res_type(res_t r) 19 | { 20 | return (enum resulttype)((unsigned long)r._p & 7); 21 | } 22 | 23 | static inline res_t to_res(res_t *_p, enum resulttype t) 24 | { 25 | return (res_t) { ._p = (res_t *)(((unsigned long)_p & ~7) | (t & 7)) }; 26 | } 27 | 28 | static const res_t res_null = { }; 29 | 30 | struct syntax { 31 | enum opcode opcode; 32 | const char *name; 33 | struct operation *(*function)(struct operation *op, struct device *dev, 34 | off_t off, off_t max, size_t len); 35 | enum { 36 | P_NUM = 1, 37 | P_VAL = 2, 38 | P_STRING = 4, 39 | P_AGGREGATE = 8, 40 | P_ATOM = 16, 41 | } param; 42 | }; 43 | 44 | static struct syntax syntax[]; 45 | 46 | int verbose = 0; 47 | 48 | struct operation *call(struct operation *op, struct device *dev, 49 | off_t off, off_t max, size_t len) 50 | { 51 | struct operation *next; 52 | 53 | if (!op) 54 | return_err("internal error: NULL operation\n"); 55 | 56 | pr_debug("call %s %lld %lld %ld\n", syntax[op->code].name, 57 | (long long)off, (long long)max, (long)len); 58 | 59 | if (op->code > O_MAX) 60 | return_err("illegal command code %d\n", op->code); 61 | 62 | if (!(syntax[op->code].param & P_NUM) != !op->num) 63 | return_err("need .num= argument\n"); 64 | if (!(syntax[op->code].param & P_VAL) != !op->val) 65 | return_err("need .param= argument\n"); 66 | if (!(syntax[op->code].param & P_STRING) != !op->string) 67 | return_err("need .string= argument\n"); 68 | if (!(syntax[op->code].param & P_AGGREGATE) != !op->aggregate) 69 | return_err("need .aggregate= argument\n"); 70 | 71 | if (op->num) { 72 | res_t *data; 73 | 74 | if (res_ptr(op->result)) 75 | return_err("%s already has result\n", syntax[op->code].name); 76 | 77 | data = calloc(sizeof (res_t), op->num); 78 | if (!data) 79 | return_err("out of memory"); 80 | 81 | op->result = to_res(data, R_NONE); 82 | op->r_type = R_ARRAY; 83 | } 84 | 85 | next = syntax[op->code].function(op, dev, off, max, len); 86 | if (!next) 87 | return_err("from %s\n", syntax[op->code].name); 88 | 89 | return next; 90 | } 91 | 92 | static struct operation *call_propagate(struct operation *op, struct device *dev, 93 | off_t off, off_t max, size_t len, struct operation *this) 94 | { 95 | struct operation *next; 96 | 97 | next = call(op, dev, off, max, len); 98 | 99 | this->result = op->result; 100 | this->size_x = op->size_x; 101 | this->size_y = op->size_y; 102 | this->r_type = op->r_type; 103 | 104 | op->result = res_null; 105 | op->size_x = op->size_y = 0; 106 | op->r_type = R_NONE; 107 | 108 | return next; 109 | } 110 | 111 | static struct operation *call_aggregate(struct operation *op, struct device *dev, 112 | off_t off, off_t max, size_t len, struct operation *this) 113 | { 114 | struct operation *next; 115 | res_t *res = res_ptr(this->result); 116 | enum resulttype type = res_type(this->result); 117 | 118 | next = call(op, dev, off, max, len); 119 | if (!next) 120 | return NULL; 121 | 122 | if (this->size_x >= this->num) 123 | return_err("array too small for %d entries\n", this->size_x); 124 | 125 | res[this->size_x] = op->result; 126 | 127 | /* no result */ 128 | if (op->r_type == R_NONE) 129 | return next; 130 | 131 | this->size_x++; 132 | /* first data in this aggregation: set type */ 133 | if (type == R_NONE) { 134 | type = op->r_type; 135 | this->result = to_res(res, type); 136 | } 137 | 138 | if (type != op->r_type) { 139 | return_err("cannot aggregate return type %d with %d\n", 140 | type, op->r_type); 141 | } 142 | 143 | if (op->r_type == R_ARRAY) { 144 | if (this->size_y && this->size_y != op->size_x) 145 | return_err("cannot aggregate different size arrays (%d, %d)\n", 146 | this->size_y, op->size_x); 147 | 148 | if (op->size_y) 149 | return_err("cannot aggregate three-dimensional array\n"); 150 | 151 | this->size_y = op->size_x; 152 | 153 | op->size_x = op->size_y = 0; 154 | } 155 | 156 | op->r_type = R_NONE; 157 | op->result = res_null; 158 | 159 | return next; 160 | } 161 | 162 | static struct operation *nop(struct operation *op, struct device *dev, 163 | off_t off, off_t max, size_t len) 164 | { 165 | return_err("command not implemented\n"); 166 | } 167 | 168 | static struct operation *do_read(struct operation *op, struct device *dev, 169 | off_t off, off_t max, size_t len) 170 | { 171 | op->result.l = time_read(dev, off, len); 172 | op->r_type = R_NS; 173 | return op+1; 174 | } 175 | 176 | static struct operation *do_write_zero(struct operation *op, struct device *dev, 177 | off_t off, off_t max, size_t len) 178 | { 179 | op->result.l = time_write(dev, off, len, WBUF_ZERO); 180 | op->r_type = R_NS; 181 | return op+1; 182 | } 183 | 184 | static struct operation *do_write_one(struct operation *op, struct device *dev, 185 | off_t off, off_t max, size_t len) 186 | { 187 | op->result.l = time_write(dev, off, len, WBUF_ONE); 188 | op->r_type = R_NS; 189 | return op+1; 190 | } 191 | 192 | static struct operation *do_write_rand(struct operation *op, struct device *dev, 193 | off_t off, off_t max, size_t len) 194 | { 195 | op->result.l = time_write(dev, off, len, WBUF_RAND); 196 | op->r_type = R_NS; 197 | return op+1; 198 | } 199 | 200 | static struct operation *do_erase(struct operation *op, struct device *dev, 201 | off_t off, off_t max, size_t len) 202 | { 203 | op->result.l = time_erase(dev, off, len); 204 | op->r_type = R_NS; 205 | return op+1; 206 | } 207 | 208 | static struct operation *length_or_offs(struct operation *op, struct device *dev, 209 | off_t off, off_t max, size_t len) 210 | { 211 | op->result.l = (op->code == O_LENGTH) ? (long long)len : off; 212 | op->r_type = R_BYTE; 213 | return op+1; 214 | } 215 | 216 | static res_t format_value(res_t val, enum resulttype type, 217 | unsigned int size_x, unsigned int size_y) 218 | { 219 | long long l = val.l; 220 | unsigned int x; 221 | res_t *res; 222 | res_t out; 223 | 224 | switch (type) { 225 | case R_ARRAY: 226 | res = res_ptr(val); 227 | for (x = 0; x < size_x; x++) { 228 | res[x] = format_value(res[x], res_type(val), size_y, 0); 229 | if (res[x].s == res_null.s) 230 | return res_null; 231 | } 232 | if (res_type(val) == R_ARRAY) 233 | out = val; 234 | else 235 | out = to_res(res_ptr(val), R_STRING); 236 | 237 | return out; 238 | 239 | case R_BYTE: 240 | if (l < 1024) 241 | snprintf(out.s, 8, "%0lldB", l); 242 | else if (l < 1024 * 1024) 243 | snprintf(out.s, 8, "%0.3gKiB", l / 1024.0); 244 | else if (l < 1024 * 1024 * 1024) 245 | snprintf(out.s, 8, "%0.3gMiB", l / (1024.0 * 1024.0)); 246 | else 247 | snprintf(out.s, 8, "%0.4gGiB", l / (1024.0 * 1024.0 * 1024.0)); 248 | break; 249 | 250 | case R_BPS: 251 | if (l < 1000) 252 | snprintf(out.s, 8, "%0lldB/s", l); 253 | else if (l < 1000 * 1000) 254 | snprintf(out.s, 8, "%.03gK/s", l / 1000.0); 255 | else if (l < 1000 * 1000 * 1000) 256 | snprintf(out.s, 8, "%.03gM/s", l / (1000.0 * 1000.0)); 257 | else 258 | snprintf(out.s, 8, "%.04gG/s", l / (1000.0 * 1000.0 * 1000.0)); 259 | break; 260 | 261 | 262 | case R_NS: 263 | if (l < 1000) 264 | snprintf(out.s, 8, "%lldns", l); 265 | else if (l < 1000 * 1000) 266 | snprintf(out.s, 8, "%.3gµs", l / 1000.0); 267 | else if (l < 1000 * 1000 * 1000) 268 | snprintf(out.s, 8, "%.3gms", l / 1000000.0); 269 | else 270 | snprintf(out.s, 8, "%.4gs", l / 1000000000.0); 271 | break; 272 | default: 273 | return res_null; 274 | } 275 | 276 | for (x = strlen(out.s); x<7; x++) 277 | out.s[x] = ' '; 278 | out.s[7] = '\0'; 279 | 280 | return out; 281 | } 282 | 283 | static struct operation *format(struct operation *op, struct device *dev, 284 | off_t off, off_t max, size_t len) 285 | { 286 | struct operation *next; 287 | 288 | next = call_propagate(op+1, dev, off, max, len, op); 289 | op->result = format_value(op->result, op->r_type, op->size_x, op->size_y); 290 | 291 | if (op->result.s == res_null.s) 292 | return NULL; 293 | 294 | if (op->r_type != R_ARRAY) 295 | op->r_type = R_STRING; 296 | 297 | return next; 298 | } 299 | 300 | static struct operation *print_string(struct operation *op, struct device *dev, 301 | off_t off, off_t max, size_t len) 302 | { 303 | printf("%s", op->string); 304 | return op+1; 305 | } 306 | 307 | static void *print_value(res_t val, enum resulttype type, 308 | unsigned int size_x, unsigned int size_y) 309 | { 310 | unsigned int x; 311 | res_t *res; 312 | 313 | switch (type) { 314 | case R_ARRAY: 315 | res = res_ptr(val); 316 | for (x=0; x < size_x; x++) { 317 | if (!print_value(res[x], res_type(val), size_y, 0)) 318 | return_err("cannot print array of type %d\n", 319 | res_type(val)); 320 | printf(size_y ? "\n" : " "); 321 | } 322 | 323 | 324 | break; 325 | case R_BYTE: 326 | case R_NS: 327 | case R_BPS: 328 | printf("%lld ", val.l); 329 | break; 330 | case R_STRING: 331 | printf("%s ", val.s); 332 | break; 333 | default: 334 | return NULL; 335 | } 336 | return (void *)1; 337 | } 338 | 339 | static struct operation *print_val(struct operation *op, struct device *dev, 340 | off_t off, off_t max, size_t len) 341 | { 342 | struct operation *next; 343 | 344 | next = call_propagate(op+1, dev, off, max, len, op); 345 | if (!next) 346 | return NULL; 347 | 348 | if (!print_value(op->result, op->r_type, op->size_x, op->size_y)) 349 | return_err("cannot print value of type %d\n", op->r_type); 350 | 351 | return next; 352 | } 353 | 354 | static struct operation *newline(struct operation *op, struct device *dev, 355 | off_t off, off_t max, size_t len) 356 | { 357 | printf("\n"); 358 | return op+1; 359 | } 360 | 361 | static res_t bytespersec_one(res_t res, size_t bytes, enum resulttype type, 362 | unsigned int size_x, unsigned int size_y) 363 | { 364 | if (type == R_NS) 365 | res.l = 1000000000ll * bytes / res.l; 366 | else if (type == R_ARRAY) { 367 | res_t *array = res_ptr(res); 368 | type = res_type(res); 369 | unsigned int x; 370 | 371 | for (x = 0; x < size_x; x++) 372 | array[x] = bytespersec_one(array[x], bytes, 373 | type, size_y, 0); 374 | 375 | if (type == R_NS) 376 | res = to_res(array, R_BPS); 377 | } else { 378 | res = res_null; 379 | } 380 | 381 | return res; 382 | } 383 | 384 | static struct operation *bytespersec(struct operation *op, struct device *dev, 385 | off_t off, off_t max, size_t len) 386 | { 387 | struct operation *next; 388 | next = call_propagate(op+1, dev, off, max, len, op); 389 | 390 | op->result = bytespersec_one(op->result, len, op->r_type, 391 | op->size_x, op->size_y); 392 | 393 | if (op->result.l == res_null.l) 394 | return_err("invalid data, type %d\n", op->r_type); 395 | 396 | if (op->r_type == R_NS) 397 | op->r_type = R_BPS; 398 | 399 | return next; 400 | } 401 | 402 | static struct operation *sequence(struct operation *op, struct device *dev, 403 | off_t off, off_t max, size_t len) 404 | { 405 | unsigned int i; 406 | struct operation *next = op+1; 407 | 408 | for (i=0; inum; i++) { 409 | next = call_aggregate(next, dev, off, max, len, op); 410 | if (!next) 411 | return NULL; 412 | } 413 | 414 | /* immediately fold sequences with a single result */ 415 | if (op->size_x == 1) { 416 | op->r_type = res_type(op->result); 417 | op->result = res_ptr(op->result)[0]; 418 | op->size_x = op->size_y; 419 | op->size_y = 0; 420 | } 421 | 422 | if (next && next->code != O_END) 423 | return_err("sequence needs to end with END command\n"); 424 | 425 | return next+1; 426 | } 427 | 428 | static struct operation *len_fixed(struct operation *op, struct device *dev, 429 | off_t off, off_t max, size_t len) 430 | { 431 | return call_propagate(op+1, dev, off, max, op->val, op); 432 | } 433 | 434 | static struct operation *len_pow2(struct operation *op, struct device *dev, 435 | off_t off, off_t max, size_t len) 436 | { 437 | unsigned int i; 438 | struct operation *next = op+1; 439 | 440 | if (!len) 441 | len = 1; 442 | 443 | if (op->val > 0) { 444 | for (i = 0; i < op->num && next; i++) 445 | next = call_aggregate(op+1, dev, off, max, 446 | len * op->val << i, op); 447 | } else { 448 | for (i = op->num; i>0 && next; i--) 449 | next = call_aggregate(op+1, dev, off, max, 450 | len * (-op->val/2) << i, op); 451 | 452 | } 453 | 454 | return next; 455 | } 456 | 457 | static struct operation *off_fixed(struct operation *op, struct device *dev, 458 | off_t off, off_t max, size_t len) 459 | { 460 | return call_propagate(op+1, dev, off + op->val, max, len, op); 461 | } 462 | 463 | static struct operation *off_lin(struct operation *op, struct device *dev, 464 | off_t off, off_t max, size_t len) 465 | { 466 | struct operation *next = op+1; 467 | unsigned int i; 468 | unsigned int num, val; 469 | 470 | if (op->val == -1) { 471 | if (len == 0 || max < (off_t)len) 472 | return_err("cannot fill %lld bytes with %ld byte chunks\n", 473 | (long long)max, (long)len); 474 | 475 | num = max/len; 476 | val = max/num; 477 | } else { 478 | val = op->val; 479 | num = op->num; 480 | } 481 | 482 | for (i = 0; i < num && next; i++) 483 | next = call_aggregate(op+1, dev, off + i * val, max, len, op); 484 | 485 | return next; 486 | } 487 | 488 | /* 489 | * Linear feedback shift register 490 | * 491 | * We use this to randomize the block positions for random-access 492 | * tests. Unlike real random data, we know that within 2^bits 493 | * accesses, every possible value up to 2^bits will be seen 494 | * exactly once, with the exception of zero, for which we have 495 | * a special treatment. 496 | */ 497 | static int lfsr(unsigned short v, unsigned int bits) 498 | { 499 | unsigned short bit; 500 | 501 | if (v >= (1 << bits)) { 502 | fprintf(stderr, "lfsr: internal error\n"); 503 | exit(-1); 504 | } 505 | 506 | if (v == (((1 << bits) - 1) & 0xace1)) 507 | return 0; 508 | 509 | if (v == 0) 510 | v = ((1 << bits) - 1) & 0xace1; 511 | 512 | switch (bits) { 513 | case 8: /* x^8 + x^6 + x^5 + x^4 + 1 */ 514 | bit = ((v >> 0) ^ (v >> 2) ^ (v >> 3) ^ (v >> 4)) & 1; 515 | break; 516 | case 9: /* x9 + x5 + 1 */ 517 | bit = ((v >> 0) ^ (v >> 4)) & 1; 518 | break; 519 | case 10: /* x10 + x7 + 1 */ 520 | bit = ((v >> 0) ^ (v >> 3)) & 1; 521 | break; 522 | case 11: /* x11 + x9 + 1 */ 523 | bit = ((v >> 0) ^ (v >> 2)) & 1; 524 | break; 525 | case 12: 526 | bit = ((v >> 0) ^ (v >> 1) ^ (v >> 2) ^ (v >> 8)) & 1; 527 | break; 528 | case 13: /* x^13 + x^12 + x^11 + x^8 + 1 */ 529 | bit = ((v >> 0) ^ (v >> 1) ^ (v >> 2) ^ (v >> 5)) & 1; 530 | break; 531 | case 14: /* x^14 + x^13 + x^12 + x^2 + 1 */ 532 | bit = ((v >> 0) ^ (v >> 1) ^ (v >> 2) ^ (v >> 12)) & 1; 533 | break; 534 | case 15: /* x^15 + x^14 + 1 */ 535 | bit = ((v >> 0) ^ (v >> 1) ) & 1; 536 | break; 537 | case 16: /* x^16 + x^14 + x^13 + x^11 + 1 */ 538 | bit = ((v >> 0) ^ (v >> 2) ^ (v >> 3) ^ (v >> 5) ) & 1; 539 | break; 540 | default: 541 | fprintf(stderr, "lfsr: internal error\n"); 542 | exit(-1); 543 | } 544 | 545 | return v >> 1 | bit << (bits - 1); 546 | } 547 | 548 | static struct operation *off_rand(struct operation *op, struct device *dev, 549 | off_t off, off_t max, size_t len) 550 | { 551 | struct operation *next = op+1; 552 | unsigned int i; 553 | unsigned int num, val; 554 | unsigned int pos = 0, bits = 0; 555 | 556 | if (op->val == -1) { 557 | if (len == 0 || max < (off_t)len) 558 | return_err("cannot fill %lld bytes with %ld byte chunks\n", 559 | (long long)max, (long)len); 560 | 561 | num = max/len; 562 | val = max/num; 563 | } else { 564 | val = op->val; 565 | num = op->num; 566 | } 567 | 568 | for (i = num; i > 0; i /= 2) 569 | bits++; 570 | 571 | if (bits < 8) 572 | bits = 8; 573 | 574 | for (i = 0; i < num && next; i++) { 575 | do { 576 | pos = lfsr(pos, bits); 577 | } while (pos >= num); 578 | next = call_aggregate(op+1, dev, off + pos * val, max, len, op); 579 | } 580 | 581 | return next; 582 | } 583 | 584 | static struct operation *repeat(struct operation *op, struct device *dev, 585 | off_t off, off_t max, size_t len) 586 | { 587 | struct operation *next = op+1; 588 | unsigned int i; 589 | 590 | for (i = 0; i < op->num && next; i++) 591 | next = call_aggregate(op+1, dev, off, max, len, op); 592 | 593 | return next; 594 | } 595 | 596 | static res_t do_reduce_int(int num, res_t *input, int aggregate) 597 | { 598 | int i; 599 | res_t result = { .l = 0 }; 600 | for (i = 0; i < num; i++) { 601 | switch (aggregate) { 602 | case A_MINIMUM: 603 | if (!result.l || result.l > input[i].l) 604 | result.l = input[i].l; 605 | break; 606 | case A_MAXIMUM: 607 | if (!result.l || result.l < input[i].l) 608 | result.l = input[i].l; 609 | break; 610 | case A_AVERAGE: 611 | case A_TOTAL: 612 | result.l += input[i].l; 613 | break; 614 | } 615 | } 616 | 617 | if (aggregate == A_AVERAGE) 618 | result.l /= num; 619 | 620 | return result; 621 | } 622 | 623 | static struct operation *reduce(struct operation *op, struct device *dev, 624 | off_t off, off_t max, size_t len) 625 | { 626 | struct operation *next, *child; 627 | unsigned int i; 628 | enum resulttype type; 629 | res_t *in; 630 | 631 | child = op+1; 632 | next = call(child, dev, off, max, len); 633 | if (!next) 634 | return NULL; 635 | 636 | /* single value */ 637 | if (child->r_type != R_ARRAY || child->size_x == 0) 638 | return_err("cannot reduce scalar further, type %d, size %d\n", 639 | child->r_type, child->size_y); 640 | 641 | /* data does not fit */ 642 | if (child->size_y > op->num) 643 | return_err("target array too short\n"); /* FIXME: is this necessary? */ 644 | 645 | /* one-dimensional array */ 646 | if (child->size_y == 0) { 647 | if (res_type(child->result) != R_NS && 648 | res_type(child->result) != R_BPS) 649 | return_err("cannot reduce type %d\n", res_type(child->result)); 650 | 651 | op->result = do_reduce_int(child->size_x, res_ptr(child->result), 652 | op->aggregate); 653 | op->size_x = op->size_y = 0; 654 | op->r_type = res_type(child->result); 655 | goto clear_child; 656 | } 657 | 658 | /* two-dimensional array */ 659 | in = res_ptr(child->result); 660 | if (res_type(child->result) != R_ARRAY) 661 | return_err("inconsistent array contents\n"); 662 | 663 | type = res_type(in[0]); 664 | for (i=0; isize_x; i++) { 665 | if (res_type(in[i]) != type) 666 | return_err("cannot combine type %d and %d\n", 667 | res_type(in[i]), type); 668 | 669 | res_ptr(op->result)[i] = do_reduce_int(child->size_y, res_ptr(in[i]), 670 | op->aggregate); 671 | } 672 | op->result = to_res(res_ptr(op->result), type); 673 | op->size_x = child->size_y; 674 | op->size_y = 0; 675 | op->r_type = R_ARRAY; 676 | 677 | clear_child: 678 | child->result = res_null; 679 | child->size_x = child->size_y = 0; 680 | child->r_type = R_NONE; 681 | 682 | return next; 683 | } 684 | 685 | static struct operation *drop(struct operation *op, struct device *dev, 686 | off_t off, off_t max, size_t len) 687 | { 688 | struct operation *next, *child; 689 | 690 | child = op+1; 691 | next = call(child, dev, off, max, len); 692 | if (!next) 693 | return NULL; 694 | 695 | child->result = res_null; 696 | child->r_type = R_NONE; 697 | child->size_x = child->size_y = 0; 698 | 699 | return next; 700 | } 701 | 702 | static struct syntax syntax[] = { 703 | { O_END, "END", nop, }, 704 | { O_READ, "READ", do_read, }, 705 | { O_WRITE_ZERO, "WRITE_ZERO", do_write_zero, }, 706 | { O_WRITE_ONE, "WRITE_ONE", do_write_one, }, 707 | { O_WRITE_RAND, "WRITE_RAND", do_write_rand, }, 708 | { O_ERASE, "ERASE", do_erase, }, 709 | { O_LENGTH, "LENGTH", length_or_offs }, 710 | { O_OFFSET, "OFFSET", length_or_offs, }, 711 | 712 | { O_PRINT, "PRINT", print_string, P_STRING }, 713 | { O_PRINTF, "PRINTF", print_val, }, 714 | { O_FORMAT, "FORMAT", format, }, 715 | { O_NEWLINE, "NEWLINE", newline, }, 716 | { O_BPS, "BPS", bytespersec, }, 717 | 718 | { O_SEQUENCE, "SEQUENCE", sequence, P_NUM }, 719 | { O_REPEAT, "REPEAT", repeat, P_NUM }, 720 | 721 | { O_OFF_FIXED, "OFF_FIXED", off_fixed, P_VAL }, 722 | { O_OFF_POW2, "OFF_POW2", nop, P_NUM | P_VAL }, 723 | { O_OFF_LIN, "OFF_LIN", off_lin, P_NUM | P_VAL }, 724 | { O_OFF_RAND, "OFF_RAND", off_rand, P_NUM | P_VAL }, 725 | { O_LEN_FIXED, "LEN_FIXED", len_fixed, P_VAL }, 726 | { O_LEN_POW2, "LEN_POW2", len_pow2, P_NUM | P_VAL }, 727 | { O_MAX_POW2, "MAX_POW2", nop, P_NUM | P_VAL }, 728 | { O_MAX_LIN, "MAX_LIN", nop, P_NUM | P_VAL }, 729 | 730 | { O_REDUCE, "REDUCE", reduce, P_AGGREGATE }, 731 | { O_DROP, "DROP", drop, }, 732 | }; 733 | 734 | -------------------------------------------------------------------------------- /vm.h: -------------------------------------------------------------------------------- 1 | #ifndef FLASHBENCH_VM_H 2 | #define FLASHBENCH_VM_H 3 | 4 | #include 5 | 6 | typedef union result res_t; 7 | 8 | enum resulttype { 9 | R_NONE, 10 | R_ARRAY, 11 | R_NS, 12 | R_BYTE, 13 | R_BPS, 14 | R_STRING, 15 | }; 16 | 17 | union result { 18 | res_t *_p; 19 | long long l; 20 | char s[8]; 21 | } __attribute__((aligned(8))); 22 | 23 | struct device; 24 | 25 | struct operation { 26 | enum opcode { 27 | /* end of program marker */ 28 | O_END = 0, 29 | 30 | /* basic operations */ 31 | O_READ, 32 | O_WRITE_ZERO, 33 | O_WRITE_ONE, 34 | O_WRITE_RAND, 35 | O_ERASE, 36 | O_LENGTH, 37 | O_OFFSET, 38 | 39 | /* output */ 40 | O_PRINT, 41 | O_PRINTF, 42 | O_FORMAT, 43 | O_NEWLINE, 44 | O_BPS, 45 | 46 | /* group */ 47 | O_SEQUENCE, 48 | O_REPEAT, 49 | 50 | /* series */ 51 | O_OFF_FIXED, 52 | O_OFF_POW2, 53 | O_OFF_LIN, 54 | O_OFF_RAND, 55 | O_LEN_FIXED, 56 | O_LEN_POW2, 57 | O_MAX_POW2, 58 | O_MAX_LIN, 59 | 60 | /* reduce dimension */ 61 | O_REDUCE, 62 | 63 | /* ignore result */ 64 | O_DROP, 65 | 66 | /* end of list */ 67 | O_MAX = O_DROP, 68 | } code; 69 | 70 | /* number of indirect results, if any */ 71 | unsigned int num; 72 | 73 | /* command code specific value */ 74 | long long val; 75 | 76 | /* output string for O_PRINT */ 77 | const char *string; 78 | 79 | /* aggregation of results from children */ 80 | enum { 81 | A_NONE, 82 | A_MINIMUM, 83 | A_MAXIMUM, 84 | A_AVERAGE, 85 | A_TOTAL, 86 | A_IGNORE, 87 | } aggregate; 88 | 89 | /* dynamic result contents */ 90 | res_t result; 91 | unsigned int size_x; 92 | unsigned int size_y; 93 | enum resulttype r_type; 94 | }; 95 | 96 | extern struct operation *call(struct operation *program, struct device *dev, 97 | off_t off, off_t max, size_t len); 98 | 99 | extern int verbose; 100 | #define pr_debug(...) do { if (verbose) printf(__VA_ARGS__); } while(0) 101 | #define return_err(...) do { printf(__VA_ARGS__); return NULL; } while(0) 102 | 103 | #endif /* FLASHBENCH_VM_H */ 104 | --------------------------------------------------------------------------------