;;; This module binds the libproccpuinfo C library. ;;; ;;; --------------------------------------------------------------------- ;;; ;;; To see an example of a slightly more complicated C library binding ;;; that this binding was modelled on, see the stemmer egg. ;;; ;;; Many thanks to DerGuteMoritz, sjamaan, C-Keen, and the rest of ;;; the #chicken freenode crew! ;;; ;;; --------------------------------------------------------------------- ;;; ;;; Copyright (C) 2012 - Sergey Goldgaber, Moritz Heidkamp, Christian Kellermann ;;; ;;; This program is free software: you can redistribute it and/or modify ;;; it under the terms of the GNU Affero General Public License as published by ;;; the Free Software Foundation, either version 3 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 Affero General Public License for more details. ;;; ;;; You should have received a copy of the GNU Affero General Public License ;;; along with this program. If not, see . ;;; ;;; --------------------------------------------------------------------- (module proccpuinfo ;; Exported procedures: (proccpuinfo) (import chicken ; Standard import scheme ; Standard import foreign) ; For various C types and the following special forms: ; foreign-lambda, ; foreign-lambda* ;; Note: For some more complicated bindings, it may be useful to import ;; the "foreigners" and/or "bind" eggs, which could reduce the amount ;; of C code you'd need to write yourself, while introducing some ;; mental overhead. For the sake of simplicity we are avoiding them in ;; this binding and writing a bit of hand-generated C code ourselves to ;; access the proccupinfo struct. ;; The C header file you're binding goes here: ;; ;; Note: The string passed to foreign-declare is injected verbatim into ;; the resulting C code's head (i.e. it isn't limited to including ;; header files). (foreign-declare "#include ") ;; The creation of an instance of the following record will be done ;; to ensure that procedures accepting arguments of this type won't break. ;; ;; The procedures in question must always first extract the pointer from the ;; record. Then the extraction procedure should check its argument. If it's ;; not a cpuinfo record, it will raise an exception. The goal is to prevent ;; segfaults caused by user mistakes. ;; ;; Record-types are the only way to create custom types that are distinct ;; from all others. ;; ;; The record also gives you a predicate for free, so people can check ;; whether an object is a cpuinfo object. (define-record cpuinfo pointer) ;; Here we create a binding to proccpuinfo_free(), which will be used ;; by a finalizer we'll register later, to free the proccpuinfo struct ;; allocated by get-cpuinfo. ;; ;; The first argument to this procedure, cpuinfo, should be a record ;; that was previously created by make-cpuinfo. This record should ;; contain a valid pointer to the proccpuinfo struct created earlier ;; by the get-cpuinfo procedure. (define (delete-cpuinfo cpuinfo) ;; We are only interested in the pointer from the cpuinfo record: (let ((cpuinfo* (cpuinfo-pointer cpuinfo))) ;; We should only try to free the proccpuinfo struct if it has ;; not been freed before. We do this by making sure cpuinfo* ;; if not #f (when cpuinfo* ;; Now we need to make the actual binding to proccpuinfo_free() ;; We do this by using foreign-lambda. ;; ;; Note: foreign-lambdas are just like plain lambdas: they're ;; first-class, and can be passed around as values. ;; ((foreign-lambda void ; The return type of the following C function: proccpuinfo_free ; The name of the C function we want to bind ;; The type of the single argument to the C function we're binding: ;; ;; Note: if there are more arguments, just put their types in order, ;; below the type of the first argument. (c-pointer (struct "proccpuinfo"))) ;; The function created by the foreign-lambda above will now be called ;; with the variable containing the pointer to the proccpuinfo struct, ;; which was bound in the above let. ;; cpuinfo*) ;; Now the final step: we make sure that the proccpuinfo struct ;; can not be double-freed: (cpuinfo-pointer-set! cpuinfo #f)))) ;; The purpose of the following procedure will be to return a cpuinfo record, ;; which will contain a pointer to the proccpuinfo struct, which ;; can be queried by the accessor procedures we'll define later. ;; ;; The proccpuinfo_read function is even easier to bind than ;; the proccpuinfo_free function, as it takes no arguments. The binding ;; proceeds much the same as did the binding of the proccpuinfo_free function ;; above. The only new and tricky part is registering the finalizer, which ;; will be used to free the memory allocated by proccpuinfo_read once it's ;; ready to be garbage collected. ;; (define (get-cpuinfo) ;; First we store the result of executing the procedure created by ;; the foreign-lambda binding ;; ;; We'll use cpuinfo* later to register a finalizer. (let ((cpuinfo* ((foreign-lambda ;; The return type of the C function we're binding: (c-pointer (struct "proccpuinfo")) ;; The name of the C function we're binding: proccpuinfo_read)))) ;; If proccpuinfo_read() fails, it will return a null pointer, which ;; will be translated by Chicken to #f ;; ;; We test for that here, only proceeding with the rest of the function ;; if it's not #f, otherwise we'll throw an error. (if cpuinfo* ;; Since we now know that we have a valid cpuinfo* we're ready ;; to register a finalizer. ;; ;; (make-cpuinfo cupinfo*) returns the record with the cpuinfo pointer ;; in it, (which isn't exposed to the user, of course). The finalizer ;; is registered on this pointer, but delete-cpuinfo won't be invoked ;; until cpuinfo* is ready to be freed by the garbage collector, which ;; will happen when there are no more references in Chicken to the ;; pointer. ;; ;; Setting the finalizer in this way will return a cpuinfo record, ;; and be used as the return value of (get-cpuinfo) itself. (set-finalizer! (make-cpuinfo cpuinfo*) delete-cpuinfo) (error "The call to proccpuinfo_read() failed.")))) ;; --------------------------------------------------------------------------- ;; ;; GLOBAL VARIABLE GET- AND SET- PROCEDURES ;; ;; The purpose of the following series of get- and set- procedures ;; is to retreive and set the values of a couple of libproccpuinfo ;; global variables. See the libproccpuinfo's documentation for more info. ;; The binding to proccpuinfo_get_arch is even easier still, as it takes ;; no arguments and simply returns an int. (define (get-arch) ;; A call to this procedure will return the result of a call to the ;; function returned by the following foreign-lambda: ((foreign-lambda int ; The type of the return value of the C function we wish to bind proccpuinfo_get_arch))) ; The name of the C function we wish to bind ;; Another easy binding, to a function which takes no arguments and ;; returns a char * (define (get-filename) ;; A call to this procedure will return the result of a call to the ;; function returned by the following foreign-lambda: ((foreign-lambda c-string ; The type of the return value of the C function we wish to bind proccpuinfo_get_filename))) ; The name of the C function we wish to bind ;; This binding is a bit tricker, as we have to call the foreign-lambda ;; with the given argument. (define (set-arch arch) ;; A call to this procedure will return the result of a call to the ;; function returned by the following foreign-lambda: ((foreign-lambda void ; The type of the return value of the C function we wish to bind proccpuinfo_set_arch ; The name of the C function we wish to bind int) ; The type of the first argument to the C function we wish to bind arch)) ; Call the foreign-lambda with this value as its argument. ; Note: The FFI automatically converts the chicken number to a C int, ; (i.e. you don't need to any manual type casting) ;; The only difference with this binding is that the C function takes a c-string ;; as an argument, rather than an int. ;; ;; Note: Chicken strings are not c-strings, i.e. they may contain NUL ;; and the FFI will throw an error when such strings are passed (define (set-filename filename) ;; A call to this procedure will return the result of a call to the ;; function returned by the following foreign-lambda: ((foreign-lambda void ; The type of the return value of the C function we wish to bind proccpuinfo_set_filename ; The name of the C function we wish to bind c-string) ; The type of the first argument to the C function we wish to bind filename)) ; Call the foreign-lambda with this value as its argument. ;; This binding is the same as the previous one, only it returns ;; an int rather than a void. (define (decode-arch arch) ;; A call to this procedure will return the result of a call to the ;; function returned by the following foreign-lambda: ((foreign-lambda int ; The type of the return value of the C function we wish to bind proccpuinfo_decode_arch ; The name of the C function we wish to bind c-string) ; The type of the first argument to the C function we wish to bind arch)) ; Call the foreign-lambda with this value as its argument. ;; --------------------------------------------------------------------------- ;; ;; ACCESSOR PROCEDURES ;; ;; Now we'll define accessor procedures that will use C code to dig in to the ;; proccpuinfo struct created by get-cpuinfo, and return a given member of ;; that struct. ;; This procedure will return the "architecture" member of the ;; proccpuinfo struct. (define (get-architecture) ;; First, we store the result of calling (get-cpuinfo), so we ;; can pass it on to the the foreign-lambda* below, which will ;; run the C code to get the struct member we're interested in. (let ((info* (cpuinfo-pointer (get-cpuinfo)))) ;; A call to (get-architecture) will return the result of a call to the ;; function returned by the following foreign-lambda*: ;; ;; Note: We use foreign-lambda* here instead of foreign-lambda ;; because the former can be passed a string containing literal ;; C code instead of being limited to calling a pre-cooked ;; C function, like the latter does. ((foreign-lambda* ;; Return value of the C code we're binding: c-string ;; First and only argument to the C code we're binding: ;; ;; Note: If there were more arguments, you'd put their ;; types and names right below the first one. (((c-pointer (struct "proccpuinfo")) ; The type of the argument info)) ; The name of the variable it is bound to. ;; Note: The name of the argument specified above ;; will be used inside the C code below. ;; ;; The C code that a call to the foreign-lambda* will execute: ;; ;; Note: according to the docs, you **must** use a C_return() ;; instead of an ordinary return(). "C_return(info->architecture);") ;; Now we call the foreign-lambda* with the variable we bound ;; in the above let. info*))) ;; This is nearly a duplicate of the previous procedure, except ;; now we're going to get the "hardware_platform" struct member. ;; ;; There are many ways to reduce code duplication of this sort in Scheme. ;; But we're not using any of them, to keep this example as simple as possible. (define (get-hardware-platform) ;; First, we store the result of calling (get-cpuinfo), so we ;; can pass it on to the the foreign-lambda* below, which will ;; run the C code to get the struct member we're interested in. (let ((info* (cpuinfo-pointer (get-cpuinfo)))) ((foreign-lambda* c-string ; The type of the return value of the C code we bind below. ;; The first and only argument to the C code we're binding: (((c-pointer (struct "proccpuinfo")) ; The type of the argument info)) ; The name of the variable it is bound to. ;; The C code that a call to the foreign-lambda* will execute: "C_return(info->hardware_platform);") ;; Now we call the foreign-lambda* with the variable we bound ;; in the above let. info*))) ;; Now we're going to continue making nearly duplicate procedures to get ;; the rest of the members of the proccpuinfo struct. ;; ;; Note that some of the return values in these procedures differ from one ;; another, according to the prototype of the proccpuinfo structure in ;; libproccpuinfo.h (define (get-frequency) ;; First, we store the result of calling (get-cpuinfo), so we ;; can pass it on to the the foreign-lambda* below, which will ;; run the C code to get the struct member we're interested in. (let ((info* (cpuinfo-pointer (get-cpuinfo)))) ;; A call to (get-frequency) will return the result of a call to the ;; function returned by the following foreign-lambda: ((foreign-lambda* double ; The type of the return value of the C code we bind below. ;; The first and only argument to the C code we're binding: (((c-pointer (struct "proccpuinfo")) ; The type of the argument info)) ; The name of the variable it is bound to. ;; The C code that a call to the foreign-lambda* will execute: "C_return(info->frequency);") ;; Now we call the foreign-lambda* with the variable we bound ;; in the above let. info*))) (define (get-bogomips) ;; First, we store the result of calling (get-cpuinfo), so we ;; can pass it on to the the foreign-lambda* below, which will ;; run the C code to get the struct member we're interested in. (let ((info* (cpuinfo-pointer (get-cpuinfo)))) ;; A call to (get-bogomips) will return the result of a call to the ;; function returned by the following foreign-lambda: ((foreign-lambda* double ; The type of the return value of the C code we bind below. ;; The first and only argument to the C code we're binding: (((c-pointer (struct "proccpuinfo")) ; The type of the argument info)) ; The name of the variable it is bound to. ;; The C code that a call to the foreign-lambda* will execute: "C_return(info->bogomips);") ;; Now we call the foreign-lambda* with the variable we bound ;; in the above let. info*))) (define (get-cache) ;; First, we store the result of calling (get-cpuinfo), so we ;; can pass it on to the the foreign-lambda* below, which will ;; run the C code to get the struct member we're interested in. (let ((info* (cpuinfo-pointer (get-cpuinfo)))) ;; A call to (get-cache) will return the result of a call to the ;; function returned by the following foreign-lambda: ((foreign-lambda* unsigned-int ; The type of the return value of the C code we bind below. ;; The first and only argument to the C code we're binding: (((c-pointer (struct "proccpuinfo")) ; The type of the argument info)) ; The name of the variable it is bound to. ;; The C code that a call to the foreign-lambda* will execute: "C_return(info->cache);") ;; Now we call the foreign-lambda* with the variable we bound ;; in the above let. info*))) (define (get-cpus) ;; First, we store the result of calling (get-cpuinfo), so we ;; can pass it on to the the foreign-lambda* below, which will ;; run the C code to get the struct member we're interested in. (let ((info* (cpuinfo-pointer (get-cpuinfo)))) ;; A call to (get-cpus) will return the result of a call to the ;; function returned by the following foreign-lambda: ((foreign-lambda* unsigned-int ; The type of the return value of the C code we bind below. ;; The first and only argument to the C code we're binding: (((c-pointer (struct "proccpuinfo")) ; The type of the argument info)) ; The name of the variable it is bound to. ;; The C code that a call to the foreign-lambda* will execute: "C_return(info->cpus);") ;; Now we call the foreign-lambda* with the variable we bound ;; in the above let. info*))) (define (get-cputype) ;; First, we store the result of calling (get-cpuinfo), so we ;; can pass it on to the the foreign-lambda* below, which will ;; run the C code to get the struct member we're interested in. (let ((info* (cpuinfo-pointer (get-cpuinfo)))) ;; A call to (get-cputype) will return the result of a call to the ;; function returned by the following foreign-lambda: ((foreign-lambda* c-string ; The type of the return value of the C code we bind below. ;; The first and only argument to the C code we're binding: (((c-pointer (struct "proccpuinfo")) ; The type of the argument info)) ; The name of the variable it is bound to. ;; The C code that a call to the foreign-lambda* will execute: "C_return(info->cputype);") ;; Now we call the foreign-lambda* with the variable we bound ;; in the above let. info*))) ;; --------------------------------------------------------------------------- ;; ;; MODULE INTERFACE PROCEDURE ;; ;; We could stop here and simply export the accessor procedures ;; we've defined above. Instead, we'll go one step further ;; and create a convenience procedure, which will be the ;; only one we'll be exporting. ;; ;; This procedure will act as the interface to this entire library. ;; Through it, the user will be able to request any member ;; of the proccpuinfo struct. ;; ;; This procedure will take one mandatory argument, and two optional ;; keyword arguments, "filename:" and "arch:" which will be used ;; to call (set-filename) and (set-arch), respectively. ;; (define (proccpuinfo desired-info . args) (let ((filename (get-keyword filename: args (lambda () #f))) (arch (get-keyword arch: args (lambda () #f))) ;; We need to save the old value of arch, ;; so we can restore it later: (old-arch (get-arch))) ;; Tell libproccpuinfo about the filename or arch to use, ;; if the user passed them along as arguments to this procedure: (when filename (set-filename filename)) (when arch (set-arch arch)) ;; The information the user desires is gotten and temporarily saved ;; here using the procedures we defined above: (let ((result (case desired-info ((architecture) (get-architecture)) ((hardware-platform) (get-hardware-platform)) ((frequency) (get-frequency)) ((bogomips) (get-bogomips)) ((cache) (get-cache)) ((cpus) (get-cpus)) ((cputype) (get-cputype)) (else 'invalid-value)))) ;; The old values of filename and arch are restored, so that they ;; won't interfere with future calls to this procedure: ;; ;; Note that #f has to be passed to set-filename to reset it. ;; Using (set-filename "/proc/cpuinfo") or even (set-filename old-filename) ;; where old-filename was the result of a previous (get-filename) ;; will not work. (when filename (set-filename #f)) (when arch (set-arch old-arch)) ;; Give the user what he asked for: result))) ) ; vim: ft=chicken